From cf2aebf67a4280b94ec25371ba902314e580ec28 Mon Sep 17 00:00:00 2001 From: Sandra Gonzales Date: Mon, 1 Jun 2020 11:02:46 -0400 Subject: [PATCH 01/50] [Ingest Manager] Optimize installation of integration (#67708) * call getArchiveInfo once first, pass paths to template * pass paths to installPreBuiltTemplates * pass paths to installILMPolicy * pass paths to ingest pipeline creation * use correct package key for cache * pass paths to kibana assets * cache other installed packages * create function for ensuring packages are cached * remove unused imports Co-authored-by: Elastic Machine --- .../services/epm/elasticsearch/ilm/install.ts | 14 ++------ .../elasticsearch/ingest_pipeline/install.ts | 22 +++++++------ .../epm/elasticsearch/template/install.ts | 32 ++++++------------- .../epm/kibana/index_pattern/install.ts | 8 +++++ .../server/services/epm/packages/assets.ts | 6 ++-- .../server/services/epm/packages/install.ts | 28 ++++++++-------- .../server/services/epm/registry/cache.ts | 1 + .../server/services/epm/registry/index.ts | 11 +++++-- 8 files changed, 60 insertions(+), 62 deletions(-) diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ilm/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ilm/install.ts index 1d06bf23a8c0..9590167657d9 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ilm/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ilm/install.ts @@ -7,16 +7,8 @@ import { CallESAsCurrentUser, ElasticsearchAssetType } from '../../../../types'; import * as Registry from '../../registry'; -export async function installILMPolicy( - pkgName: string, - pkgVersion: string, - callCluster: CallESAsCurrentUser -) { - const ilmPaths = await Registry.getArchiveInfo( - pkgName, - pkgVersion, - (entry: Registry.ArchiveEntry) => isILMPolicy(entry) - ); +export async function installILMPolicy(paths: string[], callCluster: CallESAsCurrentUser) { + const ilmPaths = paths.filter((path) => isILMPolicy(path)); if (!ilmPaths.length) return; await Promise.all( ilmPaths.map(async (path) => { @@ -36,7 +28,7 @@ export async function installILMPolicy( }) ); } -const isILMPolicy = ({ path }: Registry.ArchiveEntry) => { +const isILMPolicy = (path: string) => { const pathParts = Registry.pathParts(path); return pathParts.type === ElasticsearchAssetType.ilmPolicy; }; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/install.ts index bdf6ecfcdb9a..11543fe73886 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/install.ts @@ -22,9 +22,11 @@ interface RewriteSubstitution { export const installPipelines = async ( registryPackage: RegistryPackage, + paths: string[], callCluster: CallESAsCurrentUser ) => { const datasets = registryPackage.datasets; + const pipelinePaths = paths.filter((path) => isPipeline(path)); if (datasets) { const pipelines = datasets.reduce>>((acc, dataset) => { if (dataset.ingest_pipeline) { @@ -32,7 +34,7 @@ export const installPipelines = async ( installPipelinesForDataset({ dataset, callCluster, - pkgName: registryPackage.name, + paths: pipelinePaths, pkgVersion: registryPackage.version, }) ); @@ -67,20 +69,16 @@ export function rewriteIngestPipeline( export async function installPipelinesForDataset({ callCluster, - pkgName, pkgVersion, + paths, dataset, }: { callCluster: CallESAsCurrentUser; - pkgName: string; pkgVersion: string; + paths: string[]; dataset: Dataset; }): Promise { - const pipelinePaths = await Registry.getArchiveInfo( - pkgName, - pkgVersion, - (entry: Registry.ArchiveEntry) => isDatasetPipeline(entry, dataset.path) - ); + const pipelinePaths = paths.filter((path) => isDatasetPipeline(path, dataset.path)); let pipelines: any[] = []; const substitutions: RewriteSubstitution[] = []; @@ -152,8 +150,8 @@ async function installPipeline({ } const isDirectory = ({ path }: Registry.ArchiveEntry) => path.endsWith('/'); -const isDatasetPipeline = ({ path }: Registry.ArchiveEntry, datasetName: string) => { - // TODO: better way to get particular assets + +const isDatasetPipeline = (path: string, datasetName: string) => { const pathParts = Registry.pathParts(path); return ( !isDirectory({ path }) && @@ -162,6 +160,10 @@ const isDatasetPipeline = ({ path }: Registry.ArchiveEntry, datasetName: string) datasetName === pathParts.dataset ); }; +const isPipeline = (path: string) => { + const pathParts = Registry.pathParts(path); + return pathParts.type === ElasticsearchAssetType.ingestPipeline; +}; // XXX: assumes path/to/file.ext -- 0..n '/' and exactly one '.' const getNameAndExtension = ( diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts index c600c8ba3efb..9d0b6b5d078a 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts @@ -16,13 +16,14 @@ export const installTemplates = async ( registryPackage: RegistryPackage, callCluster: CallESAsCurrentUser, pkgName: string, - pkgVersion: string + pkgVersion: string, + paths: string[] ): Promise => { // install any pre-built index template assets, // atm, this is only the base package's global index templates // Install component templates first, as they are used by the index templates - await installPreBuiltComponentTemplates(pkgName, pkgVersion, callCluster); - await installPreBuiltTemplates(pkgName, pkgVersion, callCluster); + await installPreBuiltComponentTemplates(paths, callCluster); + await installPreBuiltTemplates(paths, callCluster); // build templates per dataset from yml files const datasets = registryPackage.datasets; @@ -44,16 +45,8 @@ export const installTemplates = async ( return []; }; -const installPreBuiltTemplates = async ( - pkgName: string, - pkgVersion: string, - callCluster: CallESAsCurrentUser -) => { - const templatePaths = await Registry.getArchiveInfo( - pkgName, - pkgVersion, - (entry: Registry.ArchiveEntry) => isTemplate(entry) - ); +const installPreBuiltTemplates = async (paths: string[], callCluster: CallESAsCurrentUser) => { + const templatePaths = paths.filter((path) => isTemplate(path)); const templateInstallPromises = templatePaths.map(async (path) => { const { file } = Registry.pathParts(path); const templateName = file.substr(0, file.lastIndexOf('.')); @@ -95,15 +88,10 @@ const installPreBuiltTemplates = async ( }; const installPreBuiltComponentTemplates = async ( - pkgName: string, - pkgVersion: string, + paths: string[], callCluster: CallESAsCurrentUser ) => { - const templatePaths = await Registry.getArchiveInfo( - pkgName, - pkgVersion, - (entry: Registry.ArchiveEntry) => isComponentTemplate(entry) - ); + const templatePaths = paths.filter((path) => isComponentTemplate(path)); const templateInstallPromises = templatePaths.map(async (path) => { const { file } = Registry.pathParts(path); const templateName = file.substr(0, file.lastIndexOf('.')); @@ -134,12 +122,12 @@ const installPreBuiltComponentTemplates = async ( } }; -const isTemplate = ({ path }: Registry.ArchiveEntry) => { +const isTemplate = (path: string) => { const pathParts = Registry.pathParts(path); return pathParts.type === ElasticsearchAssetType.indexTemplate; }; -const isComponentTemplate = ({ path }: Registry.ArchiveEntry) => { +const isComponentTemplate = (path: string) => { const pathParts = Registry.pathParts(path); return pathParts.type === ElasticsearchAssetType.componentTemplate; }; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.ts index f321e2d614a0..0f7b1d6cab17 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.ts @@ -86,6 +86,14 @@ export async function installIndexPatterns( savedObjectsClient, InstallationStatus.installed ); + + // TODO: move to install package + // cache all installed packages if they don't exist + const packagePromises = installedPackages.map((pkg) => + Registry.ensureCachedArchiveInfo(pkg.pkgName, pkg.pkgVersion) + ); + await Promise.all(packagePromises); + if (pkgName && pkgVersion) { // add this package to the array if it doesn't already exist const foundPkg = installedPackages.find((pkg) => pkg.pkgName === pkgName); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts index c6f7a1f6b97a..37fcf0db6713 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts @@ -6,7 +6,7 @@ import { RegistryPackage } from '../../../types'; import * as Registry from '../registry'; -import { cacheHas } from '../registry/cache'; +import { ensureCachedArchiveInfo } from '../registry'; // paths from RegistryPackage are routes to the assets on EPR // e.g. `/package/nginx/1.2.0/dataset/access/fields/fields.yml` @@ -57,8 +57,8 @@ export async function getAssetsData( datasetName?: string ): Promise { // TODO: Needs to be called to fill the cache but should not be required - const pkgkey = packageInfo.name + '-' + packageInfo.version; - if (!cacheHas(pkgkey)) await Registry.getArchiveInfo(packageInfo.name, packageInfo.version); + + await ensureCachedArchiveInfo(packageInfo.name, packageInfo.version); // Gather all asset data const assets = getAssets(packageInfo, filter, datasetName); 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 dddb21bc4e07..7c0d5d571f6a 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 @@ -90,7 +90,7 @@ export async function installPackage(options: { const { savedObjectsClient, pkgkey, callCluster } = options; // TODO: change epm API to /packageName/version so we don't need to do this const [pkgName, pkgVersion] = pkgkey.split('-'); - + const paths = await Registry.getArchiveInfo(pkgName, pkgVersion); // see if some version of this package is already installed // TODO: calls to getInstallationObject, Registry.fetchInfo, and Registry.fetchFindLatestPackge // and be replaced by getPackageInfo after adjusting for it to not group/use archive assets @@ -119,15 +119,16 @@ export async function installPackage(options: { savedObjectsClient, pkgName, pkgVersion, + paths, }), - installPipelines(registryPackageInfo, callCluster), + installPipelines(registryPackageInfo, paths, callCluster), // index patterns and ilm policies are not currently associated with a particular package // so we do not save them in the package saved object state. installIndexPatterns(savedObjectsClient, pkgName, pkgVersion), // currenly only the base package has an ILM policy // at some point ILM policies can be installed/modified // per dataset and we should then save them - installILMPolicy(pkgName, pkgVersion, callCluster), + installILMPolicy(paths, callCluster), ]); // install or update the templates @@ -135,7 +136,8 @@ export async function installPackage(options: { registryPackageInfo, callCluster, pkgName, - pkgVersion + pkgVersion, + paths ); const toSaveESIndexPatterns = generateESIndexPatterns(registryPackageInfo.datasets); @@ -186,13 +188,14 @@ export async function installKibanaAssets(options: { savedObjectsClient: SavedObjectsClientContract; pkgName: string; pkgVersion: string; + paths: string[]; }) { - const { savedObjectsClient, pkgName, pkgVersion } = options; + const { savedObjectsClient, paths } = options; // Only install Kibana assets during package installation. const kibanaAssetTypes = Object.values(KibanaAssetType); const installationPromises = kibanaAssetTypes.map(async (assetType) => - installKibanaSavedObjects({ savedObjectsClient, pkgName, pkgVersion, assetType }) + installKibanaSavedObjects({ savedObjectsClient, assetType, paths }) ); // installKibanaSavedObjects returns AssetReference[], so .map creates AssetReference[][] @@ -237,19 +240,16 @@ export async function saveInstallationReferences(options: { async function installKibanaSavedObjects({ savedObjectsClient, - pkgName, - pkgVersion, assetType, + paths, }: { savedObjectsClient: SavedObjectsClientContract; - pkgName: string; - pkgVersion: string; assetType: KibanaAssetType; + paths: string[]; }) { - const isSameType = ({ path }: Registry.ArchiveEntry) => - assetType === Registry.pathParts(path).type; - const paths = await Registry.getArchiveInfo(pkgName, pkgVersion, isSameType); - const toBeSavedObjects = await Promise.all(paths.map(getObject)); + const isSameType = (path: string) => assetType === Registry.pathParts(path).type; + const pathsOfType = paths.filter((path) => isSameType(path)); + const toBeSavedObjects = await Promise.all(pathsOfType.map(getObject)); if (toBeSavedObjects.length === 0) { return []; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/cache.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/cache.ts index 17d52bc745a5..d2a14fcf04df 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/cache.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/cache.ts @@ -8,3 +8,4 @@ const cache: Map = new Map(); export const cacheGet = (key: string) => cache.get(key); export const cacheSet = (key: string, value: Buffer) => cache.set(key, value); export const cacheHas = (key: string) => cache.has(key); +export const getCacheKey = (key: string) => key + '.tar.gz'; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts index 8e9b92087561..0393cabca8ba 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts @@ -16,7 +16,7 @@ import { RegistrySearchResults, RegistrySearchResult, } from '../../../types'; -import { cacheGet, cacheSet } from './cache'; +import { cacheGet, cacheSet, getCacheKey, cacheHas } from './cache'; import { ArchiveEntry, untarBuffer } from './extract'; import { fetchUrl, getResponse, getResponseStream } from './requests'; import { streamToBuffer } from './streams'; @@ -135,7 +135,7 @@ async function extract( async function getOrFetchArchiveBuffer(pkgName: string, pkgVersion: string): Promise { // assume .tar.gz for now. add support for .zip if/when we need it - const key = `${pkgName}-${pkgVersion}.tar.gz`; + const key = getCacheKey(`${pkgName}-${pkgVersion}`); let buffer = cacheGet(key); if (!buffer) { buffer = await fetchArchiveBuffer(pkgName, pkgVersion); @@ -149,6 +149,13 @@ async function getOrFetchArchiveBuffer(pkgName: string, pkgVersion: string): Pro } } +export async function ensureCachedArchiveInfo(name: string, version: string) { + const pkgkey = getCacheKey(`${name}-${version}`); + if (!cacheHas(pkgkey)) { + await getArchiveInfo(name, version); + } +} + async function fetchArchiveBuffer(pkgName: string, pkgVersion: string): Promise { const { download: archivePath } = await fetchInfo(pkgName, pkgVersion); const registryUrl = getRegistryUrl(); From f31330a01b21cda1a35a83a6dbf4998264817fc6 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen <43350163+qn895@users.noreply.github.com> Date: Mon, 1 Jun 2020 10:53:33 -0500 Subject: [PATCH 02/50] [ML] Add ability to delete target index & index pattern when deleting DFA job (#66934) Co-authored-by: Elastic Machine --- .../ml/common/types/data_frame_analytics.ts | 11 + .../analytics_list/action_delete.test.tsx | 51 +++- .../analytics_list/action_delete.tsx | 150 +++++++++++- .../analytics_service/delete_analytics.ts | 131 ++++++++++- .../services/analytics_service/index.ts | 3 +- .../ml_api_service/data_frame_analytics.ts | 19 ++ .../services/ml_api_service/jobs.ts | 1 - .../ml/public/application/util/error_utils.ts | 32 +++ .../data_frame_analytics/index_patterns.ts | 32 +++ .../ml/server/routes/data_frame_analytics.ts | 112 ++++++++- .../routes/schemas/data_analytics_schema.ts | 8 + .../apis/ml/data_frame_analytics/delete.ts | 218 ++++++++++++++++++ .../apis/ml/data_frame_analytics/index.ts | 1 + x-pack/test/functional/services/ml/api.ts | 53 ++++- .../functional/services/ml/test_resources.ts | 17 +- 15 files changed, 797 insertions(+), 42 deletions(-) create mode 100644 x-pack/plugins/ml/common/types/data_frame_analytics.ts create mode 100644 x-pack/plugins/ml/public/application/util/error_utils.ts create mode 100644 x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts create mode 100644 x-pack/test/api_integration/apis/ml/data_frame_analytics/delete.ts diff --git a/x-pack/plugins/ml/common/types/data_frame_analytics.ts b/x-pack/plugins/ml/common/types/data_frame_analytics.ts new file mode 100644 index 000000000000..5ba7f9c191a7 --- /dev/null +++ b/x-pack/plugins/ml/common/types/data_frame_analytics.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 { CustomHttpResponseOptions, ResponseError } from 'kibana/server'; +export interface DeleteDataFrameAnalyticsWithIndexStatus { + success: boolean; + error?: CustomHttpResponseOptions; +} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.test.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.test.tsx index 2ef1515726d1..33217f127f99 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.test.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.test.tsx @@ -5,20 +5,41 @@ */ import React from 'react'; -import { render } from '@testing-library/react'; - +import { fireEvent, render } from '@testing-library/react'; import * as CheckPrivilige from '../../../../../capabilities/check_capabilities'; - -import { DeleteAction } from './action_delete'; - import mockAnalyticsListItem from './__mocks__/analytics_list_item.json'; +import { DeleteAction } from './action_delete'; +import { I18nProvider } from '@kbn/i18n/react'; +import { + coreMock as mockCoreServices, + i18nServiceMock, +} from '../../../../../../../../../../src/core/public/mocks'; jest.mock('../../../../../capabilities/check_capabilities', () => ({ checkPermission: jest.fn(() => false), createPermissionFailureMessage: jest.fn(), })); +jest.mock('../../../../../../application/util/dependency_cache', () => ({ + getToastNotifications: () => ({ addSuccess: jest.fn(), addDanger: jest.fn() }), +})); + +jest.mock('../../../../../contexts/kibana', () => ({ + useMlKibana: () => ({ + services: mockCoreServices.createStart(), + }), +})); +export const MockI18nService = i18nServiceMock.create(); +export const I18nServiceConstructor = jest.fn().mockImplementation(() => MockI18nService); +jest.doMock('@kbn/i18n', () => ({ + I18nService: I18nServiceConstructor, +})); + describe('DeleteAction', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + test('When canDeleteDataFrameAnalytics permission is false, button should be disabled.', () => { const { getByTestId } = render(); expect(getByTestId('mlAnalyticsJobDeleteButton')).toHaveAttribute('disabled'); @@ -46,4 +67,24 @@ describe('DeleteAction', () => { expect(getByTestId('mlAnalyticsJobDeleteButton')).toHaveAttribute('disabled'); }); + + describe('When delete model is open', () => { + test('should allow to delete target index by default.', () => { + const mock = jest.spyOn(CheckPrivilige, 'checkPermission'); + mock.mockImplementation((p) => p === 'canDeleteDataFrameAnalytics'); + const { getByTestId, queryByTestId } = render( + + + + ); + const deleteButton = getByTestId('mlAnalyticsJobDeleteButton'); + fireEvent.click(deleteButton); + expect(getByTestId('mlAnalyticsJobDeleteModal')).toBeInTheDocument(); + expect(getByTestId('mlAnalyticsJobDeleteIndexSwitch')).toBeInTheDocument(); + const mlAnalyticsJobDeleteIndexSwitch = getByTestId('mlAnalyticsJobDeleteIndexSwitch'); + expect(mlAnalyticsJobDeleteIndexSwitch).toHaveAttribute('aria-checked', 'true'); + expect(queryByTestId('mlAnalyticsJobDeleteIndexPatternSwitch')).toBeNull(); + mock.mockRestore(); + }); + }); }); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx index 2923938ae68a..2d433f6b1848 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx @@ -4,24 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, FC, useState } from 'react'; +import React, { Fragment, FC, useState, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiButtonEmpty, EuiConfirmModal, EuiOverlayMask, EuiToolTip, + EuiSwitch, + EuiFlexGroup, + EuiFlexItem, EUI_MODAL_CONFIRM_BUTTON, } from '@elastic/eui'; - -import { deleteAnalytics } from '../../services/analytics_service'; - +import { IIndexPattern } from 'src/plugins/data/common'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + deleteAnalytics, + deleteAnalyticsAndDestIndex, + canDeleteIndex, +} from '../../services/analytics_service'; import { checkPermission, createPermissionFailureMessage, } from '../../../../../capabilities/check_capabilities'; - +import { useMlKibana } from '../../../../../contexts/kibana'; import { isDataFrameAnalyticsRunning, DataFrameAnalyticsListRow } from './common'; +import { extractErrorMessage } from '../../../../../util/error_utils'; interface DeleteActionProps { item: DataFrameAnalyticsListRow; @@ -29,17 +37,99 @@ interface DeleteActionProps { export const DeleteAction: FC = ({ item }) => { const disabled = isDataFrameAnalyticsRunning(item.stats.state); - const canDeleteDataFrameAnalytics: boolean = checkPermission('canDeleteDataFrameAnalytics'); const [isModalVisible, setModalVisible] = useState(false); + const [deleteTargetIndex, setDeleteTargetIndex] = useState(true); + const [deleteIndexPattern, setDeleteIndexPattern] = useState(true); + const [userCanDeleteIndex, setUserCanDeleteIndex] = useState(false); + const [indexPatternExists, setIndexPatternExists] = useState(false); + + const { savedObjects, notifications } = useMlKibana().services; + const savedObjectsClient = savedObjects.client; + + const indexName = item.config.dest.index; + + const checkIndexPatternExists = async () => { + try { + const response = await savedObjectsClient.find({ + type: 'index-pattern', + perPage: 10, + search: `"${indexName}"`, + searchFields: ['title'], + fields: ['title'], + }); + const ip = response.savedObjects.find( + (obj) => obj.attributes.title.toLowerCase() === indexName.toLowerCase() + ); + if (ip !== undefined) { + setIndexPatternExists(true); + } + } catch (e) { + const { toasts } = notifications; + const error = extractErrorMessage(e); + + toasts.addDanger( + i18n.translate( + 'xpack.ml.dataframe.analyticsList.errorWithCheckingIfIndexPatternExistsNotificationErrorMessage', + { + defaultMessage: + 'An error occurred checking if index pattern {indexPattern} exists: {error}', + values: { indexPattern: indexName, error }, + } + ) + ); + } + }; + const checkUserIndexPermission = () => { + try { + const userCanDelete = canDeleteIndex(indexName); + if (userCanDelete) { + setUserCanDeleteIndex(true); + } + } catch (e) { + const { toasts } = notifications; + const error = extractErrorMessage(e); + + toasts.addDanger( + i18n.translate( + 'xpack.ml.dataframe.analyticsList.errorWithCheckingIfUserCanDeleteIndexNotificationErrorMessage', + { + defaultMessage: + 'An error occurred checking if user can delete {destinationIndex}: {error}', + values: { destinationIndex: indexName, error }, + } + ) + ); + } + }; + + useEffect(() => { + // Check if an index pattern exists corresponding to current DFA job + // if pattern does exist, show it to user + checkIndexPatternExists(); + + // Check if an user has permission to delete the index & index pattern + checkUserIndexPermission(); + }, []); const closeModal = () => setModalVisible(false); const deleteAndCloseModal = () => { setModalVisible(false); - deleteAnalytics(item); + + if ((userCanDeleteIndex && deleteTargetIndex) || (userCanDeleteIndex && deleteIndexPattern)) { + deleteAnalyticsAndDestIndex( + item, + deleteTargetIndex, + indexPatternExists && deleteIndexPattern + ); + } else { + deleteAnalytics(item); + } }; const openModal = () => setModalVisible(true); + const toggleDeleteIndex = () => setDeleteTargetIndex(!deleteTargetIndex); + const toggleDeleteIndexPattern = () => setDeleteIndexPattern(!deleteIndexPattern); const buttonDeleteText = i18n.translate('xpack.ml.dataframe.analyticsList.deleteActionName', { defaultMessage: 'Delete', @@ -84,8 +174,9 @@ export const DeleteAction: FC = ({ item }) => { {deleteButton} {isModalVisible && ( - + = ({ item }) => { buttonColor="danger" >

- {i18n.translate('xpack.ml.dataframe.analyticsList.deleteModalBody', { - defaultMessage: `Are you sure you want to delete this analytics job? The analytics job's destination index and optional Kibana index pattern will not be deleted.`, - })} +

+ + + + {userCanDeleteIndex && ( + + )} + + + {userCanDeleteIndex && indexPatternExists && ( + + )} + +
)} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/delete_analytics.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/delete_analytics.ts index 7383f565bd67..26cefff0a3f5 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/delete_analytics.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/delete_analytics.ts @@ -3,17 +3,15 @@ * 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 { getToastNotifications } from '../../../../../util/dependency_cache'; import { ml } from '../../../../../services/ml_api_service'; - import { refreshAnalyticsList$, REFRESH_ANALYTICS_LIST_STATE } from '../../../../common'; - import { isDataFrameAnalyticsFailed, DataFrameAnalyticsListRow, } from '../../components/analytics_list/common'; +import { extractErrorMessage } from '../../../../../util/error_utils'; export const deleteAnalytics = async (d: DataFrameAnalyticsListRow) => { const toastNotifications = getToastNotifications(); @@ -24,18 +22,139 @@ export const deleteAnalytics = async (d: DataFrameAnalyticsListRow) => { await ml.dataFrameAnalytics.deleteDataFrameAnalytics(d.config.id); toastNotifications.addSuccess( i18n.translate('xpack.ml.dataframe.analyticsList.deleteAnalyticsSuccessMessage', { - defaultMessage: 'Request to delete data frame analytics {analyticsId} acknowledged.', + defaultMessage: 'Request to delete data frame analytics job {analyticsId} acknowledged.', values: { analyticsId: d.config.id }, }) ); } catch (e) { + const error = extractErrorMessage(e); + toastNotifications.addDanger( i18n.translate('xpack.ml.dataframe.analyticsList.deleteAnalyticsErrorMessage', { defaultMessage: - 'An error occurred deleting the data frame analytics {analyticsId}: {error}', - values: { analyticsId: d.config.id, error: JSON.stringify(e) }, + 'An error occurred deleting the data frame analytics job {analyticsId}: {error}', + values: { analyticsId: d.config.id, error }, }) ); } refreshAnalyticsList$.next(REFRESH_ANALYTICS_LIST_STATE.REFRESH); }; + +export const deleteAnalyticsAndDestIndex = async ( + d: DataFrameAnalyticsListRow, + deleteDestIndex: boolean, + deleteDestIndexPattern: boolean +) => { + const toastNotifications = getToastNotifications(); + const destinationIndex = Array.isArray(d.config.dest.index) + ? d.config.dest.index[0] + : d.config.dest.index; + try { + if (isDataFrameAnalyticsFailed(d.stats.state)) { + await ml.dataFrameAnalytics.stopDataFrameAnalytics(d.config.id, true); + } + const status = await ml.dataFrameAnalytics.deleteDataFrameAnalyticsAndDestIndex( + d.config.id, + deleteDestIndex, + deleteDestIndexPattern + ); + if (status.analyticsJobDeleted?.success) { + toastNotifications.addSuccess( + i18n.translate('xpack.ml.dataframe.analyticsList.deleteAnalyticsSuccessMessage', { + defaultMessage: 'Request to delete data frame analytics job {analyticsId} acknowledged.', + values: { analyticsId: d.config.id }, + }) + ); + } + if (status.analyticsJobDeleted?.error) { + const error = extractErrorMessage(status.analyticsJobDeleted.error); + toastNotifications.addDanger( + i18n.translate('xpack.ml.dataframe.analyticsList.deleteAnalyticsErrorMessage', { + defaultMessage: + 'An error occurred deleting the data frame analytics job {analyticsId}: {error}', + values: { analyticsId: d.config.id, error }, + }) + ); + } + + if (status.destIndexDeleted?.success) { + toastNotifications.addSuccess( + i18n.translate('xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexSuccessMessage', { + defaultMessage: 'Request to delete destination index {destinationIndex} acknowledged.', + values: { destinationIndex }, + }) + ); + } + if (status.destIndexDeleted?.error) { + const error = extractErrorMessage(status.destIndexDeleted.error); + toastNotifications.addDanger( + i18n.translate('xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexErrorMessage', { + defaultMessage: + 'An error occurred deleting destination index {destinationIndex}: {error}', + values: { destinationIndex, error }, + }) + ); + } + + if (status.destIndexPatternDeleted?.success) { + toastNotifications.addSuccess( + i18n.translate( + 'xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexPatternSuccessMessage', + { + defaultMessage: 'Request to delete index pattern {destinationIndex} acknowledged.', + values: { destinationIndex }, + } + ) + ); + } + if (status.destIndexPatternDeleted?.error) { + const error = extractErrorMessage(status.destIndexPatternDeleted.error); + toastNotifications.addDanger( + i18n.translate( + 'xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexPatternErrorMessage', + { + defaultMessage: 'An error occurred deleting index pattern {destinationIndex}: {error}', + values: { destinationIndex, error }, + } + ) + ); + } + } catch (e) { + const error = extractErrorMessage(e); + + toastNotifications.addDanger( + i18n.translate('xpack.ml.dataframe.analyticsList.deleteAnalyticsErrorMessage', { + defaultMessage: + 'An error occurred deleting the data frame analytics job {analyticsId}: {error}', + values: { analyticsId: d.config.id, error }, + }) + ); + } + refreshAnalyticsList$.next(REFRESH_ANALYTICS_LIST_STATE.REFRESH); +}; + +export const canDeleteIndex = async (indexName: string) => { + const toastNotifications = getToastNotifications(); + try { + const privilege = await ml.hasPrivileges({ + index: [ + { + names: [indexName], // uses wildcard + privileges: ['delete_index'], + }, + ], + }); + if (!privilege) { + return false; + } + return privilege.securityDisabled === true || privilege.has_all_requested === true; + } catch (e) { + const error = extractErrorMessage(e); + toastNotifications.addDanger( + i18n.translate('xpack.ml.dataframe.analyticsList.deleteAnalyticsPrivilegeErrorMessage', { + defaultMessage: 'User does not have permission to delete index {indexName}: {error}', + values: { indexName, error }, + }) + ); + } +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/index.ts index 0d1a87e7c4c1..68aa58e7e1f1 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/index.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/index.ts @@ -3,8 +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. */ - export { getAnalyticsFactory } from './get_analytics'; -export { deleteAnalytics } from './delete_analytics'; +export { deleteAnalytics, deleteAnalyticsAndDestIndex, canDeleteIndex } from './delete_analytics'; export { startAnalytics } from './start_analytics'; export { stopAnalytics } from './stop_analytics'; diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/data_frame_analytics.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/data_frame_analytics.ts index 89950a659f60..7cdd5478e398 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/data_frame_analytics.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/data_frame_analytics.ts @@ -10,6 +10,7 @@ import { basePath } from './index'; import { DataFrameAnalyticsStats } from '../../data_frame_analytics/pages/analytics_management/components/analytics_list/common'; import { DataFrameAnalyticsConfig } from '../../data_frame_analytics/common'; import { DeepPartial } from '../../../../common/types/common'; +import { DeleteDataFrameAnalyticsWithIndexStatus } from '../../../../common/types/data_frame_analytics'; export interface GetDataFrameAnalyticsStatsResponseOk { node_failures?: object; @@ -32,6 +33,13 @@ interface GetDataFrameAnalyticsResponse { data_frame_analytics: DataFrameAnalyticsConfig[]; } +interface DeleteDataFrameAnalyticsWithIndexResponse { + acknowledged: boolean; + analyticsJobDeleted: DeleteDataFrameAnalyticsWithIndexStatus; + destIndexDeleted: DeleteDataFrameAnalyticsWithIndexStatus; + destIndexPatternDeleted: DeleteDataFrameAnalyticsWithIndexStatus; +} + export const dataFrameAnalytics = { getDataFrameAnalytics(analyticsId?: string) { const analyticsIdString = analyticsId !== undefined ? `/${analyticsId}` : ''; @@ -86,6 +94,17 @@ export const dataFrameAnalytics = { method: 'DELETE', }); }, + deleteDataFrameAnalyticsAndDestIndex( + analyticsId: string, + deleteDestIndex: boolean, + deleteDestIndexPattern: boolean + ) { + return http({ + path: `${basePath()}/data_frame/analytics/${analyticsId}`, + query: { deleteDestIndex, deleteDestIndexPattern }, + method: 'DELETE', + }); + }, startDataFrameAnalytics(analyticsId: string) { return http({ path: `${basePath()}/data_frame/analytics/${analyticsId}/_start`, diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts index 16e25067fd91..e2569f6217b3 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts @@ -95,7 +95,6 @@ export const jobs = { body, }); }, - closeJobs(jobIds: string[]) { const body = JSON.stringify({ jobIds }); return http({ diff --git a/x-pack/plugins/ml/public/application/util/error_utils.ts b/x-pack/plugins/ml/public/application/util/error_utils.ts new file mode 100644 index 000000000000..2ce8f4ffc583 --- /dev/null +++ b/x-pack/plugins/ml/public/application/util/error_utils.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 { CustomHttpResponseOptions, ResponseError } from 'kibana/server'; + +export const extractErrorMessage = ( + error: CustomHttpResponseOptions | undefined | string +): string | undefined => { + if (typeof error === 'string') { + return error; + } + + if (error?.body) { + if (typeof error.body === 'string') { + return error.body; + } + if (typeof error.body === 'object' && 'message' in error.body) { + if (typeof error.body.message === 'string') { + return error.body.message; + } + // @ts-ignore + if (typeof (error.body.message?.msg === 'string')) { + // @ts-ignore + return error.body.message?.msg; + } + } + } + return undefined; +}; diff --git a/x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts b/x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts new file mode 100644 index 000000000000..d1a4df768a6a --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.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 { SavedObjectsClientContract } from 'kibana/server'; +import { IIndexPattern } from 'src/plugins/data/server'; + +export class IndexPatternHandler { + constructor(private savedObjectsClient: SavedObjectsClientContract) {} + // returns a id based on an index pattern name + async getIndexPatternId(indexName: string) { + const response = await this.savedObjectsClient.find({ + type: 'index-pattern', + perPage: 10, + search: `"${indexName}"`, + searchFields: ['title'], + fields: ['title'], + }); + + const ip = response.saved_objects.find( + (obj) => obj.attributes.title.toLowerCase() === indexName.toLowerCase() + ); + + return ip?.id; + } + + async deleteIndexPatternById(indexId: string) { + return await this.savedObjectsClient.delete('index-pattern', indexId); + } +} diff --git a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts index 894c4739ef96..e2601c7ad6a2 100644 --- a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts +++ b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { RequestHandlerContext } from 'kibana/server'; import { wrapError } from '../client/error_wrapper'; import { analyticsAuditMessagesProvider } from '../models/data_frame_analytics/analytics_audit_messages'; import { RouteInitialization } from '../types'; @@ -13,12 +14,48 @@ import { dataAnalyticsExplainSchema, analyticsIdSchema, stopsDataFrameAnalyticsJobQuerySchema, + deleteDataFrameAnalyticsJobSchema, } from './schemas/data_analytics_schema'; +import { IndexPatternHandler } from '../models/data_frame_analytics/index_patterns'; +import { DeleteDataFrameAnalyticsWithIndexStatus } from '../../common/types/data_frame_analytics'; + +function getIndexPatternId(context: RequestHandlerContext, patternName: string) { + const iph = new IndexPatternHandler(context.core.savedObjects.client); + return iph.getIndexPatternId(patternName); +} + +function deleteDestIndexPatternById(context: RequestHandlerContext, indexPatternId: string) { + const iph = new IndexPatternHandler(context.core.savedObjects.client); + return iph.deleteIndexPatternById(indexPatternId); +} /** * Routes for the data frame analytics */ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitialization) { + async function userCanDeleteIndex( + context: RequestHandlerContext, + destinationIndex: string + ): Promise { + if (!mlLicense.isSecurityEnabled()) { + return true; + } + const privilege = await context.ml!.mlClient.callAsCurrentUser('ml.privilegeCheck', { + body: { + index: [ + { + names: [destinationIndex], // uses wildcard + privileges: ['delete_index'], + }, + ], + }, + }); + if (!privilege) { + return false; + } + return privilege.has_all_requested === true; + } + /** * @apiGroup DataFrameAnalytics * @@ -277,6 +314,7 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat path: '/api/ml/data_frame/analytics/{analyticsId}', validate: { params: analyticsIdSchema, + query: deleteDataFrameAnalyticsJobSchema, }, options: { tags: ['access:ml:canDeleteDataFrameAnalytics'], @@ -285,12 +323,78 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat mlLicense.fullLicenseAPIGuard(async (context, request, response) => { try { const { analyticsId } = request.params; - const results = await context.ml!.mlClient.callAsCurrentUser( - 'ml.deleteDataFrameAnalytics', - { + const { deleteDestIndex, deleteDestIndexPattern } = request.query; + let destinationIndex: string | undefined; + const analyticsJobDeleted: DeleteDataFrameAnalyticsWithIndexStatus = { success: false }; + const destIndexDeleted: DeleteDataFrameAnalyticsWithIndexStatus = { success: false }; + const destIndexPatternDeleted: DeleteDataFrameAnalyticsWithIndexStatus = { + success: false, + }; + + // Check if analyticsId is valid and get destination index + if (deleteDestIndex || deleteDestIndexPattern) { + try { + const dfa = await context.ml!.mlClient.callAsCurrentUser('ml.getDataFrameAnalytics', { + analyticsId, + }); + if (Array.isArray(dfa.data_frame_analytics) && dfa.data_frame_analytics.length > 0) { + destinationIndex = dfa.data_frame_analytics[0].dest.index; + } + } catch (e) { + return response.customError(wrapError(e)); + } + + // If user checks box to delete the destinationIndex associated with the job + if (destinationIndex && deleteDestIndex) { + // Verify if user has privilege to delete the destination index + const userCanDeleteDestIndex = await userCanDeleteIndex(context, destinationIndex); + // If user does have privilege to delete the index, then delete the index + if (userCanDeleteDestIndex) { + try { + await context.ml!.mlClient.callAsCurrentUser('indices.delete', { + index: destinationIndex, + }); + destIndexDeleted.success = true; + } catch (deleteIndexError) { + destIndexDeleted.error = wrapError(deleteIndexError); + } + } else { + return response.forbidden(); + } + } + + // Delete the index pattern if there's an index pattern that matches the name of dest index + if (destinationIndex && deleteDestIndexPattern) { + try { + const indexPatternId = await getIndexPatternId(context, destinationIndex); + if (indexPatternId) { + await deleteDestIndexPatternById(context, indexPatternId); + } + destIndexPatternDeleted.success = true; + } catch (deleteDestIndexPatternError) { + destIndexPatternDeleted.error = wrapError(deleteDestIndexPatternError); + } + } + } + // Grab the target index from the data frame analytics job id + // Delete the data frame analytics + + try { + await context.ml!.mlClient.callAsCurrentUser('ml.deleteDataFrameAnalytics', { analyticsId, + }); + analyticsJobDeleted.success = true; + } catch (deleteDFAError) { + analyticsJobDeleted.error = wrapError(deleteDFAError); + if (analyticsJobDeleted.error.statusCode === 404) { + return response.notFound(); } - ); + } + const results = { + analyticsJobDeleted, + destIndexDeleted, + destIndexPatternDeleted, + }; return response.ok({ body: results, }); diff --git a/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts b/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts index f1d4947a7abc..0b2469c10357 100644 --- a/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts @@ -60,6 +60,14 @@ export const analyticsIdSchema = schema.object({ analyticsId: schema.string(), }); +export const deleteDataFrameAnalyticsJobSchema = schema.object({ + /** + * Analytics Destination Index + */ + deleteDestIndex: schema.maybe(schema.boolean()), + deleteDestIndexPattern: schema.maybe(schema.boolean()), +}); + export const stopsDataFrameAnalyticsJobQuerySchema = schema.object({ force: schema.maybe(schema.boolean()), }); diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/delete.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/delete.ts new file mode 100644 index 000000000000..23bff0d0c285 --- /dev/null +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/delete.ts @@ -0,0 +1,218 @@ +/* + * 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { USER } from '../../../../functional/services/ml/security_common'; +import { DataFrameAnalyticsConfig } from '../../../../../plugins/ml/public/application/data_frame_analytics/common'; +import { DeepPartial } from '../../../../../plugins/ml/common/types/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; + +export default ({ getService }: FtrProviderContext) => { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertestWithoutAuth'); + const ml = getService('ml'); + + const jobId = `bm_${Date.now()}`; + const generateDestinationIndex = (analyticsId: string) => `user-${analyticsId}`; + const commonJobConfig = { + source: { + index: ['ft_bank_marketing'], + query: { + match_all: {}, + }, + }, + analysis: { + classification: { + dependent_variable: 'y', + training_percent: 20, + }, + }, + analyzed_fields: { + includes: [], + excludes: [], + }, + model_memory_limit: '350mb', + }; + + const testJobConfigs: Array> = [ + 'Test delete job only', + 'Test delete job and target index', + 'Test delete job and index pattern', + 'Test delete job, target index, and index pattern', + ].map((description, idx) => { + const analyticsId = `${jobId}_${idx + 1}`; + return { + id: analyticsId, + description, + dest: { + index: generateDestinationIndex(analyticsId), + results_field: 'ml', + }, + ...commonJobConfig, + }; + }); + + async function createJobs(mockJobConfigs: Array>) { + for (const jobConfig of mockJobConfigs) { + await ml.api.createDataFrameAnalyticsJob(jobConfig as DataFrameAnalyticsConfig); + } + } + + describe('DELETE data_frame/analytics', () => { + before(async () => { + await esArchiver.loadIfNeeded('ml/bm_classification'); + await ml.testResources.setKibanaTimeZoneToUTC(); + await createJobs(testJobConfigs); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + }); + + describe('DeleteDataFrameAnalytics', () => { + it('should delete analytics jobs by id', async () => { + const analyticsId = `${jobId}_1`; + const { body } = await supertest + .delete(`/api/ml/data_frame/analytics/${analyticsId}`) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .expect(200); + + expect(body.analyticsJobDeleted.success).to.eql(true); + await ml.api.waitForDataFrameAnalyticsJobNotToExist(analyticsId); + }); + + it('should not allow to retrieve analytics jobs for unauthorized user', async () => { + const analyticsId = `${jobId}_2`; + const { body } = await supertest + .delete(`/api/ml/data_frame/analytics/${analyticsId}`) + .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) + .set(COMMON_REQUEST_HEADERS) + .expect(404); + + expect(body.error).to.eql('Not Found'); + expect(body.message).to.eql('Not Found'); + await ml.api.waitForDataFrameAnalyticsJobToExist(analyticsId); + }); + + it('should not allow to retrieve analytics jobs for the user with only view permission', async () => { + const analyticsId = `${jobId}_2`; + const { body } = await supertest + .delete(`/api/ml/data_frame/analytics/${analyticsId}`) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .expect(404); + + expect(body.error).to.eql('Not Found'); + expect(body.message).to.eql('Not Found'); + await ml.api.waitForDataFrameAnalyticsJobToExist(analyticsId); + }); + + it('should show 404 error if job does not exist or has already been deleted', async () => { + const { body } = await supertest + .delete(`/api/ml/data_frame/analytics/${jobId}_invalid`) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .expect(404); + + expect(body.error).to.eql('Not Found'); + expect(body.message).to.eql('Not Found'); + }); + + describe('with deleteDestIndex setting', function () { + const analyticsId = `${jobId}_2`; + const destinationIndex = generateDestinationIndex(analyticsId); + + before(async () => { + await ml.api.createIndices(destinationIndex); + await ml.api.assertIndicesExist(destinationIndex); + }); + + after(async () => { + await ml.api.deleteIndices(destinationIndex); + }); + + it('should delete job and destination index by id', async () => { + const { body } = await supertest + .delete(`/api/ml/data_frame/analytics/${analyticsId}`) + .query({ deleteDestIndex: true }) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .expect(200); + + expect(body.analyticsJobDeleted.success).to.eql(true); + expect(body.destIndexDeleted.success).to.eql(true); + expect(body.destIndexPatternDeleted.success).to.eql(false); + await ml.api.waitForDataFrameAnalyticsJobNotToExist(analyticsId); + await ml.api.assertIndicesNotToExist(destinationIndex); + }); + }); + + describe('with deleteDestIndexPattern setting', function () { + const analyticsId = `${jobId}_3`; + const destinationIndex = generateDestinationIndex(analyticsId); + + before(async () => { + // Mimic real job by creating index pattern after job is created + await ml.testResources.createIndexPatternIfNeeded(destinationIndex); + }); + + after(async () => { + await ml.testResources.deleteIndexPattern(destinationIndex); + }); + + it('should delete job and index pattern by id', async () => { + const { body } = await supertest + .delete(`/api/ml/data_frame/analytics/${analyticsId}`) + .query({ deleteDestIndexPattern: true }) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .expect(200); + + expect(body.analyticsJobDeleted.success).to.eql(true); + expect(body.destIndexDeleted.success).to.eql(false); + expect(body.destIndexPatternDeleted.success).to.eql(true); + await ml.api.waitForDataFrameAnalyticsJobNotToExist(analyticsId); + await ml.testResources.assertIndexPatternNotExist(destinationIndex); + }); + }); + + describe('with deleteDestIndex & deleteDestIndexPattern setting', function () { + const analyticsId = `${jobId}_4`; + const destinationIndex = generateDestinationIndex(analyticsId); + + before(async () => { + // Mimic real job by creating target index & index pattern after DFA job is created + await ml.api.createIndices(destinationIndex); + await ml.api.assertIndicesExist(destinationIndex); + await ml.testResources.createIndexPatternIfNeeded(destinationIndex); + }); + + after(async () => { + await ml.api.deleteIndices(destinationIndex); + await ml.testResources.deleteIndexPattern(destinationIndex); + }); + + it('deletes job, target index, and index pattern by id', async () => { + const { body } = await supertest + .delete(`/api/ml/data_frame/analytics/${analyticsId}`) + .query({ deleteDestIndex: true, deleteDestIndexPattern: true }) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .expect(200); + + expect(body.analyticsJobDeleted.success).to.eql(true); + expect(body.destIndexDeleted.success).to.eql(true); + expect(body.destIndexPatternDeleted.success).to.eql(true); + await ml.api.waitForDataFrameAnalyticsJobNotToExist(analyticsId); + await ml.api.assertIndicesNotToExist(destinationIndex); + await ml.testResources.assertIndexPatternNotExist(destinationIndex); + }); + }); + }); + }); +}; diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/index.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/index.ts index 9e0f952ad501..6693561076fd 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/index.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/index.ts @@ -9,5 +9,6 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('data frame analytics', function () { loadTestFile(require.resolve('./get')); + loadTestFile(require.resolve('./delete')); }); } diff --git a/x-pack/test/functional/services/ml/api.ts b/x-pack/test/functional/services/ml/api.ts index 897f37821001..fc2ce4bb16b9 100644 --- a/x-pack/test/functional/services/ml/api.ts +++ b/x-pack/test/functional/services/ml/api.ts @@ -9,9 +9,9 @@ import { DataFrameAnalyticsConfig } from '../../../../plugins/ml/public/applicat import { FtrProviderContext } from '../../ftr_provider_context'; -import { JOB_STATE, DATAFEED_STATE } from '../../../../plugins/ml/common/constants/states'; +import { DATAFEED_STATE, JOB_STATE } from '../../../../plugins/ml/common/constants/states'; import { DATA_FRAME_TASK_STATE } from '../../../../plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common'; -import { Job, Datafeed } from '../../../../plugins/ml/common/types/anomaly_detection_jobs'; +import { Datafeed, Job } from '../../../../plugins/ml/common/types/anomaly_detection_jobs'; export type MlApi = ProvidedType; @@ -110,6 +110,21 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { ); }, + async createIndices(indices: string) { + log.debug(`Creating indices: '${indices}'...`); + if ((await es.indices.exists({ index: indices, allowNoIndices: false })) === true) { + log.debug(`Indices '${indices}' already exist. Nothing to create.`); + return; + } + + const createResponse = await es.indices.create({ index: indices }); + expect(createResponse) + .to.have.property('acknowledged') + .eql(true, 'Response for create request indices should be acknowledged.'); + + await this.assertIndicesExist(indices); + }, + async deleteIndices(indices: string) { log.debug(`Deleting indices: '${indices}'...`); if ((await es.indices.exists({ index: indices, allowNoIndices: false })) === false) { @@ -122,15 +137,9 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { }); expect(deleteResponse) .to.have.property('acknowledged') - .eql(true, 'Response for delete request should be acknowledged'); + .eql(true, 'Response for delete request should be acknowledged.'); - await retry.waitForWithTimeout(`'${indices}' indices to be deleted`, 30 * 1000, async () => { - if ((await es.indices.exists({ index: indices, allowNoIndices: false })) === false) { - return true; - } else { - throw new Error(`expected indices '${indices}' to be deleted`); - } - }); + await this.assertIndicesNotToExist(indices); }, async cleanMlIndices() { @@ -251,6 +260,16 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { }); }, + async assertIndicesNotToExist(indices: string) { + await retry.tryForTime(30 * 1000, async () => { + if ((await es.indices.exists({ index: indices, allowNoIndices: false })) === false) { + return true; + } else { + throw new Error(`indices '${indices}' should not exist`); + } + }); + }, + async assertIndicesNotEmpty(indices: string) { await retry.tryForTime(30 * 1000, async () => { const response = await es.search({ @@ -394,9 +413,9 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { await this.waitForJobState(jobConfig.job_id, JOB_STATE.CLOSED); }, - async getDataFrameAnalyticsJob(analyticsId: string) { + async getDataFrameAnalyticsJob(analyticsId: string, statusCode = 200) { log.debug(`Fetching data frame analytics job '${analyticsId}'...`); - return await esSupertest.get(`/_ml/data_frame/analytics/${analyticsId}`).expect(200); + return await esSupertest.get(`/_ml/data_frame/analytics/${analyticsId}`).expect(statusCode); }, async waitForDataFrameAnalyticsJobToExist(analyticsId: string) { @@ -409,6 +428,16 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { }); }, + async waitForDataFrameAnalyticsJobNotToExist(analyticsId: string) { + await retry.waitForWithTimeout(`'${analyticsId}' not to exist`, 5 * 1000, async () => { + if (await this.getDataFrameAnalyticsJob(analyticsId, 404)) { + return true; + } else { + throw new Error(`expected data frame analytics job '${analyticsId}' not to exist`); + } + }); + }, + async createDataFrameAnalyticsJob(jobConfig: DataFrameAnalyticsConfig) { const { id: analyticsId, ...analyticsConfig } = jobConfig; log.debug(`Creating data frame analytic job with id '${analyticsId}'...`); diff --git a/x-pack/test/functional/services/ml/test_resources.ts b/x-pack/test/functional/services/ml/test_resources.ts index d349416ec90f..739fd844f119 100644 --- a/x-pack/test/functional/services/ml/test_resources.ts +++ b/x-pack/test/functional/services/ml/test_resources.ts @@ -5,7 +5,6 @@ */ import { ProvidedType } from '@kbn/test/types/ftr'; - import { savedSearches } from './test_resources_data'; import { COMMON_REQUEST_HEADERS } from './common'; import { FtrProviderContext } from '../../ftr_provider_context'; @@ -24,6 +23,7 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider const kibanaServer = getService('kibanaServer'); const log = getService('log'); const supertest = getService('supertest'); + const retry = getService('retry'); return { async setKibanaTimeZoneToUTC() { @@ -98,6 +98,21 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider } }, + async assertIndexPatternNotExist(title: string) { + await retry.waitForWithTimeout( + `index pattern '${title}' to not exist`, + 5 * 1000, + async () => { + const indexPatternId = await this.getIndexPatternId(title); + if (!indexPatternId) { + return true; + } else { + throw new Error(`Index pattern '${title}' should not exist.`); + } + } + ); + }, + async createSavedSearch(title: string, body: object): Promise { log.debug(`Creating saved search with title '${title}'`); From daf26b90667677b310ca0d0ae28be47c5d58a734 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen <43350163+qn895@users.noreply.github.com> Date: Mon, 1 Jun 2020 11:13:59 -0500 Subject: [PATCH 03/50] [ML] Add minor refresh button to DFA and AD Job Messages tabs (#67750) * [ML] Add minor refresh button to DFA and AD Job Messages tabs * [ML] Update refresh logic for DFA [ML] Update refresh logic for DFA * [ML] Update fetchMessages callback Co-authored-by: Elastic Machine --- .../components/job_messages/job_messages.tsx | 23 ++++++++++++++++--- .../expanded_row_messages_pane.tsx | 11 ++++++--- .../job_details/job_messages_pane.tsx | 15 ++++++++---- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx b/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx index 9cea47ded09b..fd2b7902833a 100644 --- a/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx +++ b/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx @@ -6,7 +6,7 @@ import React, { FC } from 'react'; -import { EuiSpacer, EuiInMemoryTable } from '@elastic/eui'; +import { EuiSpacer, EuiInMemoryTable, EuiButtonIcon, EuiToolTip } from '@elastic/eui'; // @ts-ignore import { formatDate } from '@elastic/eui/lib/services/format'; import { i18n } from '@kbn/i18n'; @@ -21,16 +21,33 @@ interface JobMessagesProps { messages: JobMessage[]; loading: boolean; error: string; + refreshMessage?: React.MouseEventHandler; } /** * Component for rendering job messages for anomaly detection * and data frame analytics jobs. */ -export const JobMessages: FC = ({ messages, loading, error }) => { +export const JobMessages: FC = ({ messages, loading, error, refreshMessage }) => { const columns = [ { - name: '', + name: refreshMessage ? ( + + + + ) : ( + '' + ), render: (message: JobMessage) => , width: `${theme.euiSizeL}`, }, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row_messages_pane.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row_messages_pane.tsx index fc860251bf83..0dd9eba172e1 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row_messages_pane.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row_messages_pane.tsx @@ -22,7 +22,6 @@ export const ExpandedRowMessagesPane: FC = ({ analyticsId }) => { const getMessagesFactory = () => { let concurrentLoads = 0; - return async function getMessages() { try { concurrentLoads++; @@ -52,8 +51,14 @@ export const ExpandedRowMessagesPane: FC = ({ analyticsId }) => { } }; }; - useRefreshAnalyticsList({ onRefresh: getMessagesFactory() }); - return ; + return ( + + ); }; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_messages_pane.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_messages_pane.tsx index fbb64db94cd5..486de90d2299 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_messages_pane.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_messages_pane.tsx @@ -4,12 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, useEffect, useState } from 'react'; - +import React, { FC, useCallback, useEffect, useState } from 'react'; import { ml } from '../../../../services/ml_api_service'; import { JobMessages } from '../../../../components/job_messages'; import { JobMessage } from '../../../../../../common/types/audit_message'; - interface JobMessagesPaneProps { jobId: string; } @@ -32,9 +30,18 @@ export const JobMessagesPane: FC = ({ jobId }) => { } }; + const refreshMessage = useCallback(fetchMessages, [jobId]); + useEffect(() => { fetchMessages(); }, []); - return ; + return ( + + ); }; From ce47ef5d24656867121e8bd2c88cd3a86286e4c4 Mon Sep 17 00:00:00 2001 From: Brandon Kobel Date: Mon, 1 Jun 2020 10:09:07 -0700 Subject: [PATCH 04/50] Updating the licensed feature usage API response format (#67712) Co-authored-by: Elastic Machine --- .../licensing/server/routes/feature_usage.ts | 16 ++--- .../services/feature_usage_service.test.ts | 70 ++++++++++++------- .../server/services/feature_usage_service.ts | 37 ++++++---- .../feature_usage_test/server/plugin.ts | 6 +- .../licensed_feature_usage/feature_usage.ts | 25 +++++-- 5 files changed, 98 insertions(+), 56 deletions(-) diff --git a/x-pack/plugins/licensing/server/routes/feature_usage.ts b/x-pack/plugins/licensing/server/routes/feature_usage.ts index 5fbfbc3f577b..fa26d09903dc 100644 --- a/x-pack/plugins/licensing/server/routes/feature_usage.ts +++ b/x-pack/plugins/licensing/server/routes/feature_usage.ts @@ -15,15 +15,13 @@ export function registerFeatureUsageRoute( async (context, request, response) => { const [, , { featureUsage }] = await getStartServices(); return response.ok({ - body: [...featureUsage.getLastUsages().entries()].reduce( - (res, [featureName, lastUsage]) => { - return { - ...res, - [featureName]: new Date(lastUsage).toISOString(), - }; - }, - {} - ), + body: { + features: featureUsage.getLastUsages().map((usage) => ({ + name: usage.name, + last_used: usage.lastUsed, + license_level: usage.licenseType, + })), + }, }); } ); diff --git a/x-pack/plugins/licensing/server/services/feature_usage_service.test.ts b/x-pack/plugins/licensing/server/services/feature_usage_service.test.ts index f0ef0dbec0b2..39f7aa6503b3 100644 --- a/x-pack/plugins/licensing/server/services/feature_usage_service.test.ts +++ b/x-pack/plugins/licensing/server/services/feature_usage_service.test.ts @@ -17,16 +17,13 @@ describe('FeatureUsageService', () => { jest.restoreAllMocks(); }); - const toObj = (map: ReadonlyMap): Record => - Object.fromEntries(map.entries()); - describe('#setup', () => { describe('#register', () => { it('throws when registering the same feature twice', () => { const setup = service.setup(); - setup.register('foo'); + setup.register('foo', 'basic'); expect(() => { - setup.register('foo'); + setup.register('foo', 'basic'); }).toThrowErrorMatchingInlineSnapshot(`"Feature 'foo' has already been registered."`); }); }); @@ -36,32 +33,50 @@ describe('FeatureUsageService', () => { describe('#notifyUsage', () => { it('allows to notify a feature usage', () => { const setup = service.setup(); - setup.register('feature'); + setup.register('feature', 'basic'); const start = service.start(); start.notifyUsage('feature', 127001); - expect(start.getLastUsages().get('feature')).toBe(127001); + expect(start.getLastUsages()).toEqual([ + { + lastUsed: new Date(127001), + licenseType: 'basic', + name: 'feature', + }, + ]); }); it('can receive a Date object', () => { const setup = service.setup(); - setup.register('feature'); + setup.register('feature', 'basic'); const start = service.start(); const usageTime = new Date(2015, 9, 21, 17, 54, 12); start.notifyUsage('feature', usageTime); - expect(start.getLastUsages().get('feature')).toBe(usageTime.getTime()); + expect(start.getLastUsages()).toEqual([ + { + lastUsed: usageTime, + licenseType: 'basic', + name: 'feature', + }, + ]); }); it('uses the current time when `usedAt` is unspecified', () => { jest.spyOn(Date, 'now').mockReturnValue(42); const setup = service.setup(); - setup.register('feature'); + setup.register('feature', 'basic'); const start = service.start(); start.notifyUsage('feature'); - expect(start.getLastUsages().get('feature')).toBe(42); + expect(start.getLastUsages()).toEqual([ + { + lastUsed: new Date(42), + licenseType: 'basic', + name: 'feature', + }, + ]); }); it('throws when notifying for an unregistered feature', () => { @@ -76,40 +91,41 @@ describe('FeatureUsageService', () => { describe('#getLastUsages', () => { it('returns the last usage for all used features', () => { const setup = service.setup(); - setup.register('featureA'); - setup.register('featureB'); + setup.register('featureA', 'basic'); + setup.register('featureB', 'gold'); const start = service.start(); start.notifyUsage('featureA', 127001); start.notifyUsage('featureB', 6666); - expect(toObj(start.getLastUsages())).toEqual({ - featureA: 127001, - featureB: 6666, - }); + expect(start.getLastUsages()).toEqual([ + { lastUsed: new Date(127001), licenseType: 'basic', name: 'featureA' }, + { lastUsed: new Date(6666), licenseType: 'gold', name: 'featureB' }, + ]); }); it('returns the last usage even after notifying for an older usage', () => { const setup = service.setup(); - setup.register('featureA'); + setup.register('featureA', 'basic'); const start = service.start(); start.notifyUsage('featureA', 1000); start.notifyUsage('featureA', 500); - expect(toObj(start.getLastUsages())).toEqual({ - featureA: 1000, - }); + expect(start.getLastUsages()).toEqual([ + { lastUsed: new Date(1000), licenseType: 'basic', name: 'featureA' }, + ]); }); - it('does not return entries for unused registered features', () => { + it('returns entries for unused registered features', () => { const setup = service.setup(); - setup.register('featureA'); - setup.register('featureB'); + setup.register('featureA', 'basic'); + setup.register('featureB', 'gold'); const start = service.start(); start.notifyUsage('featureA', 127001); - expect(toObj(start.getLastUsages())).toEqual({ - featureA: 127001, - }); + expect(start.getLastUsages()).toEqual([ + { lastUsed: new Date(127001), licenseType: 'basic', name: 'featureA' }, + { lastUsed: null, licenseType: 'gold', name: 'featureB' }, + ]); }); }); }); diff --git a/x-pack/plugins/licensing/server/services/feature_usage_service.ts b/x-pack/plugins/licensing/server/services/feature_usage_service.ts index 0c6613d37f63..9bfcb28f36b2 100644 --- a/x-pack/plugins/licensing/server/services/feature_usage_service.ts +++ b/x-pack/plugins/licensing/server/services/feature_usage_service.ts @@ -5,13 +5,20 @@ */ import { isDate } from 'lodash'; +import { LicenseType } from '../../common/types'; /** @public */ export interface FeatureUsageServiceSetup { /** * Register a feature to be able to notify of it's usages using the {@link FeatureUsageServiceStart | service start contract}. */ - register(featureName: string): void; + register(featureName: string, licenseType: LicenseType): void; +} + +export interface LastFeatureUsage { + name: string; + lastUsed: Date | null; + licenseType: LicenseType; } /** @public */ @@ -27,20 +34,23 @@ export interface FeatureUsageServiceStart { * Return a map containing last usage timestamp for all features. * Features that were not used yet do not appear in the map. */ - getLastUsages(): ReadonlyMap; + getLastUsages(): LastFeatureUsage[]; } export class FeatureUsageService { - private readonly features: string[] = []; - private readonly lastUsages = new Map(); + private readonly lastUsages = new Map(); public setup(): FeatureUsageServiceSetup { return { - register: (featureName) => { - if (this.features.includes(featureName)) { + register: (featureName, licenseType) => { + if (this.lastUsages.has(featureName)) { throw new Error(`Feature '${featureName}' has already been registered.`); } - this.features.push(featureName); + this.lastUsages.set(featureName, { + name: featureName, + lastUsed: null, + licenseType, + }); }, }; } @@ -48,16 +58,17 @@ export class FeatureUsageService { public start(): FeatureUsageServiceStart { return { notifyUsage: (featureName, usedAt = Date.now()) => { - if (!this.features.includes(featureName)) { + const usage = this.lastUsages.get(featureName); + if (!usage) { throw new Error(`Feature '${featureName}' is not registered.`); } - if (isDate(usedAt)) { - usedAt = usedAt.getTime(); + + const lastUsed = isDate(usedAt) ? usedAt : new Date(usedAt); + if (usage.lastUsed == null || lastUsed > usage.lastUsed) { + usage.lastUsed = lastUsed; } - const currentValue = this.lastUsages.get(featureName) ?? 0; - this.lastUsages.set(featureName, Math.max(usedAt, currentValue)); }, - getLastUsages: () => new Map(this.lastUsages.entries()), + getLastUsages: () => Array.from(this.lastUsages.values()), }; } } diff --git a/x-pack/test/plugin_api_integration/plugins/feature_usage_test/server/plugin.ts b/x-pack/test/plugin_api_integration/plugins/feature_usage_test/server/plugin.ts index b36d6dca077f..af410d457fc0 100644 --- a/x-pack/test/plugin_api_integration/plugins/feature_usage_test/server/plugin.ts +++ b/x-pack/test/plugin_api_integration/plugins/feature_usage_test/server/plugin.ts @@ -38,9 +38,9 @@ export class FeatureUsageTestPlugin }: CoreSetup, { licensing }: FeatureUsageTestSetupDependencies ) { - licensing.featureUsage.register('test_feature_a'); - licensing.featureUsage.register('test_feature_b'); - licensing.featureUsage.register('test_feature_c'); + licensing.featureUsage.register('Test feature A', 'basic'); + licensing.featureUsage.register('Test feature B', 'gold'); + licensing.featureUsage.register('Test feature C', 'platinum'); registerRoutes(http.createRouter(), getStartServices); diff --git a/x-pack/test/plugin_api_integration/test_suites/licensed_feature_usage/feature_usage.ts b/x-pack/test/plugin_api_integration/test_suites/licensed_feature_usage/feature_usage.ts index dfbc41d883e0..5c8fac9586e3 100644 --- a/x-pack/test/plugin_api_integration/test_suites/licensed_feature_usage/feature_usage.ts +++ b/x-pack/test/plugin_api_integration/test_suites/licensed_feature_usage/feature_usage.ts @@ -20,15 +20,32 @@ export default function ({ getService }: FtrProviderContext) { describe('/api/licensing/feature_usage', () => { it('returns a map of last feature usages', async () => { const timeA = Date.now(); - await notifyUsage('test_feature_a', timeA); + await notifyUsage('Test feature C', timeA); const timeB = Date.now() - 4567; - await notifyUsage('test_feature_b', timeB); + await notifyUsage('Test feature B', timeB); const response = await supertest.get('/api/licensing/feature_usage').expect(200); - expect(response.body.test_feature_a).to.eql(toISO(timeA)); - expect(response.body.test_feature_b).to.eql(toISO(timeB)); + expect(response.body).to.eql({ + features: [ + { + last_used: null, + license_level: 'basic', + name: 'Test feature A', + }, + { + last_used: toISO(timeB), + license_level: 'gold', + name: 'Test feature B', + }, + { + last_used: toISO(timeA), + license_level: 'platinum', + name: 'Test feature C', + }, + ], + }); }); }); } From be51ca6041f77c48c53d6dd4ac30b2510bb73da3 Mon Sep 17 00:00:00 2001 From: Wylie Conlon Date: Mon, 1 Jun 2020 13:33:23 -0400 Subject: [PATCH 05/50] [Lens] Allow visualizations to provide a dimension editor (#67560) * [Lens] Allow visualizations to provide a dimension editor * Update to tab style * Remove table update * Update class name * typecheck fix * Add test * Require each dimension group to enable editor Co-authored-by: Elastic Machine Co-authored-by: Marta Bondyra --- .../config_panel/_layer_panel.scss | 3 + .../config_panel/config_panel.tsx | 3 - .../config_panel/dimension_popover.tsx | 2 +- .../config_panel/layer_panel.test.tsx | 271 ++++++++++++++++++ .../editor_frame/config_panel/layer_panel.tsx | 263 ++++++++++------- .../editor_frame/config_panel/types.ts | 1 + x-pack/plugins/lens/public/types.ts | 21 ++ 7 files changed, 458 insertions(+), 106 deletions(-) create mode 100644 x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_layer_panel.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_layer_panel.scss index 3fbc42f9a25a..924f44a37c45 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_layer_panel.scss +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_layer_panel.scss @@ -31,3 +31,6 @@ min-height: $euiSizeXXL; } +.lnsLayerPanel__styleEditor { + width: $euiSize * 28; +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx index 0d86a051b0fa..e53e465c1895 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx @@ -45,7 +45,6 @@ function LayerPanels( } ) { const { - framePublicAPI, activeVisualization, visualizationState, dispatch, @@ -109,12 +108,10 @@ function LayerPanels( {...props} key={layerId} layerId={layerId} - activeVisualization={activeVisualization} visualizationState={visualizationState} updateVisualization={setVisualizationState} updateDatasource={updateDatasource} updateAll={updateAll} - frame={framePublicAPI} isOnlyLayer={layerIds.length === 1} onRemoveLayer={() => { dispatch({ diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_popover.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_popover.tsx index f89b6ef32d3f..cc8d97a44501 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_popover.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_popover.tsx @@ -36,7 +36,7 @@ export function DimensionPopover({ (popoverState.openId === accessor || (noMatch && popoverState.addingToGroupId === groupId)) } closePopover={() => { - setPopoverState({ isOpen: false, openId: null, addingToGroupId: null }); + setPopoverState({ isOpen: false, openId: null, addingToGroupId: null, tabId: null }); }} button={trigger} anchorPosition="leftUp" 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 new file mode 100644 index 000000000000..1f987f86d395 --- /dev/null +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx @@ -0,0 +1,271 @@ +/* + * 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 { + createMockVisualization, + createMockFramePublicAPI, + createMockDatasource, + DatasourceMock, +} from '../../mocks'; +import { EuiFormRow, EuiPopover } from '@elastic/eui'; +import { mount } from 'enzyme'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { Visualization } from '../../../types'; +import { LayerPanel } from './layer_panel'; +import { coreMock } from 'src/core/public/mocks'; +import { generateId } from '../../../id_generator'; + +jest.mock('../../../id_generator'); + +describe('LayerPanel', () => { + let mockVisualization: jest.Mocked; + let mockDatasource: DatasourceMock; + + function getDefaultProps() { + const frame = createMockFramePublicAPI(); + frame.datasourceLayers = { + first: mockDatasource.publicAPIMock, + }; + return { + layerId: 'first', + activeVisualizationId: 'vis1', + visualizationMap: { + vis1: mockVisualization, + }, + activeDatasourceId: 'ds1', + datasourceMap: { + ds1: mockDatasource, + }, + datasourceStates: { + ds1: { + isLoading: false, + state: 'state', + }, + }, + visualizationState: 'state', + updateVisualization: jest.fn(), + updateDatasource: jest.fn(), + updateAll: jest.fn(), + framePublicAPI: frame, + isOnlyLayer: true, + onRemoveLayer: jest.fn(), + dispatch: jest.fn(), + core: coreMock.createStart(), + }; + } + + beforeEach(() => { + mockVisualization = { + ...createMockVisualization(), + id: 'testVis', + visualizationTypes: [ + { + icon: 'empty', + id: 'testVis', + label: 'TEST1', + }, + ], + }; + + mockVisualization.getLayerIds.mockReturnValue(['first']); + mockDatasource = createMockDatasource('ds1'); + }); + + it('should fail to render if the public API is out of date', () => { + const props = getDefaultProps(); + props.framePublicAPI.datasourceLayers = {}; + const component = mountWithIntl(); + expect(component.isEmptyRender()).toBe(true); + }); + + it('should fail to render if the active visualization is missing', () => { + const component = mountWithIntl( + + ); + expect(component.isEmptyRender()).toBe(true); + }); + + describe('layer reset and remove', () => { + it('should show the reset button when single layer', () => { + const component = mountWithIntl(); + expect(component.find('[data-test-subj="lns_layer_remove"]').first().text()).toContain( + 'Reset layer' + ); + }); + + it('should show the delete button when multiple layers', () => { + const component = mountWithIntl(); + expect(component.find('[data-test-subj="lns_layer_remove"]').first().text()).toContain( + 'Delete layer' + ); + }); + + it('should call the clear callback', () => { + const cb = jest.fn(); + const component = mountWithIntl(); + act(() => { + component.find('[data-test-subj="lns_layer_remove"]').first().simulate('click'); + }); + expect(cb).toHaveBeenCalled(); + }); + }); + + describe('single group', () => { + it('should render the non-editable state', () => { + mockVisualization.getConfiguration.mockReturnValue({ + groups: [ + { + groupLabel: 'A', + groupId: 'a', + accessors: ['x'], + filterOperations: () => true, + supportsMoreColumns: false, + dataTestSubj: 'lnsGroup', + }, + ], + }); + + const component = mountWithIntl(); + + const group = component.find('DragDrop[data-test-subj="lnsGroup"]'); + expect(group).toHaveLength(1); + }); + + it('should render the group with a way to add a new column', () => { + mockVisualization.getConfiguration.mockReturnValue({ + groups: [ + { + groupLabel: 'A', + groupId: 'a', + accessors: [], + filterOperations: () => true, + supportsMoreColumns: true, + dataTestSubj: 'lnsGroup', + }, + ], + }); + + const component = mountWithIntl(); + + const group = component.find('DragDrop[data-test-subj="lnsGroup"]'); + expect(group).toHaveLength(1); + }); + + it('should render the required warning when only one group is configured', () => { + mockVisualization.getConfiguration.mockReturnValue({ + groups: [ + { + groupLabel: 'A', + groupId: 'a', + accessors: ['x'], + filterOperations: () => true, + supportsMoreColumns: false, + dataTestSubj: 'lnsGroup', + }, + { + groupLabel: 'B', + groupId: 'b', + accessors: [], + filterOperations: () => true, + supportsMoreColumns: true, + dataTestSubj: 'lnsGroup', + required: true, + }, + ], + }); + + const component = mountWithIntl(); + + const group = component + .find(EuiFormRow) + .findWhere((e) => e.prop('error') === 'Required dimension'); + expect(group).toHaveLength(1); + }); + + it('should render the datasource and visualization panels inside the dimension popover', () => { + mockVisualization.getConfiguration.mockReturnValueOnce({ + groups: [ + { + groupLabel: 'A', + groupId: 'a', + accessors: ['newid'], + filterOperations: () => true, + supportsMoreColumns: false, + dataTestSubj: 'lnsGroup', + enableDimensionEditor: true, + }, + ], + }); + mockVisualization.renderDimensionEditor = jest.fn(); + + const component = mountWithIntl(); + + const group = component.find('DimensionPopover'); + const panel = mount(group.prop('panel')); + + expect(panel.find('EuiTabbedContent').prop('tabs')).toHaveLength(2); + act(() => { + panel.find('EuiTab#visualization').simulate('click'); + }); + expect(mockVisualization.renderDimensionEditor).toHaveBeenCalledWith( + expect.any(Element), + expect.objectContaining({ + groupId: 'a', + accessor: 'newid', + }) + ); + }); + + it('should keep the popover open when configuring a new dimension', () => { + /** + * The ID generation system for new dimensions has been messy before, so + * this tests that the ID used in the first render is used to keep the popover + * open in future renders + */ + (generateId as jest.Mock).mockReturnValueOnce(`newid`); + (generateId as jest.Mock).mockReturnValueOnce(`bad`); + mockVisualization.getConfiguration.mockReturnValueOnce({ + groups: [ + { + groupLabel: 'A', + groupId: 'a', + accessors: [], + filterOperations: () => true, + supportsMoreColumns: true, + dataTestSubj: 'lnsGroup', + }, + ], + }); + // Normally the configuration would change in response to a state update, + // but this test is updating it directly + mockVisualization.getConfiguration.mockReturnValueOnce({ + groups: [ + { + groupLabel: 'A', + groupId: 'a', + accessors: ['newid'], + filterOperations: () => true, + supportsMoreColumns: false, + dataTestSubj: 'lnsGroup', + }, + ], + }); + + const component = mountWithIntl(); + + const group = component.find('DimensionPopover'); + const triggerButton = mountWithIntl(group.prop('trigger')); + act(() => { + triggerButton.find('[data-test-subj="lns-empty-dimension"]').first().simulate('click'); + }); + component.update(); + + expect(component.find(EuiPopover).prop('isOpen')).toBe(true); + }); + }); +}); 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 814b7fc644c9..bd501db2b752 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 @@ -13,11 +13,12 @@ import { EuiFlexItem, EuiButtonEmpty, EuiFormRow, + EuiTabbedContent, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { NativeRenderer } from '../../../native_renderer'; -import { Visualization, FramePublicAPI, StateSetter } from '../../../types'; +import { StateSetter } from '../../../types'; import { DragContext, DragDrop, ChildDragDropProvider } from '../../../drag_drop'; import { LayerSettings } from './layer_settings'; import { trackUiEvent } from '../../../lens_ui_telemetry'; @@ -27,11 +28,8 @@ import { DimensionPopover } from './dimension_popover'; export function LayerPanel( props: Exclude & { - frame: FramePublicAPI; layerId: string; isOnlyLayer: boolean; - activeVisualization: Visualization; - visualizationState: unknown; updateVisualization: StateSetter; updateDatasource: (datasourceId: string, newState: unknown) => void; updateAll: ( @@ -47,13 +45,19 @@ export function LayerPanel( isOpen: false, openId: null, addingToGroupId: null, + tabId: null, }); - const { framePublicAPI, layerId, activeVisualization, isOnlyLayer, onRemoveLayer } = props; + const { framePublicAPI, layerId, isOnlyLayer, onRemoveLayer } = props; const datasourcePublicAPI = framePublicAPI.datasourceLayers[layerId]; - if (!datasourcePublicAPI) { + if ( + !datasourcePublicAPI || + !props.activeVisualizationId || + !props.visualizationMap[props.activeVisualizationId] + ) { return null; } + const activeVisualization = props.visualizationMap[props.activeVisualizationId]; const layerVisualizationConfigProps = { layerId, dragDropContext, @@ -158,104 +162,156 @@ export function LayerPanel( } > <> - {group.accessors.map((accessor) => ( - { - layerDatasource.onDrop({ - ...layerDatasourceDropProps, - droppedItem, - columnId: accessor, - filterOperations: group.filterOperations, - }); - }} - > - { - if (popoverState.isOpen) { - setPopoverState({ - isOpen: false, - openId: null, - addingToGroupId: null, - }); - } else { - setPopoverState({ - isOpen: true, - openId: accessor, - addingToGroupId: null, // not set for existing dimension - }); - } - }, - }} - /> - } - panel={ - - } - /> + {group.accessors.map((accessor) => { + const tabs = [ + { + id: 'datasource', + name: i18n.translate('xpack.lens.editorFrame.quickFunctionsLabel', { + defaultMessage: 'Quick functions', + }), + content: ( + <> + + + + ), + }, + ]; - { - trackUiEvent('indexpattern_dimension_removed'); - props.updateAll( - datasourceId, - layerDatasource.removeColumn({ - layerId, - columnId: accessor, - prevState: layerDatasourceState, - }), - props.activeVisualization.removeDimension({ - layerId, - columnId: accessor, - prevState: props.visualizationState, - }) - ); + if (activeVisualization.renderDimensionEditor) { + tabs.push({ + id: 'visualization', + name: i18n.translate('xpack.lens.editorFrame.formatStyleLabel', { + defaultMessage: 'Format & style', + }), + content: ( +
+ + +
+ ), + }); + } + + return ( + { + layerDatasource.onDrop({ + ...layerDatasourceDropProps, + droppedItem, + columnId: accessor, + filterOperations: group.filterOperations, + }); }} - /> - - ))} + > + { + if (popoverState.isOpen) { + setPopoverState({ + isOpen: false, + openId: null, + addingToGroupId: null, + tabId: null, + }); + } else { + setPopoverState({ + isOpen: true, + openId: accessor, + addingToGroupId: null, // not set for existing dimension + tabId: 'datasource', + }); + } + }, + }} + /> + } + panel={ + t.id === popoverState.tabId)} + size="s" + onTabClick={(tab) => { + setPopoverState({ + ...popoverState, + tabId: tab.id as typeof popoverState['tabId'], + }); + }} + /> + } + /> + + { + trackUiEvent('indexpattern_dimension_removed'); + props.updateAll( + datasourceId, + layerDatasource.removeColumn({ + layerId, + columnId: accessor, + prevState: layerDatasourceState, + }), + activeVisualization.removeDimension({ + layerId, + columnId: accessor, + prevState: props.visualizationState, + }) + ); + }} + /> +
+ ); + })} {group.supportsMoreColumns ? ( = VisualizationConfigProp setState: (newState: T) => void; }; +export type VisualizationDimensionEditorProps = VisualizationConfigProps & { + groupId: string; + accessor: string; + setState: (newState: T) => void; +}; + export type VisualizationDimensionGroupConfig = SharedDimensionProps & { groupLabel: string; @@ -300,6 +306,12 @@ export type VisualizationDimensionGroupConfig = SharedDimensionProps & { /** If required, a warning will appear if accessors are empty */ required?: boolean; dataTestSubj?: string; + + /** + * When the dimension editor is enabled for this group, all dimensions in the group + * will render the extra tab for the dimension editor + */ + enableDimensionEditor?: boolean; }; interface VisualizationDimensionChangeProps { @@ -459,6 +471,15 @@ export interface Visualization { */ removeDimension: (props: VisualizationDimensionChangeProps) => T; + /** + * Additional editor that gets rendered inside the dimension popover. + * This can be used to configure dimension-specific options + */ + renderDimensionEditor?: ( + domElement: Element, + props: VisualizationDimensionEditorProps + ) => void; + /** * The frame will call this function on all visualizations at different times. The * main use cases where visualization suggestions are requested are: From 5a6c77226c212ad51ce84a23d81c8781c6af8d72 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 1 Jun 2020 18:37:56 +0100 Subject: [PATCH 06/50] skip flaky suite (#67833) --- .../__jest__/client_integration/template_create.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx index 05abe284fab3..8f464987418c 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx @@ -59,7 +59,8 @@ const KEYWORD_MAPPING_FIELD = { type: 'keyword', }; -describe('', () => { +// FLAKY: https://github.com/elastic/kibana/issues/67833 +describe.skip('', () => { let testBed: TemplateFormTestBed; const { server, httpRequestsMockHelpers } = setupEnvironment(); From cdbcb9720b69d6373dbe8da06bd15d9d0324e795 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 1 Jun 2020 19:53:16 +0200 Subject: [PATCH 07/50] [Uptime] Use date histogram in monitor states (#67558) Co-authored-by: Elastic Machine --- .../server/lib/requests/search/enrich_monitor_groups.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/uptime/server/lib/requests/search/enrich_monitor_groups.ts b/x-pack/plugins/uptime/server/lib/requests/search/enrich_monitor_groups.ts index 9f153e186420..53b1fe881cd9 100644 --- a/x-pack/plugins/uptime/server/lib/requests/search/enrich_monitor_groups.ts +++ b/x-pack/plugins/uptime/server/lib/requests/search/enrich_monitor_groups.ts @@ -15,6 +15,7 @@ import { SortOrder, } from '../../../../common/runtime_types'; import { MonitorEnricher } from './fetch_page'; +import { getHistogramInterval } from '../../helper/get_histogram_interval'; export const enrichMonitorGroups: MonitorEnricher = async ( queryContext: QueryContext, @@ -317,11 +318,13 @@ const getHistogramForMonitors = async ( }, aggs: { histogram: { - auto_date_histogram: { + date_histogram: { field: '@timestamp', // 12 seems to be a good size for performance given // long monitor lists of up to 100 on the overview page - buckets: 12, + fixed_interval: + getHistogramInterval(queryContext.dateRangeStart, queryContext.dateRangeEnd, 12) + + 'ms', missing: 0, }, aggs: { From 279b11b78d22b385d152fb66eee0dfa979366ce0 Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Mon, 1 Jun 2020 14:32:42 -0400 Subject: [PATCH 08/50] [SIEM][Exceptions] - Update exceptions hooks to include _find filtering (#67435) ### Summary - Updates exception list hooks to include filtering options and updates corresponding unit tests. - Adds refreshList callback to hook that fetches the list and its items - Updates hooks tests to test onError callback - Updates tests to use type checking more effectively per feedback from @FrankHassanabad (thanks!) --- x-pack/plugins/lists/common/constants.mock.ts | 10 + x-pack/plugins/lists/common/constants.ts | 6 + .../create_exception_list_item_schema.mock.ts | 34 +++ .../lists/public/exceptions/__mocks__/api.ts | 2 + .../lists/public/exceptions/api.test.ts | 220 +++++++++++++----- x-pack/plugins/lists/public/exceptions/api.ts | 69 +++++- .../hooks/persist_exception_item.test.tsx | 56 +++-- .../hooks/persist_exception_item.tsx | 7 + .../hooks/persist_exception_list.test.tsx | 53 +++-- .../hooks/persist_exception_list.tsx | 7 + .../hooks/use_exception_list.test.tsx | 200 ++++++++++------ .../exceptions/hooks/use_exception_list.tsx | 132 ++++++++--- .../plugins/lists/public/exceptions/types.ts | 27 ++- 13 files changed, 627 insertions(+), 196 deletions(-) create mode 100644 x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.mock.ts diff --git a/x-pack/plugins/lists/common/constants.mock.ts b/x-pack/plugins/lists/common/constants.mock.ts index 8c5f6b0cbe56..d8e4dfba1599 100644 --- a/x-pack/plugins/lists/common/constants.mock.ts +++ b/x-pack/plugins/lists/common/constants.mock.ts @@ -29,3 +29,13 @@ export const TYPE = 'ip'; export const VALUE = '127.0.0.1'; export const VALUE_2 = '255.255.255'; export const NAMESPACE_TYPE = 'single'; + +// Exception List specific +export const ENDPOINT_TYPE = 'endpoint'; +export const ENTRIES = [ + { field: 'some.field', match: 'some value', match_any: undefined, operator: 'included' }, +]; +export const ITEM_TYPE = 'simple'; +export const _TAGS = []; +export const TAGS = []; +export const COMMENT = []; diff --git a/x-pack/plugins/lists/common/constants.ts b/x-pack/plugins/lists/common/constants.ts index 96d28bf618ce..6cb88b19483c 100644 --- a/x-pack/plugins/lists/common/constants.ts +++ b/x-pack/plugins/lists/common/constants.ts @@ -16,3 +16,9 @@ export const LIST_ITEM_URL = `${LIST_URL}/items`; */ export const EXCEPTION_LIST_URL = '/api/exception_lists'; export const EXCEPTION_LIST_ITEM_URL = '/api/exception_lists/items'; + +/** + * Exception list spaces + */ +export const EXCEPTION_LIST_NAMESPACE_AGNOSTIC = 'exception-list-agnostic'; +export const EXCEPTION_LIST_NAMESPACE = 'exception-list'; diff --git a/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.mock.ts new file mode 100644 index 000000000000..f9af10245b7e --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.mock.ts @@ -0,0 +1,34 @@ +/* + * 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 { + COMMENT, + DESCRIPTION, + ENTRIES, + ITEM_TYPE, + LIST_ID, + META, + NAME, + NAMESPACE_TYPE, + TAGS, + _TAGS, +} from '../../constants.mock'; + +import { CreateExceptionListItemSchema } from './create_exception_list_item_schema'; + +export const getCreateExceptionListItemSchemaMock = (): CreateExceptionListItemSchema => ({ + _tags: _TAGS, + comment: COMMENT, + description: DESCRIPTION, + entries: ENTRIES, + item_id: undefined, + list_id: LIST_ID, + meta: META, + name: NAME, + namespace_type: NAMESPACE_TYPE, + tags: TAGS, + type: ITEM_TYPE, +}); diff --git a/x-pack/plugins/lists/public/exceptions/__mocks__/api.ts b/x-pack/plugins/lists/public/exceptions/__mocks__/api.ts index 787d374ab2ca..ecc771279b3a 100644 --- a/x-pack/plugins/lists/public/exceptions/__mocks__/api.ts +++ b/x-pack/plugins/lists/public/exceptions/__mocks__/api.ts @@ -40,8 +40,10 @@ export const fetchExceptionListById = async ({ }: ApiCallByIdProps): Promise => Promise.resolve(getExceptionListSchemaMock()); export const fetchExceptionListItemsByListId = async ({ + filterOptions, http, listId, + pagination, signal, }: ApiCallByListIdProps): Promise => Promise.resolve({ data: [getExceptionListItemSchemaMock()], page: 1, per_page: 20, total: 1 }); diff --git a/x-pack/plugins/lists/public/exceptions/api.test.ts b/x-pack/plugins/lists/public/exceptions/api.test.ts index 18a89071e988..b9512bb39874 100644 --- a/x-pack/plugins/lists/public/exceptions/api.test.ts +++ b/x-pack/plugins/lists/public/exceptions/api.test.ts @@ -6,8 +6,9 @@ import { createKibanaCoreStartMock } from '../common/mocks/kibana_core'; import { getExceptionListSchemaMock } from '../../common/schemas/response/exception_list_schema.mock'; import { getExceptionListItemSchemaMock } from '../../common/schemas/response/exception_list_item_schema.mock'; +import { getCreateExceptionListSchemaMock } from '../../common/schemas/request/create_exception_list_schema.mock'; +import { getCreateExceptionListItemSchemaMock } from '../../common/schemas/request/create_exception_list_item_schema.mock'; -import { mockNewExceptionItem, mockNewExceptionList } from './mock'; import { addExceptionList, addExceptionListItem, @@ -37,188 +38,291 @@ const mockKibanaHttpService = ((createKibanaCoreStartMock() as unknown) as jest. ); describe('Exceptions Lists API', () => { - describe('addExceptionList', () => { + describe('#addExceptionList', () => { beforeEach(() => { fetchMock.mockClear(); fetchMock.mockResolvedValue(getExceptionListSchemaMock()); }); - test('check parameter url, body', async () => { - await addExceptionList({ + test('it uses POST when "list.id" does not exist', async () => { + const payload = getCreateExceptionListSchemaMock(); + const exceptionResponse = await addExceptionList({ http: mockKibanaHttpService(), - list: mockNewExceptionList, + list: payload, signal: abortCtrl.signal, }); + expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists', { - body: - '{"_tags":["endpoint","process","malware","os:linux"],"description":"This is a sample endpoint type exception","list_id":"endpoint_list","name":"Sample Endpoint Exception List","tags":["user added string for a tag","malware"],"type":"endpoint"}', + body: JSON.stringify(payload), method: 'POST', signal: abortCtrl.signal, }); + expect(exceptionResponse).toEqual({ id: '1', ...getExceptionListSchemaMock() }); }); - test('check parameter url, body when "list.id" exists', async () => { - await addExceptionList({ + test('it uses PUT when "list.id" exists', async () => { + const payload = getExceptionListSchemaMock(); + const exceptionResponse = await addExceptionList({ http: mockKibanaHttpService(), list: getExceptionListSchemaMock(), signal: abortCtrl.signal, }); + expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists', { - body: - '{"_tags":["endpoint","process","malware","os:linux"],"created_at":"2020-04-23T00:19:13.289Z","created_by":"user_name","description":"This is a sample endpoint type exception","id":"1","list_id":"endpoint_list","meta":{},"name":"Sample Endpoint Exception List","namespace_type":"single","tags":["user added string for a tag","malware"],"tie_breaker_id":"77fd1909-6786-428a-a671-30229a719c1f","type":"endpoint","updated_at":"2020-04-23T00:19:13.289Z","updated_by":"user_name"}', + body: JSON.stringify(payload), method: 'PUT', signal: abortCtrl.signal, }); - }); - - test('happy path', async () => { - const exceptionResponse = await addExceptionList({ - http: mockKibanaHttpService(), - list: mockNewExceptionList, - signal: abortCtrl.signal, - }); expect(exceptionResponse).toEqual(getExceptionListSchemaMock()); }); }); - describe('addExceptionListItem', () => { + describe('#addExceptionListItem', () => { beforeEach(() => { fetchMock.mockClear(); fetchMock.mockResolvedValue(getExceptionListItemSchemaMock()); }); - test('check parameter url, body', async () => { - await addExceptionListItem({ + test('it uses POST when "listItem.id" does not exist', async () => { + const payload = getCreateExceptionListItemSchemaMock(); + const exceptionResponse = await addExceptionListItem({ http: mockKibanaHttpService(), - listItem: mockNewExceptionItem, + listItem: payload, signal: abortCtrl.signal, }); + expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists/items', { - body: - '{"_tags":["endpoint","process","malware","os:linux"],"description":"This is a sample endpoint type exception","entries":[{"field":"actingProcess.file.signer","match":"Elastic, N.V.","operator":"included"},{"field":"event.category","match_any":["process","malware"],"operator":"included"}],"item_id":"endpoint_list_item","list_id":"endpoint_list","name":"Sample Endpoint Exception List","tags":["user added string for a tag","malware"],"type":"simple"}', + body: JSON.stringify(payload), method: 'POST', signal: abortCtrl.signal, }); + expect(exceptionResponse).toEqual(getExceptionListItemSchemaMock()); }); test('check parameter url, body when "listItem.id" exists', async () => { - await addExceptionListItem({ + const payload = getExceptionListItemSchemaMock(); + const exceptionResponse = await addExceptionListItem({ http: mockKibanaHttpService(), listItem: getExceptionListItemSchemaMock(), signal: abortCtrl.signal, }); + expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists/items', { - body: - '{"_tags":["endpoint","process","malware","os:linux"],"comment":[],"created_at":"2020-04-23T00:19:13.289Z","created_by":"user_name","description":"This is a sample endpoint type exception","entries":[{"field":"actingProcess.file.signer","match":"Elastic, N.V.","operator":"included"},{"field":"event.category","match_any":["process","malware"],"operator":"included"}],"id":"1","item_id":"endpoint_list_item","list_id":"endpoint_list","meta":{},"name":"Sample Endpoint Exception List","namespace_type":"single","tags":["user added string for a tag","malware"],"tie_breaker_id":"77fd1909-6786-428a-a671-30229a719c1f","type":"simple","updated_at":"2020-04-23T00:19:13.289Z","updated_by":"user_name"}', + body: JSON.stringify(payload), method: 'PUT', signal: abortCtrl.signal, }); - }); - - test('happy path', async () => { - const exceptionResponse = await addExceptionListItem({ - http: mockKibanaHttpService(), - listItem: mockNewExceptionItem, - signal: abortCtrl.signal, - }); expect(exceptionResponse).toEqual(getExceptionListItemSchemaMock()); }); }); - describe('fetchExceptionListById', () => { + describe('#fetchExceptionListById', () => { beforeEach(() => { fetchMock.mockClear(); fetchMock.mockResolvedValue(getExceptionListSchemaMock()); }); - test('check parameter url, body', async () => { + test('it invokes "fetchExceptionListById" with expected url and body values', async () => { await fetchExceptionListById({ http: mockKibanaHttpService(), id: '1', + namespaceType: 'single', signal: abortCtrl.signal, }); expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists', { method: 'GET', query: { id: '1', + namespace_type: 'single', }, signal: abortCtrl.signal, }); }); - test('happy path', async () => { + test('it returns expected exception list on success', async () => { const exceptionResponse = await fetchExceptionListById({ http: mockKibanaHttpService(), id: '1', + namespaceType: 'single', signal: abortCtrl.signal, }); expect(exceptionResponse).toEqual(getExceptionListSchemaMock()); }); }); - describe('fetchExceptionListItemsByListId', () => { + describe('#fetchExceptionListItemsByListId', () => { beforeEach(() => { fetchMock.mockClear(); - fetchMock.mockResolvedValue([mockNewExceptionItem]); + fetchMock.mockResolvedValue([getExceptionListItemSchemaMock()]); }); - test('check parameter url, body', async () => { + test('it invokes "fetchExceptionListItemsByListId" with expected url and body values', async () => { await fetchExceptionListItemsByListId({ http: mockKibanaHttpService(), - listId: 'endpoint_list', + listId: 'myList', + namespaceType: 'single', + signal: abortCtrl.signal, + }); + + expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists/items/_find', { + method: 'GET', + query: { + list_id: 'myList', + namespace_type: 'single', + page: 1, + per_page: 20, + }, + signal: abortCtrl.signal, + }); + }); + + test('it invokes with expected url and body values when a filter exists and "namespaceType" of "single"', async () => { + await fetchExceptionListItemsByListId({ + filterOptions: { + filter: 'hello world', + tags: [], + }, + http: mockKibanaHttpService(), + listId: 'myList', + namespaceType: 'single', signal: abortCtrl.signal, }); + + expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists/items/_find', { + method: 'GET', + query: { + filter: 'exception-list.attributes.entries.field:hello world*', + list_id: 'myList', + namespace_type: 'single', + page: 1, + per_page: 20, + }, + signal: abortCtrl.signal, + }); + }); + + test('it invokes with expected url and body values when a filter exists and "namespaceType" of "agnostic"', async () => { + await fetchExceptionListItemsByListId({ + filterOptions: { + filter: 'hello world', + tags: [], + }, + http: mockKibanaHttpService(), + listId: 'myList', + namespaceType: 'agnostic', + signal: abortCtrl.signal, + }); + + expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists/items/_find', { + method: 'GET', + query: { + filter: 'exception-list-agnostic.attributes.entries.field:hello world*', + list_id: 'myList', + namespace_type: 'agnostic', + page: 1, + per_page: 20, + }, + signal: abortCtrl.signal, + }); + }); + + test('it invokes with expected url and body values when tags exists', async () => { + await fetchExceptionListItemsByListId({ + filterOptions: { + filter: '', + tags: ['malware'], + }, + http: mockKibanaHttpService(), + listId: 'myList', + namespaceType: 'agnostic', + signal: abortCtrl.signal, + }); + + expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists/items/_find', { + method: 'GET', + query: { + filter: 'exception-list-agnostic.attributes.tags:malware', + list_id: 'myList', + namespace_type: 'agnostic', + page: 1, + per_page: 20, + }, + signal: abortCtrl.signal, + }); + }); + + test('it invokes with expected url and body values when filter and tags exists', async () => { + await fetchExceptionListItemsByListId({ + filterOptions: { + filter: 'host.name', + tags: ['malware'], + }, + http: mockKibanaHttpService(), + listId: 'myList', + namespaceType: 'agnostic', + signal: abortCtrl.signal, + }); + expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists/items/_find', { method: 'GET', query: { - list_id: 'endpoint_list', + filter: + 'exception-list-agnostic.attributes.entries.field:host.name* AND exception-list-agnostic.attributes.tags:malware', + list_id: 'myList', + namespace_type: 'agnostic', + page: 1, + per_page: 20, }, signal: abortCtrl.signal, }); }); - test('happy path', async () => { + test('it returns expected format when call succeeds', async () => { const exceptionResponse = await fetchExceptionListItemsByListId({ http: mockKibanaHttpService(), listId: 'endpoint_list', + namespaceType: 'single', signal: abortCtrl.signal, }); - expect(exceptionResponse).toEqual([mockNewExceptionItem]); + expect(exceptionResponse).toEqual([getExceptionListItemSchemaMock()]); }); }); - describe('fetchExceptionListItemById', () => { + describe('#fetchExceptionListItemById', () => { beforeEach(() => { fetchMock.mockClear(); - fetchMock.mockResolvedValue([mockNewExceptionItem]); + fetchMock.mockResolvedValue([getExceptionListItemSchemaMock()]); }); - test('check parameter url, body', async () => { + test('it invokes "fetchExceptionListItemById" with expected url and body values', async () => { await fetchExceptionListItemById({ http: mockKibanaHttpService(), id: '1', + namespaceType: 'single', signal: abortCtrl.signal, }); expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists/items', { method: 'GET', query: { id: '1', + namespace_type: 'single', }, signal: abortCtrl.signal, }); }); - test('happy path', async () => { + test('it returns expected format when call succeeds', async () => { const exceptionResponse = await fetchExceptionListItemById({ http: mockKibanaHttpService(), id: '1', + namespaceType: 'single', signal: abortCtrl.signal, }); - expect(exceptionResponse).toEqual([mockNewExceptionItem]); + expect(exceptionResponse).toEqual([getExceptionListItemSchemaMock()]); }); }); - describe('deleteExceptionListById', () => { + describe('#deleteExceptionListById', () => { beforeEach(() => { fetchMock.mockClear(); fetchMock.mockResolvedValue(getExceptionListSchemaMock()); @@ -228,28 +332,31 @@ describe('Exceptions Lists API', () => { await deleteExceptionListById({ http: mockKibanaHttpService(), id: '1', + namespaceType: 'single', signal: abortCtrl.signal, }); expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists', { method: 'DELETE', query: { id: '1', + namespace_type: 'single', }, signal: abortCtrl.signal, }); }); - test('happy path', async () => { + test('it returns expected format when call succeeds', async () => { const exceptionResponse = await deleteExceptionListById({ http: mockKibanaHttpService(), id: '1', + namespaceType: 'single', signal: abortCtrl.signal, }); expect(exceptionResponse).toEqual(getExceptionListSchemaMock()); }); }); - describe('deleteExceptionListItemById', () => { + describe('#deleteExceptionListItemById', () => { beforeEach(() => { fetchMock.mockClear(); fetchMock.mockResolvedValue(getExceptionListItemSchemaMock()); @@ -259,21 +366,24 @@ describe('Exceptions Lists API', () => { await deleteExceptionListItemById({ http: mockKibanaHttpService(), id: '1', + namespaceType: 'single', signal: abortCtrl.signal, }); expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists/items', { method: 'DELETE', query: { id: '1', + namespace_type: 'single', }, signal: abortCtrl.signal, }); }); - test('happy path', async () => { + test('it returns expected format when call succeeds', async () => { const exceptionResponse = await deleteExceptionListItemById({ http: mockKibanaHttpService(), id: '1', + namespaceType: 'single', signal: abortCtrl.signal, }); expect(exceptionResponse).toEqual(getExceptionListItemSchemaMock()); diff --git a/x-pack/plugins/lists/public/exceptions/api.ts b/x-pack/plugins/lists/public/exceptions/api.ts index fdd9d62539e0..6968ba5f50e7 100644 --- a/x-pack/plugins/lists/public/exceptions/api.ts +++ b/x-pack/plugins/lists/public/exceptions/api.ts @@ -4,7 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '../../common/constants'; +import { + EXCEPTION_LIST_ITEM_URL, + EXCEPTION_LIST_NAMESPACE, + EXCEPTION_LIST_NAMESPACE_AGNOSTIC, + EXCEPTION_LIST_URL, +} from '../../common/constants'; import { ExceptionListItemSchema, ExceptionListSchema, @@ -21,6 +26,7 @@ import { /** * Add provided ExceptionList * + * @param http Kibana http service * @param list exception list to add * @param signal to cancel request * @@ -43,6 +49,7 @@ export const addExceptionList = async ({ /** * Add provided ExceptionListItem * + * @param http Kibana http service * @param listItem exception list item to add * @param signal to cancel request * @@ -65,7 +72,9 @@ export const addExceptionListItem = async ({ /** * Fetch an ExceptionList by providing a ExceptionList ID * + * @param http Kibana http service * @param id ExceptionList ID (not list_id) + * @param namespaceType ExceptionList namespace_type * @param signal to cancel request * * @throws An error if response is not OK @@ -73,18 +82,23 @@ export const addExceptionListItem = async ({ export const fetchExceptionListById = async ({ http, id, + namespaceType, signal, }: ApiCallByIdProps): Promise => http.fetch(`${EXCEPTION_LIST_URL}`, { method: 'GET', - query: { id }, + query: { id, namespace_type: namespaceType }, signal, }); /** * Fetch an ExceptionList's ExceptionItems by providing a ExceptionList list_id * - * @param id ExceptionList list_id (not ID) + * @param http Kibana http service + * @param listId ExceptionList list_id (not ID) + * @param namespaceType ExceptionList namespace_type + * @param filterOptions optional - filter by field or tags + * @param pagination optional * @param signal to cancel request * * @throws An error if response is not OK @@ -92,18 +106,48 @@ export const fetchExceptionListById = async ({ export const fetchExceptionListItemsByListId = async ({ http, listId, + namespaceType, + filterOptions = { + filter: '', + tags: [], + }, + pagination = { + page: 1, + perPage: 20, + total: 0, + }, signal, -}: ApiCallByListIdProps): Promise => - http.fetch(`${EXCEPTION_LIST_ITEM_URL}/_find`, { +}: ApiCallByListIdProps): Promise => { + const namespace = + namespaceType === 'agnostic' ? EXCEPTION_LIST_NAMESPACE_AGNOSTIC : EXCEPTION_LIST_NAMESPACE; + const filters = [ + ...(filterOptions.filter.length + ? [`${namespace}.attributes.entries.field:${filterOptions.filter}*`] + : []), + ...(filterOptions.tags?.map((t) => `${namespace}.attributes.tags:${t}`) ?? []), + ]; + + const query = { + list_id: listId, + namespace_type: namespaceType, + page: pagination.page, + per_page: pagination.perPage, + ...(filters.length ? { filter: filters.join(' AND ') } : {}), + }; + + return http.fetch(`${EXCEPTION_LIST_ITEM_URL}/_find`, { method: 'GET', - query: { list_id: listId }, + query, signal, }); +}; /** * Fetch an ExceptionListItem by providing a ExceptionListItem ID * + * @param http Kibana http service * @param id ExceptionListItem ID (not item_id) + * @param namespaceType ExceptionList namespace_type * @param signal to cancel request * * @throws An error if response is not OK @@ -111,18 +155,21 @@ export const fetchExceptionListItemsByListId = async ({ export const fetchExceptionListItemById = async ({ http, id, + namespaceType, signal, }: ApiCallByIdProps): Promise => http.fetch(`${EXCEPTION_LIST_ITEM_URL}`, { method: 'GET', - query: { id }, + query: { id, namespace_type: namespaceType }, signal, }); /** * Delete an ExceptionList by providing a ExceptionList ID * + * @param http Kibana http service * @param id ExceptionList ID (not list_id) + * @param namespaceType ExceptionList namespace_type * @param signal to cancel request * * @throws An error if response is not OK @@ -130,18 +177,21 @@ export const fetchExceptionListItemById = async ({ export const deleteExceptionListById = async ({ http, id, + namespaceType, signal, }: ApiCallByIdProps): Promise => http.fetch(`${EXCEPTION_LIST_URL}`, { method: 'DELETE', - query: { id }, + query: { id, namespace_type: namespaceType }, signal, }); /** * Delete an ExceptionListItem by providing a ExceptionListItem ID * + * @param http Kibana http service * @param id ExceptionListItem ID (not item_id) + * @param namespaceType ExceptionList namespace_type * @param signal to cancel request * * @throws An error if response is not OK @@ -149,10 +199,11 @@ export const deleteExceptionListById = async ({ export const deleteExceptionListItemById = async ({ http, id, + namespaceType, signal, }: ApiCallByIdProps): Promise => http.fetch(`${EXCEPTION_LIST_ITEM_URL}`, { method: 'DELETE', - query: { id }, + query: { id, namespace_type: namespaceType }, signal, }); diff --git a/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_item.test.tsx b/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_item.test.tsx index b78ad250b891..1db18168b11f 100644 --- a/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_item.test.tsx +++ b/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_item.test.tsx @@ -6,8 +6,10 @@ import { act, renderHook } from '@testing-library/react-hooks'; +import * as api from '../api'; import { getExceptionListItemSchemaMock } from '../../../common/schemas/response/exception_list_item_schema.mock'; import { createKibanaCoreStartMock } from '../../common/mocks/kibana_core'; +import { PersistHookProps } from '../types'; import { ReturnPersistExceptionItem, usePersistExceptionItem } from './persist_exception_item'; @@ -16,38 +18,66 @@ jest.mock('../api'); const mockKibanaHttpService = createKibanaCoreStartMock().http; describe('usePersistExceptionItem', () => { - test('init', async () => { - const onError = jest.fn(); - const { result } = renderHook(() => + const onError = jest.fn(); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('initializes hook', async () => { + const { result } = renderHook(() => usePersistExceptionItem({ http: mockKibanaHttpService, onError }) ); expect(result.current).toEqual([{ isLoading: false, isSaved: false }, result.current[1]]); }); - test('saving exception item with isLoading === true', async () => { + test('"isLoading" is "true" when exception item is being saved', async () => { await act(async () => { - const onError = jest.fn(); - const { result, rerender, waitForNextUpdate } = renderHook( - () => usePersistExceptionItem({ http: mockKibanaHttpService, onError }) - ); + const { result, rerender, waitForNextUpdate } = renderHook< + PersistHookProps, + ReturnPersistExceptionItem + >(() => usePersistExceptionItem({ http: mockKibanaHttpService, onError })); + await waitForNextUpdate(); result.current[1](getExceptionListItemSchemaMock()); rerender(); + expect(result.current).toEqual([{ isLoading: true, isSaved: false }, result.current[1]]); }); }); - test('saved exception item with isSaved === true', async () => { - const onError = jest.fn(); + test('"isSaved" is "true" when exception item saved successfully', async () => { await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - usePersistExceptionItem({ http: mockKibanaHttpService, onError }) - ); + const { result, waitForNextUpdate } = renderHook< + PersistHookProps, + ReturnPersistExceptionItem + >(() => usePersistExceptionItem({ http: mockKibanaHttpService, onError })); + await waitForNextUpdate(); result.current[1](getExceptionListItemSchemaMock()); await waitForNextUpdate(); + expect(result.current).toEqual([{ isLoading: false, isSaved: true }, result.current[1]]); }); }); + + test('"onError" callback is invoked and "isSaved" is "false" when api call fails', async () => { + const error = new Error('persist rule failed'); + jest.spyOn(api, 'addExceptionListItem').mockRejectedValue(error); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook< + PersistHookProps, + ReturnPersistExceptionItem + >(() => usePersistExceptionItem({ http: mockKibanaHttpService, onError })); + + await waitForNextUpdate(); + result.current[1](getExceptionListItemSchemaMock()); + await waitForNextUpdate(); + + expect(result.current).toEqual([{ isLoading: false, isSaved: false }, result.current[1]]); + expect(onError).toHaveBeenCalledWith(error); + }); + }); }); diff --git a/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_item.tsx b/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_item.tsx index 0ed007e80501..d9fe3a82ac17 100644 --- a/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_item.tsx +++ b/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_item.tsx @@ -19,6 +19,13 @@ export type ReturnPersistExceptionItem = [ Dispatch ]; +/** + * Hook for creating or updating ExceptionListItem + * + * @param http Kibana http service + * @param onError error callback + * + */ export const usePersistExceptionItem = ({ http, onError, diff --git a/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_list.test.tsx b/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_list.test.tsx index 605dd635aa4f..80d6e27043c9 100644 --- a/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_list.test.tsx +++ b/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_list.test.tsx @@ -6,8 +6,10 @@ import { act, renderHook } from '@testing-library/react-hooks'; +import * as api from '../api'; import { getExceptionListSchemaMock } from '../../../common/schemas/response/exception_list_schema.mock'; import { createKibanaCoreStartMock } from '../../common/mocks/kibana_core'; +import { PersistHookProps } from '../types'; import { ReturnPersistExceptionList, usePersistExceptionList } from './persist_exception_list'; @@ -16,38 +18,63 @@ jest.mock('../api'); const mockKibanaHttpService = createKibanaCoreStartMock().http; describe('usePersistExceptionList', () => { - test('init', async () => { - const onError = jest.fn(); - const { result } = renderHook(() => + const onError = jest.fn(); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('initializes hook', async () => { + const { result } = renderHook(() => usePersistExceptionList({ http: mockKibanaHttpService, onError }) ); expect(result.current).toEqual([{ isLoading: false, isSaved: false }, result.current[1]]); }); - test('saving exception list with isLoading === true', async () => { - const onError = jest.fn(); + test('"isLoading" is "true" when exception item is being saved', async () => { await act(async () => { - const { result, rerender, waitForNextUpdate } = renderHook( - () => usePersistExceptionList({ http: mockKibanaHttpService, onError }) - ); + const { result, rerender, waitForNextUpdate } = renderHook< + PersistHookProps, + ReturnPersistExceptionList + >(() => usePersistExceptionList({ http: mockKibanaHttpService, onError })); await waitForNextUpdate(); result.current[1](getExceptionListSchemaMock()); rerender(); + expect(result.current).toEqual([{ isLoading: true, isSaved: false }, result.current[1]]); }); }); - test('saved exception list with isSaved === true', async () => { - const onError = jest.fn(); + test('"isSaved" is "true" when exception item saved successfully', async () => { await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - usePersistExceptionList({ http: mockKibanaHttpService, onError }) - ); + const { result, waitForNextUpdate } = renderHook< + PersistHookProps, + ReturnPersistExceptionList + >(() => usePersistExceptionList({ http: mockKibanaHttpService, onError })); await waitForNextUpdate(); result.current[1](getExceptionListSchemaMock()); await waitForNextUpdate(); + expect(result.current).toEqual([{ isLoading: false, isSaved: true }, result.current[1]]); }); }); + + test('"onError" callback is invoked and "isSaved" is "false" when api call fails', async () => { + const error = new Error('persist rule failed'); + jest.spyOn(api, 'addExceptionList').mockRejectedValue(error); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook< + PersistHookProps, + ReturnPersistExceptionList + >(() => usePersistExceptionList({ http: mockKibanaHttpService, onError })); + await waitForNextUpdate(); + result.current[1](getExceptionListSchemaMock()); + await waitForNextUpdate(); + + expect(result.current).toEqual([{ isLoading: false, isSaved: false }, result.current[1]]); + expect(onError).toHaveBeenCalledWith(error); + }); + }); }); diff --git a/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_list.tsx b/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_list.tsx index 45330c9725ae..5848a1714519 100644 --- a/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_list.tsx +++ b/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_list.tsx @@ -19,6 +19,13 @@ export type ReturnPersistExceptionList = [ Dispatch ]; +/** + * Hook for creating or updating ExceptionList + * + * @param http Kibana http service + * @param onError error callback + * + */ export const usePersistExceptionList = ({ http, onError, diff --git a/x-pack/plugins/lists/public/exceptions/hooks/use_exception_list.test.tsx b/x-pack/plugins/lists/public/exceptions/hooks/use_exception_list.test.tsx index 308d1cf4d1b1..a6a25ab4d4e9 100644 --- a/x-pack/plugins/lists/public/exceptions/hooks/use_exception_list.test.tsx +++ b/x-pack/plugins/lists/public/exceptions/hooks/use_exception_list.test.tsx @@ -8,6 +8,9 @@ import { act, renderHook } from '@testing-library/react-hooks'; import * as api from '../api'; import { createKibanaCoreStartMock } from '../../common/mocks/kibana_core'; +import { getExceptionListSchemaMock } from '../../../common/schemas/response/exception_list_schema.mock'; +import { getExceptionListItemSchemaMock } from '../../../common/schemas/response/exception_list_item_schema.mock'; +import { ExceptionListAndItems, UseExceptionListProps } from '../types'; import { ReturnExceptionListAndItems, useExceptionList } from './use_exception_list'; @@ -16,103 +19,166 @@ jest.mock('../api'); const mockKibanaHttpService = createKibanaCoreStartMock().http; describe('useExceptionList', () => { - test('init', async () => { - const onError = jest.fn(); + const onErrorMock = jest.fn(); + + afterEach(() => { + onErrorMock.mockClear(); + jest.clearAllMocks(); + }); + + test('initializes hook', async () => { await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useExceptionList({ http: mockKibanaHttpService, id: 'myListId', onError }) + const { result, waitForNextUpdate } = renderHook< + UseExceptionListProps, + ReturnExceptionListAndItems + >(() => + useExceptionList({ + http: mockKibanaHttpService, + id: 'myListId', + namespaceType: 'single', + onError: onErrorMock, + }) ); await waitForNextUpdate(); - expect(result.current).toEqual([true, null]); + + expect(result.current).toEqual([true, null, result.current[2]]); + expect(typeof result.current[2]).toEqual('function'); }); }); test('fetch exception list and items', async () => { - const onError = jest.fn(); await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useExceptionList({ http: mockKibanaHttpService, id: 'myListId', onError }) + const { result, waitForNextUpdate } = renderHook< + UseExceptionListProps, + ReturnExceptionListAndItems + >(() => + useExceptionList({ + http: mockKibanaHttpService, + id: 'myListId', + namespaceType: 'single', + onError: onErrorMock, + }) ); await waitForNextUpdate(); await waitForNextUpdate(); - expect(result.current).toEqual([ - false, - { - _tags: ['endpoint', 'process', 'malware', 'os:linux'], - created_at: '2020-04-23T00:19:13.289Z', - created_by: 'user_name', - description: 'This is a sample endpoint type exception', - exceptionItems: { - data: [ - { - _tags: ['endpoint', 'process', 'malware', 'os:linux'], - comment: [], - created_at: '2020-04-23T00:19:13.289Z', - created_by: 'user_name', - description: 'This is a sample endpoint type exception', - entries: [ - { - field: 'actingProcess.file.signer', - match: 'Elastic, N.V.', - match_any: undefined, - operator: 'included', - }, - { - field: 'event.category', - match: undefined, - match_any: ['process', 'malware'], - operator: 'included', - }, - ], - id: '1', - item_id: 'endpoint_list_item', - list_id: 'endpoint_list', - meta: {}, - name: 'Sample Endpoint Exception List', - namespace_type: 'single', - tags: ['user added string for a tag', 'malware'], - tie_breaker_id: '77fd1909-6786-428a-a671-30229a719c1f', - type: 'simple', - updated_at: '2020-04-23T00:19:13.289Z', - updated_by: 'user_name', - }, - ], + + const expectedResult: ExceptionListAndItems = { + ...getExceptionListSchemaMock(), + exceptionItems: { + items: [{ ...getExceptionListItemSchemaMock() }], + pagination: { page: 1, - per_page: 20, + perPage: 20, total: 1, }, - id: '1', - list_id: 'endpoint_list', - meta: {}, - name: 'Sample Endpoint Exception List', - namespace_type: 'single', - tags: ['user added string for a tag', 'malware'], - tie_breaker_id: '77fd1909-6786-428a-a671-30229a719c1f', - type: 'endpoint', - updated_at: '2020-04-23T00:19:13.289Z', - updated_by: 'user_name', }, - ]); + }; + + expect(result.current).toEqual([false, expectedResult, result.current[2]]); }); }); test('fetch a new exception list and its items', async () => { - const onError = jest.fn(); const spyOnfetchExceptionListById = jest.spyOn(api, 'fetchExceptionListById'); const spyOnfetchExceptionListItemsByListId = jest.spyOn(api, 'fetchExceptionListItemsByListId'); await act(async () => { - const { rerender, waitForNextUpdate } = renderHook( - (id) => useExceptionList({ http: mockKibanaHttpService, id, onError }), + const { rerender, waitForNextUpdate } = renderHook< + UseExceptionListProps, + ReturnExceptionListAndItems + >( + ({ filterOptions, http, id, namespaceType, pagination, onError }) => + useExceptionList({ filterOptions, http, id, namespaceType, onError, pagination }), { - initialProps: 'myListId', + initialProps: { + http: mockKibanaHttpService, + id: 'myListId', + namespaceType: 'single', + onError: onErrorMock, + }, } ); await waitForNextUpdate(); + rerender({ + http: mockKibanaHttpService, + id: 'newListId', + namespaceType: 'single', + onError: onErrorMock, + }); + await waitForNextUpdate(); + + expect(spyOnfetchExceptionListById).toHaveBeenCalledTimes(2); + expect(spyOnfetchExceptionListItemsByListId).toHaveBeenCalledTimes(2); + }); + }); + + test('fetches list and items when refreshExceptionList callback invoked', async () => { + const spyOnfetchExceptionListById = jest.spyOn(api, 'fetchExceptionListById'); + const spyOnfetchExceptionListItemsByListId = jest.spyOn(api, 'fetchExceptionListItemsByListId'); + await act(async () => { + const { result, waitForNextUpdate } = renderHook< + UseExceptionListProps, + ReturnExceptionListAndItems + >(() => + useExceptionList({ + http: mockKibanaHttpService, + id: 'myListId', + namespaceType: 'single', + onError: onErrorMock, + }) + ); + await waitForNextUpdate(); await waitForNextUpdate(); - rerender('newListId'); + result.current[2](); await waitForNextUpdate(); + expect(spyOnfetchExceptionListById).toHaveBeenCalledTimes(2); expect(spyOnfetchExceptionListItemsByListId).toHaveBeenCalledTimes(2); }); }); + + test('invokes "onError" callback if "fetchExceptionListItemsByListId" fails', async () => { + const mockError = new Error('failed to fetch list items'); + const spyOnfetchExceptionListById = jest.spyOn(api, 'fetchExceptionListById'); + const spyOnfetchExceptionListItemsByListId = jest + .spyOn(api, 'fetchExceptionListItemsByListId') + .mockRejectedValue(mockError); + await act(async () => { + const { waitForNextUpdate } = renderHook( + () => + useExceptionList({ + http: mockKibanaHttpService, + id: 'myListId', + namespaceType: 'single', + onError: onErrorMock, + }) + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + + expect(spyOnfetchExceptionListById).toHaveBeenCalledTimes(1); + expect(onErrorMock).toHaveBeenCalledWith(mockError); + expect(spyOnfetchExceptionListItemsByListId).toHaveBeenCalledTimes(1); + }); + }); + + test('invokes "onError" callback if "fetchExceptionListById" fails', async () => { + const mockError = new Error('failed to fetch list'); + jest.spyOn(api, 'fetchExceptionListById').mockRejectedValue(mockError); + + await act(async () => { + const { waitForNextUpdate } = renderHook( + () => + useExceptionList({ + http: mockKibanaHttpService, + id: 'myListId', + namespaceType: 'single', + onError: onErrorMock, + }) + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + + expect(onErrorMock).toHaveBeenCalledWith(mockError); + }); + }); }); diff --git a/x-pack/plugins/lists/public/exceptions/hooks/use_exception_list.tsx b/x-pack/plugins/lists/public/exceptions/hooks/use_exception_list.tsx index d0ac357e05aa..116233cd8934 100644 --- a/x-pack/plugins/lists/public/exceptions/hooks/use_exception_list.tsx +++ b/x-pack/plugins/lists/public/exceptions/hooks/use_exception_list.tsx @@ -4,66 +4,124 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { fetchExceptionListById, fetchExceptionListItemsByListId } from '../api'; import { ExceptionListAndItems, UseExceptionListProps } from '../types'; -export type ReturnExceptionListAndItems = [boolean, ExceptionListAndItems | null]; +export type ReturnExceptionListAndItems = [boolean, ExceptionListAndItems | null, () => void]; /** * Hook for using to get an ExceptionList and it's ExceptionListItems * + * @param http Kibana http service * @param id desired ExceptionList ID (not list_id) + * @param namespaceType list namespaceType determines list space + * @param onError error callback + * @param filterOptions optional - filter by fields or tags + * @param pagination optional * */ export const useExceptionList = ({ http, id, + namespaceType, + pagination = { + page: 1, + perPage: 20, + total: 0, + }, + filterOptions = { + filter: '', + tags: [], + }, onError, }: UseExceptionListProps): ReturnExceptionListAndItems => { const [exceptionListAndItems, setExceptionList] = useState(null); + const [shouldRefresh, setRefresh] = useState(true); + const refreshExceptionList = useCallback(() => setRefresh(true), [setRefresh]); const [loading, setLoading] = useState(true); + const tags = filterOptions.tags.sort().join(); - useEffect(() => { - let isSubscribed = true; - const abortCtrl = new AbortController(); + useEffect( + () => { + let isSubscribed = true; + const abortCtrl = new AbortController(); - const fetchData = async (idToFetch: string): Promise => { - try { - setLoading(true); - const exceptionList = await fetchExceptionListById({ - http, - id: idToFetch, - signal: abortCtrl.signal, - }); - const exceptionListItems = await fetchExceptionListItemsByListId({ - http, - listId: exceptionList.list_id, - signal: abortCtrl.signal, - }); - if (isSubscribed) { - setExceptionList({ ...exceptionList, exceptionItems: { ...exceptionListItems } }); + const fetchData = async (idToFetch: string): Promise => { + if (shouldRefresh) { + try { + setLoading(true); + + const { + list_id, + namespace_type, + ...restOfExceptionList + } = await fetchExceptionListById({ + http, + id: idToFetch, + namespaceType, + signal: abortCtrl.signal, + }); + const fetchListItemsResult = await fetchExceptionListItemsByListId({ + filterOptions, + http, + listId: list_id, + namespaceType: namespace_type, + pagination, + signal: abortCtrl.signal, + }); + + setRefresh(false); + + if (isSubscribed) { + setExceptionList({ + list_id, + namespace_type, + ...restOfExceptionList, + exceptionItems: { + items: [...fetchListItemsResult.data], + pagination: { + page: fetchListItemsResult.page, + perPage: fetchListItemsResult.per_page, + total: fetchListItemsResult.total, + }, + }, + }); + } + } catch (error) { + setRefresh(false); + if (isSubscribed) { + setExceptionList(null); + onError(error); + } + } } - } catch (error) { + if (isSubscribed) { - setExceptionList(null); - onError(error); + setLoading(false); } - } - if (isSubscribed) { - setLoading(false); - } - }; + }; - if (id != null) { - fetchData(id); - } - return (): void => { - isSubscribed = false; - abortCtrl.abort(); - }; - }, [http, id, onError]); + if (id != null) { + fetchData(id); + } + return (): void => { + isSubscribed = false; + abortCtrl.abort(); + }; + }, // eslint-disable-next-line react-hooks/exhaustive-deps + [ + http, + id, + onError, + shouldRefresh, + pagination.page, + pagination.perPage, + filterOptions.filter, + tags, + ] + ); - return [loading, exceptionListAndItems]; + return [loading, exceptionListAndItems, refreshExceptionList]; }; diff --git a/x-pack/plugins/lists/public/exceptions/types.ts b/x-pack/plugins/lists/public/exceptions/types.ts index fcf2108e7323..cf6b6c3ec1c5 100644 --- a/x-pack/plugins/lists/public/exceptions/types.ts +++ b/x-pack/plugins/lists/public/exceptions/types.ts @@ -9,12 +9,28 @@ import { CreateExceptionListSchemaPartial, ExceptionListItemSchema, ExceptionListSchema, - FoundExceptionListItemSchema, + NamespaceType, } from '../../common/schemas'; import { HttpStart } from '../../../../../src/core/public'; +export interface FilterExceptionsOptions { + filter: string; + tags: string[]; +} + +export interface Pagination { + page: number; + perPage: number; + total: number; +} + +export interface ExceptionItemsAndPagination { + items: ExceptionListItemSchema[]; + pagination: Pagination; +} + export interface ExceptionListAndItems extends ExceptionListSchema { - exceptionItems: FoundExceptionListItemSchema; + exceptionItems: ExceptionItemsAndPagination; } export type AddExceptionList = ExceptionListSchema | CreateExceptionListSchemaPartial; @@ -27,20 +43,27 @@ export interface PersistHookProps { } export interface UseExceptionListProps { + filterOptions?: FilterExceptionsOptions; http: HttpStart; id: string | undefined; + namespaceType: NamespaceType; onError: (arg: Error) => void; + pagination?: Pagination; } export interface ApiCallByListIdProps { http: HttpStart; listId: string; + namespaceType: NamespaceType; + filterOptions?: FilterExceptionsOptions; + pagination?: Pagination; signal: AbortSignal; } export interface ApiCallByIdProps { http: HttpStart; id: string; + namespaceType: NamespaceType; signal: AbortSignal; } From add5b11611973d58c5ed2806f525ab8a00250830 Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Mon, 1 Jun 2020 14:41:30 -0400 Subject: [PATCH 09/50] [CI] Fix packer cache node_modules references --- .ci/packer_cache.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.ci/packer_cache.sh b/.ci/packer_cache.sh index ab68a60dcfc2..d47ef93172a9 100755 --- a/.ci/packer_cache.sh +++ b/.ci/packer_cache.sh @@ -35,20 +35,20 @@ mkdir -p ".geckodriver" cp "node_modules/geckodriver/geckodriver.tar.gz" .geckodriver/geckodriver.tar.gz echo "$geckodriverPkgVersion" > .geckodriver/pkgVersion +echo "Creating bootstrap_cache archive" + # archive cacheable directories mkdir -p "$HOME/.kibana/bootstrap_cache" tar -cf "$HOME/.kibana/bootstrap_cache/$branch.tar" \ - node_modules \ - packages/*/node_modules \ - x-pack/node_modules \ - x-pack/legacy/plugins/*/node_modules \ x-pack/legacy/plugins/reporting/.chromium \ - test/plugin_functional/plugins/*/node_modules \ - examples/*/node_modules \ .es \ .chromedriver \ .geckodriver; +echo "Adding node_modules" +# Find all of the node_modules directories that aren't test fixtures, and aren't inside other node_modules directories, and append them to the tar +find . -type d -name node_modules -not -path '*__fixtures__*' -prune -print0 | xargs -0I % tar -rf "$HOME/.kibana/bootstrap_cache/$branch.tar" "%" + echo "created $HOME/.kibana/bootstrap_cache/$branch.tar" if [ "$branch" == "master" ]; then From b061d85f9a85d467beae8da9a0d46758d6e194eb Mon Sep 17 00:00:00 2001 From: Wylie Conlon Date: Mon, 1 Jun 2020 15:05:27 -0400 Subject: [PATCH 10/50] [Lens] Warn if leaving with unsaved visualization (#67689) * [Lens] Warn if leaving with unsaved visualization * Made confirmation logic more robust and add title Co-authored-by: Elastic Machine --- .../lens/public/app_plugin/app.test.tsx | 216 +++++++++++++----- x-pack/plugins/lens/public/app_plugin/app.tsx | 57 ++++- .../lens/public/app_plugin/mounter.tsx | 1 + 3 files changed, 208 insertions(+), 66 deletions(-) diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index 176296547829..f1a2edd2d554 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -9,6 +9,7 @@ import { ReactWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { App } from './app'; import { EditorFrameInstance } from '../types'; +import { AppMountParameters } from 'kibana/public'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { Document, SavedObjectStore } from '../persistence'; import { mount } from 'enzyme'; @@ -111,6 +112,7 @@ describe('Lens App', () => { newlyCreated?: boolean ) => void; originatingApp: string | undefined; + onAppLeave: AppMountParameters['onAppLeave']; }> { return ({ navigation: navigationStartMock, @@ -153,6 +155,7 @@ describe('Lens App', () => { newlyCreated?: boolean ) => {} ), + onAppLeave: jest.fn(), } as unknown) as jest.Mocked<{ navigation: typeof navigationStartMock; editorFrame: EditorFrameInstance; @@ -168,6 +171,7 @@ describe('Lens App', () => { newlyCreated?: boolean ) => void; originatingApp: string | undefined; + onAppLeave: AppMountParameters['onAppLeave']; }>; } @@ -357,22 +361,7 @@ describe('Lens App', () => { newTitle: string; } - let defaultArgs: jest.Mocked<{ - editorFrame: EditorFrameInstance; - navigation: typeof navigationStartMock; - data: typeof dataStartMock; - core: typeof core; - storage: Storage; - docId?: string; - docStorage: SavedObjectStore; - redirectTo: ( - id?: string, - returnToOrigin?: boolean, - originatingApp?: string | undefined, - newlyCreated?: boolean - ) => void; - originatingApp: string | undefined; - }>; + let defaultArgs: ReturnType; beforeEach(() => { defaultArgs = makeDefaultArgs(); @@ -486,30 +475,6 @@ describe('Lens App', () => { expect(getButton(instance).disableButton).toEqual(true); }); - it('shows a disabled save button when there are no changes to the document', async () => { - const args = defaultArgs; - (args.docStorage.load as jest.Mock).mockResolvedValue({ - id: '1234', - title: 'My cool doc', - expression: '', - } as jest.ResolvedValue); - args.editorFrame = frame; - - instance = mount(); - expect(getButton(instance).disableButton).toEqual(true); - - const onChange = frame.mount.mock.calls[0][1].onChange; - - act(() => { - onChange({ - filterableIndexPatterns: [], - doc: ({ id: '1234', expression: 'valid expression' } as unknown) as Document, - }); - }); - instance.update(); - expect(getButton(instance).disableButton).toEqual(false); - }); - it('shows a save button that is enabled when the frame has provided its state', async () => { const args = defaultArgs; args.editorFrame = frame; @@ -691,21 +656,7 @@ describe('Lens App', () => { }); describe('query bar state management', () => { - let defaultArgs: jest.Mocked<{ - editorFrame: EditorFrameInstance; - data: typeof dataStartMock; - navigation: typeof navigationStartMock; - core: typeof core; - storage: Storage; - docId?: string; - docStorage: SavedObjectStore; - redirectTo: ( - id?: string, - returnToOrigin?: boolean, - originatingApp?: string | undefined, - newlyCreated?: boolean - ) => void; - }>; + let defaultArgs: ReturnType; beforeEach(() => { defaultArgs = makeDefaultArgs(); @@ -1001,4 +952,159 @@ describe('Lens App', () => { expect(args.core.notifications.toasts.addDanger).toHaveBeenCalled(); }); + + describe('showing a confirm message when leaving', () => { + let defaultArgs: ReturnType; + let defaultLeave: jest.Mock; + let confirmLeave: jest.Mock; + + beforeEach(() => { + defaultArgs = makeDefaultArgs(); + defaultLeave = jest.fn(); + confirmLeave = jest.fn(); + (defaultArgs.docStorage.load as jest.Mock).mockResolvedValue({ + id: '1234', + title: 'My cool doc', + expression: 'valid expression', + state: { + query: 'kuery', + datasourceMetaData: { filterableIndexPatterns: [{ id: '1', title: 'saved' }] }, + }, + } as jest.ResolvedValue); + }); + + it('should not show a confirm message if there is no expression to save', () => { + instance = mount(); + + const lastCall = + defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0]; + lastCall({ default: defaultLeave, confirm: confirmLeave }); + + expect(defaultLeave).toHaveBeenCalled(); + expect(confirmLeave).not.toHaveBeenCalled(); + }); + + it('does not confirm if the user is missing save permissions', () => { + const args = defaultArgs; + args.core.application = { + ...args.core.application, + capabilities: { + ...args.core.application.capabilities, + visualize: { save: false, saveQuery: false, show: true }, + }, + }; + args.editorFrame = frame; + + instance = mount(); + + const onChange = frame.mount.mock.calls[0][1].onChange; + act(() => + onChange({ + filterableIndexPatterns: [], + doc: ({ id: undefined, expression: 'valid expression' } as unknown) as Document, + }) + ); + instance.update(); + + const lastCall = + defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0]; + lastCall({ default: defaultLeave, confirm: confirmLeave }); + + expect(defaultLeave).toHaveBeenCalled(); + expect(confirmLeave).not.toHaveBeenCalled(); + }); + + it('should confirm when leaving with an unsaved doc', () => { + defaultArgs.editorFrame = frame; + instance = mount(); + + const onChange = frame.mount.mock.calls[0][1].onChange; + act(() => + onChange({ + filterableIndexPatterns: [], + doc: ({ id: undefined, expression: 'valid expression' } as unknown) as Document, + }) + ); + instance.update(); + + const lastCall = + defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0]; + lastCall({ default: defaultLeave, confirm: confirmLeave }); + + expect(confirmLeave).toHaveBeenCalled(); + expect(defaultLeave).not.toHaveBeenCalled(); + }); + + it('should confirm when leaving with unsaved changes to an existing doc', async () => { + defaultArgs.editorFrame = frame; + instance = mount(); + await act(async () => { + instance.setProps({ docId: '1234' }); + }); + + const onChange = frame.mount.mock.calls[0][1].onChange; + act(() => + onChange({ + filterableIndexPatterns: [], + doc: ({ id: '1234', expression: 'different expression' } as unknown) as Document, + }) + ); + instance.update(); + + const lastCall = + defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0]; + lastCall({ default: defaultLeave, confirm: confirmLeave }); + + expect(confirmLeave).toHaveBeenCalled(); + expect(defaultLeave).not.toHaveBeenCalled(); + }); + + it('should not confirm when changes are saved', async () => { + defaultArgs.editorFrame = frame; + instance = mount(); + await act(async () => { + instance.setProps({ docId: '1234' }); + }); + + const onChange = frame.mount.mock.calls[0][1].onChange; + act(() => + onChange({ + filterableIndexPatterns: [], + doc: ({ id: '1234', expression: 'valid expression' } as unknown) as Document, + }) + ); + instance.update(); + + const lastCall = + defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0]; + lastCall({ default: defaultLeave, confirm: confirmLeave }); + + expect(defaultLeave).toHaveBeenCalled(); + expect(confirmLeave).not.toHaveBeenCalled(); + }); + + it('should confirm when the latest doc is invalid', async () => { + defaultArgs.editorFrame = frame; + instance = mount(); + await act(async () => { + instance.setProps({ docId: '1234' }); + }); + + const onChange = frame.mount.mock.calls[0][1].onChange; + act(() => + onChange({ + filterableIndexPatterns: [], + doc: ({ id: '1234', expression: null } as unknown) as Document, + }) + ); + instance.update(); + + const lastCall = + defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0]; + lastCall({ default: defaultLeave, confirm: confirmLeave }); + + expect(confirmLeave).toHaveBeenCalled(); + expect(defaultLeave).not.toHaveBeenCalled(); + }); + }); }); diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index a77fbbb59756..ffa59a6fb6bc 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -10,7 +10,7 @@ import { I18nProvider } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { Query, DataPublicPluginStart } from 'src/plugins/data/public'; import { NavigationPublicPluginStart } from 'src/plugins/navigation/public'; -import { AppMountContext, NotificationsStart } from 'kibana/public'; +import { AppMountContext, AppMountParameters, NotificationsStart } from 'kibana/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; import { @@ -57,6 +57,7 @@ export function App({ redirectTo, originatingAppFromUrl, navigation, + onAppLeave, }: { editorFrame: EditorFrameInstance; data: DataPublicPluginStart; @@ -72,6 +73,7 @@ export function App({ newlyCreated?: boolean ) => void; originatingAppFromUrl?: string | undefined; + onAppLeave: AppMountParameters['onAppLeave']; }) { const language = storage.get('kibana.userQueryLanguage') || core.uiSettings.get('search:queryLanguage'); @@ -94,6 +96,12 @@ export function App({ const { lastKnownDoc } = state; + const isSaveable = + lastKnownDoc && + lastKnownDoc.expression && + lastKnownDoc.expression.length > 0 && + core.application.capabilities.visualize.save; + useEffect(() => { // Clear app-specific filters when navigating to Lens. Necessary because Lens // can be loaded without a full page refresh @@ -123,7 +131,31 @@ export function App({ filterSubscription.unsubscribe(); timeSubscription.unsubscribe(); }; - }, []); + }, [data.query.filterManager, data.query.timefilter.timefilter]); + + useEffect(() => { + onAppLeave((actions) => { + // Confirm when the user has made any changes to an existing doc + // or when the user has configured something without saving + if ( + core.application.capabilities.visualize.save && + (state.persistedDoc?.expression + ? !_.isEqual(lastKnownDoc?.expression, state.persistedDoc.expression) + : lastKnownDoc?.expression) + ) { + return actions.confirm( + i18n.translate('xpack.lens.app.unsavedWorkMessage', { + defaultMessage: 'Leave Lens with unsaved work?', + }), + i18n.translate('xpack.lens.app.unsavedWorkTitle', { + defaultMessage: 'Unsaved changes', + }) + ); + } else { + return actions.default(); + } + }); + }, [lastKnownDoc, onAppLeave, state.persistedDoc, core.application.capabilities.visualize.save]); // Sync Kibana breadcrumbs any time the saved document's title changes useEffect(() => { @@ -144,7 +176,7 @@ export function App({ : i18n.translate('xpack.lens.breadcrumbsCreate', { defaultMessage: 'Create' }), }, ]); - }, [state.persistedDoc && state.persistedDoc.title]); + }, [core.application, core.chrome, core.http.basePath, state.persistedDoc]); useEffect(() => { if (docId && (!state.persistedDoc || state.persistedDoc.id !== docId)) { @@ -187,13 +219,16 @@ export function App({ redirectTo(); }); } - }, [docId]); - - const isSaveable = - lastKnownDoc && - lastKnownDoc.expression && - lastKnownDoc.expression.length > 0 && - core.application.capabilities.visualize.save; + }, [ + core.notifications, + data.indexPatterns, + data.query.filterManager, + docId, + // TODO: These dependencies are changing too often + // docStorage, + // redirectTo, + // state.persistedDoc, + ]); const runSave = ( saveProps: Omit & { @@ -257,7 +292,7 @@ export function App({ core.notifications.toasts.addDanger({ title: e.message, }), - [] + [core.notifications.toasts] ); const { TopNavMenu } = navigation.ui; diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index 7c875935f632..032ce8325dca 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -92,6 +92,7 @@ export async function mountApp( redirectTo(routeProps, id, returnToOrigin, originatingApp, newlyCreated) } originatingAppFromUrl={originatingAppFromUrl} + onAppLeave={params.onAppLeave} /> ); }; From 071f7ef20be94bf8f36007eddc5aa62d42b594df Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 1 Jun 2020 22:57:44 +0100 Subject: [PATCH 11/50] skip flaky suite (#67821) --- x-pack/test/accessibility/apps/search_profiler.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/accessibility/apps/search_profiler.ts b/x-pack/test/accessibility/apps/search_profiler.ts index 8a13940695f9..138231d3cf02 100644 --- a/x-pack/test/accessibility/apps/search_profiler.ts +++ b/x-pack/test/accessibility/apps/search_profiler.ts @@ -14,7 +14,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const a11y = getService('a11y'); const flyout = getService('flyout'); - describe('Accessibility Search Profiler Editor', () => { + // FLAKY: https://github.com/elastic/kibana/issues/67821 + describe.skip('Accessibility Search Profiler Editor', () => { before(async () => { await PageObjects.common.navigateToApp('searchProfiler'); await a11y.testAppSnapshot(); From 571b3de667b1da2281ad49cbfec948869f7df86e Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Mon, 1 Jun 2020 15:25:20 -0700 Subject: [PATCH 12/50] [DOCS] Replace docdir attribute with kib-repo-dir (#67907) --- docs/index.asciidoc | 6 +++--- docs/setup/settings.asciidoc | 26 +++++++++++++------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/index.asciidoc b/docs/index.asciidoc index 5474772ab7da..add91600a34e 100644 --- a/docs/index.asciidoc +++ b/docs/index.asciidoc @@ -3,11 +3,11 @@ :include-xpack: true :lang: en -:kib-repo-dir: {docdir} +:kib-repo-dir: {kibana-root}/docs :blog-ref: https://www.elastic.co/blog/ :wikipedia: https://en.wikipedia.org/wiki -include::{asciidoc-dir}/../../shared/versions/stack/{source_branch}.asciidoc[] +include::{docs-root}/shared/versions/stack/{source_branch}.asciidoc[] :docker-repo: docker.elastic.co/kibana/kibana :docker-image: docker.elastic.co/kibana/kibana:{version} @@ -18,7 +18,7 @@ include::{asciidoc-dir}/../../shared/versions/stack/{source_branch}.asciidoc[] :blob: {repo}blob/{branch}/ :security-ref: https://www.elastic.co/community/security/ -include::{asciidoc-dir}/../../shared/attributes.asciidoc[] +include::{docs-root}/shared/attributes.asciidoc[] include::user/index.asciidoc[] diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 6596f93a88f5..42d616c80119 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -627,17 +627,17 @@ Valid locales are: `en`, `zh-CN`, `ja-JP`. *Default: `en`* |=== -include::{docdir}/settings/alert-action-settings.asciidoc[] -include::{docdir}/settings/apm-settings.asciidoc[] -include::{docdir}/settings/dev-settings.asciidoc[] -include::{docdir}/settings/graph-settings.asciidoc[] -include::{docdir}/settings/infrastructure-ui-settings.asciidoc[] -include::{docdir}/settings/i18n-settings.asciidoc[] -include::{docdir}/settings/logs-ui-settings.asciidoc[] -include::{docdir}/settings/ml-settings.asciidoc[] -include::{docdir}/settings/monitoring-settings.asciidoc[] -include::{docdir}/settings/reporting-settings.asciidoc[] +include::{kib-repo-dir}/settings/alert-action-settings.asciidoc[] +include::{kib-repo-dir}/settings/apm-settings.asciidoc[] +include::{kib-repo-dir}/settings/dev-settings.asciidoc[] +include::{kib-repo-dir}/settings/graph-settings.asciidoc[] +include::{kib-repo-dir}/settings/infrastructure-ui-settings.asciidoc[] +include::{kib-repo-dir}/settings/i18n-settings.asciidoc[] +include::{kib-repo-dir}/settings/logs-ui-settings.asciidoc[] +include::{kib-repo-dir}/settings/ml-settings.asciidoc[] +include::{kib-repo-dir}/settings/monitoring-settings.asciidoc[] +include::{kib-repo-dir}/settings/reporting-settings.asciidoc[] include::secure-settings.asciidoc[] -include::{docdir}/settings/security-settings.asciidoc[] -include::{docdir}/settings/spaces-settings.asciidoc[] -include::{docdir}/settings/telemetry-settings.asciidoc[] +include::{kib-repo-dir}/settings/security-settings.asciidoc[] +include::{kib-repo-dir}/settings/spaces-settings.asciidoc[] +include::{kib-repo-dir}/settings/telemetry-settings.asciidoc[] From 0dca28b6dd0c6a3c5ef51e5c9578a5f1809c86a8 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Mon, 1 Jun 2020 16:30:28 -0600 Subject: [PATCH 13/50] [SEIM][Detection Engine] Moves the io-ts schemas to the common folder from the server side ## Summary This moves the io-ts schemas from the common folder from the server side up to the common folder. ### Checklist - [x] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios --- .../schemas/common}/schemas.ts | 0 .../schemas/response/__mocks__/utils.ts | 0 .../schemas/response/error_schema.test.ts | 13 +- .../schemas/response/error_schema.ts | 2 +- .../response/find_rules_schema.test.ts | 13 +- .../schemas/response/find_rules_schema.ts | 2 +- .../response/import_rules_schema.test.ts | 13 +- .../schemas/response/import_rules_schema.ts | 2 +- .../response/prepackaged_rules_schema.test.ts | 13 +- .../response/prepackaged_rules_schema.ts | 2 +- .../prepackaged_rules_status_schema.test.ts | 13 +- .../prepackaged_rules_status_schema.ts | 2 +- .../response/rules_bulk_schema.test.ts | 13 +- .../schemas/response/rules_bulk_schema.ts | 0 .../schemas/response/rules_schema.test.ts} | 201 +++++++++++- .../schemas/response/rules_schema.ts | 110 +++++-- .../type_timeline_only_schema.test.ts | 13 +- .../response/type_timeline_only_schema.ts | 2 +- .../schemas/types/iso_date_string.test.ts | 2 +- .../schemas/types/iso_date_string.ts | 0 .../schemas/types/lists_default_array.test.ts | 2 +- .../schemas/types/lists_default_array.ts | 2 +- .../schemas/types/positive_integer.ts | 0 ...positive_integer_greater_than_zero.test.ts | 2 +- .../positive_integer_greater_than_zero.ts | 0 .../schemas/types/postive_integer.test.ts | 2 +- .../types/references_default_array.test.ts | 2 +- .../schemas/types/references_default_array.ts | 0 .../schemas/types/risk_score.test.ts | 2 +- .../schemas/types/risk_score.ts | 0 .../schemas/types/uuid.test.ts | 2 +- .../detection_engine}/schemas/types/uuid.ts | 0 .../rules/add_prepackaged_rules_route.ts | 8 +- .../routes/rules/create_rules_bulk_route.ts | 2 +- .../routes/rules/delete_rules_bulk_route.ts | 2 +- .../get_prepackaged_rules_status_route.ts | 8 +- .../routes/rules/import_rules_route.ts | 5 +- .../routes/rules/patch_rules_bulk_route.ts | 2 +- .../routes/rules/update_rules_bulk_route.ts | 2 +- .../routes/rules/validate.test.ts | 2 +- .../detection_engine/routes/rules/validate.ts | 7 +- .../schemas/response/check_type_dependents.ts | 97 ------ .../schemas/response/rules_schema.test.ts | 289 ------------------ .../signals/build_exceptions_query.test.ts | 2 +- .../signals/build_exceptions_query.ts | 6 +- .../signals/filter_events_with_list.ts | 2 +- .../siem/server/lib/detection_engine/types.ts | 2 +- .../timeline/routes/import_timelines_route.ts | 2 +- 48 files changed, 333 insertions(+), 535 deletions(-) rename x-pack/plugins/siem/{server/lib/detection_engine/routes/schemas/response => common/detection_engine/schemas/common}/schemas.ts (100%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/response/__mocks__/utils.ts (100%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/response/error_schema.test.ts (86%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/response/error_schema.ts (92%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/response/find_rules_schema.test.ts (93%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/response/find_rules_schema.ts (89%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/response/import_rules_schema.test.ts (92%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/response/import_rules_schema.ts (91%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/response/prepackaged_rules_schema.test.ts (90%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/response/prepackaged_rules_schema.ts (89%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/response/prepackaged_rules_status_schema.test.ts (92%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/response/prepackaged_rules_status_schema.ts (96%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/response/rules_bulk_schema.test.ts (93%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/response/rules_bulk_schema.ts (100%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes/schemas/response/check_type_dependents.test.ts => common/detection_engine/schemas/response/rules_schema.test.ts} (70%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/response/rules_schema.ts (54%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/response/type_timeline_only_schema.test.ts (85%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/response/type_timeline_only_schema.ts (92%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/types/iso_date_string.test.ts (95%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/types/iso_date_string.ts (100%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/types/lists_default_array.test.ts (98%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/types/lists_default_array.ts (97%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/types/positive_integer.ts (100%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/types/positive_integer_greater_than_zero.test.ts (95%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/types/positive_integer_greater_than_zero.ts (100%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/types/postive_integer.test.ts (95%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/types/references_default_array.test.ts (95%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/types/references_default_array.ts (100%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/types/risk_score.test.ts (96%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/types/risk_score.ts (100%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/types/uuid.test.ts (94%) rename x-pack/plugins/siem/{server/lib/detection_engine/routes => common/detection_engine}/schemas/types/uuid.ts (100%) delete mode 100644 x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/check_type_dependents.ts delete mode 100644 x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_schema.test.ts diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/schemas.ts b/x-pack/plugins/siem/common/detection_engine/schemas/common/schemas.ts similarity index 100% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/schemas.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/common/schemas.ts diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/__mocks__/utils.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/__mocks__/utils.ts similarity index 100% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/__mocks__/utils.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/__mocks__/utils.ts diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/error_schema.test.ts similarity index 86% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/error_schema.test.ts index 9bbde3d5236d..2a4d75522d01 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/error_schema.test.ts @@ -9,19 +9,10 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { getErrorPayload } from './__mocks__/utils'; import { errorSchema, ErrorSchema } from './error_schema'; -import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../../common/exact_check'; -import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; +import { exactCheck } from '../../../exact_check'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('error_schema', () => { - beforeAll(() => { - setFeatureFlagsForTestsOnly(); - }); - - afterAll(() => { - unSetFeatureFlagsForTestsOnly(); - }); - test('it should validate an error with a UUID given for id', () => { const error = getErrorPayload(); const decoded = errorSchema.decode(getErrorPayload()); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/error_schema.ts similarity index 92% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/error_schema.ts index f9c776e3b3cd..986d3ad87ec8 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/error_schema.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; /* eslint-disable @typescript-eslint/camelcase */ -import { rule_id, status_code, message } from './schemas'; +import { rule_id, status_code, message } from '../common/schemas'; /* eslint-enable @typescript-eslint/camelcase */ // We use id: t.string intentionally and _never_ the id from global schemas as diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/find_rules_schema.test.ts similarity index 93% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/find_rules_schema.test.ts index 1b7d7994462c..51163c3d76ed 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/find_rules_schema.test.ts @@ -9,19 +9,10 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { getFindResponseSingle, getBaseResponsePayload } from './__mocks__/utils'; import { left } from 'fp-ts/lib/Either'; import { RulesSchema } from './rules_schema'; -import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; -import { exactCheck } from '../../../../../../common/exact_check'; +import { exactCheck } from '../../../exact_check'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('find_rules_schema', () => { - beforeAll(() => { - setFeatureFlagsForTestsOnly(); - }); - - afterAll(() => { - unSetFeatureFlagsForTestsOnly(); - }); - test('it should validate a typical single find rules response', () => { const payload = getFindResponseSingle(); const decoded = findRulesSchema.decode(payload); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/find_rules_schema.ts similarity index 89% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/find_rules_schema.ts index d7e8a246cfe0..77077ce2e22a 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/find_rules_schema.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; import { rulesSchema } from './rules_schema'; -import { page, perPage, total } from './schemas'; +import { page, perPage, total } from '../common/schemas'; export const findRulesSchema = t.exact( t.type({ diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/import_rules_schema.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/import_rules_schema.test.ts similarity index 92% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/import_rules_schema.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/import_rules_schema.test.ts index 18e17a319883..d7efe4b30af1 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/import_rules_schema.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/import_rules_schema.test.ts @@ -8,20 +8,11 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { left, Either } from 'fp-ts/lib/Either'; import { ImportRulesSchema, importRulesSchema } from './import_rules_schema'; import { ErrorSchema } from './error_schema'; -import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; import { Errors } from 'io-ts'; -import { exactCheck } from '../../../../../../common/exact_check'; -import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; +import { exactCheck } from '../../../exact_check'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('import_rules_schema', () => { - beforeAll(() => { - setFeatureFlagsForTestsOnly(); - }); - - afterAll(() => { - unSetFeatureFlagsForTestsOnly(); - }); - test('it should validate an empty import response with no errors', () => { const payload: ImportRulesSchema = { success: true, success_count: 0, errors: [] }; const decoded = importRulesSchema.decode(payload); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/import_rules_schema.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/import_rules_schema.ts similarity index 91% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/import_rules_schema.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/import_rules_schema.ts index dec32b18e2b2..adea77e7b933 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/import_rules_schema.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/import_rules_schema.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; /* eslint-disable @typescript-eslint/camelcase */ -import { success, success_count } from './schemas'; +import { success, success_count } from '../common/schemas'; import { errorSchema } from './error_schema'; /* eslint-enable @typescript-eslint/camelcase */ diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/prepackaged_rules_schema.test.ts similarity index 90% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/prepackaged_rules_schema.test.ts index 2d3fd7591482..fc3f89996daf 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/prepackaged_rules_schema.test.ts @@ -7,19 +7,10 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; import { PrePackagedRulesSchema, prePackagedRulesSchema } from './prepackaged_rules_schema'; -import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../../common/exact_check'; -import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; +import { exactCheck } from '../../../exact_check'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('prepackaged_rules_schema', () => { - beforeAll(() => { - setFeatureFlagsForTestsOnly(); - }); - - afterAll(() => { - unSetFeatureFlagsForTestsOnly(); - }); - test('it should validate an empty prepackaged response with defaults', () => { const payload: PrePackagedRulesSchema = { rules_installed: 0, rules_updated: 0 }; const decoded = prePackagedRulesSchema.decode(payload); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/prepackaged_rules_schema.ts similarity index 89% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/prepackaged_rules_schema.ts index f0eff0ba1975..3b0107c91fee 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/prepackaged_rules_schema.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; /* eslint-disable @typescript-eslint/camelcase */ -import { rules_installed, rules_updated } from './schemas'; +import { rules_installed, rules_updated } from '../common/schemas'; /* eslint-enable @typescript-eslint/camelcase */ export const prePackagedRulesSchema = t.exact( diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/prepackaged_rules_status_schema.test.ts similarity index 92% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/prepackaged_rules_status_schema.test.ts index abe601a54611..eeae72209829 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/prepackaged_rules_status_schema.test.ts @@ -10,19 +10,10 @@ import { PrePackagedRulesStatusSchema, prePackagedRulesStatusSchema, } from './prepackaged_rules_status_schema'; -import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../../common/exact_check'; -import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; +import { exactCheck } from '../../../exact_check'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('prepackaged_rules_schema', () => { - beforeAll(() => { - setFeatureFlagsForTestsOnly(); - }); - - afterAll(() => { - unSetFeatureFlagsForTestsOnly(); - }); - test('it should validate an empty prepackaged response with defaults', () => { const payload: PrePackagedRulesStatusSchema = { rules_installed: 0, diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/prepackaged_rules_status_schema.ts similarity index 96% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/prepackaged_rules_status_schema.ts index 72e5821eb469..ee8e7b48a58b 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/prepackaged_rules_status_schema.ts @@ -12,7 +12,7 @@ import { rules_custom_installed, rules_not_installed, rules_not_updated, -} from './schemas'; +} from '../common/schemas'; /* eslint-enable @typescript-eslint/camelcase */ export const prePackagedRulesStatusSchema = t.exact( diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_bulk_schema.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/rules_bulk_schema.test.ts similarity index 93% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_bulk_schema.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/rules_bulk_schema.test.ts index 98cb2ef05848..04cf012f36db 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_bulk_schema.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/rules_bulk_schema.test.ts @@ -11,19 +11,10 @@ import { getBaseResponsePayload, getErrorPayload } from './__mocks__/utils'; import { RulesBulkSchema, rulesBulkSchema } from './rules_bulk_schema'; import { RulesSchema } from './rules_schema'; import { ErrorSchema } from './error_schema'; -import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../../common/exact_check'; -import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; +import { exactCheck } from '../../../exact_check'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('prepackaged_rule_schema', () => { - beforeAll(() => { - setFeatureFlagsForTestsOnly(); - }); - - afterAll(() => { - unSetFeatureFlagsForTestsOnly(); - }); - test('it should validate a regular message and and error together with a uuid', () => { const payload: RulesBulkSchema = [getBaseResponsePayload(), getErrorPayload()]; const decoded = rulesBulkSchema.decode(payload); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_bulk_schema.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/rules_bulk_schema.ts similarity index 100% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_bulk_schema.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/rules_bulk_schema.ts diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/check_type_dependents.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/rules_schema.test.ts similarity index 70% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/check_type_dependents.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/rules_schema.test.ts index 0b0d3bf43b1e..8ed9c30507f4 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/check_type_dependents.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/rules_schema.test.ts @@ -4,32 +4,209 @@ * you may not use this file except in compliance with the Elastic License. */ +import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import * as t from 'io-ts'; import { + rulesSchema, + RulesSchema, checkTypeDependents, getDependents, addSavedId, - addTimelineTitle, addQueryFields, + addTimelineTitle, addMlFields, -} from './check_type_dependents'; +} from './rules_schema'; import { getBaseResponsePayload, getMlRuleResponsePayload } from './__mocks__/utils'; -import { left } from 'fp-ts/lib/Either'; -import { RulesSchema } from './rules_schema'; +import { exactCheck } from '../../../exact_check'; +import { foldLeftRight, getPaths } from '../../../test_utils'; import { TypeAndTimelineOnly } from './type_timeline_only_schema'; -import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../../common/exact_check'; -import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; -describe('check_type_dependents', () => { - beforeAll(() => { - setFeatureFlagsForTestsOnly(); +export const ANCHOR_DATE = '2020-02-20T03:57:54.037Z'; + +describe('rules_schema', () => { + test('it should validate a type of "query" without anything extra', () => { + const payload = getBaseResponsePayload(); + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = getBaseResponsePayload(); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); + + test('it should NOT validate a type of "query" when it has extra data', () => { + const payload: RulesSchema & { invalid_extra_data?: string } = getBaseResponsePayload(); + payload.invalid_extra_data = 'invalid_extra_data'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate invalid_data for the type', () => { + const payload: Omit & { type: string } = getBaseResponsePayload(); + payload.type = 'invalid_data'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "invalid_data" supplied to "type"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "query" with a saved_id together', () => { + const payload = getBaseResponsePayload(); + payload.type = 'query'; + payload.saved_id = 'save id 123'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "saved_id"']); + expect(message.schema).toEqual({}); + }); + + test('it should validate a type of "saved_query" with a "saved_id" dependent', () => { + const payload = getBaseResponsePayload(); + payload.type = 'saved_query'; + payload.saved_id = 'save id 123'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = getBaseResponsePayload(); + + expected.type = 'saved_query'; + expected.saved_id = 'save id 123'; + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); + + test('it should NOT validate a type of "saved_query" without a "saved_id" dependent', () => { + const payload = getBaseResponsePayload(); + payload.type = 'saved_query'; + delete payload.saved_id; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "saved_id"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "saved_query" when it has extra data', () => { + const payload: RulesSchema & { invalid_extra_data?: string } = getBaseResponsePayload(); + payload.type = 'saved_query'; + payload.saved_id = 'save id 123'; + payload.invalid_extra_data = 'invalid_extra_data'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); + expect(message.schema).toEqual({}); + }); + + test('it should validate a type of "timeline_id" if there is a "timeline_title" dependent', () => { + const payload = getBaseResponsePayload(); + payload.timeline_id = 'some timeline id'; + payload.timeline_title = 'some timeline title'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = getBaseResponsePayload(); + expected.timeline_id = 'some timeline id'; + expected.timeline_title = 'some timeline title'; + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); }); - afterAll(() => { - unSetFeatureFlagsForTestsOnly(); + test('it should NOT validate a type of "timeline_id" if there is "timeline_title" dependent when it has extra invalid data', () => { + const payload: RulesSchema & { invalid_extra_data?: string } = getBaseResponsePayload(); + payload.timeline_id = 'some timeline id'; + payload.timeline_title = 'some timeline title'; + payload.invalid_extra_data = 'invalid_extra_data'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "timeline_id" if there is NOT a "timeline_title" dependent', () => { + const payload = getBaseResponsePayload(); + payload.timeline_id = 'some timeline id'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "timeline_title"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "timeline_title" if there is NOT a "timeline_id" dependent', () => { + const payload = getBaseResponsePayload(); + payload.timeline_title = 'some timeline title'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_title" but there is NOT a "timeline_id"', () => { + const payload = getBaseResponsePayload(); + payload.saved_id = 'some saved id'; + payload.type = 'saved_query'; + payload.timeline_title = 'some timeline title'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_id" but there is NOT a "timeline_title"', () => { + const payload = getBaseResponsePayload(); + payload.saved_id = 'some saved id'; + payload.type = 'saved_query'; + payload.timeline_id = 'some timeline id'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "timeline_title"', + ]); + expect(message.schema).toEqual({}); }); describe('checkTypeDependents', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_schema.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/rules_schema.ts similarity index 54% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_schema.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/rules_schema.ts index fb1ee8e670e3..a7a31ec9e1b5 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_schema.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/rules_schema.ts @@ -7,10 +7,11 @@ /* eslint-disable @typescript-eslint/camelcase */ import * as t from 'io-ts'; import { isObject } from 'lodash/fp'; -import { Either, fold, right, left } from 'fp-ts/lib/Either'; - +import { Either, left, fold } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; -import { checkTypeDependents } from './check_type_dependents'; +import { typeAndTimelineOnlySchema, TypeAndTimelineOnly } from './type_timeline_only_schema'; +import { isMlRule } from '../../../machine_learning/helpers'; + import { actions, anomaly_threshold, @@ -54,9 +55,8 @@ import { filters, meta, note, -} from './schemas'; +} from '../common/schemas'; import { ListsDefaultArray } from '../types/lists_default_array'; -import { hasListsFeature } from '../../../feature_flags'; /** * This is the required fields for the rules schema response. Put all required properties on @@ -155,32 +155,92 @@ export const rulesSchema = new t.Type< 'RulesSchema', (input: unknown): input is RulesWithoutTypeDependentsSchema => isObject(input), (input): Either => { - const output = checkTypeDependents(input); - if (!hasListsFeature()) { - // TODO: (LIST-FEATURE) Remove this after the lists feature is an accepted feature for a particular release - return removeList(output); - } else { - return output; - } + return checkTypeDependents(input); }, t.identity ); -// TODO: (LIST-FEATURE) Remove this after the lists feature is an accepted feature for a particular release -export const removeList = ( - decoded: Either -): Either => { - const onLeft = (errors: t.Errors): Either => left(errors); - const onRight = (decodedValue: RequiredRulesSchema): Either => { - delete decodedValue.exceptions_list; - return right(decodedValue); - }; - const folded = fold(onLeft, onRight); - return pipe(decoded, folded); -}; - /** * This is the correct type you want to use for Rules that are outputted from the * REST interface. This has all base and all optional properties merged together. */ export type RulesSchema = t.TypeOf; + +export const addSavedId = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { + if (typeAndTimelineOnly.type === 'saved_query') { + return [t.exact(t.type({ saved_id: dependentRulesSchema.props.saved_id }))]; + } else { + return []; + } +}; + +export const addTimelineTitle = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { + if (typeAndTimelineOnly.timeline_id != null) { + return [ + t.exact(t.type({ timeline_title: dependentRulesSchema.props.timeline_title })), + t.exact(t.type({ timeline_id: dependentRulesSchema.props.timeline_id })), + ]; + } else { + return []; + } +}; + +export const addQueryFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { + if (typeAndTimelineOnly.type === 'query' || typeAndTimelineOnly.type === 'saved_query') { + return [ + t.exact(t.type({ query: dependentRulesSchema.props.query })), + t.exact(t.type({ language: dependentRulesSchema.props.language })), + ]; + } else { + return []; + } +}; + +export const addMlFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { + if (isMlRule(typeAndTimelineOnly.type)) { + return [ + t.exact(t.type({ anomaly_threshold: dependentRulesSchema.props.anomaly_threshold })), + t.exact( + t.type({ machine_learning_job_id: dependentRulesSchema.props.machine_learning_job_id }) + ), + ]; + } else { + return []; + } +}; + +export const getDependents = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed => { + const dependents: t.Mixed[] = [ + t.exact(requiredRulesSchema), + t.exact(partialRulesSchema), + ...addSavedId(typeAndTimelineOnly), + ...addTimelineTitle(typeAndTimelineOnly), + ...addQueryFields(typeAndTimelineOnly), + ...addMlFields(typeAndTimelineOnly), + ]; + + if (dependents.length > 1) { + // This unsafe cast is because t.intersection does not use an array but rather a set of + // tuples and really does not look like they expected us to ever dynamically build up + // intersections, but here we are doing that. Looking at their code, although they limit + // the array elements to 5, it looks like you have N number of intersections + const unsafeCast: [t.Mixed, t.Mixed] = dependents as [t.Mixed, t.Mixed]; + return t.intersection(unsafeCast); + } else { + // We are not allowed to call t.intersection with a single value so we return without + // it here normally. + return dependents[0]; + } +}; + +export const checkTypeDependents = (input: unknown): Either => { + const typeOnlyDecoded = typeAndTimelineOnlySchema.decode(input); + const onLeft = (errors: t.Errors): Either => left(errors); + const onRight = ( + typeAndTimelineOnly: TypeAndTimelineOnly + ): Either => { + const intersections = getDependents(typeAndTimelineOnly); + return intersections.decode(input); + }; + return pipe(typeOnlyDecoded, fold(onLeft, onRight)); +}; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/type_timeline_only_schema.test.ts similarity index 85% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/type_timeline_only_schema.test.ts index 8f06e2c6e49b..c7335ffd62f0 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/type_timeline_only_schema.test.ts @@ -8,19 +8,10 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import { TypeAndTimelineOnly, typeAndTimelineOnlySchema } from './type_timeline_only_schema'; -import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../../common/exact_check'; -import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; +import { exactCheck } from '../../../exact_check'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('prepackaged_rule_schema', () => { - beforeAll(() => { - setFeatureFlagsForTestsOnly(); - }); - - afterAll(() => { - unSetFeatureFlagsForTestsOnly(); - }); - test('it should validate a a type and timeline_id together', () => { const payload: TypeAndTimelineOnly = { type: 'query', diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/type_timeline_only_schema.ts similarity index 92% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/type_timeline_only_schema.ts index 6d11ff03563d..d23d4ad2e83d 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/type_timeline_only_schema.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; /* eslint-disable @typescript-eslint/camelcase */ -import { timeline_id, type } from './schemas'; +import { timeline_id, type } from '../common/schemas'; /* eslint-enable @typescript-eslint/camelcase */ /** diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/iso_date_string.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/iso_date_string.test.ts similarity index 95% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/iso_date_string.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/iso_date_string.test.ts index 9f9181359d44..e8bce3f38f4b 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/iso_date_string.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/types/iso_date_string.test.ts @@ -7,7 +7,7 @@ import { IsoDateString } from './iso_date_string'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('ios_date_string', () => { test('it should validate a iso string', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/iso_date_string.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/iso_date_string.ts similarity index 100% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/iso_date_string.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/iso_date_string.ts diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/lists_default_array.test.ts similarity index 98% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/lists_default_array.test.ts index dc0bd6cacf0d..31e0a8e5c2c7 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/types/lists_default_array.test.ts @@ -7,7 +7,7 @@ import { ListsDefaultArray } from './lists_default_array'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('lists_default_array', () => { test('it should validate an empty array', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/lists_default_array.ts similarity index 97% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/lists_default_array.ts index 743914ad070a..8244f4a29e19 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/types/lists_default_array.ts @@ -11,7 +11,7 @@ import { list_and as listAnd, list_values as listValues, list_values_operator as listOperator, -} from '../response/schemas'; +} from '../common/schemas'; export type ListsDefaultArrayC = t.Type; export type List = t.TypeOf; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/positive_integer.ts similarity index 100% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/positive_integer.ts diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer_greater_than_zero.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/positive_integer_greater_than_zero.test.ts similarity index 95% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer_greater_than_zero.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/positive_integer_greater_than_zero.test.ts index a3338c878bd7..821eb066a653 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer_greater_than_zero.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/types/positive_integer_greater_than_zero.test.ts @@ -7,7 +7,7 @@ import { PositiveIntegerGreaterThanZero } from './positive_integer_greater_than_zero'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('positive_integer_greater_than_zero', () => { test('it should validate a positive number', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer_greater_than_zero.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/positive_integer_greater_than_zero.ts similarity index 100% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer_greater_than_zero.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/positive_integer_greater_than_zero.ts diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/postive_integer.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/postive_integer.test.ts similarity index 95% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/postive_integer.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/postive_integer.test.ts index 48ea2025b9b1..ea00ecf5efe0 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/postive_integer.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/types/postive_integer.test.ts @@ -7,7 +7,7 @@ import { PositiveInteger } from './positive_integer'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('positive_integer_greater_than_zero', () => { test('it should validate a positive number', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/references_default_array.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/references_default_array.test.ts similarity index 95% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/references_default_array.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/references_default_array.test.ts index 3aaff7e00ad5..43e2dbdac1fe 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/references_default_array.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/types/references_default_array.test.ts @@ -7,7 +7,7 @@ import { ReferencesDefaultArray } from './references_default_array'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('references_default_array', () => { test('it should validate an empty array', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/references_default_array.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/references_default_array.ts similarity index 100% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/references_default_array.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/references_default_array.ts diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/risk_score.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/risk_score.test.ts similarity index 96% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/risk_score.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/risk_score.test.ts index 41c0faf4d608..cf849f28a096 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/risk_score.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/types/risk_score.test.ts @@ -7,7 +7,7 @@ import { RiskScore } from './risk_score'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('risk_score', () => { test('it should validate a positive number', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/risk_score.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/risk_score.ts similarity index 100% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/risk_score.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/risk_score.ts diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/uuid.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/uuid.test.ts similarity index 94% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/uuid.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/uuid.test.ts index b640b449e6b8..d3a68a757548 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/uuid.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/types/uuid.test.ts @@ -7,7 +7,7 @@ import { UUID } from './uuid'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('uuid', () => { test('it should validate a uuid', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/uuid.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/uuid.ts similarity index 100% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/uuid.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/uuid.ts diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts index 83dd87002e8f..6268451042da 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts @@ -4,6 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { + PrePackagedRulesSchema, + prePackagedRulesSchema, +} from '../../../../../common/detection_engine/schemas/response/prepackaged_rules_schema'; import { IRouter } from '../../../../../../../../src/core/server'; import { DETECTION_ENGINE_PREPACKAGED_URL } from '../../../../../common/constants'; import { getIndexExists } from '../../index/get_index_exists'; @@ -14,10 +18,6 @@ import { updatePrepackagedRules } from '../../rules/update_prepacked_rules'; import { getRulesToInstall } from '../../rules/get_rules_to_install'; import { getRulesToUpdate } from '../../rules/get_rules_to_update'; import { getExistingPrepackagedRules } from '../../rules/get_existing_prepackaged_rules'; -import { - PrePackagedRulesSchema, - prePackagedRulesSchema, -} from '../schemas/response/prepackaged_rules_schema'; import { validate } from './validate'; export const addPrepackedRulesRoute = (router: IRouter) => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts index ff6d212deb58..d88cc7fcde50 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts @@ -6,6 +6,7 @@ import uuid from 'uuid'; +import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; import { IRouter } from '../../../../../../../../src/core/server'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; @@ -24,7 +25,6 @@ import { buildSiemResponse, } from '../utils'; import { createRulesBulkSchema } from '../schemas/create_rules_bulk_schema'; -import { rulesBulkSchema } from '../schemas/response/rules_bulk_schema'; import { updateRulesNotifications } from '../../rules/update_rules_notifications'; export const createRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts index 036e29aa0ebe..01ad3c7d4e72 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; import { IRouter, RouteConfig, RequestHandler } from '../../../../../../../../src/core/server'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { queryRulesBulkSchema } from '../schemas/query_rules_bulk_schema'; -import { rulesBulkSchema } from '../schemas/response/rules_bulk_schema'; import { getIdBulkError } from './utils'; import { transformValidateBulkError, validate } from './validate'; import { transformBulkError, buildRouteValidation, buildSiemResponse } from '../utils'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts index 67a54f3ba492..90380b0483c8 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts @@ -4,6 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { + PrePackagedRulesStatusSchema, + prePackagedRulesStatusSchema, +} from '../../../../../common/detection_engine/schemas/response/prepackaged_rules_status_schema'; import { IRouter } from '../../../../../../../../src/core/server'; import { DETECTION_ENGINE_PREPACKAGED_URL } from '../../../../../common/constants'; import { transformError, buildSiemResponse } from '../utils'; @@ -12,10 +16,6 @@ import { getRulesToInstall } from '../../rules/get_rules_to_install'; import { getRulesToUpdate } from '../../rules/get_rules_to_update'; import { findRules } from '../../rules/find_rules'; import { getExistingPrepackagedRules } from '../../rules/get_existing_prepackaged_rules'; -import { - PrePackagedRulesStatusSchema, - prePackagedRulesStatusSchema, -} from '../schemas/response/prepackaged_rules_status_schema'; import { validate } from './validate'; export const getPrepackagedRulesStatusRoute = (router: IRouter) => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts index 901e7403bb95..311149087cc4 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts @@ -7,6 +7,10 @@ import { chunk } from 'lodash/fp'; import { extname } from 'path'; +import { + ImportRulesSchema, + importRulesSchema, +} from '../../../../../common/detection_engine/schemas/response/import_rules_schema'; import { IRouter } from '../../../../../../../../src/core/server'; import { createPromiseFromStreams } from '../../../../../../../../src/legacy/utils/streams'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; @@ -31,7 +35,6 @@ import { import { ImportRuleAlertRest } from '../../types'; import { patchRules } from '../../rules/patch_rules'; import { importRulesQuerySchema, importRulesPayloadSchema } from '../schemas/import_rules_schema'; -import { ImportRulesSchema, importRulesSchema } from '../schemas/response/import_rules_schema'; import { getTupleDuplicateErrorsAndUniqueRules } from './utils'; import { validate } from './validate'; import { createRulesStreamFromNdJson } from '../../rules/create_rules_stream_from_ndjson'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts index cc4d5e03500a..0d0cd28738c9 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; import { IRouter } from '../../../../../../../../src/core/server'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; @@ -14,7 +15,6 @@ import { transformBulkError, buildRouteValidation, buildSiemResponse } from '../ import { getIdBulkError } from './utils'; import { transformValidateBulkError, validate } from './validate'; import { patchRulesBulkSchema } from '../schemas/patch_rules_bulk_schema'; -import { rulesBulkSchema } from '../schemas/response/rules_bulk_schema'; import { patchRules } from '../../rules/patch_rules'; import { updateRulesNotifications } from '../../rules/update_rules_notifications'; import { ruleStatusSavedObjectsClientFactory } from '../../signals/rule_status_saved_objects_client'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts index c0dfecc71ce0..335684dc38b3 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; import { IRouter } from '../../../../../../../../src/core/server'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; @@ -15,7 +16,6 @@ import { transformValidateBulkError, validate } from './validate'; import { buildRouteValidation, transformBulkError, buildSiemResponse } from '../utils'; import { updateRulesBulkSchema } from '../schemas/update_rules_bulk_schema'; import { updateRules } from '../../rules/update_rules'; -import { rulesBulkSchema } from '../schemas/response/rules_bulk_schema'; import { updateRulesNotifications } from '../../rules/update_rules_notifications'; import { ruleStatusSavedObjectsClientFactory } from '../../signals/rule_status_saved_objects_client'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.test.ts index 9069202d4d3a..13a5bbd2afc0 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.test.ts @@ -14,9 +14,9 @@ import { } from './validate'; import { getResult } from '../__mocks__/request_responses'; import { FindResult } from '../../../../../../alerting/server'; -import { RulesSchema } from '../schemas/response/rules_schema'; import { BulkError } from '../utils'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../feature_flags'; +import { RulesSchema } from '../../../../../common/detection_engine/schemas/response/rules_schema'; export const ruleOutput: RulesSchema = { actions: [], diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.ts index cda3a4b81ed9..1220b12d1d1b 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.ts @@ -9,6 +9,11 @@ import { fold } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import * as t from 'io-ts'; +import { findRulesSchema } from '../../../../../common/detection_engine/schemas/response/find_rules_schema'; +import { + RulesSchema, + rulesSchema, +} from '../../../../../common/detection_engine/schemas/response/rules_schema'; import { formatErrors } from '../../../../../common/format_errors'; import { exactCheck } from '../../../../../common/exact_check'; import { PartialAlert, FindResult } from '../../../../../../alerting/server'; @@ -19,9 +24,7 @@ import { } from '../../rules/types'; import { OutputRuleAlertRest } from '../../types'; import { createBulkErrorObject, BulkError } from '../utils'; -import { rulesSchema, RulesSchema } from '../schemas/response/rules_schema'; import { transformFindAlerts, transform, transformAlertToRule } from './utils'; -import { findRulesSchema } from '../schemas/response/find_rules_schema'; import { RuleActions } from '../../rule_actions/types'; export const transformValidateFindAlerts = ( diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/check_type_dependents.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/check_type_dependents.ts deleted file mode 100644 index 1c1bee58f0c9..000000000000 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/check_type_dependents.ts +++ /dev/null @@ -1,97 +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 * as t from 'io-ts'; -import { Either, left, fold } from 'fp-ts/lib/Either'; -import { pipe } from 'fp-ts/lib/pipeable'; - -import { isMlRule } from '../../../../../../common/machine_learning/helpers'; -import { - dependentRulesSchema, - RequiredRulesSchema, - partialRulesSchema, - requiredRulesSchema, -} from './rules_schema'; -import { typeAndTimelineOnlySchema, TypeAndTimelineOnly } from './type_timeline_only_schema'; - -export const addSavedId = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { - if (typeAndTimelineOnly.type === 'saved_query') { - return [t.exact(t.type({ saved_id: dependentRulesSchema.props.saved_id }))]; - } else { - return []; - } -}; - -export const addTimelineTitle = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { - if (typeAndTimelineOnly.timeline_id != null) { - return [ - t.exact(t.type({ timeline_title: dependentRulesSchema.props.timeline_title })), - t.exact(t.type({ timeline_id: dependentRulesSchema.props.timeline_id })), - ]; - } else { - return []; - } -}; - -export const addQueryFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { - if (typeAndTimelineOnly.type === 'query' || typeAndTimelineOnly.type === 'saved_query') { - return [ - t.exact(t.type({ query: dependentRulesSchema.props.query })), - t.exact(t.type({ language: dependentRulesSchema.props.language })), - ]; - } else { - return []; - } -}; - -export const addMlFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { - if (isMlRule(typeAndTimelineOnly.type)) { - return [ - t.exact(t.type({ anomaly_threshold: dependentRulesSchema.props.anomaly_threshold })), - t.exact( - t.type({ machine_learning_job_id: dependentRulesSchema.props.machine_learning_job_id }) - ), - ]; - } else { - return []; - } -}; - -export const getDependents = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed => { - const dependents: t.Mixed[] = [ - t.exact(requiredRulesSchema), - t.exact(partialRulesSchema), - ...addSavedId(typeAndTimelineOnly), - ...addTimelineTitle(typeAndTimelineOnly), - ...addQueryFields(typeAndTimelineOnly), - ...addMlFields(typeAndTimelineOnly), - ]; - - if (dependents.length > 1) { - // This unsafe cast is because t.intersection does not use an array but rather a set of - // tuples and really does not look like they expected us to ever dynamically build up - // intersections, but here we are doing that. Looking at their code, although they limit - // the array elements to 5, it looks like you have N number of intersections - const unsafeCast: [t.Mixed, t.Mixed] = dependents as [t.Mixed, t.Mixed]; - return t.intersection(unsafeCast); - } else { - // We are not allowed to call t.intersection with a single value so we return without - // it here normally. - return dependents[0]; - } -}; - -export const checkTypeDependents = (input: unknown): Either => { - const typeOnlyDecoded = typeAndTimelineOnlySchema.decode(input); - const onLeft = (errors: t.Errors): Either => left(errors); - const onRight = ( - typeAndTimelineOnly: TypeAndTimelineOnly - ): Either => { - const intersections = getDependents(typeAndTimelineOnly); - return intersections.decode(input); - }; - return pipe(typeOnlyDecoded, fold(onLeft, onRight)); -}; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_schema.test.ts deleted file mode 100644 index ade4d12517ac..000000000000 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_schema.test.ts +++ /dev/null @@ -1,289 +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 { left } from 'fp-ts/lib/Either'; -import { pipe } from 'fp-ts/lib/pipeable'; - -import { rulesSchema, RulesSchema, removeList } from './rules_schema'; -import { getBaseResponsePayload } from './__mocks__/utils'; -import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; -import { exactCheck } from '../../../../../../common/exact_check'; - -export const ANCHOR_DATE = '2020-02-20T03:57:54.037Z'; - -describe('rules_schema', () => { - beforeAll(() => { - setFeatureFlagsForTestsOnly(); - }); - - afterAll(() => { - unSetFeatureFlagsForTestsOnly(); - }); - - test('it should validate a type of "query" without anything extra', () => { - const payload = getBaseResponsePayload(); - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - const expected = getBaseResponsePayload(); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); - }); - - test('it should NOT validate a type of "query" when it has extra data', () => { - const payload: RulesSchema & { invalid_extra_data?: string } = getBaseResponsePayload(); - payload.invalid_extra_data = 'invalid_extra_data'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate invalid_data for the type', () => { - const payload: Omit & { type: string } = getBaseResponsePayload(); - payload.type = 'invalid_data'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "invalid_data" supplied to "type"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "query" with a saved_id together', () => { - const payload = getBaseResponsePayload(); - payload.type = 'query'; - payload.saved_id = 'save id 123'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "saved_id"']); - expect(message.schema).toEqual({}); - }); - - test('it should validate a type of "saved_query" with a "saved_id" dependent', () => { - const payload = getBaseResponsePayload(); - payload.type = 'saved_query'; - payload.saved_id = 'save id 123'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - const expected = getBaseResponsePayload(); - - expected.type = 'saved_query'; - expected.saved_id = 'save id 123'; - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); - }); - - test('it should NOT validate a type of "saved_query" without a "saved_id" dependent', () => { - const payload = getBaseResponsePayload(); - payload.type = 'saved_query'; - delete payload.saved_id; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "saved_id"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "saved_query" when it has extra data', () => { - const payload: RulesSchema & { invalid_extra_data?: string } = getBaseResponsePayload(); - payload.type = 'saved_query'; - payload.saved_id = 'save id 123'; - payload.invalid_extra_data = 'invalid_extra_data'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); - expect(message.schema).toEqual({}); - }); - - test('it should validate a type of "timeline_id" if there is a "timeline_title" dependent', () => { - const payload = getBaseResponsePayload(); - payload.timeline_id = 'some timeline id'; - payload.timeline_title = 'some timeline title'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - const expected = getBaseResponsePayload(); - expected.timeline_id = 'some timeline id'; - expected.timeline_title = 'some timeline title'; - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); - }); - - test('it should NOT validate a type of "timeline_id" if there is "timeline_title" dependent when it has extra invalid data', () => { - const payload: RulesSchema & { invalid_extra_data?: string } = getBaseResponsePayload(); - payload.timeline_id = 'some timeline id'; - payload.timeline_title = 'some timeline title'; - payload.invalid_extra_data = 'invalid_extra_data'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "timeline_id" if there is NOT a "timeline_title" dependent', () => { - const payload = getBaseResponsePayload(); - payload.timeline_id = 'some timeline id'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "timeline_title"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "timeline_title" if there is NOT a "timeline_id" dependent', () => { - const payload = getBaseResponsePayload(); - payload.timeline_title = 'some timeline title'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_title" but there is NOT a "timeline_id"', () => { - const payload = getBaseResponsePayload(); - payload.saved_id = 'some saved id'; - payload.type = 'saved_query'; - payload.timeline_title = 'some timeline title'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_id" but there is NOT a "timeline_title"', () => { - const payload = getBaseResponsePayload(); - payload.saved_id = 'some saved id'; - payload.type = 'saved_query'; - payload.timeline_id = 'some timeline id'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "timeline_title"', - ]); - expect(message.schema).toEqual({}); - }); - - // TODO: (LIST-FEATURE) Remove this test once the feature flag is deployed - test('it should remove exceptions_list when we need it to be removed because the feature is off but there exists a list in the data', () => { - const payload = getBaseResponsePayload(); - const decoded = rulesSchema.decode(payload); - const listRemoved = removeList(decoded); - const message = pipe(listRemoved, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual({ - id: '7a7065d7-6e8b-4aae-8d20-c93613dec9f9', - created_at: '2020-02-20T03:57:54.037Z', - updated_at: '2020-02-20T03:57:54.037Z', - created_by: 'elastic', - description: 'some description', - enabled: true, - false_positives: ['false positive 1', 'false positive 2'], - from: 'now-6m', - immutable: false, - name: 'Query with a rule id', - query: 'user.name: root or user.name: admin', - references: ['test 1', 'test 2'], - severity: 'high', - updated_by: 'elastic_kibana', - tags: [], - to: 'now', - type: 'query', - threat: [], - version: 1, - output_index: '.siem-signals-hassanabad-frank-default', - max_signals: 100, - risk_score: 55, - language: 'kuery', - rule_id: 'query-rule-id', - interval: '5m', - status: 'succeeded', - status_date: '2020-02-22T16:47:50.047Z', - last_success_at: '2020-02-22T16:47:50.047Z', - last_success_message: 'succeeded', - }); - }); - - test('it should work with exceptions_list that are not there and not cause invalidation or errors', () => { - const payload = getBaseResponsePayload(); - const { exceptions_list, ...payloadWithoutLists } = payload; - const decoded = rulesSchema.decode(payloadWithoutLists); - const listRemoved = removeList(decoded); - const message = pipe(listRemoved, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual({ - id: '7a7065d7-6e8b-4aae-8d20-c93613dec9f9', - created_at: '2020-02-20T03:57:54.037Z', - updated_at: '2020-02-20T03:57:54.037Z', - created_by: 'elastic', - description: 'some description', - enabled: true, - false_positives: ['false positive 1', 'false positive 2'], - from: 'now-6m', - immutable: false, - name: 'Query with a rule id', - query: 'user.name: root or user.name: admin', - references: ['test 1', 'test 2'], - severity: 'high', - updated_by: 'elastic_kibana', - tags: [], - to: 'now', - type: 'query', - threat: [], - version: 1, - output_index: '.siem-signals-hassanabad-frank-default', - max_signals: 100, - risk_score: 55, - language: 'kuery', - rule_id: 'query-rule-id', - interval: '5m', - status: 'succeeded', - status_date: '2020-02-22T16:47:50.047Z', - last_success_at: '2020-02-22T16:47:50.047Z', - last_success_message: 'succeeded', - }); - }); -}); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/build_exceptions_query.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/build_exceptions_query.test.ts index ec8db77dac72..772ebd932698 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/build_exceptions_query.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/build_exceptions_query.test.ts @@ -15,7 +15,7 @@ import { formatQuery, getLanguageBooleanOperator, } from './build_exceptions_query'; -import { List } from '../routes/schemas/types/lists_default_array'; +import { List } from '../../../../common/detection_engine/schemas/types/lists_default_array'; describe('build_exceptions_query', () => { describe('getLanguageBooleanOperator', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/build_exceptions_query.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/build_exceptions_query.ts index e7be5025a51f..b33a2376589e 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/build_exceptions_query.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/build_exceptions_query.ts @@ -3,8 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { + ListOperator, + ListValues, + List, +} from '../../../../common/detection_engine/schemas/types/lists_default_array'; import { Query } from '../../../../../../../src/plugins/data/server'; -import { List, ListOperator, ListValues } from '../routes/schemas/types/lists_default_array'; import { RuleAlertParams, Language } from '../types'; type Operators = 'and' | 'or' | 'not'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/filter_events_with_list.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/filter_events_with_list.ts index 400bb5dda46e..07435fda0da2 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/filter_events_with_list.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/filter_events_with_list.ts @@ -6,11 +6,11 @@ import { get } from 'lodash/fp'; import { Logger } from 'src/core/server'; +import { List } from '../../../../common/detection_engine/schemas/types/lists_default_array'; import { type } from '../../../../../lists/common/schemas/common'; import { ListClient } from '../../../../../lists/server'; import { SignalSearchResponse, SearchTypes } from './types'; import { RuleAlertParams } from '../types'; -import { List } from '../routes/schemas/types/lists_default_array'; interface FilterEventsAgainstList { listClient: ListClient; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/types.ts b/x-pack/plugins/siem/server/lib/detection_engine/types.ts index f2026804da51..53c8a9bf0a7e 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/types.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/types.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ListsDefaultArraySchema } from '../../../common/detection_engine/schemas/types/lists_default_array'; import { CallAPIOptions } from '../../../../../../src/core/server'; import { Filter } from '../../../../../../src/plugins/data/server'; import { IRuleStatusAttributes } from './rules/types'; -import { ListsDefaultArraySchema } from './routes/schemas/types/lists_default_array'; import { RuleAlertAction, RuleType } from '../../../common/detection_engine/types'; export type PartialFilter = Partial; diff --git a/x-pack/plugins/siem/server/lib/timeline/routes/import_timelines_route.ts b/x-pack/plugins/siem/server/lib/timeline/routes/import_timelines_route.ts index 48c6081e855a..c16b73ff51b5 100644 --- a/x-pack/plugins/siem/server/lib/timeline/routes/import_timelines_route.ts +++ b/x-pack/plugins/siem/server/lib/timeline/routes/import_timelines_route.ts @@ -7,6 +7,7 @@ import { extname } from 'path'; import { chunk, omit } from 'lodash/fp'; +import { importRulesSchema } from '../../../../common/detection_engine/schemas/response/import_rules_schema'; import { createPromiseFromStreams } from '../../../../../../../src/legacy/utils'; import { IRouter } from '../../../../../../../src/core/server'; @@ -16,7 +17,6 @@ import { SetupPlugins } from '../../../plugin'; import { ConfigType } from '../../../config'; import { buildRouteValidation } from '../../../utils/build_validation/route_validation'; -import { importRulesSchema } from '../../detection_engine/routes/schemas/response/import_rules_schema'; import { validate } from '../../detection_engine/routes/rules/validate'; import { buildSiemResponse, From 78d5026fbd833ddb8deb0f6bb968f4f82d2559e3 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Mon, 1 Jun 2020 20:38:53 -0500 Subject: [PATCH 14/50] APM-specific Jest configuration (#67858) Update the x-pack `createJestConfig` function to take the `rootDir` as an argument, which allows for easier overriding of the Jest configuration for a specific directory. Previously we would run Jest in development from the x-pack directory by running something like: ``` node scripts/jest.js --testPathPattern=plugins/apm --watch ``` Currently (for me anyway) this is failing with: ``` Error: EMFILE: too many open files, watch at FSEvent.FSWatcher._handle.onchange (internal/fs/watchers.js:123:28) ``` and it would sometimes not correctly test only the changed files when a change in APM was made. It was also difficult to configure correctly with the [VSCode Jest extension](https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest). Add a jest.config.js for APM. This makes running with `--watch` better about which files it chooses to re-run and makes the VSCode extension work (including coverage mapping) with minimal configuration. --- x-pack/dev-tools/jest/create_jest_config.js | 12 +++--- x-pack/dev-tools/jest/index.js | 1 + x-pack/plugins/apm/dev_docs/vscode_setup.md | 28 +++++-------- x-pack/plugins/apm/jest.config.js | 44 +++++++++++++++++++++ x-pack/plugins/apm/readme.md | 14 +++++-- 5 files changed, 71 insertions(+), 28 deletions(-) create mode 100644 x-pack/plugins/apm/jest.config.js diff --git a/x-pack/dev-tools/jest/create_jest_config.js b/x-pack/dev-tools/jest/create_jest_config.js index 3d8b45e7d1b8..a222e11d28f4 100644 --- a/x-pack/dev-tools/jest/create_jest_config.js +++ b/x-pack/dev-tools/jest/create_jest_config.js @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -export function createJestConfig({ kibanaDirectory, xPackKibanaDirectory }) { +export function createJestConfig({ kibanaDirectory, rootDir, xPackKibanaDirectory }) { const fileMockPath = `${kibanaDirectory}/src/dev/jest/mocks/file_mock.js`; return { - rootDir: xPackKibanaDirectory, + rootDir, roots: ['/plugins', '/legacy/plugins', '/legacy/server'], moduleFileExtensions: ['js', 'json', 'ts', 'tsx'], moduleNameMapper: { @@ -44,15 +44,15 @@ export function createJestConfig({ kibanaDirectory, xPackKibanaDirectory }) { '!**/plugins/apm/e2e/**', ], coveragePathIgnorePatterns: ['.*\\.d\\.ts'], - coverageDirectory: '/../target/kibana-coverage/jest', + coverageDirectory: `${kibanaDirectory}/target/kibana-coverage/jest`, coverageReporters: !!process.env.CODE_COVERAGE ? ['json'] : ['html'], setupFiles: [ `${kibanaDirectory}/src/dev/jest/setup/babel_polyfill.js`, - `/dev-tools/jest/setup/polyfills.js`, - `/dev-tools/jest/setup/enzyme.js`, + `${xPackKibanaDirectory}/dev-tools/jest/setup/polyfills.js`, + `${xPackKibanaDirectory}/dev-tools/jest/setup/enzyme.js`, ], setupFilesAfterEnv: [ - `/dev-tools/jest/setup/setup_test.js`, + `${xPackKibanaDirectory}/dev-tools/jest/setup/setup_test.js`, `${kibanaDirectory}/src/dev/jest/setup/mocks.js`, `${kibanaDirectory}/src/dev/jest/setup/react_testing_library.js`, ], diff --git a/x-pack/dev-tools/jest/index.js b/x-pack/dev-tools/jest/index.js index f61c50f98950..2f831e33cdd1 100644 --- a/x-pack/dev-tools/jest/index.js +++ b/x-pack/dev-tools/jest/index.js @@ -14,6 +14,7 @@ export function runJest() { const config = JSON.stringify( createJestConfig({ kibanaDirectory: resolve(__dirname, '../../..'), + rootDir: resolve(__dirname, '../..'), xPackKibanaDirectory: resolve(__dirname, '../..'), }) ); diff --git a/x-pack/plugins/apm/dev_docs/vscode_setup.md b/x-pack/plugins/apm/dev_docs/vscode_setup.md index 1c80d1476520..c7adad4fd094 100644 --- a/x-pack/plugins/apm/dev_docs/vscode_setup.md +++ b/x-pack/plugins/apm/dev_docs/vscode_setup.md @@ -1,8 +1,8 @@ -### Visual Studio Code +# Visual Studio Code When using [Visual Studio Code](https://code.visualstudio.com/) with APM it's best to set up a [multi-root workspace](https://code.visualstudio.com/docs/editor/multi-root-workspaces) and add the `x-pack/plugins/apm` directory, the `x-pack` directory, and the root of the Kibana repository to the workspace. This makes it so you can navigate and search within APM and use the wider workspace roots when you need to widen your search. -#### Using the Jest extension +## Using the Jest extension The [vscode-jest extension](https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest) is a good way to run your Jest tests inside the editor. @@ -22,31 +22,21 @@ If you have a workspace configured as described above you should have: "jest.disabledWorkspaceFolders": ["kibana", "x-pack"] ``` -in your Workspace settings, and: - -```json -"jest.pathToJest": "node scripts/jest.js --testPathPattern=plugins/apm", -"jest.rootPath": "../../.." -``` - -in the settings for the APM folder. - -#### Jest debugging +## Jest debugging To make the [VSCode debugger](https://vscode.readthedocs.io/en/latest/editor/debugging/) work with Jest (you can set breakpoints in the code and tests and use the VSCode debugger) you'll need the [Node Debug extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.node-debug2) installed and can set up a launch configuration like: ```json { "type": "node", - "name": "APM Jest", + "name": "vscode-jest-tests", "request": "launch", - "args": ["--runInBand", "--testPathPattern=plugins/apm"], - "cwd": "${workspaceFolder}/../../..", - "console": "internalConsole", - "internalConsoleOptions": "openOnSessionStart", + "args": ["--runInBand"], + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", "disableOptimisticBPs": true, - "program": "${workspaceFolder}/../../../scripts/jest.js", - "runtimeVersion": "10.15.2" + "program": "${workspaceFolder}/../../../node_modules/jest/bin/jest" } ``` diff --git a/x-pack/plugins/apm/jest.config.js b/x-pack/plugins/apm/jest.config.js new file mode 100644 index 000000000000..c3ae694fe8e1 --- /dev/null +++ b/x-pack/plugins/apm/jest.config.js @@ -0,0 +1,44 @@ +/* + * 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. + */ + +// This is an APM-specific Jest configuration which overrides the x-pack +// configuration. It's intended for use in development and does not run in CI, +// which runs the entire x-pack suite. Run `npx jest`. + +require('../../../src/setup_node_env'); + +const { createJestConfig } = require('../../dev-tools/jest/create_jest_config'); +const { resolve } = require('path'); + +const rootDir = resolve(__dirname, '.'); +const xPackKibanaDirectory = resolve(__dirname, '../..'); +const kibanaDirectory = resolve(__dirname, '../../..'); + +const jestConfig = createJestConfig({ + kibanaDirectory, + rootDir, + xPackKibanaDirectory, +}); + +module.exports = { + ...jestConfig, + reporters: ['default'], + roots: [`${rootDir}/common`, `${rootDir}/public`, `${rootDir}/server`], + collectCoverage: true, + collectCoverageFrom: [ + '**/*.{js,jsx,ts,tsx}', + '!**/{__test__,__snapshots__,__examples__,integration_tests,tests}/**', + '!**/*.test.{js,ts,tsx}', + '!**/dev_docs/**', + '!**/e2e/**', + '!**/scripts/**', + '!**/target/**', + '!**/typings/**', + '!**/mocks/**', + ], + coverageDirectory: `${rootDir}/target/coverage/jest`, + coverageReporters: ['html'], +}; diff --git a/x-pack/plugins/apm/readme.md b/x-pack/plugins/apm/readme.md index 62465e920d79..ceed5e6c3971 100644 --- a/x-pack/plugins/apm/readme.md +++ b/x-pack/plugins/apm/readme.md @@ -39,18 +39,26 @@ _Starts Kibana (:5701), APM Server (:8201) and Elasticsearch (:9201). Ingests sa ### Unit testing -Note: Run the following commands from `kibana/x-pack`. +Note: Run the following commands from `kibana/x-pack/plugins/apm`. #### Run unit tests ``` -node scripts/jest.js plugins/apm --watch +npx jest --watch ``` #### Update snapshots ``` -node scripts/jest.js plugins/apm --updateSnapshot +npx jest --updateSnapshot +``` + +#### Coverage + +HTML coverage report can be found in target/coverage/jest after tests have run. + +``` +open target/coverage/jest/index.html ``` ### Functional tests From ce45dad8b6979b0123d15920e25b4289bc5bdb9b Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Mon, 1 Jun 2020 18:45:36 -0700 Subject: [PATCH 15/50] Changed alerting API endpoints urls, bodies and params to follow Kibana STYLEGUIDE (#66838) * Changed alerting API endpoints urls, bodies and params to follow Kibana STYLEGUIDE * Changed alerting REST API to keep the pattern 'alerts/alert/{id}' * fixed tests * fixed tests * Fixed jest tests * Renamed plugin from alerting to alerts * fixed tests * fixed tests * Fixed alert type check error * Fixed find api * fixed type checks * fixed tests security issues * Fixed view in app * - Co-authored-by: Elastic Machine --- .github/CODEOWNERS | 2 +- examples/alerting_example/kibana.json | 2 +- .../public/alert_types/astros.tsx | 8 +- .../public/alert_types/index.ts | 10 +-- .../public/components/view_alert.tsx | 2 +- .../public/components/view_astros_alert.tsx | 2 +- examples/alerting_example/public/plugin.tsx | 10 +-- .../server/alert_types/always_firing.ts | 2 +- .../server/alert_types/astros.ts | 2 +- examples/alerting_example/server/plugin.ts | 10 +-- rfcs/text/0003_handler_interface.md | 2 +- x-pack/.i18nrc.json | 2 +- x-pack/legacy/plugins/monitoring/index.ts | 2 +- x-pack/plugins/actions/README.md | 4 +- x-pack/plugins/alerting_builtins/README.md | 2 +- x-pack/plugins/alerting_builtins/kibana.json | 2 +- .../server/alert_types/index.ts | 2 +- .../index_threshold/action_context.ts | 2 +- .../alert_types/index_threshold/index.ts | 6 +- .../index_threshold/lib/date_range_info.ts | 2 +- .../index_threshold/lib/time_series_types.ts | 2 +- .../alerting_builtins/server/plugin.test.ts | 6 +- .../alerting_builtins/server/plugin.ts | 4 +- .../plugins/alerting_builtins/server/types.ts | 6 +- x-pack/plugins/{alerting => alerts}/README.md | 62 +++++++-------- .../{alerting => alerts}/common/alert.ts | 0 .../common/alert_instance.ts | 0 .../common/alert_navigation.ts | 0 .../common/alert_task_instance.ts | 0 .../{alerting => alerts}/common/alert_type.ts | 0 .../common/date_from_string.test.ts | 0 .../common/date_from_string.ts | 0 .../{alerting => alerts}/common/index.ts | 2 +- .../common/parse_duration.test.ts | 0 .../common/parse_duration.ts | 0 .../plugins/{alerting => alerts}/kibana.json | 4 +- .../public/alert_api.test.ts | 12 +-- .../{alerting => alerts}/public/alert_api.ts | 10 +-- .../alert_navigation_registry.mock.ts | 0 .../alert_navigation_registry.test.ts | 0 .../alert_navigation_registry.ts | 6 +- .../public/alert_navigation_registry/index.ts | 0 .../public/alert_navigation_registry/types.ts | 0 .../{alerting => alerts}/public/index.ts | 0 .../{alerting => alerts}/public/mocks.ts | 0 .../{alerting => alerts}/public/plugin.ts | 0 .../alert_instance/alert_instance.test.ts | 0 .../server/alert_instance/alert_instance.ts | 0 .../create_alert_instance_factory.test.ts | 0 .../create_alert_instance_factory.ts | 0 .../server/alert_instance/index.ts | 0 .../server/alert_type_registry.mock.ts | 0 .../server/alert_type_registry.test.ts | 2 +- .../server/alert_type_registry.ts | 6 +- .../server/alerts_client.mock.ts | 0 .../server/alerts_client.test.ts | 8 +- .../server/alerts_client.ts | 55 +++++++------- .../server/alerts_client_factory.test.ts | 8 +- .../server/alerts_client_factory.ts | 6 +- .../server/constants/plugin.ts | 6 +- .../{alerting => alerts}/server/index.ts | 0 .../lib/delete_task_if_it_exists.test.ts | 0 .../server/lib/delete_task_if_it_exists.ts | 0 .../{alerting => alerts}/server/lib/index.ts | 0 .../lib/is_alert_not_found_error.test.ts | 0 .../server/lib/is_alert_not_found_error.ts | 0 .../server/lib/license_api_access.ts | 0 .../server/lib/license_state.mock.ts | 0 .../server/lib/license_state.test.ts | 2 +- .../server/lib/license_state.ts | 6 +- .../server/lib/result_type.ts | 0 .../server/lib/types.test.ts | 0 .../{alerting => alerts}/server/lib/types.ts | 0 .../lib/validate_alert_type_params.test.ts | 0 .../server/lib/validate_alert_type_params.ts | 0 .../{alerting => alerts}/server/mocks.ts | 0 .../server/plugin.test.ts | 4 +- .../{alerting => alerts}/server/plugin.ts | 2 +- .../server/routes/_mock_handler_arguments.ts | 0 .../server/routes/create.test.ts | 2 +- .../server/routes/create.ts | 2 +- .../server/routes/delete.test.ts | 2 +- .../server/routes/delete.ts | 2 +- .../server/routes/disable.test.ts | 2 +- .../server/routes/disable.ts | 2 +- .../server/routes/enable.test.ts | 2 +- .../server/routes/enable.ts | 2 +- .../server/routes/find.test.ts | 7 +- .../server/routes/find.ts | 33 ++++---- .../server/routes/get.test.ts | 2 +- .../{alerting => alerts}/server/routes/get.ts | 2 +- .../server/routes/get_alert_state.test.ts | 6 +- .../server/routes/get_alert_state.ts | 2 +- .../server/routes/health.test.ts | 2 +- .../server/routes/health.ts | 2 +- .../server/routes/index.ts | 0 .../server/routes/lib/error_handler.ts | 2 +- .../alerts/server/routes/lib/rename_keys.ts | 16 ++++ .../server/routes/list_alert_types.test.ts | 6 +- .../server/routes/list_alert_types.ts | 2 +- .../server/routes/mute_all.test.ts | 2 +- .../server/routes/mute_all.ts | 2 +- .../server/routes/mute_instance.test.ts | 6 +- .../server/routes/mute_instance.ts | 18 +++-- .../server/routes/unmute_all.test.ts | 2 +- .../server/routes/unmute_all.ts | 2 +- .../server/routes/unmute_instance.test.ts | 2 +- .../server/routes/unmute_instance.ts | 2 +- .../server/routes/update.test.ts | 2 +- .../server/routes/update.ts | 2 +- .../server/routes/update_api_key.test.ts | 2 +- .../server/routes/update_api_key.ts | 2 +- .../server/saved_objects/index.ts | 0 .../server/saved_objects/mappings.json | 0 .../task_runner/alert_task_instance.test.ts | 2 +- .../server/task_runner/alert_task_instance.ts | 2 +- .../create_execution_handler.test.ts | 0 .../task_runner/create_execution_handler.ts | 2 +- .../task_runner/get_next_run_at.test.ts | 0 .../server/task_runner/get_next_run_at.ts | 0 .../server/task_runner/index.ts | 0 .../server/task_runner/task_runner.test.ts | 4 +- .../server/task_runner/task_runner.ts | 2 +- .../task_runner/task_runner_factory.test.ts | 4 +- .../server/task_runner/task_runner_factory.ts | 6 +- .../transform_action_params.test.ts | 0 .../task_runner/transform_action_params.ts | 0 .../server/test_utils/index.ts | 0 .../{alerting => alerts}/server/types.ts | 0 .../server/usage/alerts_telemetry.test.ts | 0 .../server/usage/alerts_telemetry.ts | 0 .../usage/alerts_usage_collector.test.ts | 0 .../server/usage/alerts_usage_collector.ts | 0 .../server/usage/index.ts | 0 .../{alerting => alerts}/server/usage/task.ts | 0 .../server/usage/types.ts | 0 x-pack/plugins/apm/kibana.json | 2 +- .../components/app/ServiceDetails/index.tsx | 2 +- x-pack/plugins/apm/public/plugin.ts | 6 +- .../server/lib/alerts/register_apm_alerts.ts | 8 +- .../alerts/register_error_rate_alert_type.ts | 8 +- ...egister_transaction_duration_alert_type.ts | 8 +- x-pack/plugins/apm/server/plugin.ts | 8 +- x-pack/plugins/infra/kibana.json | 2 +- .../lib/adapters/framework/adapter_types.ts | 4 +- .../inventory_metric_threshold_executor.ts | 2 +- .../log_threshold_executor.test.ts | 4 +- .../log_threshold/log_threshold_executor.ts | 2 +- .../register_log_threshold_alert_type.ts | 2 +- .../metric_threshold_executor.test.ts | 4 +- .../metric_threshold_executor.ts | 2 +- .../lib/alerting/register_alert_types.ts | 2 +- x-pack/plugins/infra/server/plugin.ts | 2 +- x-pack/plugins/monitoring/kibana.json | 2 +- .../public/components/alerts/status.tsx | 2 +- .../server/alerts/cluster_state.test.ts | 2 +- .../monitoring/server/alerts/cluster_state.ts | 2 +- .../server/alerts/license_expiration.test.ts | 2 +- .../server/alerts/license_expiration.ts | 2 +- .../monitoring/server/alerts/types.d.ts | 2 +- .../server/lib/alerts/cluster_state.lib.ts | 2 +- .../server/lib/alerts/fetch_status.ts | 2 +- .../server/lib/alerts/get_prepared_alert.ts | 2 +- .../lib/alerts/license_expiration.lib.ts | 2 +- x-pack/plugins/monitoring/server/plugin.ts | 12 +-- .../schemas/common/schemas.ts | 2 +- .../detection_engine/transform_actions.ts | 2 +- .../siem/common/detection_engine/types.ts | 2 +- x-pack/plugins/siem/kibana.json | 2 +- .../description_step/actions_description.tsx | 2 +- .../rules/rule_actions_field/index.tsx | 2 +- .../detection_engine/rules/types.ts | 2 +- .../pages/detection_engine/rules/types.ts | 2 +- .../server/lib/detection_engine/README.md | 2 +- .../create_notifications.test.ts | 2 +- .../notifications/create_notifications.ts | 2 +- .../delete_notifications.test.ts | 2 +- .../notifications/find_notifications.ts | 2 +- .../notifications/get_signals_count.ts | 2 +- .../notifications/read_notifications.test.ts | 2 +- .../notifications/read_notifications.ts | 2 +- .../rules_notification_alert_type.test.ts | 2 +- .../schedule_notification_actions.ts | 2 +- .../detection_engine/notifications/types.ts | 4 +- .../update_notifications.test.ts | 2 +- .../notifications/update_notifications.ts | 2 +- .../routes/__mocks__/request_context.ts | 2 +- .../routes/rules/utils.test.ts | 4 +- .../detection_engine/routes/rules/utils.ts | 2 +- .../routes/rules/validate.test.ts | 2 +- .../detection_engine/routes/rules/validate.ts | 2 +- .../add_prepackaged_rules_schema.test.ts | 2 +- .../schemas/create_rules_schema.test.ts | 2 +- .../schemas/import_rules_schema.test.ts | 2 +- .../routes/schemas/patch_rules_schema.test.ts | 2 +- .../schemas/update_rules_schema.test.ts | 2 +- .../create_rule_actions_saved_object.ts | 2 +- .../delete_rule_actions_saved_object.ts | 2 +- .../get_rule_actions_saved_object.ts | 2 +- ...ate_or_create_rule_actions_saved_object.ts | 2 +- .../update_rule_actions_saved_object.ts | 2 +- .../rules/create_rules.test.ts | 2 +- .../detection_engine/rules/create_rules.ts | 2 +- .../rules/delete_rules.test.ts | 2 +- .../lib/detection_engine/rules/find_rules.ts | 2 +- .../get_existing_prepackaged_rules.test.ts | 2 +- .../rules/get_existing_prepackaged_rules.ts | 2 +- .../rules/get_export_all.test.ts | 2 +- .../detection_engine/rules/get_export_all.ts | 2 +- .../rules/get_export_by_object_ids.test.ts | 2 +- .../rules/get_export_by_object_ids.ts | 2 +- .../rules/install_prepacked_rules.ts | 4 +- .../rules/patch_rules.test.ts | 2 +- .../lib/detection_engine/rules/patch_rules.ts | 2 +- .../detection_engine/rules/read_rules.test.ts | 2 +- .../lib/detection_engine/rules/read_rules.ts | 2 +- .../lib/detection_engine/rules/types.ts | 4 +- .../rules/update_prepacked_rules.test.ts | 2 +- .../rules/update_prepacked_rules.ts | 2 +- .../rules/update_rules.test.ts | 2 +- .../detection_engine/rules/update_rules.ts | 2 +- .../rules/update_rules_notifications.ts | 2 +- .../scripts/get_alert_instances.sh | 4 +- .../scripts/get_alert_types.sh | 4 +- .../signals/bulk_create_ml_signals.ts | 2 +- .../signals/get_filter.test.ts | 2 +- .../detection_engine/signals/get_filter.ts | 2 +- .../signals/get_input_output_index.test.ts | 2 +- .../signals/get_input_output_index.ts | 2 +- .../signals/search_after_bulk_create.test.ts | 2 +- .../signals/search_after_bulk_create.ts | 2 +- .../signals/signal_rule_alert_type.test.ts | 2 +- .../signals/single_bulk_create.test.ts | 2 +- .../signals/single_bulk_create.ts | 2 +- .../signals/single_search_after.test.ts | 2 +- .../signals/single_search_after.ts | 2 +- .../lib/detection_engine/signals/types.ts | 2 +- .../lib/detection_engine/signals/utils.ts | 2 +- .../detection_engine/tags/read_tags.test.ts | 2 +- .../lib/detection_engine/tags/read_tags.ts | 2 +- x-pack/plugins/siem/server/plugin.ts | 10 +-- .../translations/translations/ja-JP.json | 20 ++--- .../translations/translations/zh-CN.json | 20 ++--- x-pack/plugins/triggers_actions_ui/README.md | 6 +- .../plugins/triggers_actions_ui/kibana.json | 2 +- .../public/application/app.tsx | 4 +- .../threshold/visualization.tsx | 2 +- .../public/application/constants/index.ts | 2 +- .../application/lib/action_variables.ts | 2 +- .../public/application/lib/alert_api.test.ts | 76 +++++++++---------- .../public/application/lib/alert_api.ts | 26 +++---- .../action_form.test.tsx | 2 +- .../actions_connectors_list.test.tsx | 2 +- .../components/view_in_app.test.tsx | 8 +- .../alert_details/components/view_in_app.tsx | 11 +-- .../sections/alert_form/alert_add.test.tsx | 6 +- .../sections/alert_form/alert_edit.test.tsx | 2 +- .../sections/alert_form/alert_form.test.tsx | 4 +- .../sections/alert_form/alert_form.tsx | 5 +- .../sections/alert_form/alert_reducer.test.ts | 2 +- .../components/alerts_list.test.tsx | 2 +- .../alerts_list/components/alerts_list.tsx | 2 +- .../triggers_actions_ui/public/plugin.ts | 6 +- .../triggers_actions_ui/public/types.ts | 4 +- x-pack/plugins/uptime/kibana.json | 2 +- .../lib/adapters/framework/adapter_types.ts | 2 +- .../lib/alerts/__tests__/status_check.test.ts | 6 +- .../uptime/server/lib/alerts/status_check.ts | 2 +- .../plugins/uptime/server/lib/alerts/types.ts | 2 +- x-pack/plugins/uptime/server/uptime_server.ts | 2 +- .../fixtures/plugins/alerts/kibana.json | 2 +- .../plugins/alerts/server/alert_types.ts | 22 +++--- .../fixtures/plugins/alerts/server/plugin.ts | 21 +++-- .../common/lib/alert_utils.ts | 28 ++++--- .../common/lib/object_remover.ts | 5 +- .../security_and_spaces/scenarios.ts | 4 +- .../tests/actions/get_all.ts | 4 +- .../tests/alerting/alerts.ts | 16 ++-- .../tests/alerting/create.ts | 18 ++--- .../tests/alerting/delete.ts | 18 ++--- .../tests/alerting/disable.ts | 12 +-- .../tests/alerting/enable.ts | 16 ++-- .../tests/alerting/find.ts | 20 ++--- .../security_and_spaces/tests/alerting/get.ts | 14 ++-- .../tests/alerting/get_alert_state.ts | 14 ++-- .../tests/alerting/index.ts | 2 +- .../tests/alerting/list_alert_types.ts | 2 +- .../tests/alerting/mute_all.ts | 6 +- .../tests/alerting/mute_instance.ts | 16 ++-- .../tests/alerting/unmute_all.ts | 8 +- .../tests/alerting/unmute_instance.ts | 10 ++- .../tests/alerting/update.ts | 40 +++++----- .../tests/alerting/update_api_key.ts | 16 ++-- .../spaces_only/tests/alerting/alerts_base.ts | 12 +-- .../index_threshold/alert.ts | 4 +- .../spaces_only/tests/alerting/create.ts | 8 +- .../spaces_only/tests/alerting/delete.ts | 8 +- .../spaces_only/tests/alerting/disable.ts | 8 +- .../spaces_only/tests/alerting/enable.ts | 10 +-- .../spaces_only/tests/alerting/find.ts | 12 +-- .../spaces_only/tests/alerting/get.ts | 14 ++-- .../tests/alerting/get_alert_state.ts | 26 ++++--- .../tests/alerting/list_alert_types.ts | 16 +++- .../spaces_only/tests/alerting/mute_all.ts | 6 +- .../tests/alerting/mute_instance.ts | 6 +- .../spaces_only/tests/alerting/unmute_all.ts | 6 +- .../tests/alerting/unmute_instance.ts | 6 +- .../spaces_only/tests/alerting/update.ts | 12 +-- .../tests/alerting/update_api_key.ts | 10 +-- .../case_api_integration/common/config.ts | 1 - .../apps/triggers_actions_ui/alerts.ts | 2 +- .../apps/uptime/alert_flyout.ts | 8 +- .../fixtures/plugins/alerts/kibana.json | 2 +- .../fixtures/plugins/alerts/public/plugin.ts | 10 +-- .../fixtures/plugins/alerts/server/plugin.ts | 18 ++--- .../services/alerting/alerts.ts | 12 +-- x-pack/typings/hapi.d.ts | 4 +- 317 files changed, 766 insertions(+), 734 deletions(-) rename x-pack/plugins/{alerting => alerts}/README.md (91%) rename x-pack/plugins/{alerting => alerts}/common/alert.ts (100%) rename x-pack/plugins/{alerting => alerts}/common/alert_instance.ts (100%) rename x-pack/plugins/{alerting => alerts}/common/alert_navigation.ts (100%) rename x-pack/plugins/{alerting => alerts}/common/alert_task_instance.ts (100%) rename x-pack/plugins/{alerting => alerts}/common/alert_type.ts (100%) rename x-pack/plugins/{alerting => alerts}/common/date_from_string.test.ts (100%) rename x-pack/plugins/{alerting => alerts}/common/date_from_string.ts (100%) rename x-pack/plugins/{alerting => alerts}/common/index.ts (92%) rename x-pack/plugins/{alerting => alerts}/common/parse_duration.test.ts (100%) rename x-pack/plugins/{alerting => alerts}/common/parse_duration.ts (100%) rename x-pack/plugins/{alerting => alerts}/kibana.json (80%) rename x-pack/plugins/{alerting => alerts}/public/alert_api.test.ts (92%) rename x-pack/plugins/{alerting => alerts}/public/alert_api.ts (84%) rename x-pack/plugins/{alerting => alerts}/public/alert_navigation_registry/alert_navigation_registry.mock.ts (100%) rename x-pack/plugins/{alerting => alerts}/public/alert_navigation_registry/alert_navigation_registry.test.ts (100%) rename x-pack/plugins/{alerting => alerts}/public/alert_navigation_registry/alert_navigation_registry.ts (90%) rename x-pack/plugins/{alerting => alerts}/public/alert_navigation_registry/index.ts (100%) rename x-pack/plugins/{alerting => alerts}/public/alert_navigation_registry/types.ts (100%) rename x-pack/plugins/{alerting => alerts}/public/index.ts (100%) rename x-pack/plugins/{alerting => alerts}/public/mocks.ts (100%) rename x-pack/plugins/{alerting => alerts}/public/plugin.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/alert_instance/alert_instance.test.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/alert_instance/alert_instance.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/alert_instance/create_alert_instance_factory.test.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/alert_instance/create_alert_instance_factory.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/alert_instance/index.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/alert_type_registry.mock.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/alert_type_registry.test.ts (98%) rename x-pack/plugins/{alerting => alerts}/server/alert_type_registry.ts (89%) rename x-pack/plugins/{alerting => alerts}/server/alerts_client.mock.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/alerts_client.test.ts (99%) rename x-pack/plugins/{alerting => alerts}/server/alerts_client.ts (96%) rename x-pack/plugins/{alerting => alerts}/server/alerts_client_factory.test.ts (95%) rename x-pack/plugins/{alerting => alerts}/server/alerts_client_factory.ts (95%) rename x-pack/plugins/{alerting => alerts}/server/constants/plugin.ts (86%) rename x-pack/plugins/{alerting => alerts}/server/index.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/lib/delete_task_if_it_exists.test.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/lib/delete_task_if_it_exists.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/lib/index.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/lib/is_alert_not_found_error.test.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/lib/is_alert_not_found_error.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/lib/license_api_access.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/lib/license_state.mock.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/lib/license_state.test.ts (95%) rename x-pack/plugins/{alerting => alerts}/server/lib/license_state.ts (90%) rename x-pack/plugins/{alerting => alerts}/server/lib/result_type.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/lib/types.test.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/lib/types.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/lib/validate_alert_type_params.test.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/lib/validate_alert_type_params.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/mocks.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/plugin.test.ts (97%) rename x-pack/plugins/{alerting => alerts}/server/plugin.ts (99%) rename x-pack/plugins/{alerting => alerts}/server/routes/_mock_handler_arguments.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/routes/create.test.ts (98%) rename x-pack/plugins/{alerting => alerts}/server/routes/create.ts (98%) rename x-pack/plugins/{alerting => alerts}/server/routes/delete.test.ts (97%) rename x-pack/plugins/{alerting => alerts}/server/routes/delete.ts (96%) rename x-pack/plugins/{alerting => alerts}/server/routes/disable.test.ts (95%) rename x-pack/plugins/{alerting => alerts}/server/routes/disable.ts (96%) rename x-pack/plugins/{alerting => alerts}/server/routes/enable.test.ts (95%) rename x-pack/plugins/{alerting => alerts}/server/routes/enable.ts (96%) rename x-pack/plugins/{alerting => alerts}/server/routes/find.test.ts (93%) rename x-pack/plugins/{alerting => alerts}/server/routes/find.ts (81%) rename x-pack/plugins/{alerting => alerts}/server/routes/get.test.ts (97%) rename x-pack/plugins/{alerting => alerts}/server/routes/get.ts (96%) rename x-pack/plugins/{alerting => alerts}/server/routes/get_alert_state.test.ts (94%) rename x-pack/plugins/{alerting => alerts}/server/routes/get_alert_state.ts (96%) rename x-pack/plugins/{alerting => alerts}/server/routes/health.test.ts (99%) rename x-pack/plugins/{alerting => alerts}/server/routes/health.ts (98%) rename x-pack/plugins/{alerting => alerts}/server/routes/index.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/routes/lib/error_handler.ts (94%) create mode 100644 x-pack/plugins/alerts/server/routes/lib/rename_keys.ts rename x-pack/plugins/{alerting => alerts}/server/routes/list_alert_types.test.ts (94%) rename x-pack/plugins/{alerting => alerts}/server/routes/list_alert_types.ts (95%) rename x-pack/plugins/{alerting => alerts}/server/routes/mute_all.test.ts (95%) rename x-pack/plugins/{alerting => alerts}/server/routes/mute_all.ts (96%) rename x-pack/plugins/{alerting => alerts}/server/routes/mute_instance.test.ts (92%) rename x-pack/plugins/{alerting => alerts}/server/routes/mute_instance.ts (72%) rename x-pack/plugins/{alerting => alerts}/server/routes/unmute_all.test.ts (95%) rename x-pack/plugins/{alerting => alerts}/server/routes/unmute_all.ts (96%) rename x-pack/plugins/{alerting => alerts}/server/routes/unmute_instance.test.ts (95%) rename x-pack/plugins/{alerting => alerts}/server/routes/unmute_instance.ts (94%) rename x-pack/plugins/{alerting => alerts}/server/routes/update.test.ts (98%) rename x-pack/plugins/{alerting => alerts}/server/routes/update.ts (98%) rename x-pack/plugins/{alerting => alerts}/server/routes/update_api_key.test.ts (95%) rename x-pack/plugins/{alerting => alerts}/server/routes/update_api_key.ts (96%) rename x-pack/plugins/{alerting => alerts}/server/saved_objects/index.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/saved_objects/mappings.json (100%) rename x-pack/plugins/{alerting => alerts}/server/task_runner/alert_task_instance.test.ts (98%) rename x-pack/plugins/{alerting => alerts}/server/task_runner/alert_task_instance.ts (95%) rename x-pack/plugins/{alerting => alerts}/server/task_runner/create_execution_handler.test.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/task_runner/create_execution_handler.ts (98%) rename x-pack/plugins/{alerting => alerts}/server/task_runner/get_next_run_at.test.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/task_runner/get_next_run_at.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/task_runner/index.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/task_runner/task_runner.test.ts (99%) rename x-pack/plugins/{alerting => alerts}/server/task_runner/task_runner.ts (99%) rename x-pack/plugins/{alerting => alerts}/server/task_runner/task_runner_factory.test.ts (93%) rename x-pack/plugins/{alerting => alerts}/server/task_runner/task_runner_factory.ts (87%) rename x-pack/plugins/{alerting => alerts}/server/task_runner/transform_action_params.test.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/task_runner/transform_action_params.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/test_utils/index.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/types.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/usage/alerts_telemetry.test.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/usage/alerts_telemetry.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/usage/alerts_usage_collector.test.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/usage/alerts_usage_collector.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/usage/index.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/usage/task.ts (100%) rename x-pack/plugins/{alerting => alerts}/server/usage/types.ts (100%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c3da7c7f00e9..83f4f90b9204 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -176,7 +176,7 @@ /x-pack/plugins/telemetry_collection_xpack/ @elastic/pulse # Kibana Alerting Services -/x-pack/plugins/alerting/ @elastic/kibana-alerting-services +/x-pack/plugins/alerts/ @elastic/kibana-alerting-services /x-pack/plugins/actions/ @elastic/kibana-alerting-services /x-pack/plugins/event_log/ @elastic/kibana-alerting-services /x-pack/plugins/task_manager/ @elastic/kibana-alerting-services diff --git a/examples/alerting_example/kibana.json b/examples/alerting_example/kibana.json index 76c549adc797..2b6389649cef 100644 --- a/examples/alerting_example/kibana.json +++ b/examples/alerting_example/kibana.json @@ -4,6 +4,6 @@ "kibanaVersion": "kibana", "server": true, "ui": true, - "requiredPlugins": ["triggers_actions_ui", "charts", "data", "alerting", "actions"], + "requiredPlugins": ["triggers_actions_ui", "charts", "data", "alerts", "actions"], "optionalPlugins": [] } diff --git a/examples/alerting_example/public/alert_types/astros.tsx b/examples/alerting_example/public/alert_types/astros.tsx index 2e263e454fa0..d52223cb6b98 100644 --- a/examples/alerting_example/public/alert_types/astros.tsx +++ b/examples/alerting_example/public/alert_types/astros.tsx @@ -32,12 +32,12 @@ import { import { i18n } from '@kbn/i18n'; import { flatten } from 'lodash'; import { ALERTING_EXAMPLE_APP_ID, Craft, Operator } from '../../common/constants'; -import { SanitizedAlert } from '../../../../x-pack/plugins/alerting/common'; -import { PluginSetupContract as AlertingSetup } from '../../../../x-pack/plugins/alerting/public'; +import { SanitizedAlert } from '../../../../x-pack/plugins/alerts/common'; +import { PluginSetupContract as AlertingSetup } from '../../../../x-pack/plugins/alerts/public'; import { AlertTypeModel } from '../../../../x-pack/plugins/triggers_actions_ui/public'; -export function registerNavigation(alerting: AlertingSetup) { - alerting.registerNavigation( +export function registerNavigation(alerts: AlertingSetup) { + alerts.registerNavigation( ALERTING_EXAMPLE_APP_ID, 'example.people-in-space', (alert: SanitizedAlert) => `/astros/${alert.id}` diff --git a/examples/alerting_example/public/alert_types/index.ts b/examples/alerting_example/public/alert_types/index.ts index 96d9c09d1583..db9f855b573e 100644 --- a/examples/alerting_example/public/alert_types/index.ts +++ b/examples/alerting_example/public/alert_types/index.ts @@ -19,15 +19,15 @@ import { registerNavigation as registerPeopleInSpaceNavigation } from './astros'; import { ALERTING_EXAMPLE_APP_ID } from '../../common/constants'; -import { SanitizedAlert } from '../../../../x-pack/plugins/alerting/common'; -import { PluginSetupContract as AlertingSetup } from '../../../../x-pack/plugins/alerting/public'; +import { SanitizedAlert } from '../../../../x-pack/plugins/alerts/common'; +import { PluginSetupContract as AlertingSetup } from '../../../../x-pack/plugins/alerts/public'; -export function registerNavigation(alerting: AlertingSetup) { +export function registerNavigation(alerts: AlertingSetup) { // register default navigation - alerting.registerDefaultNavigation( + alerts.registerDefaultNavigation( ALERTING_EXAMPLE_APP_ID, (alert: SanitizedAlert) => `/alert/${alert.id}` ); - registerPeopleInSpaceNavigation(alerting); + registerPeopleInSpaceNavigation(alerts); } diff --git a/examples/alerting_example/public/components/view_alert.tsx b/examples/alerting_example/public/components/view_alert.tsx index e418ed91096e..75a515bfa1b2 100644 --- a/examples/alerting_example/public/components/view_alert.tsx +++ b/examples/alerting_example/public/components/view_alert.tsx @@ -36,7 +36,7 @@ import { Alert, AlertTaskState, BASE_ALERT_API_PATH, -} from '../../../../x-pack/plugins/alerting/common'; +} from '../../../../x-pack/plugins/alerts/common'; import { ALERTING_EXAMPLE_APP_ID } from '../../common/constants'; type Props = RouteComponentProps & { diff --git a/examples/alerting_example/public/components/view_astros_alert.tsx b/examples/alerting_example/public/components/view_astros_alert.tsx index 296269180dd7..19f235a3f3e4 100644 --- a/examples/alerting_example/public/components/view_astros_alert.tsx +++ b/examples/alerting_example/public/components/view_astros_alert.tsx @@ -38,7 +38,7 @@ import { Alert, AlertTaskState, BASE_ALERT_API_PATH, -} from '../../../../x-pack/plugins/alerting/common'; +} from '../../../../x-pack/plugins/alerts/common'; import { ALERTING_EXAMPLE_APP_ID } from '../../common/constants'; type Props = RouteComponentProps & { diff --git a/examples/alerting_example/public/plugin.tsx b/examples/alerting_example/public/plugin.tsx index e3748e3235f4..524ff18bd434 100644 --- a/examples/alerting_example/public/plugin.tsx +++ b/examples/alerting_example/public/plugin.tsx @@ -18,7 +18,7 @@ */ import { Plugin, CoreSetup, AppMountParameters } from 'kibana/public'; -import { PluginSetupContract as AlertingSetup } from '../../../x-pack/plugins/alerting/public'; +import { PluginSetupContract as AlertingSetup } from '../../../x-pack/plugins/alerts/public'; import { ChartsPluginStart } from '../../../src/plugins/charts/public'; import { TriggersAndActionsUIPublicPluginSetup } from '../../../x-pack/plugins/triggers_actions_ui/public'; import { DataPublicPluginStart } from '../../../src/plugins/data/public'; @@ -30,12 +30,12 @@ export type Setup = void; export type Start = void; export interface AlertingExamplePublicSetupDeps { - alerting: AlertingSetup; + alerts: AlertingSetup; triggers_actions_ui: TriggersAndActionsUIPublicPluginSetup; } export interface AlertingExamplePublicStartDeps { - alerting: AlertingSetup; + alerts: AlertingSetup; triggers_actions_ui: TriggersAndActionsUIPublicPluginSetup; charts: ChartsPluginStart; data: DataPublicPluginStart; @@ -44,7 +44,7 @@ export interface AlertingExamplePublicStartDeps { export class AlertingExamplePlugin implements Plugin { public setup( core: CoreSetup, - { alerting, triggers_actions_ui }: AlertingExamplePublicSetupDeps + { alerts, triggers_actions_ui }: AlertingExamplePublicSetupDeps ) { core.application.register({ id: 'AlertingExample', @@ -59,7 +59,7 @@ export class AlertingExamplePlugin implements Plugin { - public setup(core: CoreSetup, { alerting }: AlertingExampleDeps) { - alerting.registerType(alwaysFiringAlert); - alerting.registerType(peopleInSpaceAlert); + public setup(core: CoreSetup, { alerts }: AlertingExampleDeps) { + alerts.registerType(alwaysFiringAlert); + alerts.registerType(peopleInSpaceAlert); } public start() {} diff --git a/rfcs/text/0003_handler_interface.md b/rfcs/text/0003_handler_interface.md index 51e78cf7c9f5..197f2a201237 100644 --- a/rfcs/text/0003_handler_interface.md +++ b/rfcs/text/0003_handler_interface.md @@ -65,7 +65,7 @@ application.registerApp({ }); // Alerting -alerting.registerType({ +alerts.registerType({ id: 'myAlert', async execute(context, params, state) { const indexPatterns = await context.core.savedObjects.find('indexPattern'); diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 7ac27dd47ad6..a479b08ef906 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -4,7 +4,7 @@ "xpack.actions": "plugins/actions", "xpack.advancedUiActions": "plugins/advanced_ui_actions", "xpack.uiActionsEnhanced": "examples/ui_actions_enhanced_examples", - "xpack.alerting": "plugins/alerting", + "xpack.alerts": "plugins/alerts", "xpack.alertingBuiltins": "plugins/alerting_builtins", "xpack.apm": ["legacy/plugins/apm", "plugins/apm"], "xpack.beatsManagement": ["legacy/plugins/beats_management", "plugins/beats_management"], diff --git a/x-pack/legacy/plugins/monitoring/index.ts b/x-pack/legacy/plugins/monitoring/index.ts index 1a0fecb9ef5b..ee31a3037a0c 100644 --- a/x-pack/legacy/plugins/monitoring/index.ts +++ b/x-pack/legacy/plugins/monitoring/index.ts @@ -15,7 +15,7 @@ import { KIBANA_ALERTING_ENABLED } from '../../../plugins/monitoring/common/cons */ const deps = ['kibana', 'elasticsearch', 'xpack_main']; if (KIBANA_ALERTING_ENABLED) { - deps.push(...['alerting', 'actions']); + deps.push(...['alerts', 'actions']); } export const monitoring = (kibana: any) => { return new kibana.Plugin({ diff --git a/x-pack/plugins/actions/README.md b/x-pack/plugins/actions/README.md index 847172ae972f..96d5f04ac088 100644 --- a/x-pack/plugins/actions/README.md +++ b/x-pack/plugins/actions/README.md @@ -26,7 +26,7 @@ Table of Contents - [Executor](#executor) - [Example](#example) - [RESTful API](#restful-api) - - [`POST /api/action`: Create action](#post-apiaction-create-action) + - [`POST /api/actions/action`: Create action](#post-apiaction-create-action) - [`DELETE /api/actions/action/{id}`: Delete action](#delete-apiactionid-delete-action) - [`GET /api/actions`: Get all actions](#get-apiactiongetall-get-all-actions) - [`GET /api/actions/action/{id}`: Get action](#get-apiactionid-get-action) @@ -163,7 +163,7 @@ The built-in email action type provides a good example of creating an action typ Using an action type requires an action to be created that will contain and encrypt configuration for a given action type. See below for CRUD operations using the API. -### `POST /api/action`: Create action +### `POST /api/actions/action`: Create action Payload: diff --git a/x-pack/plugins/alerting_builtins/README.md b/x-pack/plugins/alerting_builtins/README.md index 233984a1ff23..2944247e4714 100644 --- a/x-pack/plugins/alerting_builtins/README.md +++ b/x-pack/plugins/alerting_builtins/README.md @@ -1,7 +1,7 @@ # alerting_builtins plugin This plugin provides alertTypes shipped with Kibana for use with the -[the alerting plugin](../alerting/README.md). When enabled, it will register +[the alerts plugin](../alerts/README.md). When enabled, it will register the built-in alertTypes with the alerting plugin, register associated HTTP routes, etc. diff --git a/x-pack/plugins/alerting_builtins/kibana.json b/x-pack/plugins/alerting_builtins/kibana.json index 78de9a1ae016..cc613d5247ef 100644 --- a/x-pack/plugins/alerting_builtins/kibana.json +++ b/x-pack/plugins/alerting_builtins/kibana.json @@ -3,7 +3,7 @@ "server": true, "version": "8.0.0", "kibanaVersion": "kibana", - "requiredPlugins": ["alerting"], + "requiredPlugins": ["alerts"], "configPath": ["xpack", "alerting_builtins"], "ui": false } diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index.ts index 475efc87b443..d9232195b0f5 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index.ts @@ -10,7 +10,7 @@ import { register as registerIndexThreshold } from './index_threshold'; interface RegisterBuiltInAlertTypesParams { service: Service; router: IRouter; - alerting: AlertingSetup; + alerts: AlertingSetup; baseRoute: string; } diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/action_context.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/action_context.ts index 15139ae34c93..c3a132bc609d 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/action_context.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/action_context.ts @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; import { Params } from './alert_type_params'; -import { AlertExecutorOptions } from '../../../../alerting/server'; +import { AlertExecutorOptions } from '../../../../alerts/server'; // alert type context provided to actions diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/index.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/index.ts index fbe107054ce9..9787ece323c5 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/index.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/index.ts @@ -23,14 +23,14 @@ export function getService() { interface RegisterParams { service: Service; router: IRouter; - alerting: AlertingSetup; + alerts: AlertingSetup; baseRoute: string; } export function register(params: RegisterParams) { - const { service, router, alerting, baseRoute } = params; + const { service, router, alerts, baseRoute } = params; - alerting.registerType(getAlertType(service)); + alerts.registerType(getAlertType(service)); const baseBuiltInRoute = `${baseRoute}/index_threshold`; registerRoutes({ service, router, baseRoute: baseBuiltInRoute }); diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/date_range_info.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/date_range_info.ts index 0a4accc983d7..fa991786a60b 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/date_range_info.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/date_range_info.ts @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; import { times } from 'lodash'; -import { parseDuration } from '../../../../../alerting/server'; +import { parseDuration } from '../../../../../alerts/server'; import { MAX_INTERVALS } from '../index'; // dates as numbers are epoch millis diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_types.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_types.ts index 40e6f187ce18..a22395cb0961 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_types.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_types.ts @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { schema, TypeOf } from '@kbn/config-schema'; -import { parseDuration } from '../../../../../alerting/server'; +import { parseDuration } from '../../../../../alerts/server'; import { MAX_INTERVALS } from '../index'; import { CoreQueryParamsSchemaProperties, validateCoreQueryBody } from './core_query_types'; import { diff --git a/x-pack/plugins/alerting_builtins/server/plugin.test.ts b/x-pack/plugins/alerting_builtins/server/plugin.test.ts index f93041fa3c14..71a904dcbab3 100644 --- a/x-pack/plugins/alerting_builtins/server/plugin.test.ts +++ b/x-pack/plugins/alerting_builtins/server/plugin.test.ts @@ -6,7 +6,7 @@ import { AlertingBuiltinsPlugin } from './plugin'; import { coreMock } from '../../../../src/core/server/mocks'; -import { alertsMock } from '../../../plugins/alerting/server/mocks'; +import { alertsMock } from '../../alerts/server/mocks'; describe('AlertingBuiltins Plugin', () => { describe('setup()', () => { @@ -22,7 +22,7 @@ describe('AlertingBuiltins Plugin', () => { it('should register built-in alert types', async () => { const alertingSetup = alertsMock.createSetup(); - await plugin.setup(coreSetup, { alerting: alertingSetup }); + await plugin.setup(coreSetup, { alerts: alertingSetup }); expect(alertingSetup.registerType).toHaveBeenCalledTimes(1); @@ -44,7 +44,7 @@ describe('AlertingBuiltins Plugin', () => { it('should return a service in the expected shape', async () => { const alertingSetup = alertsMock.createSetup(); - const service = await plugin.setup(coreSetup, { alerting: alertingSetup }); + const service = await plugin.setup(coreSetup, { alerts: alertingSetup }); expect(typeof service.indexThreshold.timeSeriesQuery).toBe('function'); }); diff --git a/x-pack/plugins/alerting_builtins/server/plugin.ts b/x-pack/plugins/alerting_builtins/server/plugin.ts index 9a9483f9c9df..12d1b080c7c6 100644 --- a/x-pack/plugins/alerting_builtins/server/plugin.ts +++ b/x-pack/plugins/alerting_builtins/server/plugin.ts @@ -22,11 +22,11 @@ export class AlertingBuiltinsPlugin implements Plugin { }; } - public async setup(core: CoreSetup, { alerting }: AlertingBuiltinsDeps): Promise { + public async setup(core: CoreSetup, { alerts }: AlertingBuiltinsDeps): Promise { registerBuiltInAlertTypes({ service: this.service, router: core.http.createRouter(), - alerting, + alerts, baseRoute: '/api/alerting_builtins', }); return this.service; diff --git a/x-pack/plugins/alerting_builtins/server/types.ts b/x-pack/plugins/alerting_builtins/server/types.ts index ff07b85fd303..95d34371a6d1 100644 --- a/x-pack/plugins/alerting_builtins/server/types.ts +++ b/x-pack/plugins/alerting_builtins/server/types.ts @@ -5,7 +5,7 @@ */ import { Logger, ScopedClusterClient } from '../../../../src/core/server'; -import { PluginSetupContract as AlertingSetup } from '../../alerting/server'; +import { PluginSetupContract as AlertingSetup } from '../../alerts/server'; import { getService as getServiceIndexThreshold } from './alert_types/index_threshold'; export { Logger, IRouter } from '../../../../src/core/server'; @@ -14,11 +14,11 @@ export { PluginSetupContract as AlertingSetup, AlertType, AlertExecutorOptions, -} from '../../alerting/server'; +} from '../../alerts/server'; // this plugin's dependendencies export interface AlertingBuiltinsDeps { - alerting: AlertingSetup; + alerts: AlertingSetup; } // external service exposed through plugin setup/start diff --git a/x-pack/plugins/alerting/README.md b/x-pack/plugins/alerts/README.md similarity index 91% rename from x-pack/plugins/alerting/README.md rename to x-pack/plugins/alerts/README.md index dfa299189542..811478426a8d 100644 --- a/x-pack/plugins/alerting/README.md +++ b/x-pack/plugins/alerts/README.md @@ -20,20 +20,20 @@ Table of Contents - [Example](#example) - [Alert Navigation](#alert-navigation) - [RESTful API](#restful-api) - - [`POST /api/alert`: Create alert](#post-apialert-create-alert) - - [`DELETE /api/alert/{id}`: Delete alert](#delete-apialertid-delete-alert) - - [`GET /api/alert/_find`: Find alerts](#get-apialertfind-find-alerts) - - [`GET /api/alert/{id}`: Get alert](#get-apialertid-get-alert) - - [`GET /api/alert/{id}/state`: Get alert state](#get-apialertidstate-get-alert-state) - - [`GET /api/alert/types`: List alert types](#get-apialerttypes-list-alert-types) - - [`PUT /api/alert/{id}`: Update alert](#put-apialertid-update-alert) - - [`POST /api/alert/{id}/_enable`: Enable an alert](#post-apialertidenable-enable-an-alert) - - [`POST /api/alert/{id}/_disable`: Disable an alert](#post-apialertiddisable-disable-an-alert) - - [`POST /api/alert/{id}/_mute_all`: Mute all alert instances](#post-apialertidmuteall-mute-all-alert-instances) - - [`POST /api/alert/{alertId}/alert_instance/{alertInstanceId}/_mute`: Mute alert instance](#post-apialertalertidalertinstancealertinstanceidmute-mute-alert-instance) - - [`POST /api/alert/{id}/_unmute_all`: Unmute all alert instances](#post-apialertidunmuteall-unmute-all-alert-instances) - - [`POST /api/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute`: Unmute an alert instance](#post-apialertalertidalertinstancealertinstanceidunmute-unmute-an-alert-instance) - - [`POST /api/alert/{id}/_update_api_key`: Update alert API key](#post-apialertidupdateapikey-update-alert-api-key) + - [`POST /api/alerts/alert`: Create alert](#post-apialert-create-alert) + - [`DELETE /api/alerts/alert/{id}`: Delete alert](#delete-apialertid-delete-alert) + - [`GET /api/alerts/_find`: Find alerts](#get-apialertfind-find-alerts) + - [`GET /api/alerts/alert/{id}`: Get alert](#get-apialertid-get-alert) + - [`GET /api/alerts/alert/{id}/state`: Get alert state](#get-apialertidstate-get-alert-state) + - [`GET /api/alerts/list_alert_types`: List alert types](#get-apialerttypes-list-alert-types) + - [`PUT /api/alerts/alert/{id}`: Update alert](#put-apialertid-update-alert) + - [`POST /api/alerts/alert/{id}/_enable`: Enable an alert](#post-apialertidenable-enable-an-alert) + - [`POST /api/alerts/alert/{id}/_disable`: Disable an alert](#post-apialertiddisable-disable-an-alert) + - [`POST /api/alerts/alert/{id}/_mute_all`: Mute all alert instances](#post-apialertidmuteall-mute-all-alert-instances) + - [`POST /api/alerts/alert/{alert_id}/alert_instance/{alert_instance_id}/_mute`: Mute alert instance](#post-apialertalertidalertinstancealertinstanceidmute-mute-alert-instance) + - [`POST /api/alerts/alert/{id}/_unmute_all`: Unmute all alert instances](#post-apialertidunmuteall-unmute-all-alert-instances) + - [`POST /api/alerts/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute`: Unmute an alert instance](#post-apialertalertidalertinstancealertinstanceidunmute-unmute-an-alert-instance) + - [`POST /api/alerts/alert/{id}/_update_api_key`: Update alert API key](#post-apialertidupdateapikey-update-alert-api-key) - [Schedule Formats](#schedule-formats) - [Alert instance factory](#alert-instance-factory) - [Templating actions](#templating-actions) @@ -78,7 +78,7 @@ Note that the `manage_own_api_key` cluster privilege is not enough - it can be u ### Methods -**server.newPlatform.setup.plugins.alerting.registerType(options)** +**server.newPlatform.setup.plugins.alerts.registerType(options)** The following table describes the properties of the `options` object. @@ -139,7 +139,7 @@ This example receives server and threshold as parameters. It will read the CPU u ```typescript import { schema } from '@kbn/config-schema'; ... -server.newPlatform.setup.plugins.alerting.registerType({ +server.newPlatform.setup.plugins.alerts.registerType({ id: 'my-alert-type', name: 'My alert type', validate: { @@ -220,7 +220,7 @@ server.newPlatform.setup.plugins.alerting.registerType({ This example only receives threshold as a parameter. It will read the CPU usage of all the servers and schedule individual actions if the reading for a server is greater than the threshold. This is a better implementation than above as only one query is performed for all the servers instead of one query per server. ```typescript -server.newPlatform.setup.plugins.alerting.registerType({ +server.newPlatform.setup.plugins.alerts.registerType({ id: 'my-alert-type', name: 'My alert type', validate: { @@ -352,7 +352,7 @@ You can use the `registerNavigation` api to specify as many AlertType specific h Using an alert type requires you to create an alert that will contain parameters and actions for a given alert type. See below for CRUD operations using the API. -### `POST /api/alert`: Create alert +### `POST /api/alerts/alert`: Create alert Payload: @@ -367,7 +367,7 @@ Payload: |params|The parameters to pass in to the alert type executor `params` value. This will also validate against the alert type params validator if defined.|object| |actions|Array of the following:
- `group` (string): We support grouping actions in the scenario of escalations or different types of alert instances. If you don't need this, feel free to use `default` as a value.
- `id` (string): The id of the action saved object to execute.
- `params` (object): The map to the `params` the action type will receive. In order to help apply context to strings, we handle them as mustache templates and pass in a default set of context. (see templating actions).|array| -### `DELETE /api/alert/{id}`: Delete alert +### `DELETE /api/alerts/alert/{id}`: Delete alert Params: @@ -375,13 +375,13 @@ Params: |---|---|---| |id|The id of the alert you're trying to delete.|string| -### `GET /api/alert/_find`: Find alerts +### `GET /api/alerts/_find`: Find alerts Params: See the saved objects API documentation for find. All the properties are the same except you cannot pass in `type`. -### `GET /api/alert/{id}`: Get alert +### `GET /api/alerts/alert/{id}`: Get alert Params: @@ -389,7 +389,7 @@ Params: |---|---|---| |id|The id of the alert you're trying to get.|string| -### `GET /api/alert/{id}/state`: Get alert state +### `GET /api/alerts/alert/{id}/state`: Get alert state Params: @@ -397,11 +397,11 @@ Params: |---|---|---| |id|The id of the alert whose state you're trying to get.|string| -### `GET /api/alert/types`: List alert types +### `GET /api/alerts/list_alert_types`: List alert types No parameters. -### `PUT /api/alert/{id}`: Update alert +### `PUT /api/alerts/alert/{id}`: Update alert Params: @@ -420,7 +420,7 @@ Payload: |params|The parameters to pass in to the alert type executor `params` value. This will also validate against the alert type params validator if defined.|object| |actions|Array of the following:
- `group` (string): We support grouping actions in the scenario of escalations or different types of alert instances. If you don't need this, feel free to use `default` as a value.
- `id` (string): The id of the action saved object to execute.
- `params` (object): There map to the `params` the action type will receive. In order to help apply context to strings, we handle them as mustache templates and pass in a default set of context. (see templating actions).|array| -### `POST /api/alert/{id}/_enable`: Enable an alert +### `POST /api/alerts/alert/{id}/_enable`: Enable an alert Params: @@ -428,7 +428,7 @@ Params: |---|---|---| |id|The id of the alert you're trying to enable.|string| -### `POST /api/alert/{id}/_disable`: Disable an alert +### `POST /api/alerts/alert/{id}/_disable`: Disable an alert Params: @@ -436,7 +436,7 @@ Params: |---|---|---| |id|The id of the alert you're trying to disable.|string| -### `POST /api/alert/{id}/_mute_all`: Mute all alert instances +### `POST /api/alerts/alert/{id}/_mute_all`: Mute all alert instances Params: @@ -444,7 +444,7 @@ Params: |---|---|---| |id|The id of the alert you're trying to mute all alert instances for.|string| -### `POST /api/alert/{alertId}/alert_instance/{alertInstanceId}/_mute`: Mute alert instance +### `POST /api/alerts/alert/{alert_id}/alert_instance/{alert_instance_id}/_mute`: Mute alert instance Params: @@ -453,7 +453,7 @@ Params: |alertId|The id of the alert you're trying to mute an instance for.|string| |alertInstanceId|The instance id of the alert instance you're trying to mute.|string| -### `POST /api/alert/{id}/_unmute_all`: Unmute all alert instances +### `POST /api/alerts/alert/{id}/_unmute_all`: Unmute all alert instances Params: @@ -461,7 +461,7 @@ Params: |---|---|---| |id|The id of the alert you're trying to unmute all alert instances for.|string| -### `POST /api/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute`: Unmute an alert instance +### `POST /api/alerts/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute`: Unmute an alert instance Params: @@ -470,7 +470,7 @@ Params: |alertId|The id of the alert you're trying to unmute an instance for.|string| |alertInstanceId|The instance id of the alert instance you're trying to unmute.|string| -### `POST /api/alert/{id}/_update_api_key`: Update alert API key +### `POST /api/alerts/alert/{id}/_update_api_key`: Update alert API key |Property|Description|Type| |---|---|---| diff --git a/x-pack/plugins/alerting/common/alert.ts b/x-pack/plugins/alerts/common/alert.ts similarity index 100% rename from x-pack/plugins/alerting/common/alert.ts rename to x-pack/plugins/alerts/common/alert.ts diff --git a/x-pack/plugins/alerting/common/alert_instance.ts b/x-pack/plugins/alerts/common/alert_instance.ts similarity index 100% rename from x-pack/plugins/alerting/common/alert_instance.ts rename to x-pack/plugins/alerts/common/alert_instance.ts diff --git a/x-pack/plugins/alerting/common/alert_navigation.ts b/x-pack/plugins/alerts/common/alert_navigation.ts similarity index 100% rename from x-pack/plugins/alerting/common/alert_navigation.ts rename to x-pack/plugins/alerts/common/alert_navigation.ts diff --git a/x-pack/plugins/alerting/common/alert_task_instance.ts b/x-pack/plugins/alerts/common/alert_task_instance.ts similarity index 100% rename from x-pack/plugins/alerting/common/alert_task_instance.ts rename to x-pack/plugins/alerts/common/alert_task_instance.ts diff --git a/x-pack/plugins/alerting/common/alert_type.ts b/x-pack/plugins/alerts/common/alert_type.ts similarity index 100% rename from x-pack/plugins/alerting/common/alert_type.ts rename to x-pack/plugins/alerts/common/alert_type.ts diff --git a/x-pack/plugins/alerting/common/date_from_string.test.ts b/x-pack/plugins/alerts/common/date_from_string.test.ts similarity index 100% rename from x-pack/plugins/alerting/common/date_from_string.test.ts rename to x-pack/plugins/alerts/common/date_from_string.test.ts diff --git a/x-pack/plugins/alerting/common/date_from_string.ts b/x-pack/plugins/alerts/common/date_from_string.ts similarity index 100% rename from x-pack/plugins/alerting/common/date_from_string.ts rename to x-pack/plugins/alerts/common/date_from_string.ts diff --git a/x-pack/plugins/alerting/common/index.ts b/x-pack/plugins/alerts/common/index.ts similarity index 92% rename from x-pack/plugins/alerting/common/index.ts rename to x-pack/plugins/alerts/common/index.ts index 2574e73dd4f9..88a8da5a3e57 100644 --- a/x-pack/plugins/alerting/common/index.ts +++ b/x-pack/plugins/alerts/common/index.ts @@ -20,4 +20,4 @@ export interface AlertingFrameworkHealth { hasPermanentEncryptionKey: boolean; } -export const BASE_ALERT_API_PATH = '/api/alert'; +export const BASE_ALERT_API_PATH = '/api/alerts'; diff --git a/x-pack/plugins/alerting/common/parse_duration.test.ts b/x-pack/plugins/alerts/common/parse_duration.test.ts similarity index 100% rename from x-pack/plugins/alerting/common/parse_duration.test.ts rename to x-pack/plugins/alerts/common/parse_duration.test.ts diff --git a/x-pack/plugins/alerting/common/parse_duration.ts b/x-pack/plugins/alerts/common/parse_duration.ts similarity index 100% rename from x-pack/plugins/alerting/common/parse_duration.ts rename to x-pack/plugins/alerts/common/parse_duration.ts diff --git a/x-pack/plugins/alerting/kibana.json b/x-pack/plugins/alerts/kibana.json similarity index 80% rename from x-pack/plugins/alerting/kibana.json rename to x-pack/plugins/alerts/kibana.json index 59c4bb2221b0..3509f79dbbe4 100644 --- a/x-pack/plugins/alerting/kibana.json +++ b/x-pack/plugins/alerts/kibana.json @@ -1,10 +1,10 @@ { - "id": "alerting", + "id": "alerts", "server": true, "ui": true, "version": "8.0.0", "kibanaVersion": "kibana", - "configPath": ["xpack", "alerting"], + "configPath": ["xpack", "alerts"], "requiredPlugins": ["licensing", "taskManager", "encryptedSavedObjects", "actions", "eventLog"], "optionalPlugins": ["usageCollection", "spaces", "security"] } diff --git a/x-pack/plugins/alerting/public/alert_api.test.ts b/x-pack/plugins/alerts/public/alert_api.test.ts similarity index 92% rename from x-pack/plugins/alerting/public/alert_api.test.ts rename to x-pack/plugins/alerts/public/alert_api.test.ts index 1149e6fc249a..45b9f5ba8fe2 100644 --- a/x-pack/plugins/alerting/public/alert_api.test.ts +++ b/x-pack/plugins/alerts/public/alert_api.test.ts @@ -31,7 +31,7 @@ describe('loadAlertTypes', () => { expect(result).toEqual(resolvedValue); expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/alert/types", + "/api/alerts/list_alert_types", ] `); }); @@ -53,7 +53,7 @@ describe('loadAlertType', () => { expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/alert/types", + "/api/alerts/list_alert_types", ] `); }); @@ -111,7 +111,7 @@ describe('loadAlert', () => { http.get.mockResolvedValueOnce(resolvedValue); expect(await loadAlert({ http, alertId })).toEqual(resolvedValue); - expect(http.get).toHaveBeenCalledWith(`/api/alert/${alertId}`); + expect(http.get).toHaveBeenCalledWith(`/api/alerts/alert/${alertId}`); }); }); @@ -130,7 +130,7 @@ describe('loadAlertState', () => { http.get.mockResolvedValueOnce(resolvedValue); expect(await loadAlertState({ http, alertId })).toEqual(resolvedValue); - expect(http.get).toHaveBeenCalledWith(`/api/alert/${alertId}/state`); + expect(http.get).toHaveBeenCalledWith(`/api/alerts/alert/${alertId}/state`); }); test('should parse AlertInstances', async () => { @@ -167,7 +167,7 @@ describe('loadAlertState', () => { }, }, }); - expect(http.get).toHaveBeenCalledWith(`/api/alert/${alertId}/state`); + expect(http.get).toHaveBeenCalledWith(`/api/alerts/alert/${alertId}/state`); }); test('should handle empty response from api', async () => { @@ -175,6 +175,6 @@ describe('loadAlertState', () => { http.get.mockResolvedValueOnce(''); expect(await loadAlertState({ http, alertId })).toEqual({}); - expect(http.get).toHaveBeenCalledWith(`/api/alert/${alertId}/state`); + expect(http.get).toHaveBeenCalledWith(`/api/alerts/alert/${alertId}/state`); }); }); diff --git a/x-pack/plugins/alerting/public/alert_api.ts b/x-pack/plugins/alerts/public/alert_api.ts similarity index 84% rename from x-pack/plugins/alerting/public/alert_api.ts rename to x-pack/plugins/alerts/public/alert_api.ts index ee9432885d67..5b7cd2128f38 100644 --- a/x-pack/plugins/alerting/public/alert_api.ts +++ b/x-pack/plugins/alerts/public/alert_api.ts @@ -16,7 +16,7 @@ import { BASE_ALERT_API_PATH, alertStateSchema } from '../common'; import { Alert, AlertType, AlertTaskState } from '../common'; export async function loadAlertTypes({ http }: { http: HttpSetup }): Promise { - return await http.get(`${BASE_ALERT_API_PATH}/types`); + return await http.get(`${BASE_ALERT_API_PATH}/list_alert_types`); } export async function loadAlertType({ @@ -27,11 +27,11 @@ export async function loadAlertType({ id: AlertType['id']; }): Promise { const maybeAlertType = findFirst((type) => type.id === id)( - await http.get(`${BASE_ALERT_API_PATH}/types`) + await http.get(`${BASE_ALERT_API_PATH}/list_alert_types`) ); if (isNone(maybeAlertType)) { throw new Error( - i18n.translate('xpack.alerting.loadAlertType.missingAlertTypeError', { + i18n.translate('xpack.alerts.loadAlertType.missingAlertTypeError', { defaultMessage: 'Alert type "{id}" is not registered.', values: { id, @@ -49,7 +49,7 @@ export async function loadAlert({ http: HttpSetup; alertId: string; }): Promise { - return await http.get(`${BASE_ALERT_API_PATH}/${alertId}`); + return await http.get(`${BASE_ALERT_API_PATH}/alert/${alertId}`); } type EmptyHttpResponse = ''; @@ -61,7 +61,7 @@ export async function loadAlertState({ alertId: string; }): Promise { return await http - .get(`${BASE_ALERT_API_PATH}/${alertId}/state`) + .get(`${BASE_ALERT_API_PATH}/alert/${alertId}/state`) .then((state: AlertTaskState | EmptyHttpResponse) => (state ? state : {})) .then((state: AlertTaskState) => { return pipe( diff --git a/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.mock.ts b/x-pack/plugins/alerts/public/alert_navigation_registry/alert_navigation_registry.mock.ts similarity index 100% rename from x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.mock.ts rename to x-pack/plugins/alerts/public/alert_navigation_registry/alert_navigation_registry.mock.ts diff --git a/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.test.ts b/x-pack/plugins/alerts/public/alert_navigation_registry/alert_navigation_registry.test.ts similarity index 100% rename from x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.test.ts rename to x-pack/plugins/alerts/public/alert_navigation_registry/alert_navigation_registry.test.ts diff --git a/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.ts b/x-pack/plugins/alerts/public/alert_navigation_registry/alert_navigation_registry.ts similarity index 90% rename from x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.ts rename to x-pack/plugins/alerts/public/alert_navigation_registry/alert_navigation_registry.ts index f30629789b4e..933ed442523c 100644 --- a/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.ts +++ b/x-pack/plugins/alerts/public/alert_navigation_registry/alert_navigation_registry.ts @@ -36,7 +36,7 @@ export class AlertNavigationRegistry { public registerDefault(consumer: string, handler: AlertNavigationHandler) { if (this.hasDefaultHandler(consumer)) { throw new Error( - i18n.translate('xpack.alerting.alertNavigationRegistry.register.duplicateDefaultError', { + i18n.translate('xpack.alerts.alertNavigationRegistry.register.duplicateDefaultError', { defaultMessage: 'Default Navigation within "{consumer}" is already registered.', values: { consumer, @@ -54,7 +54,7 @@ export class AlertNavigationRegistry { public register(consumer: string, alertType: AlertType, handler: AlertNavigationHandler) { if (this.hasTypedHandler(consumer, alertType)) { throw new Error( - i18n.translate('xpack.alerting.alertNavigationRegistry.register.duplicateNavigationError', { + i18n.translate('xpack.alerts.alertNavigationRegistry.register.duplicateNavigationError', { defaultMessage: 'Navigation for Alert type "{alertType}" within "{consumer}" is already registered.', values: { @@ -78,7 +78,7 @@ export class AlertNavigationRegistry { } throw new Error( - i18n.translate('xpack.alerting.alertNavigationRegistry.get.missingNavigationError', { + i18n.translate('xpack.alerts.alertNavigationRegistry.get.missingNavigationError', { defaultMessage: 'Navigation for Alert type "{alertType}" within "{consumer}" is not registered.', values: { diff --git a/x-pack/plugins/alerting/public/alert_navigation_registry/index.ts b/x-pack/plugins/alerts/public/alert_navigation_registry/index.ts similarity index 100% rename from x-pack/plugins/alerting/public/alert_navigation_registry/index.ts rename to x-pack/plugins/alerts/public/alert_navigation_registry/index.ts diff --git a/x-pack/plugins/alerting/public/alert_navigation_registry/types.ts b/x-pack/plugins/alerts/public/alert_navigation_registry/types.ts similarity index 100% rename from x-pack/plugins/alerting/public/alert_navigation_registry/types.ts rename to x-pack/plugins/alerts/public/alert_navigation_registry/types.ts diff --git a/x-pack/plugins/alerting/public/index.ts b/x-pack/plugins/alerts/public/index.ts similarity index 100% rename from x-pack/plugins/alerting/public/index.ts rename to x-pack/plugins/alerts/public/index.ts diff --git a/x-pack/plugins/alerting/public/mocks.ts b/x-pack/plugins/alerts/public/mocks.ts similarity index 100% rename from x-pack/plugins/alerting/public/mocks.ts rename to x-pack/plugins/alerts/public/mocks.ts diff --git a/x-pack/plugins/alerting/public/plugin.ts b/x-pack/plugins/alerts/public/plugin.ts similarity index 100% rename from x-pack/plugins/alerting/public/plugin.ts rename to x-pack/plugins/alerts/public/plugin.ts diff --git a/x-pack/plugins/alerting/server/alert_instance/alert_instance.test.ts b/x-pack/plugins/alerts/server/alert_instance/alert_instance.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/alert_instance/alert_instance.test.ts rename to x-pack/plugins/alerts/server/alert_instance/alert_instance.test.ts diff --git a/x-pack/plugins/alerting/server/alert_instance/alert_instance.ts b/x-pack/plugins/alerts/server/alert_instance/alert_instance.ts similarity index 100% rename from x-pack/plugins/alerting/server/alert_instance/alert_instance.ts rename to x-pack/plugins/alerts/server/alert_instance/alert_instance.ts diff --git a/x-pack/plugins/alerting/server/alert_instance/create_alert_instance_factory.test.ts b/x-pack/plugins/alerts/server/alert_instance/create_alert_instance_factory.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/alert_instance/create_alert_instance_factory.test.ts rename to x-pack/plugins/alerts/server/alert_instance/create_alert_instance_factory.test.ts diff --git a/x-pack/plugins/alerting/server/alert_instance/create_alert_instance_factory.ts b/x-pack/plugins/alerts/server/alert_instance/create_alert_instance_factory.ts similarity index 100% rename from x-pack/plugins/alerting/server/alert_instance/create_alert_instance_factory.ts rename to x-pack/plugins/alerts/server/alert_instance/create_alert_instance_factory.ts diff --git a/x-pack/plugins/alerting/server/alert_instance/index.ts b/x-pack/plugins/alerts/server/alert_instance/index.ts similarity index 100% rename from x-pack/plugins/alerting/server/alert_instance/index.ts rename to x-pack/plugins/alerts/server/alert_instance/index.ts diff --git a/x-pack/plugins/alerting/server/alert_type_registry.mock.ts b/x-pack/plugins/alerts/server/alert_type_registry.mock.ts similarity index 100% rename from x-pack/plugins/alerting/server/alert_type_registry.mock.ts rename to x-pack/plugins/alerts/server/alert_type_registry.mock.ts diff --git a/x-pack/plugins/alerting/server/alert_type_registry.test.ts b/x-pack/plugins/alerts/server/alert_type_registry.test.ts similarity index 98% rename from x-pack/plugins/alerting/server/alert_type_registry.test.ts rename to x-pack/plugins/alerts/server/alert_type_registry.test.ts index e78e5ab7932c..6d7cf621ab0c 100644 --- a/x-pack/plugins/alerting/server/alert_type_registry.test.ts +++ b/x-pack/plugins/alerts/server/alert_type_registry.test.ts @@ -7,7 +7,7 @@ import { TaskRunnerFactory } from './task_runner'; import { AlertTypeRegistry } from './alert_type_registry'; import { AlertType } from './types'; -import { taskManagerMock } from '../../../plugins/task_manager/server/task_manager.mock'; +import { taskManagerMock } from '../../task_manager/server/task_manager.mock'; const taskManager = taskManagerMock.setup(); const alertTypeRegistryParams = { diff --git a/x-pack/plugins/alerting/server/alert_type_registry.ts b/x-pack/plugins/alerts/server/alert_type_registry.ts similarity index 89% rename from x-pack/plugins/alerting/server/alert_type_registry.ts rename to x-pack/plugins/alerts/server/alert_type_registry.ts index 0163cb71166e..8f36afe062aa 100644 --- a/x-pack/plugins/alerting/server/alert_type_registry.ts +++ b/x-pack/plugins/alerts/server/alert_type_registry.ts @@ -6,7 +6,7 @@ import Boom from 'boom'; import { i18n } from '@kbn/i18n'; -import { RunContext, TaskManagerSetupContract } from '../../../plugins/task_manager/server'; +import { RunContext, TaskManagerSetupContract } from '../../task_manager/server'; import { TaskRunnerFactory } from './task_runner'; import { AlertType } from './types'; @@ -32,7 +32,7 @@ export class AlertTypeRegistry { public register(alertType: AlertType) { if (this.has(alertType.id)) { throw new Error( - i18n.translate('xpack.alerting.alertTypeRegistry.register.duplicateAlertTypeError', { + i18n.translate('xpack.alerts.alertTypeRegistry.register.duplicateAlertTypeError', { defaultMessage: 'Alert type "{id}" is already registered.', values: { id: alertType.id, @@ -55,7 +55,7 @@ export class AlertTypeRegistry { public get(id: string): AlertType { if (!this.has(id)) { throw Boom.badRequest( - i18n.translate('xpack.alerting.alertTypeRegistry.get.missingAlertTypeError', { + i18n.translate('xpack.alerts.alertTypeRegistry.get.missingAlertTypeError', { defaultMessage: 'Alert type "{id}" is not registered.', values: { id, diff --git a/x-pack/plugins/alerting/server/alerts_client.mock.ts b/x-pack/plugins/alerts/server/alerts_client.mock.ts similarity index 100% rename from x-pack/plugins/alerting/server/alerts_client.mock.ts rename to x-pack/plugins/alerts/server/alerts_client.mock.ts diff --git a/x-pack/plugins/alerting/server/alerts_client.test.ts b/x-pack/plugins/alerts/server/alerts_client.test.ts similarity index 99% rename from x-pack/plugins/alerting/server/alerts_client.test.ts rename to x-pack/plugins/alerts/server/alerts_client.test.ts index 12106100602e..9685f58b8fb3 100644 --- a/x-pack/plugins/alerting/server/alerts_client.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client.test.ts @@ -7,12 +7,12 @@ import uuid from 'uuid'; import { schema } from '@kbn/config-schema'; import { AlertsClient, CreateOptions } from './alerts_client'; import { savedObjectsClientMock, loggingServiceMock } from '../../../../src/core/server/mocks'; -import { taskManagerMock } from '../../../plugins/task_manager/server/task_manager.mock'; +import { taskManagerMock } from '../../task_manager/server/task_manager.mock'; import { alertTypeRegistryMock } from './alert_type_registry.mock'; -import { TaskStatus } from '../../../plugins/task_manager/server'; +import { TaskStatus } from '../../task_manager/server'; import { IntervalSchedule } from './types'; import { resolvable } from './test_utils'; -import { encryptedSavedObjectsMock } from '../../../plugins/encrypted_saved_objects/server/mocks'; +import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks'; import { actionsClientMock } from '../../actions/server/mocks'; const taskManager = taskManagerMock.start(); @@ -1677,7 +1677,7 @@ describe('find()', () => { }, ], }); - const result = await alertsClient.find(); + const result = await alertsClient.find({ options: {} }); expect(result).toMatchInlineSnapshot(` Object { "data": Array [ diff --git a/x-pack/plugins/alerting/server/alerts_client.ts b/x-pack/plugins/alerts/server/alerts_client.ts similarity index 96% rename from x-pack/plugins/alerting/server/alerts_client.ts rename to x-pack/plugins/alerts/server/alerts_client.ts index 382e9d1a616a..6b091a5a4503 100644 --- a/x-pack/plugins/alerting/server/alerts_client.ts +++ b/x-pack/plugins/alerts/server/alerts_client.ts @@ -30,9 +30,9 @@ import { InvalidateAPIKeyParams, GrantAPIKeyResult as SecurityPluginGrantAPIKeyResult, InvalidateAPIKeyResult as SecurityPluginInvalidateAPIKeyResult, -} from '../../../plugins/security/server'; -import { EncryptedSavedObjectsClient } from '../../../plugins/encrypted_saved_objects/server'; -import { TaskManagerStartContract } from '../../../plugins/task_manager/server'; +} from '../../security/server'; +import { EncryptedSavedObjectsClient } from '../../encrypted_saved_objects/server'; +import { TaskManagerStartContract } from '../../task_manager/server'; import { taskInstanceToAlertTaskInstance } from './task_runner/alert_task_instance'; import { deleteTaskIfItExists } from './lib/delete_task_if_it_exists'; @@ -58,22 +58,29 @@ interface ConstructorOptions { getActionsClient: () => Promise; } -export interface FindOptions { - options?: { - perPage?: number; - page?: number; - search?: string; - defaultSearchOperator?: 'AND' | 'OR'; - searchFields?: string[]; - sortField?: string; - sortOrder?: string; - hasReference?: { - type: string; - id: string; - }; - fields?: string[]; - filter?: string; +export interface MuteOptions extends IndexType { + alertId: string; + alertInstanceId: string; +} + +export interface FindOptions extends IndexType { + perPage?: number; + page?: number; + search?: string; + defaultSearchOperator?: 'AND' | 'OR'; + searchFields?: string[]; + sortField?: string; + sortOrder?: string; + hasReference?: { + type: string; + id: string; }; + fields?: string[]; + filter?: string; +} + +interface IndexType { + [key: string]: unknown; } export interface FindResult { @@ -225,7 +232,7 @@ export class AlertsClient { } } - public async find({ options = {} }: FindOptions = {}): Promise { + public async find({ options = {} }: { options: FindOptions }): Promise { const { page, per_page: perPage, @@ -533,13 +540,7 @@ export class AlertsClient { }); } - public async muteInstance({ - alertId, - alertInstanceId, - }: { - alertId: string; - alertInstanceId: string; - }) { + public async muteInstance({ alertId, alertInstanceId }: MuteOptions) { const { attributes, version } = await this.savedObjectsClient.get('alert', alertId); const mutedInstanceIds = attributes.mutedInstanceIds || []; if (!attributes.muteAll && !mutedInstanceIds.includes(alertInstanceId)) { @@ -652,7 +653,7 @@ export class AlertsClient { ); if (invalidActionGroups.length) { throw Boom.badRequest( - i18n.translate('xpack.alerting.alertsClient.validateActions.invalidGroups', { + i18n.translate('xpack.alerts.alertsClient.validateActions.invalidGroups', { defaultMessage: 'Invalid action groups: {groups}', values: { groups: invalidActionGroups.join(', '), diff --git a/x-pack/plugins/alerting/server/alerts_client_factory.test.ts b/x-pack/plugins/alerts/server/alerts_client_factory.test.ts similarity index 95% rename from x-pack/plugins/alerting/server/alerts_client_factory.test.ts rename to x-pack/plugins/alerts/server/alerts_client_factory.test.ts index d1a7c60bb9a6..50dafba00a7e 100644 --- a/x-pack/plugins/alerting/server/alerts_client_factory.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client_factory.test.ts @@ -7,12 +7,12 @@ import { Request } from 'hapi'; import { AlertsClientFactory, AlertsClientFactoryOpts } from './alerts_client_factory'; import { alertTypeRegistryMock } from './alert_type_registry.mock'; -import { taskManagerMock } from '../../../plugins/task_manager/server/task_manager.mock'; +import { taskManagerMock } from '../../task_manager/server/task_manager.mock'; import { KibanaRequest } from '../../../../src/core/server'; import { loggingServiceMock, savedObjectsClientMock } from '../../../../src/core/server/mocks'; -import { encryptedSavedObjectsMock } from '../../../plugins/encrypted_saved_objects/server/mocks'; -import { AuthenticatedUser } from '../../../plugins/security/public'; -import { securityMock } from '../../../plugins/security/server/mocks'; +import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks'; +import { AuthenticatedUser } from '../../security/public'; +import { securityMock } from '../../security/server/mocks'; import { actionsMock } from '../../actions/server/mocks'; jest.mock('./alerts_client'); diff --git a/x-pack/plugins/alerting/server/alerts_client_factory.ts b/x-pack/plugins/alerts/server/alerts_client_factory.ts similarity index 95% rename from x-pack/plugins/alerting/server/alerts_client_factory.ts rename to x-pack/plugins/alerts/server/alerts_client_factory.ts index 2924736330ab..af546f965d7d 100644 --- a/x-pack/plugins/alerting/server/alerts_client_factory.ts +++ b/x-pack/plugins/alerts/server/alerts_client_factory.ts @@ -8,9 +8,9 @@ import { PluginStartContract as ActionsPluginStartContract } from '../../actions import { AlertsClient } from './alerts_client'; import { AlertTypeRegistry, SpaceIdToNamespaceFunction } from './types'; import { KibanaRequest, Logger, SavedObjectsClientContract } from '../../../../src/core/server'; -import { InvalidateAPIKeyParams, SecurityPluginSetup } from '../../../plugins/security/server'; -import { EncryptedSavedObjectsClient } from '../../../plugins/encrypted_saved_objects/server'; -import { TaskManagerStartContract } from '../../../plugins/task_manager/server'; +import { InvalidateAPIKeyParams, SecurityPluginSetup } from '../../security/server'; +import { EncryptedSavedObjectsClient } from '../../encrypted_saved_objects/server'; +import { TaskManagerStartContract } from '../../task_manager/server'; export interface AlertsClientFactoryOpts { logger: Logger; diff --git a/x-pack/plugins/alerting/server/constants/plugin.ts b/x-pack/plugins/alerts/server/constants/plugin.ts similarity index 86% rename from x-pack/plugins/alerting/server/constants/plugin.ts rename to x-pack/plugins/alerts/server/constants/plugin.ts index 9c276ed1d75d..c180b6868084 100644 --- a/x-pack/plugins/alerting/server/constants/plugin.ts +++ b/x-pack/plugins/alerts/server/constants/plugin.ts @@ -7,12 +7,12 @@ import { LICENSE_TYPE_BASIC, LicenseType } from '../../../../legacy/common/constants'; export const PLUGIN = { - ID: 'alerting', + ID: 'alerts', MINIMUM_LICENSE_REQUIRED: LICENSE_TYPE_BASIC as LicenseType, // TODO: supposed to be changed up on requirements // all plugins seem to use getI18nName with any // eslint-disable-next-line @typescript-eslint/no-explicit-any getI18nName: (i18n: any): string => - i18n.translate('xpack.alerting.appName', { - defaultMessage: 'Alerting', + i18n.translate('xpack.alerts.appName', { + defaultMessage: 'Alerts', }), }; diff --git a/x-pack/plugins/alerting/server/index.ts b/x-pack/plugins/alerts/server/index.ts similarity index 100% rename from x-pack/plugins/alerting/server/index.ts rename to x-pack/plugins/alerts/server/index.ts diff --git a/x-pack/plugins/alerting/server/lib/delete_task_if_it_exists.test.ts b/x-pack/plugins/alerts/server/lib/delete_task_if_it_exists.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/lib/delete_task_if_it_exists.test.ts rename to x-pack/plugins/alerts/server/lib/delete_task_if_it_exists.test.ts diff --git a/x-pack/plugins/alerting/server/lib/delete_task_if_it_exists.ts b/x-pack/plugins/alerts/server/lib/delete_task_if_it_exists.ts similarity index 100% rename from x-pack/plugins/alerting/server/lib/delete_task_if_it_exists.ts rename to x-pack/plugins/alerts/server/lib/delete_task_if_it_exists.ts diff --git a/x-pack/plugins/alerting/server/lib/index.ts b/x-pack/plugins/alerts/server/lib/index.ts similarity index 100% rename from x-pack/plugins/alerting/server/lib/index.ts rename to x-pack/plugins/alerts/server/lib/index.ts diff --git a/x-pack/plugins/alerting/server/lib/is_alert_not_found_error.test.ts b/x-pack/plugins/alerts/server/lib/is_alert_not_found_error.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/lib/is_alert_not_found_error.test.ts rename to x-pack/plugins/alerts/server/lib/is_alert_not_found_error.test.ts diff --git a/x-pack/plugins/alerting/server/lib/is_alert_not_found_error.ts b/x-pack/plugins/alerts/server/lib/is_alert_not_found_error.ts similarity index 100% rename from x-pack/plugins/alerting/server/lib/is_alert_not_found_error.ts rename to x-pack/plugins/alerts/server/lib/is_alert_not_found_error.ts diff --git a/x-pack/plugins/alerting/server/lib/license_api_access.ts b/x-pack/plugins/alerts/server/lib/license_api_access.ts similarity index 100% rename from x-pack/plugins/alerting/server/lib/license_api_access.ts rename to x-pack/plugins/alerts/server/lib/license_api_access.ts diff --git a/x-pack/plugins/alerting/server/lib/license_state.mock.ts b/x-pack/plugins/alerts/server/lib/license_state.mock.ts similarity index 100% rename from x-pack/plugins/alerting/server/lib/license_state.mock.ts rename to x-pack/plugins/alerts/server/lib/license_state.mock.ts diff --git a/x-pack/plugins/alerting/server/lib/license_state.test.ts b/x-pack/plugins/alerts/server/lib/license_state.test.ts similarity index 95% rename from x-pack/plugins/alerting/server/lib/license_state.test.ts rename to x-pack/plugins/alerts/server/lib/license_state.test.ts index cbab98a6311d..50b4e6b4439f 100644 --- a/x-pack/plugins/alerting/server/lib/license_state.test.ts +++ b/x-pack/plugins/alerts/server/lib/license_state.test.ts @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { LicenseState } from './license_state'; -import { licensingMock } from '../../../../plugins/licensing/server/mocks'; +import { licensingMock } from '../../../licensing/server/mocks'; describe('license_state', () => { const getRawLicense = jest.fn(); diff --git a/x-pack/plugins/alerting/server/lib/license_state.ts b/x-pack/plugins/alerts/server/lib/license_state.ts similarity index 90% rename from x-pack/plugins/alerting/server/lib/license_state.ts rename to x-pack/plugins/alerts/server/lib/license_state.ts index 211d7a75dc4f..ea0106f717b0 100644 --- a/x-pack/plugins/alerting/server/lib/license_state.ts +++ b/x-pack/plugins/alerts/server/lib/license_state.ts @@ -7,7 +7,7 @@ import Boom from 'boom'; import { i18n } from '@kbn/i18n'; import { Observable, Subscription } from 'rxjs'; -import { ILicense } from '../../../../plugins/licensing/common/types'; +import { ILicense } from '../../../licensing/common/types'; import { assertNever } from '../../../../../src/core/server'; import { PLUGIN } from '../constants/plugin'; @@ -43,10 +43,10 @@ export class LicenseState { showAppLink: true, enableAppLink: false, message: i18n.translate( - 'xpack.alerting.serverSideErrors.unavailableLicenseInformationErrorMessage', + 'xpack.alerts.serverSideErrors.unavailableLicenseInformationErrorMessage', { defaultMessage: - 'Alerting is unavailable - license information is not available at this time.', + 'Alerts is unavailable - license information is not available at this time.', } ), }; diff --git a/x-pack/plugins/alerting/server/lib/result_type.ts b/x-pack/plugins/alerts/server/lib/result_type.ts similarity index 100% rename from x-pack/plugins/alerting/server/lib/result_type.ts rename to x-pack/plugins/alerts/server/lib/result_type.ts diff --git a/x-pack/plugins/alerting/server/lib/types.test.ts b/x-pack/plugins/alerts/server/lib/types.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/lib/types.test.ts rename to x-pack/plugins/alerts/server/lib/types.test.ts diff --git a/x-pack/plugins/alerting/server/lib/types.ts b/x-pack/plugins/alerts/server/lib/types.ts similarity index 100% rename from x-pack/plugins/alerting/server/lib/types.ts rename to x-pack/plugins/alerts/server/lib/types.ts diff --git a/x-pack/plugins/alerting/server/lib/validate_alert_type_params.test.ts b/x-pack/plugins/alerts/server/lib/validate_alert_type_params.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/lib/validate_alert_type_params.test.ts rename to x-pack/plugins/alerts/server/lib/validate_alert_type_params.test.ts diff --git a/x-pack/plugins/alerting/server/lib/validate_alert_type_params.ts b/x-pack/plugins/alerts/server/lib/validate_alert_type_params.ts similarity index 100% rename from x-pack/plugins/alerting/server/lib/validate_alert_type_params.ts rename to x-pack/plugins/alerts/server/lib/validate_alert_type_params.ts diff --git a/x-pack/plugins/alerting/server/mocks.ts b/x-pack/plugins/alerts/server/mocks.ts similarity index 100% rename from x-pack/plugins/alerting/server/mocks.ts rename to x-pack/plugins/alerts/server/mocks.ts diff --git a/x-pack/plugins/alerting/server/plugin.test.ts b/x-pack/plugins/alerts/server/plugin.test.ts similarity index 97% rename from x-pack/plugins/alerting/server/plugin.test.ts rename to x-pack/plugins/alerts/server/plugin.test.ts index 0411899290ab..008a9bb804c5 100644 --- a/x-pack/plugins/alerting/server/plugin.test.ts +++ b/x-pack/plugins/alerts/server/plugin.test.ts @@ -6,8 +6,8 @@ import { AlertingPlugin, AlertingPluginsSetup, AlertingPluginsStart } from './plugin'; import { coreMock } from '../../../../src/core/server/mocks'; -import { licensingMock } from '../../../plugins/licensing/server/mocks'; -import { encryptedSavedObjectsMock } from '../../../plugins/encrypted_saved_objects/server/mocks'; +import { licensingMock } from '../../licensing/server/mocks'; +import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks'; import { taskManagerMock } from '../../task_manager/server/mocks'; import { eventLogServiceMock } from '../../event_log/server/event_log_service.mock'; import { KibanaRequest, CoreSetup } from 'kibana/server'; diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerts/server/plugin.ts similarity index 99% rename from x-pack/plugins/alerting/server/plugin.ts rename to x-pack/plugins/alerts/server/plugin.ts index e789e655774a..324bc9fbfb72 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerts/server/plugin.ts @@ -53,7 +53,7 @@ import { LicensingPluginSetup } from '../../licensing/server'; import { PluginSetupContract as ActionsPluginSetupContract, PluginStartContract as ActionsPluginStartContract, -} from '../../../plugins/actions/server'; +} from '../../actions/server'; import { Services } from './types'; import { registerAlertsUsageCollector } from './usage'; import { initializeAlertingTelemetry, scheduleAlertingTelemetry } from './usage/task'; diff --git a/x-pack/plugins/alerting/server/routes/_mock_handler_arguments.ts b/x-pack/plugins/alerts/server/routes/_mock_handler_arguments.ts similarity index 100% rename from x-pack/plugins/alerting/server/routes/_mock_handler_arguments.ts rename to x-pack/plugins/alerts/server/routes/_mock_handler_arguments.ts diff --git a/x-pack/plugins/alerting/server/routes/create.test.ts b/x-pack/plugins/alerts/server/routes/create.test.ts similarity index 98% rename from x-pack/plugins/alerting/server/routes/create.test.ts rename to x-pack/plugins/alerts/server/routes/create.test.ts index a4910495c8a4..9e941903eeae 100644 --- a/x-pack/plugins/alerting/server/routes/create.test.ts +++ b/x-pack/plugins/alerts/server/routes/create.test.ts @@ -74,7 +74,7 @@ describe('createAlertRoute', () => { const [config, handler] = router.post.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ diff --git a/x-pack/plugins/alerting/server/routes/create.ts b/x-pack/plugins/alerts/server/routes/create.ts similarity index 98% rename from x-pack/plugins/alerting/server/routes/create.ts rename to x-pack/plugins/alerts/server/routes/create.ts index cc3b7d48162e..6238fca024e5 100644 --- a/x-pack/plugins/alerting/server/routes/create.ts +++ b/x-pack/plugins/alerts/server/routes/create.ts @@ -43,7 +43,7 @@ export const bodySchema = schema.object({ export const createAlertRoute = (router: IRouter, licenseState: LicenseState) => { router.post( { - path: BASE_ALERT_API_PATH, + path: `${BASE_ALERT_API_PATH}/alert`, validate: { body: bodySchema, }, diff --git a/x-pack/plugins/alerting/server/routes/delete.test.ts b/x-pack/plugins/alerts/server/routes/delete.test.ts similarity index 97% rename from x-pack/plugins/alerting/server/routes/delete.test.ts rename to x-pack/plugins/alerts/server/routes/delete.test.ts index 416628d015b5..9ba4e20312e1 100644 --- a/x-pack/plugins/alerting/server/routes/delete.test.ts +++ b/x-pack/plugins/alerts/server/routes/delete.test.ts @@ -29,7 +29,7 @@ describe('deleteAlertRoute', () => { const [config, handler] = router.delete.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/{id}"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ diff --git a/x-pack/plugins/alerting/server/routes/delete.ts b/x-pack/plugins/alerts/server/routes/delete.ts similarity index 96% rename from x-pack/plugins/alerting/server/routes/delete.ts rename to x-pack/plugins/alerts/server/routes/delete.ts index f5a7add632ed..2034bd21fbed 100644 --- a/x-pack/plugins/alerting/server/routes/delete.ts +++ b/x-pack/plugins/alerts/server/routes/delete.ts @@ -23,7 +23,7 @@ const paramSchema = schema.object({ export const deleteAlertRoute = (router: IRouter, licenseState: LicenseState) => { router.delete( { - path: `${BASE_ALERT_API_PATH}/{id}`, + path: `${BASE_ALERT_API_PATH}/alert/{id}`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/disable.test.ts b/x-pack/plugins/alerts/server/routes/disable.test.ts similarity index 95% rename from x-pack/plugins/alerting/server/routes/disable.test.ts rename to x-pack/plugins/alerts/server/routes/disable.test.ts index fde095e9145b..a82d09854a60 100644 --- a/x-pack/plugins/alerting/server/routes/disable.test.ts +++ b/x-pack/plugins/alerts/server/routes/disable.test.ts @@ -29,7 +29,7 @@ describe('disableAlertRoute', () => { const [config, handler] = router.post.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/{id}/_disable"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/_disable"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ diff --git a/x-pack/plugins/alerting/server/routes/disable.ts b/x-pack/plugins/alerts/server/routes/disable.ts similarity index 96% rename from x-pack/plugins/alerting/server/routes/disable.ts rename to x-pack/plugins/alerts/server/routes/disable.ts index e1eb089cf4e8..dfc5dfbdd5aa 100644 --- a/x-pack/plugins/alerting/server/routes/disable.ts +++ b/x-pack/plugins/alerts/server/routes/disable.ts @@ -23,7 +23,7 @@ const paramSchema = schema.object({ export const disableAlertRoute = (router: IRouter, licenseState: LicenseState) => { router.post( { - path: `${BASE_ALERT_API_PATH}/{id}/_disable`, + path: `${BASE_ALERT_API_PATH}/alert/{id}/_disable`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/enable.test.ts b/x-pack/plugins/alerts/server/routes/enable.test.ts similarity index 95% rename from x-pack/plugins/alerting/server/routes/enable.test.ts rename to x-pack/plugins/alerts/server/routes/enable.test.ts index e4e89e3f0638..4ee3a12a59dc 100644 --- a/x-pack/plugins/alerting/server/routes/enable.test.ts +++ b/x-pack/plugins/alerts/server/routes/enable.test.ts @@ -28,7 +28,7 @@ describe('enableAlertRoute', () => { const [config, handler] = router.post.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/{id}/_enable"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/_enable"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ diff --git a/x-pack/plugins/alerting/server/routes/enable.ts b/x-pack/plugins/alerts/server/routes/enable.ts similarity index 96% rename from x-pack/plugins/alerting/server/routes/enable.ts rename to x-pack/plugins/alerts/server/routes/enable.ts index 90e8f552898d..b6f86b97d6a3 100644 --- a/x-pack/plugins/alerting/server/routes/enable.ts +++ b/x-pack/plugins/alerts/server/routes/enable.ts @@ -24,7 +24,7 @@ const paramSchema = schema.object({ export const enableAlertRoute = (router: IRouter, licenseState: LicenseState) => { router.post( { - path: `${BASE_ALERT_API_PATH}/{id}/_enable`, + path: `${BASE_ALERT_API_PATH}/alert/{id}/_enable`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/find.test.ts b/x-pack/plugins/alerts/server/routes/find.test.ts similarity index 93% rename from x-pack/plugins/alerting/server/routes/find.test.ts rename to x-pack/plugins/alerts/server/routes/find.test.ts index cc601bd42b8c..f20ee0a54dcd 100644 --- a/x-pack/plugins/alerting/server/routes/find.test.ts +++ b/x-pack/plugins/alerts/server/routes/find.test.ts @@ -30,7 +30,7 @@ describe('findAlertRoute', () => { const [config, handler] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/_find"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/_find"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ @@ -76,13 +76,8 @@ describe('findAlertRoute', () => { Object { "options": Object { "defaultSearchOperator": "OR", - "fields": undefined, - "filter": undefined, "page": 1, "perPage": 1, - "search": undefined, - "sortField": undefined, - "sortOrder": undefined, }, }, ] diff --git a/x-pack/plugins/alerting/server/routes/find.ts b/x-pack/plugins/alerts/server/routes/find.ts similarity index 81% rename from x-pack/plugins/alerting/server/routes/find.ts rename to x-pack/plugins/alerts/server/routes/find.ts index 3de95c9580cd..80c9c20eec7d 100644 --- a/x-pack/plugins/alerting/server/routes/find.ts +++ b/x-pack/plugins/alerts/server/routes/find.ts @@ -12,10 +12,11 @@ import { IKibanaResponse, KibanaResponseFactory, } from 'kibana/server'; -import { FindOptions } from '../../../alerting/server'; import { LicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { BASE_ALERT_API_PATH } from '../../common'; +import { renameKeys } from './lib/rename_keys'; +import { FindOptions } from '..'; // config definition const querySchema = schema.object({ @@ -63,31 +64,29 @@ export const findAlertRoute = (router: IRouter, licenseState: LicenseState) => { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); } const alertsClient = context.alerting.getAlertsClient(); + const query = req.query; - const options: FindOptions['options'] = { - perPage: query.per_page, - page: query.page, - search: query.search, - defaultSearchOperator: query.default_search_operator, - sortField: query.sort_field, - fields: query.fields, - filter: query.filter, - sortOrder: query.sort_order, + const renameMap = { + default_search_operator: 'defaultSearchOperator', + fields: 'fields', + has_reference: 'hasReference', + page: 'page', + per_page: 'perPage', + search: 'search', + sort_field: 'sortField', + sort_order: 'sortOrder', + filter: 'filter', }; + const options = renameKeys>(renameMap, query); + if (query.search_fields) { options.searchFields = Array.isArray(query.search_fields) ? query.search_fields : [query.search_fields]; } - if (query.has_reference) { - options.hasReference = query.has_reference; - } - - const findResult = await alertsClient.find({ - options, - }); + const findResult = await alertsClient.find({ options }); return res.ok({ body: findResult, }); diff --git a/x-pack/plugins/alerting/server/routes/get.test.ts b/x-pack/plugins/alerts/server/routes/get.test.ts similarity index 97% rename from x-pack/plugins/alerting/server/routes/get.test.ts rename to x-pack/plugins/alerts/server/routes/get.test.ts index 7335f13c85a4..b11224ff4794 100644 --- a/x-pack/plugins/alerting/server/routes/get.test.ts +++ b/x-pack/plugins/alerts/server/routes/get.test.ts @@ -60,7 +60,7 @@ describe('getAlertRoute', () => { getAlertRoute(router, licenseState); const [config, handler] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/{id}"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ diff --git a/x-pack/plugins/alerting/server/routes/get.ts b/x-pack/plugins/alerts/server/routes/get.ts similarity index 96% rename from x-pack/plugins/alerting/server/routes/get.ts rename to x-pack/plugins/alerts/server/routes/get.ts index cd78e7fbacdd..ae9ebe129937 100644 --- a/x-pack/plugins/alerting/server/routes/get.ts +++ b/x-pack/plugins/alerts/server/routes/get.ts @@ -23,7 +23,7 @@ const paramSchema = schema.object({ export const getAlertRoute = (router: IRouter, licenseState: LicenseState) => { router.get( { - path: `${BASE_ALERT_API_PATH}/{id}`, + path: `${BASE_ALERT_API_PATH}/alert/{id}`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/get_alert_state.test.ts b/x-pack/plugins/alerts/server/routes/get_alert_state.test.ts similarity index 94% rename from x-pack/plugins/alerting/server/routes/get_alert_state.test.ts rename to x-pack/plugins/alerts/server/routes/get_alert_state.test.ts index 20a420ca0098..8c9051093f85 100644 --- a/x-pack/plugins/alerting/server/routes/get_alert_state.test.ts +++ b/x-pack/plugins/alerts/server/routes/get_alert_state.test.ts @@ -47,7 +47,7 @@ describe('getAlertStateRoute', () => { const [config, handler] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/{id}/state"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/state"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ @@ -90,7 +90,7 @@ describe('getAlertStateRoute', () => { const [config, handler] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/{id}/state"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/state"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ @@ -133,7 +133,7 @@ describe('getAlertStateRoute', () => { const [config, handler] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/{id}/state"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/state"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ diff --git a/x-pack/plugins/alerting/server/routes/get_alert_state.ts b/x-pack/plugins/alerts/server/routes/get_alert_state.ts similarity index 96% rename from x-pack/plugins/alerting/server/routes/get_alert_state.ts rename to x-pack/plugins/alerts/server/routes/get_alert_state.ts index a5cb14154db6..b27ae3758e1b 100644 --- a/x-pack/plugins/alerting/server/routes/get_alert_state.ts +++ b/x-pack/plugins/alerts/server/routes/get_alert_state.ts @@ -23,7 +23,7 @@ const paramSchema = schema.object({ export const getAlertStateRoute = (router: IRouter, licenseState: LicenseState) => { router.get( { - path: `${BASE_ALERT_API_PATH}/{id}/state`, + path: `${BASE_ALERT_API_PATH}/alert/{id}/state`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/health.test.ts b/x-pack/plugins/alerts/server/routes/health.test.ts similarity index 99% rename from x-pack/plugins/alerting/server/routes/health.test.ts rename to x-pack/plugins/alerts/server/routes/health.test.ts index 9b1c95c393f5..b3f41e03ebdc 100644 --- a/x-pack/plugins/alerting/server/routes/health.test.ts +++ b/x-pack/plugins/alerts/server/routes/health.test.ts @@ -31,7 +31,7 @@ describe('healthRoute', () => { const [config] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/_health"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/_health"`); }); it('queries the usage api', async () => { diff --git a/x-pack/plugins/alerting/server/routes/health.ts b/x-pack/plugins/alerts/server/routes/health.ts similarity index 98% rename from x-pack/plugins/alerting/server/routes/health.ts rename to x-pack/plugins/alerts/server/routes/health.ts index fb4c5e76a02c..b66e28b24e8a 100644 --- a/x-pack/plugins/alerting/server/routes/health.ts +++ b/x-pack/plugins/alerts/server/routes/health.ts @@ -34,7 +34,7 @@ export function healthRoute( ) { router.get( { - path: '/api/alert/_health', + path: '/api/alerts/_health', validate: false, }, router.handleLegacyErrors(async function ( diff --git a/x-pack/plugins/alerting/server/routes/index.ts b/x-pack/plugins/alerts/server/routes/index.ts similarity index 100% rename from x-pack/plugins/alerting/server/routes/index.ts rename to x-pack/plugins/alerts/server/routes/index.ts diff --git a/x-pack/plugins/alerting/server/routes/lib/error_handler.ts b/x-pack/plugins/alerts/server/routes/lib/error_handler.ts similarity index 94% rename from x-pack/plugins/alerting/server/routes/lib/error_handler.ts rename to x-pack/plugins/alerts/server/routes/lib/error_handler.ts index b3cf48c52fe1..e0c620b0670c 100644 --- a/x-pack/plugins/alerting/server/routes/lib/error_handler.ts +++ b/x-pack/plugins/alerts/server/routes/lib/error_handler.ts @@ -27,7 +27,7 @@ export function handleDisabledApiKeysError( if (isApiKeyDisabledError(e)) { return response.badRequest({ body: new Error( - i18n.translate('xpack.alerting.api.error.disabledApiKeys', { + i18n.translate('xpack.alerts.api.error.disabledApiKeys', { defaultMessage: 'Alerting relies upon API keys which appear to be disabled', }) ), diff --git a/x-pack/plugins/alerts/server/routes/lib/rename_keys.ts b/x-pack/plugins/alerts/server/routes/lib/rename_keys.ts new file mode 100644 index 000000000000..bfe60a0ecc64 --- /dev/null +++ b/x-pack/plugins/alerts/server/routes/lib/rename_keys.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. + */ + +export const renameKeys = , U extends Record>( + keysMap: Record, + obj: Record +): T => + Object.keys(obj).reduce((acc, key) => { + return { + ...acc, + ...{ [keysMap[key] || key]: obj[key] }, + }; + }, {} as T); diff --git a/x-pack/plugins/alerting/server/routes/list_alert_types.test.ts b/x-pack/plugins/alerts/server/routes/list_alert_types.test.ts similarity index 94% rename from x-pack/plugins/alerting/server/routes/list_alert_types.test.ts rename to x-pack/plugins/alerts/server/routes/list_alert_types.test.ts index e940b2d10204..3192154f6664 100644 --- a/x-pack/plugins/alerting/server/routes/list_alert_types.test.ts +++ b/x-pack/plugins/alerts/server/routes/list_alert_types.test.ts @@ -27,7 +27,7 @@ describe('listAlertTypesRoute', () => { const [config, handler] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/types"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/list_alert_types"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ @@ -89,7 +89,7 @@ describe('listAlertTypesRoute', () => { const [config, handler] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/types"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/list_alert_types"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ @@ -140,7 +140,7 @@ describe('listAlertTypesRoute', () => { const [config, handler] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/types"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/list_alert_types"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ diff --git a/x-pack/plugins/alerting/server/routes/list_alert_types.ts b/x-pack/plugins/alerts/server/routes/list_alert_types.ts similarity index 95% rename from x-pack/plugins/alerting/server/routes/list_alert_types.ts rename to x-pack/plugins/alerts/server/routes/list_alert_types.ts index f5b4e3263f34..51a4558108e2 100644 --- a/x-pack/plugins/alerting/server/routes/list_alert_types.ts +++ b/x-pack/plugins/alerts/server/routes/list_alert_types.ts @@ -18,7 +18,7 @@ import { BASE_ALERT_API_PATH } from '../../common'; export const listAlertTypesRoute = (router: IRouter, licenseState: LicenseState) => { router.get( { - path: `${BASE_ALERT_API_PATH}/types`, + path: `${BASE_ALERT_API_PATH}/list_alert_types`, validate: {}, options: { tags: ['access:alerting-read'], diff --git a/x-pack/plugins/alerting/server/routes/mute_all.test.ts b/x-pack/plugins/alerts/server/routes/mute_all.test.ts similarity index 95% rename from x-pack/plugins/alerting/server/routes/mute_all.test.ts rename to x-pack/plugins/alerts/server/routes/mute_all.test.ts index 5ef9e3694f8f..bcdb8cbd022a 100644 --- a/x-pack/plugins/alerting/server/routes/mute_all.test.ts +++ b/x-pack/plugins/alerts/server/routes/mute_all.test.ts @@ -28,7 +28,7 @@ describe('muteAllAlertRoute', () => { const [config, handler] = router.post.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/{id}/_mute_all"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/_mute_all"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ diff --git a/x-pack/plugins/alerting/server/routes/mute_all.ts b/x-pack/plugins/alerts/server/routes/mute_all.ts similarity index 96% rename from x-pack/plugins/alerting/server/routes/mute_all.ts rename to x-pack/plugins/alerts/server/routes/mute_all.ts index b43a1ec30ed1..5b05d7231c38 100644 --- a/x-pack/plugins/alerting/server/routes/mute_all.ts +++ b/x-pack/plugins/alerts/server/routes/mute_all.ts @@ -23,7 +23,7 @@ const paramSchema = schema.object({ export const muteAllAlertRoute = (router: IRouter, licenseState: LicenseState) => { router.post( { - path: `${BASE_ALERT_API_PATH}/{id}/_mute_all`, + path: `${BASE_ALERT_API_PATH}/alert/{id}/_mute_all`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/mute_instance.test.ts b/x-pack/plugins/alerts/server/routes/mute_instance.test.ts similarity index 92% rename from x-pack/plugins/alerting/server/routes/mute_instance.test.ts rename to x-pack/plugins/alerts/server/routes/mute_instance.test.ts index 2e6adedb76df..c382c12de21c 100644 --- a/x-pack/plugins/alerting/server/routes/mute_instance.test.ts +++ b/x-pack/plugins/alerts/server/routes/mute_instance.test.ts @@ -29,7 +29,7 @@ describe('muteAlertInstanceRoute', () => { const [config, handler] = router.post.mock.calls[0]; expect(config.path).toMatchInlineSnapshot( - `"/api/alert/{alertId}/alert_instance/{alertInstanceId}/_mute"` + `"/api/alerts/alert/{alert_id}/alert_instance/{alert_instance_id}/_mute"` ); expect(config.options).toMatchInlineSnapshot(` Object { @@ -45,8 +45,8 @@ describe('muteAlertInstanceRoute', () => { { alertsClient }, { params: { - alertId: '1', - alertInstanceId: '2', + alert_id: '1', + alert_instance_id: '2', }, }, ['noContent'] diff --git a/x-pack/plugins/alerting/server/routes/mute_instance.ts b/x-pack/plugins/alerts/server/routes/mute_instance.ts similarity index 72% rename from x-pack/plugins/alerting/server/routes/mute_instance.ts rename to x-pack/plugins/alerts/server/routes/mute_instance.ts index c0c69fe9653d..00550f4af341 100644 --- a/x-pack/plugins/alerting/server/routes/mute_instance.ts +++ b/x-pack/plugins/alerts/server/routes/mute_instance.ts @@ -15,16 +15,18 @@ import { import { LicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { BASE_ALERT_API_PATH } from '../../common'; +import { renameKeys } from './lib/rename_keys'; +import { MuteOptions } from '../alerts_client'; const paramSchema = schema.object({ - alertId: schema.string(), - alertInstanceId: schema.string(), + alert_id: schema.string(), + alert_instance_id: schema.string(), }); export const muteAlertInstanceRoute = (router: IRouter, licenseState: LicenseState) => { router.post( { - path: `${BASE_ALERT_API_PATH}/{alertId}/alert_instance/{alertInstanceId}/_mute`, + path: `${BASE_ALERT_API_PATH}/alert/{alert_id}/alert_instance/{alert_instance_id}/_mute`, validate: { params: paramSchema, }, @@ -42,8 +44,14 @@ export const muteAlertInstanceRoute = (router: IRouter, licenseState: LicenseSta return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); } const alertsClient = context.alerting.getAlertsClient(); - const { alertId, alertInstanceId } = req.params; - await alertsClient.muteInstance({ alertId, alertInstanceId }); + + const renameMap = { + alert_id: 'alertId', + alert_instance_id: 'alertInstanceId', + }; + + const renamedQuery = renameKeys>(renameMap, req.params); + await alertsClient.muteInstance(renamedQuery); return res.noContent(); }) ); diff --git a/x-pack/plugins/alerting/server/routes/unmute_all.test.ts b/x-pack/plugins/alerts/server/routes/unmute_all.test.ts similarity index 95% rename from x-pack/plugins/alerting/server/routes/unmute_all.test.ts rename to x-pack/plugins/alerts/server/routes/unmute_all.test.ts index 1756dbd3fb41..e13af38fe4cb 100644 --- a/x-pack/plugins/alerting/server/routes/unmute_all.test.ts +++ b/x-pack/plugins/alerts/server/routes/unmute_all.test.ts @@ -27,7 +27,7 @@ describe('unmuteAllAlertRoute', () => { const [config, handler] = router.post.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/{id}/_unmute_all"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/_unmute_all"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ diff --git a/x-pack/plugins/alerting/server/routes/unmute_all.ts b/x-pack/plugins/alerts/server/routes/unmute_all.ts similarity index 96% rename from x-pack/plugins/alerting/server/routes/unmute_all.ts rename to x-pack/plugins/alerts/server/routes/unmute_all.ts index d4b6e8b7d61b..1efc9ed40054 100644 --- a/x-pack/plugins/alerting/server/routes/unmute_all.ts +++ b/x-pack/plugins/alerts/server/routes/unmute_all.ts @@ -23,7 +23,7 @@ const paramSchema = schema.object({ export const unmuteAllAlertRoute = (router: IRouter, licenseState: LicenseState) => { router.post( { - path: `${BASE_ALERT_API_PATH}/{id}/_unmute_all`, + path: `${BASE_ALERT_API_PATH}/alert/{id}/_unmute_all`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/unmute_instance.test.ts b/x-pack/plugins/alerts/server/routes/unmute_instance.test.ts similarity index 95% rename from x-pack/plugins/alerting/server/routes/unmute_instance.test.ts rename to x-pack/plugins/alerts/server/routes/unmute_instance.test.ts index 9b9542c60674..b2e2f24e91de 100644 --- a/x-pack/plugins/alerting/server/routes/unmute_instance.test.ts +++ b/x-pack/plugins/alerts/server/routes/unmute_instance.test.ts @@ -29,7 +29,7 @@ describe('unmuteAlertInstanceRoute', () => { const [config, handler] = router.post.mock.calls[0]; expect(config.path).toMatchInlineSnapshot( - `"/api/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute"` + `"/api/alerts/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute"` ); expect(config.options).toMatchInlineSnapshot(` Object { diff --git a/x-pack/plugins/alerting/server/routes/unmute_instance.ts b/x-pack/plugins/alerts/server/routes/unmute_instance.ts similarity index 94% rename from x-pack/plugins/alerting/server/routes/unmute_instance.ts rename to x-pack/plugins/alerts/server/routes/unmute_instance.ts index 97ccd8f0adce..967f9f890c9f 100644 --- a/x-pack/plugins/alerting/server/routes/unmute_instance.ts +++ b/x-pack/plugins/alerts/server/routes/unmute_instance.ts @@ -24,7 +24,7 @@ const paramSchema = schema.object({ export const unmuteAlertInstanceRoute = (router: IRouter, licenseState: LicenseState) => { router.post( { - path: `${BASE_ALERT_API_PATH}/{alertId}/alert_instance/{alertInstanceId}/_unmute`, + path: `${BASE_ALERT_API_PATH}/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/update.test.ts b/x-pack/plugins/alerts/server/routes/update.test.ts similarity index 98% rename from x-pack/plugins/alerting/server/routes/update.test.ts rename to x-pack/plugins/alerts/server/routes/update.test.ts index cd96f289b871..c7d23f2670b4 100644 --- a/x-pack/plugins/alerting/server/routes/update.test.ts +++ b/x-pack/plugins/alerts/server/routes/update.test.ts @@ -51,7 +51,7 @@ describe('updateAlertRoute', () => { const [config, handler] = router.put.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/{id}"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ diff --git a/x-pack/plugins/alerting/server/routes/update.ts b/x-pack/plugins/alerts/server/routes/update.ts similarity index 98% rename from x-pack/plugins/alerting/server/routes/update.ts rename to x-pack/plugins/alerts/server/routes/update.ts index 23fea7dc4002..99b81dfc5b56 100644 --- a/x-pack/plugins/alerting/server/routes/update.ts +++ b/x-pack/plugins/alerts/server/routes/update.ts @@ -44,7 +44,7 @@ const bodySchema = schema.object({ export const updateAlertRoute = (router: IRouter, licenseState: LicenseState) => { router.put( { - path: `${BASE_ALERT_API_PATH}/{id}`, + path: `${BASE_ALERT_API_PATH}/alert/{id}`, validate: { body: bodySchema, params: paramSchema, diff --git a/x-pack/plugins/alerting/server/routes/update_api_key.test.ts b/x-pack/plugins/alerts/server/routes/update_api_key.test.ts similarity index 95% rename from x-pack/plugins/alerting/server/routes/update_api_key.test.ts rename to x-pack/plugins/alerts/server/routes/update_api_key.test.ts index 0347feb24a23..babae59553b5 100644 --- a/x-pack/plugins/alerting/server/routes/update_api_key.test.ts +++ b/x-pack/plugins/alerts/server/routes/update_api_key.test.ts @@ -28,7 +28,7 @@ describe('updateApiKeyRoute', () => { const [config, handler] = router.post.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/{id}/_update_api_key"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/_update_api_key"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ diff --git a/x-pack/plugins/alerting/server/routes/update_api_key.ts b/x-pack/plugins/alerts/server/routes/update_api_key.ts similarity index 96% rename from x-pack/plugins/alerting/server/routes/update_api_key.ts rename to x-pack/plugins/alerts/server/routes/update_api_key.ts index 9d88201d7cd4..4736351a25cb 100644 --- a/x-pack/plugins/alerting/server/routes/update_api_key.ts +++ b/x-pack/plugins/alerts/server/routes/update_api_key.ts @@ -24,7 +24,7 @@ const paramSchema = schema.object({ export const updateApiKeyRoute = (router: IRouter, licenseState: LicenseState) => { router.post( { - path: `${BASE_ALERT_API_PATH}/{id}/_update_api_key`, + path: `${BASE_ALERT_API_PATH}/alert/{id}/_update_api_key`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/saved_objects/index.ts b/x-pack/plugins/alerts/server/saved_objects/index.ts similarity index 100% rename from x-pack/plugins/alerting/server/saved_objects/index.ts rename to x-pack/plugins/alerts/server/saved_objects/index.ts diff --git a/x-pack/plugins/alerting/server/saved_objects/mappings.json b/x-pack/plugins/alerts/server/saved_objects/mappings.json similarity index 100% rename from x-pack/plugins/alerting/server/saved_objects/mappings.json rename to x-pack/plugins/alerts/server/saved_objects/mappings.json diff --git a/x-pack/plugins/alerting/server/task_runner/alert_task_instance.test.ts b/x-pack/plugins/alerts/server/task_runner/alert_task_instance.test.ts similarity index 98% rename from x-pack/plugins/alerting/server/task_runner/alert_task_instance.test.ts rename to x-pack/plugins/alerts/server/task_runner/alert_task_instance.test.ts index 28b3576dffc6..efac4c5dcdc0 100644 --- a/x-pack/plugins/alerting/server/task_runner/alert_task_instance.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/alert_task_instance.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ConcreteTaskInstance, TaskStatus } from '../../../../plugins/task_manager/server'; +import { ConcreteTaskInstance, TaskStatus } from '../../../task_manager/server'; import { AlertTaskInstance, taskInstanceToAlertTaskInstance } from './alert_task_instance'; import uuid from 'uuid'; import { SanitizedAlert } from '../types'; diff --git a/x-pack/plugins/alerting/server/task_runner/alert_task_instance.ts b/x-pack/plugins/alerts/server/task_runner/alert_task_instance.ts similarity index 95% rename from x-pack/plugins/alerting/server/task_runner/alert_task_instance.ts rename to x-pack/plugins/alerts/server/task_runner/alert_task_instance.ts index 4be506b78493..a290f3fa33c7 100644 --- a/x-pack/plugins/alerting/server/task_runner/alert_task_instance.ts +++ b/x-pack/plugins/alerts/server/task_runner/alert_task_instance.ts @@ -6,7 +6,7 @@ import * as t from 'io-ts'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; -import { ConcreteTaskInstance } from '../../../../plugins/task_manager/server'; +import { ConcreteTaskInstance } from '../../../task_manager/server'; import { SanitizedAlert, AlertTaskState, diff --git a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts b/x-pack/plugins/alerts/server/task_runner/create_execution_handler.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts rename to x-pack/plugins/alerts/server/task_runner/create_execution_handler.test.ts diff --git a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts b/x-pack/plugins/alerts/server/task_runner/create_execution_handler.ts similarity index 98% rename from x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts rename to x-pack/plugins/alerts/server/task_runner/create_execution_handler.ts index 61bbab50b122..3c58c6d9ba28 100644 --- a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts +++ b/x-pack/plugins/alerts/server/task_runner/create_execution_handler.ts @@ -8,7 +8,7 @@ import { pluck } from 'lodash'; import { AlertAction, State, Context, AlertType } from '../types'; import { Logger } from '../../../../../src/core/server'; import { transformActionParams } from './transform_action_params'; -import { PluginStartContract as ActionsPluginStartContract } from '../../../../plugins/actions/server'; +import { PluginStartContract as ActionsPluginStartContract } from '../../../actions/server'; import { IEventLogger, IEvent, SAVED_OBJECT_REL_PRIMARY } from '../../../event_log/server'; import { EVENT_LOG_ACTIONS } from '../plugin'; diff --git a/x-pack/plugins/alerting/server/task_runner/get_next_run_at.test.ts b/x-pack/plugins/alerts/server/task_runner/get_next_run_at.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/task_runner/get_next_run_at.test.ts rename to x-pack/plugins/alerts/server/task_runner/get_next_run_at.test.ts diff --git a/x-pack/plugins/alerting/server/task_runner/get_next_run_at.ts b/x-pack/plugins/alerts/server/task_runner/get_next_run_at.ts similarity index 100% rename from x-pack/plugins/alerting/server/task_runner/get_next_run_at.ts rename to x-pack/plugins/alerts/server/task_runner/get_next_run_at.ts diff --git a/x-pack/plugins/alerting/server/task_runner/index.ts b/x-pack/plugins/alerts/server/task_runner/index.ts similarity index 100% rename from x-pack/plugins/alerting/server/task_runner/index.ts rename to x-pack/plugins/alerts/server/task_runner/index.ts diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts similarity index 99% rename from x-pack/plugins/alerting/server/task_runner/task_runner.test.ts rename to x-pack/plugins/alerts/server/task_runner/task_runner.test.ts index 98824b8cf4e1..983dff86d560 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts @@ -7,10 +7,10 @@ import sinon from 'sinon'; import { schema } from '@kbn/config-schema'; import { AlertExecutorOptions } from '../types'; -import { ConcreteTaskInstance, TaskStatus } from '../../../../plugins/task_manager/server'; +import { ConcreteTaskInstance, TaskStatus } from '../../../task_manager/server'; import { TaskRunnerContext } from './task_runner_factory'; import { TaskRunner } from './task_runner'; -import { encryptedSavedObjectsMock } from '../../../../plugins/encrypted_saved_objects/server/mocks'; +import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks'; import { loggingServiceMock } from '../../../../../src/core/server/mocks'; import { PluginStartContract as ActionsPluginStart } from '../../../actions/server'; import { actionsMock } from '../../../actions/server/mocks'; diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.ts similarity index 99% rename from x-pack/plugins/alerting/server/task_runner/task_runner.ts rename to x-pack/plugins/alerts/server/task_runner/task_runner.ts index 0831163d1d32..be399893088e 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.ts @@ -7,7 +7,7 @@ import { pick, mapValues, omit, without } from 'lodash'; import { Logger, SavedObject, KibanaRequest } from '../../../../../src/core/server'; import { TaskRunnerContext } from './task_runner_factory'; -import { ConcreteTaskInstance } from '../../../../plugins/task_manager/server'; +import { ConcreteTaskInstance } from '../../../task_manager/server'; import { createExecutionHandler } from './create_execution_handler'; import { AlertInstance, createAlertInstanceFactory } from '../alert_instance'; import { getNextRunAt } from './get_next_run_at'; diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner_factory.test.ts similarity index 93% rename from x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts rename to x-pack/plugins/alerts/server/task_runner/task_runner_factory.test.ts index c1318bac48df..7d9710d8a3e0 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner_factory.test.ts @@ -5,9 +5,9 @@ */ import sinon from 'sinon'; -import { ConcreteTaskInstance, TaskStatus } from '../../../../plugins/task_manager/server'; +import { ConcreteTaskInstance, TaskStatus } from '../../../task_manager/server'; import { TaskRunnerContext, TaskRunnerFactory } from './task_runner_factory'; -import { encryptedSavedObjectsMock } from '../../../../plugins/encrypted_saved_objects/server/mocks'; +import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks'; import { loggingServiceMock } from '../../../../../src/core/server/mocks'; import { actionsMock } from '../../../actions/server/mocks'; import { alertsMock } from '../mocks'; diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts b/x-pack/plugins/alerts/server/task_runner/task_runner_factory.ts similarity index 87% rename from x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts rename to x-pack/plugins/alerts/server/task_runner/task_runner_factory.ts index c50e288d2e52..ca762cf2b210 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner_factory.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ import { Logger } from '../../../../../src/core/server'; -import { RunContext } from '../../../../plugins/task_manager/server'; -import { EncryptedSavedObjectsClient } from '../../../../plugins/encrypted_saved_objects/server'; -import { PluginStartContract as ActionsPluginStartContract } from '../../../../plugins/actions/server'; +import { RunContext } from '../../../task_manager/server'; +import { EncryptedSavedObjectsClient } from '../../../encrypted_saved_objects/server'; +import { PluginStartContract as ActionsPluginStartContract } from '../../../actions/server'; import { AlertType, GetBasePathFunction, diff --git a/x-pack/plugins/alerting/server/task_runner/transform_action_params.test.ts b/x-pack/plugins/alerts/server/task_runner/transform_action_params.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/task_runner/transform_action_params.test.ts rename to x-pack/plugins/alerts/server/task_runner/transform_action_params.test.ts diff --git a/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts b/x-pack/plugins/alerts/server/task_runner/transform_action_params.ts similarity index 100% rename from x-pack/plugins/alerting/server/task_runner/transform_action_params.ts rename to x-pack/plugins/alerts/server/task_runner/transform_action_params.ts diff --git a/x-pack/plugins/alerting/server/test_utils/index.ts b/x-pack/plugins/alerts/server/test_utils/index.ts similarity index 100% rename from x-pack/plugins/alerting/server/test_utils/index.ts rename to x-pack/plugins/alerts/server/test_utils/index.ts diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerts/server/types.ts similarity index 100% rename from x-pack/plugins/alerting/server/types.ts rename to x-pack/plugins/alerts/server/types.ts diff --git a/x-pack/plugins/alerting/server/usage/alerts_telemetry.test.ts b/x-pack/plugins/alerts/server/usage/alerts_telemetry.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/usage/alerts_telemetry.test.ts rename to x-pack/plugins/alerts/server/usage/alerts_telemetry.test.ts diff --git a/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts b/x-pack/plugins/alerts/server/usage/alerts_telemetry.ts similarity index 100% rename from x-pack/plugins/alerting/server/usage/alerts_telemetry.ts rename to x-pack/plugins/alerts/server/usage/alerts_telemetry.ts diff --git a/x-pack/plugins/alerting/server/usage/alerts_usage_collector.test.ts b/x-pack/plugins/alerts/server/usage/alerts_usage_collector.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/usage/alerts_usage_collector.test.ts rename to x-pack/plugins/alerts/server/usage/alerts_usage_collector.test.ts diff --git a/x-pack/plugins/alerting/server/usage/alerts_usage_collector.ts b/x-pack/plugins/alerts/server/usage/alerts_usage_collector.ts similarity index 100% rename from x-pack/plugins/alerting/server/usage/alerts_usage_collector.ts rename to x-pack/plugins/alerts/server/usage/alerts_usage_collector.ts diff --git a/x-pack/plugins/alerting/server/usage/index.ts b/x-pack/plugins/alerts/server/usage/index.ts similarity index 100% rename from x-pack/plugins/alerting/server/usage/index.ts rename to x-pack/plugins/alerts/server/usage/index.ts diff --git a/x-pack/plugins/alerting/server/usage/task.ts b/x-pack/plugins/alerts/server/usage/task.ts similarity index 100% rename from x-pack/plugins/alerting/server/usage/task.ts rename to x-pack/plugins/alerts/server/usage/task.ts diff --git a/x-pack/plugins/alerting/server/usage/types.ts b/x-pack/plugins/alerts/server/usage/types.ts similarity index 100% rename from x-pack/plugins/alerting/server/usage/types.ts rename to x-pack/plugins/alerts/server/usage/types.ts diff --git a/x-pack/plugins/apm/kibana.json b/x-pack/plugins/apm/kibana.json index dd89fac66f6e..2de3c9c97065 100644 --- a/x-pack/plugins/apm/kibana.json +++ b/x-pack/plugins/apm/kibana.json @@ -15,7 +15,7 @@ "usageCollection", "taskManager", "actions", - "alerting", + "alerts", "observability", "security" ], diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/index.tsx index c3d426a6275a..0dbde5ea86a1 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/index.tsx @@ -28,7 +28,7 @@ export function ServiceDetails({ tab }: Props) { const canSaveAlerts = !!plugin.core.application.capabilities.apm[ 'alerting:save' ]; - const isAlertingPluginEnabled = 'alerting' in plugin.plugins; + const isAlertingPluginEnabled = 'alerts' in plugin.plugins; const isAlertingAvailable = isAlertingPluginEnabled && (canReadAlerts || canSaveAlerts); diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index e732e695b36b..76320efe617e 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -17,7 +17,7 @@ import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public'; import { PluginSetupContract as AlertingPluginPublicSetup, PluginStartContract as AlertingPluginPublicStart, -} from '../../alerting/public'; +} from '../../alerts/public'; import { FeaturesPluginSetup } from '../../features/public'; import { DataPublicPluginSetup, @@ -44,7 +44,7 @@ export type ApmPluginSetup = void; export type ApmPluginStart = void; export interface ApmPluginSetupDeps { - alerting?: AlertingPluginPublicSetup; + alerts?: AlertingPluginPublicSetup; data: DataPublicPluginSetup; features: FeaturesPluginSetup; home: HomePublicPluginSetup; @@ -53,7 +53,7 @@ export interface ApmPluginSetupDeps { } export interface ApmPluginStartDeps { - alerting?: AlertingPluginPublicStart; + alerts?: AlertingPluginPublicStart; data: DataPublicPluginStart; home: void; licensing: void; diff --git a/x-pack/plugins/apm/server/lib/alerts/register_apm_alerts.ts b/x-pack/plugins/apm/server/lib/alerts/register_apm_alerts.ts index 8af9f386eceb..4b8e9cf937a2 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_apm_alerts.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_apm_alerts.ts @@ -5,25 +5,25 @@ */ import { Observable } from 'rxjs'; -import { AlertingPlugin } from '../../../../alerting/server'; +import { AlertingPlugin } from '../../../../alerts/server'; import { ActionsPlugin } from '../../../../actions/server'; import { registerTransactionDurationAlertType } from './register_transaction_duration_alert_type'; import { registerErrorRateAlertType } from './register_error_rate_alert_type'; import { APMConfig } from '../..'; interface Params { - alerting: AlertingPlugin['setup']; + alerts: AlertingPlugin['setup']; actions: ActionsPlugin['setup']; config$: Observable; } export function registerApmAlerts(params: Params) { registerTransactionDurationAlertType({ - alerting: params.alerting, + alerts: params.alerts, config$: params.config$, }); registerErrorRateAlertType({ - alerting: params.alerting, + alerts: params.alerts, config$: params.config$, }); } diff --git a/x-pack/plugins/apm/server/lib/alerts/register_error_rate_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_error_rate_alert_type.ts index ee7bd9eeb4b6..53843b7f7412 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_error_rate_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_error_rate_alert_type.ts @@ -19,12 +19,12 @@ import { SERVICE_NAME, SERVICE_ENVIRONMENT, } from '../../../common/elasticsearch_fieldnames'; -import { AlertingPlugin } from '../../../../alerting/server'; +import { AlertingPlugin } from '../../../../alerts/server'; import { getApmIndices } from '../settings/apm_indices/get_apm_indices'; import { APMConfig } from '../..'; interface RegisterAlertParams { - alerting: AlertingPlugin['setup']; + alerts: AlertingPlugin['setup']; config$: Observable; } @@ -39,10 +39,10 @@ const paramsSchema = schema.object({ const alertTypeConfig = ALERT_TYPES_CONFIG[AlertType.ErrorRate]; export function registerErrorRateAlertType({ - alerting, + alerts, config$, }: RegisterAlertParams) { - alerting.registerType({ + alerts.registerType({ id: AlertType.ErrorRate, name: alertTypeConfig.name, actionGroups: alertTypeConfig.actionGroups, diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts index afb402200a07..1fd1aef4c8b7 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts @@ -18,12 +18,12 @@ import { TRANSACTION_DURATION, SERVICE_ENVIRONMENT, } from '../../../common/elasticsearch_fieldnames'; -import { AlertingPlugin } from '../../../../alerting/server'; +import { AlertingPlugin } from '../../../../alerts/server'; import { getApmIndices } from '../settings/apm_indices/get_apm_indices'; import { APMConfig } from '../..'; interface RegisterAlertParams { - alerting: AlertingPlugin['setup']; + alerts: AlertingPlugin['setup']; config$: Observable; } @@ -44,10 +44,10 @@ const paramsSchema = schema.object({ const alertTypeConfig = ALERT_TYPES_CONFIG[AlertType.TransactionDuration]; export function registerTransactionDurationAlertType({ - alerting, + alerts, config$, }: RegisterAlertParams) { - alerting.registerType({ + alerts.registerType({ id: AlertType.TransactionDuration, name: alertTypeConfig.name, actionGroups: alertTypeConfig.actionGroups, diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index b9ad14f7ec47..d32d16d4c3cc 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -17,7 +17,7 @@ import { ObservabilityPluginSetup } from '../../observability/server'; import { SecurityPluginSetup } from '../../security/public'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/server'; import { TaskManagerSetupContract } from '../../task_manager/server'; -import { AlertingPlugin } from '../../alerting/server'; +import { AlertingPlugin } from '../../alerts/server'; import { ActionsPlugin } from '../../actions/server'; import { APMOSSPluginSetup } from '../../../../src/plugins/apm_oss/server'; import { createApmAgentConfigurationIndex } from './lib/settings/agent_configuration/create_agent_config_index'; @@ -57,7 +57,7 @@ export class APMPlugin implements Plugin { cloud?: CloudSetup; usageCollection?: UsageCollectionSetup; taskManager?: TaskManagerSetupContract; - alerting?: AlertingPlugin['setup']; + alerts?: AlertingPlugin['setup']; actions?: ActionsPlugin['setup']; observability?: ObservabilityPluginSetup; features: FeaturesPluginSetup; @@ -73,9 +73,9 @@ export class APMPlugin implements Plugin { core.savedObjects.registerType(apmIndices); core.savedObjects.registerType(apmTelemetry); - if (plugins.actions && plugins.alerting) { + if (plugins.actions && plugins.alerts) { registerApmAlerts({ - alerting: plugins.alerting, + alerts: plugins.alerts, actions: plugins.actions, config$: mergedConfig$, }); diff --git a/x-pack/plugins/infra/kibana.json b/x-pack/plugins/infra/kibana.json index ea66ae7a46d4..4701182c9681 100644 --- a/x-pack/plugins/infra/kibana.json +++ b/x-pack/plugins/infra/kibana.json @@ -10,7 +10,7 @@ "data", "dataEnhanced", "visTypeTimeseries", - "alerting", + "alerts", "triggers_actions_ui" ], "server": true, diff --git a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts index 4bbbf8dcdee0..d00afbc7b497 100644 --- a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts @@ -13,7 +13,7 @@ import { SpacesPluginSetup } from '../../../../../../plugins/spaces/server'; import { VisTypeTimeseriesSetup } from '../../../../../../../src/plugins/vis_type_timeseries/server'; import { APMPluginSetup } from '../../../../../../plugins/apm/server'; import { HomeServerPluginSetup } from '../../../../../../../src/plugins/home/server'; -import { PluginSetupContract as AlertingPluginContract } from '../../../../../../plugins/alerting/server'; +import { PluginSetupContract as AlertingPluginContract } from '../../../../../alerts/server'; // NP_TODO: Compose real types from plugins we depend on, no "any" export interface InfraServerPluginDeps { @@ -23,7 +23,7 @@ export interface InfraServerPluginDeps { visTypeTimeseries: VisTypeTimeseriesSetup; features: FeaturesPluginSetup; apm: APMPluginSetup; - alerting: AlertingPluginContract; + alerts: AlertingPluginContract; } export interface CallWithRequestParams extends GenericParams { diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index b36de2a3bd09..5a34a6665e78 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -11,7 +11,7 @@ import { CallWithRequestParams, } from '../../adapters/framework/adapter_types'; import { Comparator, AlertStates, InventoryMetricConditions } from './types'; -import { AlertServices, AlertExecutorOptions } from '../../../../../alerting/server'; +import { AlertServices, AlertExecutorOptions } from '../../../../../alerts/server'; import { InfraSnapshot } from '../../snapshot'; import { parseFilterQuery } from '../../../utils/serialized_query'; import { InventoryItemType, SnapshotMetricType } from '../../../../common/inventory_models/types'; diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.test.ts index 995d415ef3c8..a3b9e8545841 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.test.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.test.ts @@ -11,12 +11,12 @@ import { LogDocumentCountAlertParams, Criterion, } from '../../../../common/alerting/logs/types'; -import { AlertExecutorOptions } from '../../../../../alerting/server'; +import { AlertExecutorOptions } from '../../../../../alerts/server'; import { alertsMock, AlertInstanceMock, AlertServicesMock, -} from '../../../../../alerting/server/mocks'; +} from '../../../../../alerts/server/mocks'; import { libsMock } from './mocks'; interface AlertTestInstance { diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts index eedaf4202b37..ee4e1fcb3f6e 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { AlertExecutorOptions, AlertServices } from '../../../../../alerting/server'; +import { AlertExecutorOptions, AlertServices } from '../../../../../alerts/server'; import { AlertStates, Comparator, diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts index cdb4d2d96847..ed7e82fe29e4 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts @@ -6,7 +6,7 @@ import uuid from 'uuid'; import { i18n } from '@kbn/i18n'; import { schema } from '@kbn/config-schema'; -import { PluginSetupContract } from '../../../../../alerting/server'; +import { PluginSetupContract } from '../../../../../alerts/server'; import { createLogThresholdExecutor, FIRED_ACTIONS } from './log_threshold_executor'; import { LOG_DOCUMENT_COUNT_ALERT_TYPE_ID, diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts index 19efc88e216c..8260ebed8462 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts @@ -6,12 +6,12 @@ import { createMetricThresholdExecutor, FIRED_ACTIONS } from './metric_threshold_executor'; import { Comparator, AlertStates } from './types'; import * as mocks from './test_mocks'; -import { AlertExecutorOptions } from '../../../../../alerting/server'; +import { AlertExecutorOptions } from '../../../../../alerts/server'; import { alertsMock, AlertServicesMock, AlertInstanceMock, -} from '../../../../../alerting/server/mocks'; +} from '../../../../../alerts/server/mocks'; import { InfraSources } from '../../sources'; interface AlertTestInstance { diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts index d1cb60112aa4..233a34a67d1e 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts @@ -17,7 +17,7 @@ import { DOCUMENT_COUNT_I18N, stateToAlertMessage, } from './messages'; -import { AlertServices, AlertExecutorOptions } from '../../../../../alerting/server'; +import { AlertServices, AlertExecutorOptions } from '../../../../../alerts/server'; import { getIntervalInSeconds } from '../../../utils/get_interval_in_seconds'; import { getDateHistogramOffset } from '../../snapshot/query_helpers'; import { InfraBackendLibs } from '../../infra_types'; diff --git a/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts b/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts index ae74ed82038f..989a2917b052 100644 --- a/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts +++ b/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginSetupContract } from '../../../../alerting/server'; +import { PluginSetupContract } from '../../../../alerts/server'; import { registerMetricThresholdAlertType } from './metric_threshold/register_metric_threshold_alert_type'; import { registerMetricInventoryThresholdAlertType } from './inventory_metric_threshold/register_inventory_metric_threshold_alert_type'; import { registerLogThresholdAlertType } from './log_threshold/register_log_threshold_alert_type'; diff --git a/x-pack/plugins/infra/server/plugin.ts b/x-pack/plugins/infra/server/plugin.ts index a265d53fc1bf..2fd614830c05 100644 --- a/x-pack/plugins/infra/server/plugin.ts +++ b/x-pack/plugins/infra/server/plugin.ts @@ -149,7 +149,7 @@ export class InfraServerPlugin { ]); initInfraServer(this.libs); - registerAlertTypes(plugins.alerting, this.libs); + registerAlertTypes(plugins.alerts, this.libs); // Telemetry UsageCollector.registerUsageCollector(plugins.usageCollection); diff --git a/x-pack/plugins/monitoring/kibana.json b/x-pack/plugins/monitoring/kibana.json index 115cc08871ea..4ed693464712 100644 --- a/x-pack/plugins/monitoring/kibana.json +++ b/x-pack/plugins/monitoring/kibana.json @@ -4,7 +4,7 @@ "kibanaVersion": "kibana", "configPath": ["monitoring"], "requiredPlugins": ["licensing", "features", "data", "navigation", "kibanaLegacy"], - "optionalPlugins": ["alerting", "actions", "infra", "telemetryCollectionManager", "usageCollection", "home"], + "optionalPlugins": ["alerts", "actions", "infra", "telemetryCollectionManager", "usageCollection", "home"], "server": true, "ui": true } diff --git a/x-pack/plugins/monitoring/public/components/alerts/status.tsx b/x-pack/plugins/monitoring/public/components/alerts/status.tsx index cdddbf103130..6f72168f5069 100644 --- a/x-pack/plugins/monitoring/public/components/alerts/status.tsx +++ b/x-pack/plugins/monitoring/public/components/alerts/status.tsx @@ -18,7 +18,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { Legacy } from '../../legacy_shims'; -import { Alert, BASE_ALERT_API_PATH } from '../../../../../plugins/alerting/common'; +import { Alert, BASE_ALERT_API_PATH } from '../../../../alerts/common'; import { getSetupModeState, addSetupModeCallback, toggleSetupMode } from '../../lib/setup_mode'; import { NUMBER_OF_MIGRATED_ALERTS, ALERT_TYPE_PREFIX } from '../../../common/constants'; import { AlertsConfiguration } from './configuration'; diff --git a/x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts b/x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts index bcc1a8abe5cb..626203603771 100644 --- a/x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts @@ -10,7 +10,7 @@ import { AlertCommonParams, AlertCommonState, AlertClusterStatePerClusterState } import { getPreparedAlert } from '../lib/alerts/get_prepared_alert'; import { executeActions } from '../lib/alerts/cluster_state.lib'; import { AlertClusterStateState } from './enums'; -import { alertsMock, AlertServicesMock } from '../../../alerting/server/mocks'; +import { alertsMock, AlertServicesMock } from '../../../alerts/server/mocks'; jest.mock('../lib/alerts/cluster_state.lib', () => ({ executeActions: jest.fn(), diff --git a/x-pack/plugins/monitoring/server/alerts/cluster_state.ts b/x-pack/plugins/monitoring/server/alerts/cluster_state.ts index 6567d1c6def3..5b6521179002 100644 --- a/x-pack/plugins/monitoring/server/alerts/cluster_state.ts +++ b/x-pack/plugins/monitoring/server/alerts/cluster_state.ts @@ -8,7 +8,7 @@ import moment from 'moment-timezone'; import { i18n } from '@kbn/i18n'; import { Logger, ICustomClusterClient, UiSettingsServiceStart } from 'src/core/server'; import { ALERT_TYPE_CLUSTER_STATE } from '../../common/constants'; -import { AlertType } from '../../../alerting/server'; +import { AlertType } from '../../../alerts/server'; import { executeActions, getUiMessage } from '../lib/alerts/cluster_state.lib'; import { AlertCommonExecutorOptions, diff --git a/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts b/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts index 6ffe937679f4..fb8d10884fdc 100644 --- a/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts @@ -16,7 +16,7 @@ import { } from './types'; import { executeActions } from '../lib/alerts/license_expiration.lib'; import { PreparedAlert, getPreparedAlert } from '../lib/alerts/get_prepared_alert'; -import { alertsMock, AlertServicesMock } from '../../../alerting/server/mocks'; +import { alertsMock, AlertServicesMock } from '../../../alerts/server/mocks'; jest.mock('../lib/alerts/license_expiration.lib', () => ({ executeActions: jest.fn(), diff --git a/x-pack/plugins/monitoring/server/alerts/license_expiration.ts b/x-pack/plugins/monitoring/server/alerts/license_expiration.ts index 00402bca57a7..d57f1a7655b1 100644 --- a/x-pack/plugins/monitoring/server/alerts/license_expiration.ts +++ b/x-pack/plugins/monitoring/server/alerts/license_expiration.ts @@ -8,7 +8,7 @@ import moment from 'moment-timezone'; import { Logger, ICustomClusterClient, UiSettingsServiceStart } from 'src/core/server'; import { i18n } from '@kbn/i18n'; import { ALERT_TYPE_LICENSE_EXPIRATION } from '../../common/constants'; -import { AlertType } from '../../../../plugins/alerting/server'; +import { AlertType } from '../../../alerts/server'; import { fetchLicenses } from '../lib/alerts/fetch_licenses'; import { AlertCommonState, diff --git a/x-pack/plugins/monitoring/server/alerts/types.d.ts b/x-pack/plugins/monitoring/server/alerts/types.d.ts index b689d008b51a..67c74635b4e3 100644 --- a/x-pack/plugins/monitoring/server/alerts/types.d.ts +++ b/x-pack/plugins/monitoring/server/alerts/types.d.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { Moment } from 'moment'; -import { AlertExecutorOptions } from '../../../alerting/server'; +import { AlertExecutorOptions } from '../../../alerts/server'; import { AlertClusterStateState, AlertCommonPerClusterMessageTokenType } from './enums'; export interface AlertLicense { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts b/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts index ae66d603507c..c4553d87980d 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { i18n } from '@kbn/i18n'; -import { AlertInstance } from '../../../../alerting/server'; +import { AlertInstance } from '../../../../alerts/server'; import { AlertCommonCluster, AlertCommonPerClusterMessage, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts index 7a6c38865ebe..614658baf5c7 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts @@ -6,7 +6,7 @@ import moment from 'moment'; import { Logger } from '../../../../../../src/core/server'; import { AlertCommonPerClusterState } from '../../alerts/types'; -import { AlertsClient } from '../../../../alerting/server'; +import { AlertsClient } from '../../../../alerts/server'; export async function fetchStatus( alertsClient: AlertsClient, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts b/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts index 83a9e26e4c58..cfaaeb36535a 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts @@ -6,7 +6,7 @@ import { Logger, ICustomClusterClient, UiSettingsServiceStart } from 'kibana/server'; import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; -import { AlertServices } from '../../../../alerting/server'; +import { AlertServices } from '../../../../alerts/server'; import { AlertCommonCluster } from '../../alerts/types'; import { INDEX_PATTERN_ELASTICSEARCH } from '../../../common/constants'; import { fetchAvailableCcs } from './fetch_available_ccs'; diff --git a/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts b/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts index cfe9f02b9bd6..97ef2790b516 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts @@ -5,7 +5,7 @@ */ import { Moment } from 'moment-timezone'; import { i18n } from '@kbn/i18n'; -import { AlertInstance } from '../../../../alerting/server'; +import { AlertInstance } from '../../../../alerts/server'; import { AlertCommonPerClusterMessageLinkToken, AlertCommonPerClusterMessageTimeToken, diff --git a/x-pack/plugins/monitoring/server/plugin.ts b/x-pack/plugins/monitoring/server/plugin.ts index 14bef307b2f8..f4f38f70b1cc 100644 --- a/x-pack/plugins/monitoring/server/plugin.ts +++ b/x-pack/plugins/monitoring/server/plugin.ts @@ -47,7 +47,7 @@ import { MonitoringLicenseService } from './types'; import { PluginStartContract as AlertingPluginStartContract, PluginSetupContract as AlertingPluginSetupContract, -} from '../../alerting/server'; +} from '../../alerts/server'; import { getLicenseExpiration } from './alerts/license_expiration'; import { getClusterState } from './alerts/cluster_state'; import { InfraPluginSetup } from '../../infra/server'; @@ -61,12 +61,12 @@ interface PluginsSetup { usageCollection?: UsageCollectionSetup; licensing: LicensingPluginSetup; features: FeaturesPluginSetupContract; - alerting: AlertingPluginSetupContract; + alerts: AlertingPluginSetupContract; infra: InfraPluginSetup; } interface PluginsStart { - alerting: AlertingPluginStartContract; + alerts: AlertingPluginStartContract; } interface MonitoringCoreConfig { @@ -156,7 +156,7 @@ export class Plugin { await this.licenseService.refresh(); if (KIBANA_ALERTING_ENABLED) { - plugins.alerting.registerType( + plugins.alerts.registerType( getLicenseExpiration( async () => { const coreStart = (await core.getStartServices())[0]; @@ -167,7 +167,7 @@ export class Plugin { config.ui.ccs.enabled ) ); - plugins.alerting.registerType( + plugins.alerts.registerType( getClusterState( async () => { const coreStart = (await core.getStartServices())[0]; @@ -357,7 +357,7 @@ export class Plugin { payload: req.body, getKibanaStatsCollector: () => this.legacyShimDependencies.kibanaStatsCollector, getUiSettingsService: () => context.core.uiSettings.client, - getAlertsClient: () => plugins.alerting.getAlertsClientWithRequest(req), + getAlertsClient: () => plugins.alerts.getAlertsClientWithRequest(req), server: { config: legacyConfigWrapper, newPlatform: { diff --git a/x-pack/plugins/siem/common/detection_engine/schemas/common/schemas.ts b/x-pack/plugins/siem/common/detection_engine/schemas/common/schemas.ts index faae1dde8354..9eb2d9abccbd 100644 --- a/x-pack/plugins/siem/common/detection_engine/schemas/common/schemas.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/common/schemas.ts @@ -27,7 +27,7 @@ export const filters = t.array(t.unknown); // Filters are not easily type-able y /** * Params is an "object", since it is a type of AlertActionParams which is action templates. - * @see x-pack/plugins/alerting/common/alert.ts + * @see x-pack/plugins/alerts/common/alert.ts */ export const action_group = t.string; export const action_id = t.string; diff --git a/x-pack/plugins/siem/common/detection_engine/transform_actions.ts b/x-pack/plugins/siem/common/detection_engine/transform_actions.ts index 4ce382357583..7c15bc143e0f 100644 --- a/x-pack/plugins/siem/common/detection_engine/transform_actions.ts +++ b/x-pack/plugins/siem/common/detection_engine/transform_actions.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertAction } from '../../../alerting/common'; +import { AlertAction } from '../../../alerts/common'; import { RuleAlertAction } from './types'; export const transformRuleToAlertAction = ({ diff --git a/x-pack/plugins/siem/common/detection_engine/types.ts b/x-pack/plugins/siem/common/detection_engine/types.ts index 5a91cfd4809c..431d716a9f20 100644 --- a/x-pack/plugins/siem/common/detection_engine/types.ts +++ b/x-pack/plugins/siem/common/detection_engine/types.ts @@ -5,7 +5,7 @@ */ import * as t from 'io-ts'; -import { AlertAction } from '../../../alerting/common'; +import { AlertAction } from '../../../alerts/common'; export type RuleAlertAction = Omit & { action_type_id: string; diff --git a/x-pack/plugins/siem/kibana.json b/x-pack/plugins/siem/kibana.json index 6b43b41df8ee..df40ad4650f4 100644 --- a/x-pack/plugins/siem/kibana.json +++ b/x-pack/plugins/siem/kibana.json @@ -5,7 +5,7 @@ "configPath": ["xpack", "siem"], "requiredPlugins": [ "actions", - "alerting", + "alerts", "data", "dataEnhanced", "embeddable", diff --git a/x-pack/plugins/siem/public/alerts/components/rules/description_step/actions_description.tsx b/x-pack/plugins/siem/public/alerts/components/rules/description_step/actions_description.tsx index be96ab10bd2b..43416abe6e28 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/description_step/actions_description.tsx +++ b/x-pack/plugins/siem/public/alerts/components/rules/description_step/actions_description.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { startCase } from 'lodash/fp'; -import { AlertAction } from '../../../../../../alerting/common'; +import { AlertAction } from '../../../../../../alerts/common'; const ActionsDescription = ({ actions }: { actions: AlertAction[] }) => { if (!actions.length) return null; diff --git a/x-pack/plugins/siem/public/alerts/components/rules/rule_actions_field/index.tsx b/x-pack/plugins/siem/public/alerts/components/rules/rule_actions_field/index.tsx index d8064eb4ad39..5823612faac1 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/rule_actions_field/index.tsx +++ b/x-pack/plugins/siem/public/alerts/components/rules/rule_actions_field/index.tsx @@ -18,7 +18,7 @@ import { ActionType, loadActionTypes, } from '../../../../../../triggers_actions_ui/public'; -import { AlertAction } from '../../../../../../alerting/common'; +import { AlertAction } from '../../../../../../alerts/common'; import { useKibana } from '../../../../common/lib/kibana'; import { FORM_ERRORS_TITLE } from './translations'; diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/types.ts b/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/types.ts index 897568cdbf16..ab9b88fb81fa 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/types.ts +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/types.ts @@ -10,7 +10,7 @@ import { RuleTypeSchema } from '../../../../../common/detection_engine/types'; /** * Params is an "record", since it is a type of AlertActionParams which is action templates. - * @see x-pack/plugins/alerting/common/alert.ts + * @see x-pack/plugins/alerts/common/alert.ts */ export const action = t.exact( t.type({ diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/types.ts b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/types.ts index 92c9780a1172..5f81409010a2 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/types.ts +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/types.ts @@ -5,7 +5,7 @@ */ import { RuleAlertAction, RuleType } from '../../../../../common/detection_engine/types'; -import { AlertAction } from '../../../../../../alerting/common'; +import { AlertAction } from '../../../../../../alerts/common'; import { Filter } from '../../../../../../../../src/plugins/data/common'; import { FormData, FormHook } from '../../../../shared_imports'; import { FieldValueQueryBar } from '../../../components/rules/query_bar'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/README.md b/x-pack/plugins/siem/server/lib/detection_engine/README.md index 695165e1990a..4c90869a9fe8 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/README.md +++ b/x-pack/plugins/siem/server/lib/detection_engine/README.md @@ -152,7 +152,7 @@ logging.events: ``` See these two README.md's pages for more references on the alerting and actions API: -https://github.com/elastic/kibana/blob/master/x-pack/plugins/alerting/README.md +https://github.com/elastic/kibana/blob/master/x-pack/plugins/alerts/README.md https://github.com/elastic/kibana/tree/master/x-pack/plugins/actions ### Signals API diff --git a/x-pack/plugins/siem/server/lib/detection_engine/notifications/create_notifications.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/notifications/create_notifications.test.ts index e0414f842ceb..440efc8d0d5a 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/notifications/create_notifications.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/notifications/create_notifications.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { createNotifications } from './create_notifications'; describe('createNotifications', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/notifications/create_notifications.ts b/x-pack/plugins/siem/server/lib/detection_engine/notifications/create_notifications.ts index 35a737177ad4..a472d8a4df4a 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/notifications/create_notifications.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/notifications/create_notifications.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Alert } from '../../../../../alerting/common'; +import { Alert } from '../../../../../alerts/common'; import { APP_ID, NOTIFICATIONS_ID } from '../../../../common/constants'; import { CreateNotificationParams } from './types'; import { addTags } from './add_tags'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/notifications/delete_notifications.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/notifications/delete_notifications.test.ts index 089822f486ae..2f754c126771 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/notifications/delete_notifications.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/notifications/delete_notifications.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { deleteNotifications } from './delete_notifications'; import { readNotifications } from './read_notifications'; jest.mock('./read_notifications'); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/notifications/find_notifications.ts b/x-pack/plugins/siem/server/lib/detection_engine/notifications/find_notifications.ts index b47ea348bd4d..5d3a328dd6fb 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/notifications/find_notifications.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/notifications/find_notifications.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { FindResult } from '../../../../../alerting/server'; +import { FindResult } from '../../../../../alerts/server'; import { NOTIFICATIONS_ID } from '../../../../common/constants'; import { FindNotificationParams } from './types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/notifications/get_signals_count.ts b/x-pack/plugins/siem/server/lib/detection_engine/notifications/get_signals_count.ts index 69f37da1e225..038a8916c87d 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/notifications/get_signals_count.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/notifications/get_signals_count.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertServices } from '../../../../../alerting/server'; +import { AlertServices } from '../../../../../alerts/server'; import { buildSignalsSearchQuery } from './build_signals_query'; interface GetSignalsCount { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/notifications/read_notifications.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/notifications/read_notifications.test.ts index 961aac15c484..a46f65da5804 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/notifications/read_notifications.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/notifications/read_notifications.test.ts @@ -5,7 +5,7 @@ */ import { readNotifications } from './read_notifications'; -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { getNotificationResult, getFindNotificationsResultWithSingleHit, diff --git a/x-pack/plugins/siem/server/lib/detection_engine/notifications/read_notifications.ts b/x-pack/plugins/siem/server/lib/detection_engine/notifications/read_notifications.ts index c585c474556a..fe9101335b4f 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/notifications/read_notifications.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/notifications/read_notifications.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SanitizedAlert } from '../../../../../alerting/common'; +import { SanitizedAlert } from '../../../../../alerts/common'; import { ReadNotificationParams, isAlertType } from './types'; import { findNotifications } from './find_notifications'; import { INTERNAL_RULE_ALERT_ID_KEY } from '../../../../common/constants'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.test.ts index e8d778bddadc..47356679c807 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.test.ts @@ -8,7 +8,7 @@ import { loggingServiceMock } from 'src/core/server/mocks'; import { getResult } from '../routes/__mocks__/request_responses'; import { rulesNotificationAlertType } from './rules_notification_alert_type'; import { buildSignalsSearchQuery } from './build_signals_query'; -import { alertsMock, AlertServicesMock } from '../../../../../../plugins/alerting/server/mocks'; +import { alertsMock, AlertServicesMock } from '../../../../../alerts/server/mocks'; import { NotificationExecutorOptions } from './types'; jest.mock('./build_signals_query'); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/notifications/schedule_notification_actions.ts b/x-pack/plugins/siem/server/lib/detection_engine/notifications/schedule_notification_actions.ts index a0bd5e092c6e..a26ddfc90434 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/notifications/schedule_notification_actions.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/notifications/schedule_notification_actions.ts @@ -5,7 +5,7 @@ */ import { mapKeys, snakeCase } from 'lodash/fp'; -import { AlertInstance } from '../../../../../alerting/server'; +import { AlertInstance } from '../../../../../alerts/server'; import { RuleTypeParams } from '../types'; export type NotificationRuleTypeParams = RuleTypeParams & { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/notifications/types.ts b/x-pack/plugins/siem/server/lib/detection_engine/notifications/types.ts index d0bb1bfdfb1c..1345bf2ac633 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/notifications/types.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/notifications/types.ts @@ -10,8 +10,8 @@ import { AlertType, State, AlertExecutorOptions, -} from '../../../../../alerting/server'; -import { Alert } from '../../../../../alerting/common'; +} from '../../../../../alerts/server'; +import { Alert } from '../../../../../alerts/common'; import { NOTIFICATIONS_ID } from '../../../../common/constants'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/notifications/update_notifications.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/notifications/update_notifications.test.ts index b9dc42b96696..c7763c7ed7e7 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/notifications/update_notifications.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/notifications/update_notifications.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { updateNotifications } from './update_notifications'; import { readNotifications } from './read_notifications'; import { createNotifications } from './create_notifications'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/notifications/update_notifications.ts b/x-pack/plugins/siem/server/lib/detection_engine/notifications/update_notifications.ts index 5889b0e4dcfb..17024c7c0d75 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/notifications/update_notifications.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/notifications/update_notifications.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PartialAlert } from '../../../../../alerting/server'; +import { PartialAlert } from '../../../../../alerts/server'; import { readNotifications } from './read_notifications'; import { UpdateNotificationParams } from './types'; import { addTags } from './add_tags'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_context.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_context.ts index a24375c368d6..65f38507605a 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_context.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_context.ts @@ -10,7 +10,7 @@ import { elasticsearchServiceMock, savedObjectsClientMock, } from '../../../../../../../../src/core/server/mocks'; -import { alertsClientMock } from '../../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../../alerts/server/mocks'; import { licensingMock } from '../../../../../../licensing/server/mocks'; import { siemMock } from '../../../../mocks'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts index ec9e84d4fa6b..df158d23c0e2 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts @@ -23,8 +23,8 @@ import { ImportRuleAlertRest, RuleAlertParamsRest, RuleTypeParams } from '../../ import { BulkError, ImportSuccessError } from '../utils'; import { getSimpleRule, getOutputRuleAlertForRest } from '../__mocks__/utils'; import { createPromiseFromStreams } from '../../../../../../../../src/legacy/utils/streams'; -import { PartialAlert } from '../../../../../../alerting/server'; -import { SanitizedAlert } from '../../../../../../alerting/server/types'; +import { PartialAlert } from '../../../../../../alerts/server'; +import { SanitizedAlert } from '../../../../../../alerts/server/types'; import { createRulesStreamFromNdJson } from '../../rules/create_rules_stream_from_ndjson'; import { RuleAlertType } from '../../rules/types'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../feature_flags'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts index 861e6463533f..5329ff04435c 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts @@ -8,7 +8,7 @@ import { pickBy, countBy } from 'lodash/fp'; import { SavedObject, SavedObjectsFindResponse } from 'kibana/server'; import uuid from 'uuid'; -import { PartialAlert, FindResult } from '../../../../../../alerting/server'; +import { PartialAlert, FindResult } from '../../../../../../alerts/server'; import { INTERNAL_IDENTIFIER } from '../../../../../common/constants'; import { RuleAlertType, diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.test.ts index 13a5bbd2afc0..07b891e2bf02 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.test.ts @@ -13,7 +13,7 @@ import { transformValidateBulkError, } from './validate'; import { getResult } from '../__mocks__/request_responses'; -import { FindResult } from '../../../../../../alerting/server'; +import { FindResult } from '../../../../../../alerts/server'; import { BulkError } from '../utils'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../feature_flags'; import { RulesSchema } from '../../../../../common/detection_engine/schemas/response/rules_schema'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.ts index 1220b12d1d1b..5fc239ed4826 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.ts @@ -16,7 +16,7 @@ import { } from '../../../../../common/detection_engine/schemas/response/rules_schema'; import { formatErrors } from '../../../../../common/format_errors'; import { exactCheck } from '../../../../../common/exact_check'; -import { PartialAlert, FindResult } from '../../../../../../alerting/server'; +import { PartialAlert, FindResult } from '../../../../../../alerts/server'; import { isAlertType, IRuleSavedAttributesSavedObjectAttributes, diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts index 226dea7c2034..66356a1d6535 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertAction } from '../../../../../../alerting/common'; +import { AlertAction } from '../../../../../../alerts/common'; import { RuleAlertAction } from '../../../../../common/detection_engine/types'; import { ThreatParams, PrepackagedRules } from '../../types'; import { addPrepackagedRulesSchema } from './add_prepackaged_rules_schema'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts index 1e2941015b73..013db2020a14 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertAction } from '../../../../../../alerting/common'; +import { AlertAction } from '../../../../../../alerts/common'; import { createRulesSchema } from './create_rules_schema'; import { PatchRuleAlertParamsRest } from '../../rules/types'; import { RuleAlertAction } from '../../../../../common/detection_engine/types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts index d28530ffb789..cb03c4781cb6 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertAction } from '../../../../../../alerting/common'; +import { AlertAction } from '../../../../../../alerts/common'; import { importRulesSchema, importRulesQuerySchema, diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/patch_rules_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/patch_rules_schema.test.ts index 755c0b2ccaa3..218cae68db03 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/patch_rules_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/patch_rules_schema.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertAction } from '../../../../../../alerting/common'; +import { AlertAction } from '../../../../../../alerts/common'; import { patchRulesSchema } from './patch_rules_schema'; import { PatchRuleAlertParamsRest } from '../../rules/types'; import { RuleAlertAction } from '../../../../../common/detection_engine/types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts index b89df0fc0f3a..8bda16de9777 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertAction } from '../../../../../../alerting/common'; +import { AlertAction } from '../../../../../../alerts/common'; import { updateRulesSchema } from './update_rules_schema'; import { PatchRuleAlertParamsRest } from '../../rules/types'; import { RuleAlertAction } from '../../../../../common/detection_engine/types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/create_rule_actions_saved_object.ts b/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/create_rule_actions_saved_object.ts index 26c3b29ff2c5..2ff6d6ac646a 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/create_rule_actions_saved_object.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/create_rule_actions_saved_object.ts @@ -5,7 +5,7 @@ */ import { RuleAlertAction } from '../../../../common/detection_engine/types'; -import { AlertServices } from '../../../../../alerting/server'; +import { AlertServices } from '../../../../../alerts/server'; import { ruleActionsSavedObjectType } from './saved_object_mappings'; import { IRuleActionsAttributesSavedObjectAttributes } from './types'; import { getThrottleOptions, getRuleActionsFromSavedObject } from './utils'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/delete_rule_actions_saved_object.ts b/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/delete_rule_actions_saved_object.ts index 251f9155f933..3d5734b13ea4 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/delete_rule_actions_saved_object.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/delete_rule_actions_saved_object.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertServices } from '../../../../../alerting/server'; +import { AlertServices } from '../../../../../alerts/server'; import { ruleActionsSavedObjectType } from './saved_object_mappings'; import { getRuleActionsSavedObject } from './get_rule_actions_saved_object'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/get_rule_actions_saved_object.ts b/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/get_rule_actions_saved_object.ts index 83cd59f0a1cd..c36f6ca831c5 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/get_rule_actions_saved_object.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/get_rule_actions_saved_object.ts @@ -5,7 +5,7 @@ */ import { RuleAlertAction } from '../../../../common/detection_engine/types'; -import { AlertServices } from '../../../../../alerting/server'; +import { AlertServices } from '../../../../../alerts/server'; import { ruleActionsSavedObjectType } from './saved_object_mappings'; import { IRuleActionsAttributesSavedObjectAttributes } from './types'; import { getRuleActionsFromSavedObject } from './utils'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/update_or_create_rule_actions_saved_object.ts b/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/update_or_create_rule_actions_saved_object.ts index 3364827d397d..c650de2a5e2b 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/update_or_create_rule_actions_saved_object.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/update_or_create_rule_actions_saved_object.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertServices } from '../../../../../alerting/server'; +import { AlertServices } from '../../../../../alerts/server'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; import { getRuleActionsSavedObject } from './get_rule_actions_saved_object'; import { createRuleActionsSavedObject } from './create_rule_actions_saved_object'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/update_rule_actions_saved_object.ts b/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/update_rule_actions_saved_object.ts index c8a3b1bbc38a..fd3d107103f1 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/update_rule_actions_saved_object.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/update_rule_actions_saved_object.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertServices } from '../../../../../alerting/server'; +import { AlertServices } from '../../../../../alerts/server'; import { ruleActionsSavedObjectType } from './saved_object_mappings'; import { RulesActionsSavedObject } from './get_rule_actions_saved_object'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/create_rules.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/create_rules.test.ts index f4f0a8042d0a..f086166d0685 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/create_rules.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/create_rules.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { getMlResult } from '../routes/__mocks__/request_responses'; import { createRules } from './create_rules'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/create_rules.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/create_rules.ts index a007fe35b407..67e066c6670f 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/create_rules.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/create_rules.ts @@ -5,7 +5,7 @@ */ import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; -import { Alert } from '../../../../../alerting/common'; +import { Alert } from '../../../../../alerts/common'; import { APP_ID, SIGNALS_ID } from '../../../../common/constants'; import { CreateRuleParams } from './types'; import { addTags } from './add_tags'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/delete_rules.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/delete_rules.test.ts index 6bc5fc2a88b6..f96a9e38d6a6 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/delete_rules.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/delete_rules.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { deleteRules } from './delete_rules'; import { readRules } from './read_rules'; jest.mock('./read_rules'); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/find_rules.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/find_rules.ts index ac600b0b5b21..c634f0738782 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/find_rules.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/find_rules.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { FindResult } from '../../../../../alerting/server'; +import { FindResult } from '../../../../../alerts/server'; import { SIGNALS_ID } from '../../../../common/constants'; import { FindRuleParams } from './types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.test.ts index d79b428a2f76..203a23402f09 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { getResult, getFindResultWithSingleHit, diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts index 512164fc3d2e..a3119131a003 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts @@ -5,7 +5,7 @@ */ import { INTERNAL_IMMUTABLE_KEY } from '../../../../common/constants'; -import { AlertsClient } from '../../../../../alerting/server'; +import { AlertsClient } from '../../../../../alerts/server'; import { RuleAlertType, isAlertTypes } from './types'; import { findRules } from './find_rules'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_all.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_all.test.ts index 6df250f1cf51..ee21c3354002 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_all.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_all.test.ts @@ -9,7 +9,7 @@ import { getFindResultWithSingleHit, FindHit, } from '../routes/__mocks__/request_responses'; -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { getExportAll } from './get_export_all'; import { unSetFeatureFlagsForTestsOnly, setFeatureFlagsForTestsOnly } from '../feature_flags'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_all.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_all.ts index 06e70f0bad18..433da2be6b34 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_all.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_all.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertsClient } from '../../../../../alerting/server'; +import { AlertsClient } from '../../../../../alerts/server'; import { getNonPackagedRules } from './get_existing_prepackaged_rules'; import { getExportDetailsNdjson } from './get_export_details_ndjson'; import { transformAlertsToRules } from '../routes/rules/utils'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts index 092a9a8faf39..b00b7353a370 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts @@ -11,7 +11,7 @@ import { FindHit, } from '../routes/__mocks__/request_responses'; import * as readRules from './read_rules'; -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../feature_flags'; describe('get_export_by_object_ids', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.ts index beaaaa8701c8..38cf8008f65c 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertsClient } from '../../../../../alerting/server'; +import { AlertsClient } from '../../../../../alerts/server'; import { getExportDetailsNdjson } from './get_export_details_ndjson'; import { isAlertType } from '../rules/types'; import { readRules } from './read_rules'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts index 0266d702b3dc..7b2cef9060f8 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Alert } from '../../../../../alerting/common'; -import { AlertsClient } from '../../../../../alerting/server'; +import { Alert } from '../../../../../alerts/common'; +import { AlertsClient } from '../../../../../alerts/server'; import { createRules } from './create_rules'; import { PrepackagedRules } from '../types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.test.ts index a42500223012..3c1267c93934 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.test.ts @@ -5,7 +5,7 @@ */ import { savedObjectsClientMock } from '../../../../../../../src/core/server/mocks'; -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { getResult, getMlResult } from '../routes/__mocks__/request_responses'; import { patchRules } from './patch_rules'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.ts index 6dfb72532afb..1e728ae7b8d0 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.ts @@ -5,7 +5,7 @@ */ import { defaults } from 'lodash/fp'; -import { PartialAlert } from '../../../../../alerting/server'; +import { PartialAlert } from '../../../../../alerts/server'; import { PatchRuleParams } from './types'; import { addTags } from './add_tags'; import { calculateVersion, calculateName, calculateInterval } from './utils'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/read_rules.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/read_rules.test.ts index 600848948be0..ef8e70c78422 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/read_rules.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/read_rules.test.ts @@ -5,7 +5,7 @@ */ import { readRules } from './read_rules'; -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { getResult, getFindResultWithSingleHit } from '../routes/__mocks__/request_responses'; export class TestError extends Error { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/read_rules.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/read_rules.ts index 9e0d5b3d05b3..a8b76aeb8c45 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/read_rules.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/read_rules.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SanitizedAlert } from '../../../../../alerting/common'; +import { SanitizedAlert } from '../../../../../alerts/common'; import { INTERNAL_RULE_ID_KEY } from '../../../../common/constants'; import { findRules } from './find_rules'; import { ReadRuleParams, isAlertType } from './types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/types.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/types.ts index d65261549232..70d53090f81c 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/types.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/types.ts @@ -13,8 +13,8 @@ import { SavedObjectsFindResponse, SavedObjectsClientContract, } from 'kibana/server'; -import { AlertsClient, PartialAlert } from '../../../../../alerting/server'; -import { Alert, SanitizedAlert } from '../../../../../alerting/common'; +import { AlertsClient, PartialAlert } from '../../../../../alerts/server'; +import { Alert, SanitizedAlert } from '../../../../../alerts/common'; import { SIGNALS_ID } from '../../../../common/constants'; import { RuleAlertParams, RuleTypeParams, RuleAlertParamsRest } from '../types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.test.ts index 2d77e9a707f7..ede5c51d1e5e 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.test.ts @@ -5,7 +5,7 @@ */ import { savedObjectsClientMock } from '../../../../../../../src/core/server/mocks'; -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { mockPrepackagedRule, getFindResultWithSingleHit, diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.ts index 5063ddd5e52e..c793d7eb9b6d 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.ts @@ -5,7 +5,7 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; -import { AlertsClient } from '../../../../../alerting/server'; +import { AlertsClient } from '../../../../../alerts/server'; import { patchRules } from './patch_rules'; import { PrepackagedRules } from '../types'; import { readRules } from './read_rules'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/update_rules.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/update_rules.test.ts index 13c601b40e4f..222411deb37a 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/update_rules.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/update_rules.test.ts @@ -5,7 +5,7 @@ */ import { savedObjectsClientMock } from '../../../../../../../src/core/server/mocks'; -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { getResult, getMlResult } from '../routes/__mocks__/request_responses'; import { updateRules } from './update_rules'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/update_rules.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/update_rules.ts index 711f01945809..54031b6e35bf 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/update_rules.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/update_rules.ts @@ -5,7 +5,7 @@ */ import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; -import { PartialAlert } from '../../../../../alerting/server'; +import { PartialAlert } from '../../../../../alerts/server'; import { readRules } from './read_rules'; import { UpdateRuleParams } from './types'; import { addTags } from './add_tags'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/update_rules_notifications.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/update_rules_notifications.ts index c5cf85d7ba01..8fceb8ef720b 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/update_rules_notifications.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/update_rules_notifications.ts @@ -5,7 +5,7 @@ */ import { RuleAlertAction } from '../../../../common/detection_engine/types'; -import { AlertsClient, AlertServices } from '../../../../../alerting/server'; +import { AlertsClient, AlertServices } from '../../../../../alerts/server'; import { updateOrCreateRuleActionsSavedObject } from '../rule_actions/update_or_create_rule_actions_saved_object'; import { updateNotifications } from '../notifications/update_notifications'; import { RuleActions } from '../rule_actions/types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/scripts/get_alert_instances.sh b/x-pack/plugins/siem/server/lib/detection_engine/scripts/get_alert_instances.sh index b5f272d0a8a0..a052123f0cc3 100755 --- a/x-pack/plugins/siem/server/lib/detection_engine/scripts/get_alert_instances.sh +++ b/x-pack/plugins/siem/server/lib/detection_engine/scripts/get_alert_instances.sh @@ -10,8 +10,8 @@ set -e ./check_env_variables.sh # Example: ./get_alert_instances.sh -# https://github.com/elastic/kibana/blob/master/x-pack/plugins/alerting/README.md#get-apialert_find-find-alerts +# https://github.com/elastic/kibana/blob/master/x-pack/plugins/alerts/README.md#get-apialert_find-find-alerts curl -s -k \ -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ - -X GET ${KIBANA_URL}${SPACE_URL}/api/alert/_find \ + -X GET ${KIBANA_URL}${SPACE_URL}/api/alerts/_find \ | jq . diff --git a/x-pack/plugins/siem/server/lib/detection_engine/scripts/get_alert_types.sh b/x-pack/plugins/siem/server/lib/detection_engine/scripts/get_alert_types.sh index 28c250e9368a..edade604d74c 100755 --- a/x-pack/plugins/siem/server/lib/detection_engine/scripts/get_alert_types.sh +++ b/x-pack/plugins/siem/server/lib/detection_engine/scripts/get_alert_types.sh @@ -10,8 +10,8 @@ set -e ./check_env_variables.sh # Example: ./get_alert_types.sh -# https://github.com/elastic/kibana/blob/master/x-pack/plugins/alerting/README.md#get-apialerttypes-list-alert-types +# https://github.com/elastic/kibana/blob/master/x-pack/plugins/alerts/README.md#get-apialerttypes-list-alert-types curl -s -k \ -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ - -X GET ${KIBANA_URL}${SPACE_URL}/api/alert/types \ + -X GET ${KIBANA_URL}${SPACE_URL}/api/alerts/list_alert_types \ | jq . diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/bulk_create_ml_signals.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/bulk_create_ml_signals.ts index 5862e6c48143..80839545951d 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/bulk_create_ml_signals.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/bulk_create_ml_signals.ts @@ -9,7 +9,7 @@ import set from 'set-value'; import { SearchResponse } from 'elasticsearch'; import { Logger } from '../../../../../../../src/core/server'; -import { AlertServices } from '../../../../../alerting/server'; +import { AlertServices } from '../../../../../alerts/server'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; import { RuleTypeParams, RefreshTypes } from '../types'; import { singleBulkCreate, SingleBulkCreateResponse } from './single_bulk_create'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/get_filter.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/get_filter.test.ts index 35ec1950ceda..0930fbdb534f 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/get_filter.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/get_filter.test.ts @@ -6,7 +6,7 @@ import { getQueryFilter, getFilter } from './get_filter'; import { PartialFilter } from '../types'; -import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks'; +import { alertsMock, AlertServicesMock } from '../../../../../alerts/server/mocks'; describe('get_filter', () => { let servicesMock: AlertServicesMock; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/get_filter.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/get_filter.ts index 3c226130faf2..1630192b3c03 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/get_filter.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/get_filter.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertServices } from '../../../../../alerting/server'; +import { AlertServices } from '../../../../../alerts/server'; import { assertUnreachable } from '../../../utils/build_query'; import { Filter, diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.test.ts index 6fc99ada16ec..a4ddec13ac51 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks'; +import { alertsMock, AlertServicesMock } from '../../../../../alerts/server/mocks'; import { DEFAULT_INDEX_KEY, DEFAULT_INDEX_PATTERN } from '../../../../common/constants'; import { getInputIndex } from './get_input_output_index'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.ts index 85e3eeac476e..c001312fbf2f 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.ts @@ -5,7 +5,7 @@ */ import { DEFAULT_INDEX_KEY, DEFAULT_INDEX_PATTERN } from '../../../../common/constants'; -import { AlertServices } from '../../../../../alerting/server'; +import { AlertServices } from '../../../../../alerts/server'; export const getInputIndex = async ( services: AlertServices, diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts index a306a016b420..163ed76d0c6c 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts @@ -13,7 +13,7 @@ import { } from './__mocks__/es_results'; import { searchAfterAndBulkCreate } from './search_after_bulk_create'; import { DEFAULT_SIGNALS_INDEX } from '../../../../common/constants'; -import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks'; +import { alertsMock, AlertServicesMock } from '../../../../../alerts/server/mocks'; import uuid from 'uuid'; import { getListItemResponseMock } from '../../../../../lists/common/schemas/response/list_item_schema.mock'; import { listMock } from '../../../../../lists/server/mocks'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts index 59c685ec3e81..e44b82224d1c 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { AlertServices } from '../../../../../alerts/server'; import { ListClient } from '../../../../../lists/server'; -import { AlertServices } from '../../../../../alerting/server'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; import { RuleTypeParams, RefreshTypes, RuleAlertParams } from '../types'; import { Logger } from '../../../../../../../src/core/server'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts index 8e7034b00632..f94eb7006829 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -8,7 +8,7 @@ import moment from 'moment'; import { loggingServiceMock } from 'src/core/server/mocks'; import { getResult, getMlResult } from '../routes/__mocks__/request_responses'; import { signalRulesAlertType } from './signal_rule_alert_type'; -import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks'; +import { alertsMock, AlertServicesMock } from '../../../../../alerts/server/mocks'; import { ruleStatusServiceFactory } from './rule_status_service'; import { getGapBetweenRuns } from './utils'; import { RuleExecutorOptions } from './types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts index 265f98653313..8b9fb0574efe 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts @@ -18,7 +18,7 @@ import { } from './__mocks__/es_results'; import { DEFAULT_SIGNALS_INDEX } from '../../../../common/constants'; import { singleBulkCreate, filterDuplicateRules } from './single_bulk_create'; -import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks'; +import { alertsMock, AlertServicesMock } from '../../../../../alerts/server/mocks'; describe('singleBulkCreate', () => { const mockService: AlertServicesMock = alertsMock.createAlertServices(); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts index 39aecde470e0..6f4d01ea73a7 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts @@ -6,7 +6,7 @@ import { countBy, isEmpty } from 'lodash'; import { performance } from 'perf_hooks'; -import { AlertServices } from '../../../../../alerting/server'; +import { AlertServices } from '../../../../../alerts/server'; import { SignalSearchResponse, BulkResponse } from './types'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; import { RuleTypeParams, RefreshTypes } from '../types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/single_search_after.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/single_search_after.test.ts index 2aa42234460d..50b0cb27990f 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/single_search_after.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/single_search_after.test.ts @@ -10,7 +10,7 @@ import { sampleDocSearchResultsWithSortId, } from './__mocks__/es_results'; import { singleSearchAfter } from './single_search_after'; -import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks'; +import { alertsMock, AlertServicesMock } from '../../../../../alerts/server/mocks'; describe('singleSearchAfter', () => { const mockService: AlertServicesMock = alertsMock.createAlertServices(); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/single_search_after.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/single_search_after.ts index a7086a4fb229..409f374d7df1 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/single_search_after.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/single_search_after.ts @@ -5,7 +5,7 @@ */ import { performance } from 'perf_hooks'; -import { AlertServices } from '../../../../../alerting/server'; +import { AlertServices } from '../../../../../alerts/server'; import { Logger } from '../../../../../../../src/core/server'; import { SignalSearchResponse } from './types'; import { buildEventsSearchQuery } from './build_events_query'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/types.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/types.ts index 32b13c5251a6..90497b6e34cb 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/types.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertType, State, AlertExecutorOptions } from '../../../../../alerting/server'; +import { AlertType, State, AlertExecutorOptions } from '../../../../../alerts/server'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; import { RuleAlertParams, OutputRuleAlertRest } from '../types'; import { SearchResponse } from '../../types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/utils.ts index 989c919244d6..f0ca08b73fac 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/utils.ts @@ -7,7 +7,7 @@ import { createHash } from 'crypto'; import moment from 'moment'; import dateMath from '@elastic/datemath'; -import { parseDuration } from '../../../../../alerting/server'; +import { parseDuration } from '../../../../../alerts/server'; import { BulkResponse, BulkResponseErrorAggregation } from './types'; export const generateId = ( diff --git a/x-pack/plugins/siem/server/lib/detection_engine/tags/read_tags.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/tags/read_tags.test.ts index d29d885f9797..d07fa382e114 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/tags/read_tags.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/tags/read_tags.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { getResult, getFindResultWithMultiHits } from '../routes/__mocks__/request_responses'; import { INTERNAL_RULE_ID_KEY, INTERNAL_IDENTIFIER } from '../../../../common/constants'; import { readRawTags, readTags, convertTagsToSet, convertToTags, isTags } from './read_tags'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/tags/read_tags.ts b/x-pack/plugins/siem/server/lib/detection_engine/tags/read_tags.ts index 003c852cb80a..2bb2b5ec47e2 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/tags/read_tags.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/tags/read_tags.ts @@ -6,7 +6,7 @@ import { has } from 'lodash/fp'; import { INTERNAL_IDENTIFIER } from '../../../../common/constants'; -import { AlertsClient } from '../../../../../alerting/server'; +import { AlertsClient } from '../../../../../alerts/server'; import { findRules } from '../rules/find_rules'; export interface TagType { diff --git a/x-pack/plugins/siem/server/plugin.ts b/x-pack/plugins/siem/server/plugin.ts index 5a47efd45888..a8858c91d677 100644 --- a/x-pack/plugins/siem/server/plugin.ts +++ b/x-pack/plugins/siem/server/plugin.ts @@ -15,7 +15,7 @@ import { PluginInitializerContext, Logger, } from '../../../../src/core/server'; -import { PluginSetupContract as AlertingSetup } from '../../alerting/server'; +import { PluginSetupContract as AlertingSetup } from '../../alerts/server'; import { SecurityPluginSetup as SecuritySetup } from '../../security/server'; import { PluginSetupContract as FeaturesSetup } from '../../features/server'; import { MlPluginSetup as MlSetup } from '../../ml/server'; @@ -46,7 +46,7 @@ import { EndpointAppContext } from './endpoint/types'; import { IngestIndexPatternRetriever } from './endpoint/alerts/index_pattern'; export interface SetupPlugins { - alerting: AlertingSetup; + alerts: AlertingSetup; encryptedSavedObjects?: EncryptedSavedObjectsSetup; features: FeaturesSetup; licensing: LicensingPluginSetup; @@ -191,7 +191,7 @@ export class Plugin implements IPlugin import('./home')); @@ -34,7 +34,7 @@ export interface AppDeps { dataPlugin: DataPublicPluginStart; charts: ChartsPluginStart; chrome: ChromeStart; - alerting?: AlertingStart; + alerts?: AlertingStart; navigateToApp: CoreStart['application']['navigateToApp']; docLinks: DocLinksStart; toastNotifications: ToastsSetup; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/visualization.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/visualization.tsx index 84cbc73ca92c..244d431930f2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/visualization.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/visualization.tsx @@ -33,7 +33,7 @@ import { getThresholdAlertVisualizationData } from '../../../../common/lib/index import { AggregationType, Comparator } from '../../../../common/types'; import { AlertsContextValue } from '../../../context/alerts_context'; import { IndexThresholdAlertParams } from './types'; -import { parseDuration } from '../../../../../../alerting/common/parse_duration'; +import { parseDuration } from '../../../../../../alerts/common/parse_duration'; const customTheme = () => { return { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts index 265cfddab4c0..47b55f44bfb9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export { BASE_ALERT_API_PATH } from '../../../../alerting/common'; +export { BASE_ALERT_API_PATH } from '../../../../alerts/common'; export { BASE_ACTION_API_PATH } from '../../../../actions/common'; export const BASE_PATH = '/management/insightsAndAlerting/triggersActions'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts index c35dd0638544..714dc5210e39 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts @@ -23,7 +23,7 @@ function prefixKeys(actionVariables: ActionVariable[], prefix: string): ActionVa } // this list should be the same as in: -// x-pack/plugins/alerting/server/task_runner/transform_action_params.ts +// x-pack/plugins/alerts/server/task_runner/transform_action_params.ts function getAlwaysProvidedActionVariables(): ActionVariable[] { const result: ActionVariable[] = []; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts index f384a78e2e08..94d9166b4090 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts @@ -53,7 +53,7 @@ describe('loadAlertTypes', () => { expect(result).toEqual(resolvedValue); expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/alert/types", + "/api/alerts/list_alert_types", ] `); }); @@ -80,7 +80,7 @@ describe('loadAlert', () => { http.get.mockResolvedValueOnce(resolvedValue); expect(await loadAlert({ http, alertId })).toEqual(resolvedValue); - expect(http.get).toHaveBeenCalledWith(`/api/alert/${alertId}`); + expect(http.get).toHaveBeenCalledWith(`/api/alerts/alert/${alertId}`); }); }); @@ -99,7 +99,7 @@ describe('loadAlertState', () => { http.get.mockResolvedValueOnce(resolvedValue); expect(await loadAlertState({ http, alertId })).toEqual(resolvedValue); - expect(http.get).toHaveBeenCalledWith(`/api/alert/${alertId}/state`); + expect(http.get).toHaveBeenCalledWith(`/api/alerts/alert/${alertId}/state`); }); test('should parse AlertInstances', async () => { @@ -136,7 +136,7 @@ describe('loadAlertState', () => { }, }, }); - expect(http.get).toHaveBeenCalledWith(`/api/alert/${alertId}/state`); + expect(http.get).toHaveBeenCalledWith(`/api/alerts/alert/${alertId}/state`); }); test('should handle empty response from api', async () => { @@ -144,7 +144,7 @@ describe('loadAlertState', () => { http.get.mockResolvedValueOnce(''); expect(await loadAlertState({ http, alertId })).toEqual({}); - expect(http.get).toHaveBeenCalledWith(`/api/alert/${alertId}/state`); + expect(http.get).toHaveBeenCalledWith(`/api/alerts/alert/${alertId}/state`); }); }); @@ -162,7 +162,7 @@ describe('loadAlerts', () => { expect(result).toEqual(resolvedValue); expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/alert/_find", + "/api/alerts/_find", Object { "query": Object { "default_search_operator": "AND", @@ -192,7 +192,7 @@ describe('loadAlerts', () => { expect(result).toEqual(resolvedValue); expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/alert/_find", + "/api/alerts/_find", Object { "query": Object { "default_search_operator": "AND", @@ -226,7 +226,7 @@ describe('loadAlerts', () => { expect(result).toEqual(resolvedValue); expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/alert/_find", + "/api/alerts/_find", Object { "query": Object { "default_search_operator": "AND", @@ -260,7 +260,7 @@ describe('loadAlerts', () => { expect(result).toEqual(resolvedValue); expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/alert/_find", + "/api/alerts/_find", Object { "query": Object { "default_search_operator": "AND", @@ -295,7 +295,7 @@ describe('loadAlerts', () => { expect(result).toEqual(resolvedValue); expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/alert/_find", + "/api/alerts/_find", Object { "query": Object { "default_search_operator": "AND", @@ -330,7 +330,7 @@ describe('loadAlerts', () => { expect(result).toEqual(resolvedValue); expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/alert/_find", + "/api/alerts/_find", Object { "query": Object { "default_search_operator": "AND", @@ -356,13 +356,13 @@ describe('deleteAlerts', () => { expect(http.delete.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alert/1", + "/api/alerts/alert/1", ], Array [ - "/api/alert/2", + "/api/alerts/alert/2", ], Array [ - "/api/alert/3", + "/api/alerts/alert/3", ], ] `); @@ -373,7 +373,7 @@ describe('createAlert', () => { test('should call create alert API', async () => { const alertToCreate = { name: 'test', - consumer: 'alerting', + consumer: 'alerts', tags: ['foo'], enabled: true, alertTypeId: 'test', @@ -402,9 +402,9 @@ describe('createAlert', () => { expect(result).toEqual(resolvedValue); expect(http.post.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/alert", + "/api/alerts/alert", Object { - "body": "{\\"name\\":\\"test\\",\\"consumer\\":\\"alerting\\",\\"tags\\":[\\"foo\\"],\\"enabled\\":true,\\"alertTypeId\\":\\"test\\",\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"actions\\":[],\\"params\\":{},\\"throttle\\":null,\\"createdAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"updatedAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"apiKey\\":null,\\"apiKeyOwner\\":null}", + "body": "{\\"name\\":\\"test\\",\\"consumer\\":\\"alerts\\",\\"tags\\":[\\"foo\\"],\\"enabled\\":true,\\"alertTypeId\\":\\"test\\",\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"actions\\":[],\\"params\\":{},\\"throttle\\":null,\\"createdAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"updatedAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"apiKey\\":null,\\"apiKeyOwner\\":null}", }, ] `); @@ -415,7 +415,7 @@ describe('updateAlert', () => { test('should call alert update API', async () => { const alertToUpdate = { throttle: '1m', - consumer: 'alerting', + consumer: 'alerts', name: 'test', tags: ['foo'], schedule: { @@ -444,7 +444,7 @@ describe('updateAlert', () => { expect(result).toEqual(resolvedValue); expect(http.put.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/alert/123", + "/api/alerts/alert/123", Object { "body": "{\\"throttle\\":\\"1m\\",\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"params\\":{},\\"actions\\":[]}", }, @@ -460,7 +460,7 @@ describe('enableAlert', () => { expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alert/1/_enable", + "/api/alerts/alert/1/_enable", ], ] `); @@ -474,7 +474,7 @@ describe('disableAlert', () => { expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alert/1/_disable", + "/api/alerts/alert/1/_disable", ], ] `); @@ -488,7 +488,7 @@ describe('muteAlertInstance', () => { expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alert/1/alert_instance/123/_mute", + "/api/alerts/alert/1/alert_instance/123/_mute", ], ] `); @@ -502,7 +502,7 @@ describe('unmuteAlertInstance', () => { expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alert/1/alert_instance/123/_unmute", + "/api/alerts/alert/1/alert_instance/123/_unmute", ], ] `); @@ -516,7 +516,7 @@ describe('muteAlert', () => { expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alert/1/_mute_all", + "/api/alerts/alert/1/_mute_all", ], ] `); @@ -530,7 +530,7 @@ describe('unmuteAlert', () => { expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alert/1/_unmute_all", + "/api/alerts/alert/1/_unmute_all", ], ] `); @@ -545,13 +545,13 @@ describe('enableAlerts', () => { expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alert/1/_enable", + "/api/alerts/alert/1/_enable", ], Array [ - "/api/alert/2/_enable", + "/api/alerts/alert/2/_enable", ], Array [ - "/api/alert/3/_enable", + "/api/alerts/alert/3/_enable", ], ] `); @@ -566,13 +566,13 @@ describe('disableAlerts', () => { expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alert/1/_disable", + "/api/alerts/alert/1/_disable", ], Array [ - "/api/alert/2/_disable", + "/api/alerts/alert/2/_disable", ], Array [ - "/api/alert/3/_disable", + "/api/alerts/alert/3/_disable", ], ] `); @@ -587,13 +587,13 @@ describe('muteAlerts', () => { expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alert/1/_mute_all", + "/api/alerts/alert/1/_mute_all", ], Array [ - "/api/alert/2/_mute_all", + "/api/alerts/alert/2/_mute_all", ], Array [ - "/api/alert/3/_mute_all", + "/api/alerts/alert/3/_mute_all", ], ] `); @@ -608,13 +608,13 @@ describe('unmuteAlerts', () => { expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alert/1/_unmute_all", + "/api/alerts/alert/1/_unmute_all", ], Array [ - "/api/alert/2/_unmute_all", + "/api/alerts/alert/2/_unmute_all", ], Array [ - "/api/alert/3/_unmute_all", + "/api/alerts/alert/3/_unmute_all", ], ] `); @@ -628,7 +628,7 @@ describe('health', () => { expect(http.get.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alert/_health", + "/api/alerts/_health", ], ] `); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts index 2176f978822c..35fdc3974a29 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts @@ -9,12 +9,12 @@ import * as t from 'io-ts'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { pick } from 'lodash'; -import { alertStateSchema, AlertingFrameworkHealth } from '../../../../alerting/common'; +import { alertStateSchema, AlertingFrameworkHealth } from '../../../../alerts/common'; import { BASE_ALERT_API_PATH } from '../constants'; import { Alert, AlertType, AlertWithoutId, AlertTaskState } from '../../types'; export async function loadAlertTypes({ http }: { http: HttpSetup }): Promise { - return await http.get(`${BASE_ALERT_API_PATH}/types`); + return await http.get(`${BASE_ALERT_API_PATH}/list_alert_types`); } export async function loadAlert({ @@ -24,7 +24,7 @@ export async function loadAlert({ http: HttpSetup; alertId: string; }): Promise { - return await http.get(`${BASE_ALERT_API_PATH}/${alertId}`); + return await http.get(`${BASE_ALERT_API_PATH}/alert/${alertId}`); } type EmptyHttpResponse = ''; @@ -36,7 +36,7 @@ export async function loadAlertState({ alertId: string; }): Promise { return await http - .get(`${BASE_ALERT_API_PATH}/${alertId}/state`) + .get(`${BASE_ALERT_API_PATH}/alert/${alertId}/state`) .then((state: AlertTaskState | EmptyHttpResponse) => (state ? state : {})) .then((state: AlertTaskState) => { return pipe( @@ -104,7 +104,7 @@ export async function deleteAlerts({ }): Promise<{ successes: string[]; errors: string[] }> { const successes: string[] = []; const errors: string[] = []; - await Promise.all(ids.map((id) => http.delete(`${BASE_ALERT_API_PATH}/${id}`))).then( + await Promise.all(ids.map((id) => http.delete(`${BASE_ALERT_API_PATH}/alert/${id}`))).then( function (fulfilled) { successes.push(...fulfilled); }, @@ -122,7 +122,7 @@ export async function createAlert({ http: HttpSetup; alert: Omit; }): Promise { - return await http.post(`${BASE_ALERT_API_PATH}`, { + return await http.post(`${BASE_ALERT_API_PATH}/alert`, { body: JSON.stringify(alert), }); } @@ -136,7 +136,7 @@ export async function updateAlert({ alert: Pick; id: string; }): Promise { - return await http.put(`${BASE_ALERT_API_PATH}/${id}`, { + return await http.put(`${BASE_ALERT_API_PATH}/alert/${id}`, { body: JSON.stringify( pick(alert, ['throttle', 'name', 'tags', 'schedule', 'params', 'actions']) ), @@ -144,7 +144,7 @@ export async function updateAlert({ } export async function enableAlert({ id, http }: { id: string; http: HttpSetup }): Promise { - await http.post(`${BASE_ALERT_API_PATH}/${id}/_enable`); + await http.post(`${BASE_ALERT_API_PATH}/alert/${id}/_enable`); } export async function enableAlerts({ @@ -158,7 +158,7 @@ export async function enableAlerts({ } export async function disableAlert({ id, http }: { id: string; http: HttpSetup }): Promise { - await http.post(`${BASE_ALERT_API_PATH}/${id}/_disable`); + await http.post(`${BASE_ALERT_API_PATH}/alert/${id}/_disable`); } export async function disableAlerts({ @@ -180,7 +180,7 @@ export async function muteAlertInstance({ instanceId: string; http: HttpSetup; }): Promise { - await http.post(`${BASE_ALERT_API_PATH}/${id}/alert_instance/${instanceId}/_mute`); + await http.post(`${BASE_ALERT_API_PATH}/alert/${id}/alert_instance/${instanceId}/_mute`); } export async function unmuteAlertInstance({ @@ -192,11 +192,11 @@ export async function unmuteAlertInstance({ instanceId: string; http: HttpSetup; }): Promise { - await http.post(`${BASE_ALERT_API_PATH}/${id}/alert_instance/${instanceId}/_unmute`); + await http.post(`${BASE_ALERT_API_PATH}/alert/${id}/alert_instance/${instanceId}/_unmute`); } export async function muteAlert({ id, http }: { id: string; http: HttpSetup }): Promise { - await http.post(`${BASE_ALERT_API_PATH}/${id}/_mute_all`); + await http.post(`${BASE_ALERT_API_PATH}/alert/${id}/_mute_all`); } export async function muteAlerts({ ids, http }: { ids: string[]; http: HttpSetup }): Promise { @@ -204,7 +204,7 @@ export async function muteAlerts({ ids, http }: { ids: string[]; http: HttpSetup } export async function unmuteAlert({ id, http }: { id: string; http: HttpSetup }): Promise { - await http.post(`${BASE_ALERT_API_PATH}/${id}/_unmute_all`); + await http.post(`${BASE_ALERT_API_PATH}/alert/${id}/_unmute_all`); } export async function unmuteAlerts({ 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 df7d1e64c8e9..7ce952e9b3e0 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 @@ -160,7 +160,7 @@ describe('action_form', () => { const initialAlert = ({ name: 'test', params: {}, - consumer: 'alerting', + consumer: 'alerts', alertTypeId: alertType.id, schedule: { interval: '1m', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx index 4a4fce5094f0..3d16bdfa61a0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx @@ -13,7 +13,7 @@ import { actionTypeRegistryMock } from '../../../action_type_registry.mock'; import { AppContextProvider } from '../../../app_context'; import { chartPluginMock } from '../../../../../../../../src/plugins/charts/public/mocks'; import { dataPluginMock } from '../../../../../../../../src/plugins/data/public/mocks'; -import { alertingPluginMock } from '../../../../../../alerting/public/mocks'; +import { alertingPluginMock } from '../../../../../../alerts/public/mocks'; jest.mock('../../../lib/action_connector_api', () => ({ loadAllActions: jest.fn(), diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.test.tsx index e2d9c5cb7fff..54d335aaba5a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.test.tsx @@ -13,7 +13,7 @@ import { ViewInApp } from './view_in_app'; import { useAppDependencies } from '../../../app_context'; jest.mock('../../../app_context', () => { - const alerting = { + const alerts = { getNavigation: jest.fn(async (id) => id === 'alert-with-nav' ? { path: '/alert' } : undefined ), @@ -23,7 +23,7 @@ jest.mock('../../../app_context', () => { useAppDependencies: jest.fn(() => ({ http: jest.fn(), navigateToApp, - alerting, + alerts, legacy: { capabilities: { get: jest.fn(() => ({})), @@ -41,7 +41,7 @@ describe('view in app', () => { describe('link to the app that created the alert', () => { it('is disabled when there is no navigation', async () => { const alert = mockAlert(); - const { alerting } = useAppDependencies(); + const { alerts } = useAppDependencies(); let component: ReactWrapper; await act(async () => { @@ -53,7 +53,7 @@ describe('view in app', () => { expect(component!.find('button').prop('disabled')).toBe(true); expect(component!.text()).toBe('View in app'); - expect(alerting!.getNavigation).toBeCalledWith(alert.id); + expect(alerts!.getNavigation).toBeCalledWith(alert.id); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.tsx index f1f5d8323c22..5b5de070a94e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.tsx @@ -16,7 +16,7 @@ import { AlertNavigation, AlertStateNavigation, AlertUrlNavigation, -} from '../../../../../../alerting/common'; +} from '../../../../../../alerts/common'; import { Alert } from '../../../../types'; export interface ViewInAppProps { @@ -28,7 +28,7 @@ const NO_NAVIGATION = false; type AlertNavigationLoadingState = AlertNavigation | false | null; export const ViewInApp: React.FunctionComponent = ({ alert }) => { - const { navigateToApp, alerting: maybeAlerting } = useAppDependencies(); + const { navigateToApp, alerts: maybeAlerting } = useAppDependencies(); const [alertNavigation, setAlertNavigation] = useState(null); useEffect(() => { @@ -40,13 +40,14 @@ export const ViewInApp: React.FunctionComponent = ({ alert }) => * navigation isn't supported */ () => setAlertNavigation(NO_NAVIGATION), - (alerting) => - alerting + (alerts) => { + return alerts .getNavigation(alert.id) .then((nav) => (nav ? setAlertNavigation(nav) : setAlertNavigation(NO_NAVIGATION))) .catch(() => { setAlertNavigation(NO_NAVIGATION); - }) + }); + } ) ); }, [alert.id, maybeAlerting]); 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 56874f3d38b6..f6e8dc49ec27 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 @@ -120,11 +120,7 @@ describe('alert_add', () => { }, }} > - {}} - /> + {}} /> ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx index bb7e593170f8..e408c7fcb814 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx @@ -84,7 +84,7 @@ describe('alert_edit', () => { window: '1s', comparator: 'between', }, - consumer: 'alerting', + consumer: 'alerts', alertTypeId: 'my-alert-type', enabled: false, schedule: { interval: '1m' }, 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 ed36bc6c8d58..c9ce2848c567 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 @@ -85,7 +85,7 @@ describe('alert_form', () => { const initialAlert = ({ name: 'test', params: {}, - consumer: 'alerting', + consumer: 'alerts', schedule: { interval: '1m', }, @@ -302,7 +302,7 @@ describe('alert_form', () => { name: 'test', alertTypeId: alertType.id, params: {}, - consumer: 'alerting', + consumer: 'alerts', schedule: { interval: '1m', }, 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 87e018ebe337..874091b2bb7a 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 @@ -30,7 +30,7 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { getDurationNumberInItsUnit, getDurationUnitValue, -} from '../../../../../alerting/common/parse_duration'; +} from '../../../../../alerts/common/parse_duration'; import { loadAlertTypes } from '../../lib/alert_api'; import { actionVariablesFromAlertType } from '../../lib/action_variables'; import { AlertReducerAction } from './alert_reducer'; @@ -168,7 +168,7 @@ export const AlertForm = ({ : null; const alertTypeRegistryList = - alert.consumer === 'alerting' + alert.consumer === 'alerts' ? alertTypeRegistry .list() .filter( @@ -179,6 +179,7 @@ export const AlertForm = ({ .filter( (alertTypeRegistryItem: AlertTypeModel) => alertTypesIndex && + alertTypesIndex[alertTypeRegistryItem.id] && alertTypesIndex[alertTypeRegistryItem.id].producer === alert.consumer ); const alertTypeNodes = alertTypeRegistryList.map(function (item, index) { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.test.ts index bd320de14402..4e4d8e237aa2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.test.ts @@ -11,7 +11,7 @@ describe('alert reducer', () => { beforeAll(() => { initialAlert = ({ params: {}, - consumer: 'alerting', + consumer: 'alerts', alertTypeId: null, schedule: { interval: '1m', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx index cf1524094b41..a59a4a37bec1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx @@ -15,7 +15,7 @@ import { ValidationResult } from '../../../../types'; import { AppContextProvider } from '../../../app_context'; import { chartPluginMock } from '../../../../../../../../src/plugins/charts/public/mocks'; import { dataPluginMock } from '../../../../../../../../src/plugins/data/public/mocks'; -import { alertingPluginMock } from '../../../../../../alerting/public/mocks'; +import { alertingPluginMock } from '../../../../../../alerts/public/mocks'; jest.mock('../../../lib/action_connector_api', () => ({ loadActionTypes: jest.fn(), 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 bd4676cd8307..2929ce6defea 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 @@ -439,7 +439,7 @@ export const AlertsList: React.FunctionComponent = () => { }} > diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index dcf120d37ef8..3453165a15f6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -15,7 +15,7 @@ import { TypeRegistry } from './application/type_registry'; import { ManagementStart, ManagementSectionId } from '../../../../src/plugins/management/public'; import { boot } from './application/boot'; import { ChartsPluginStart } from '../../../../src/plugins/charts/public'; -import { PluginStartContract as AlertingStart } from '../../alerting/public'; +import { PluginStartContract as AlertingStart } from '../../alerts/public'; import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; export interface TriggersAndActionsUIPublicPluginSetup { @@ -32,7 +32,7 @@ interface PluginsStart { data: DataPublicPluginStart; charts: ChartsPluginStart; management: ManagementStart; - alerting?: AlertingStart; + alerts?: AlertingStart; navigateToApp: CoreStart['application']['navigateToApp']; } @@ -83,7 +83,7 @@ export class Plugin boot({ dataPlugin: plugins.data, charts: plugins.charts, - alerting: plugins.alerting, + alerts: plugins.alerts, element: params.element, toastNotifications: core.notifications.toasts, http: core.http, diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index 11152c56c49e..52179dd35767 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -5,7 +5,7 @@ */ import { HttpSetup, DocLinksStart } from 'kibana/public'; import { ComponentType } from 'react'; -import { ActionGroup } from '../../alerting/common'; +import { ActionGroup } from '../../alerts/common'; import { ActionType } from '../../actions/common'; import { TypeRegistry } from './application/type_registry'; import { @@ -14,7 +14,7 @@ import { AlertTaskState, RawAlertInstance, AlertingFrameworkHealth, -} from '../../../plugins/alerting/common'; +} from '../../alerts/common'; export { Alert, AlertAction, AlertTaskState, RawAlertInstance, AlertingFrameworkHealth }; export { ActionType }; diff --git a/x-pack/plugins/uptime/kibana.json b/x-pack/plugins/uptime/kibana.json index ce8b64ce0725..5fbd6129fd18 100644 --- a/x-pack/plugins/uptime/kibana.json +++ b/x-pack/plugins/uptime/kibana.json @@ -4,7 +4,7 @@ "kibanaVersion": "kibana", "optionalPlugins": ["capabilities", "data", "home"], "requiredPlugins": [ - "alerting", + "alerts", "embeddable", "features", "licensing", diff --git a/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts index f4d1c7277049..5ffc71945cae 100644 --- a/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts @@ -36,7 +36,7 @@ export interface UptimeCoreSetup { export interface UptimeCorePlugins { features: PluginSetupContract; - alerting: any; + alerts: any; elasticsearch: any; usageCollection: UsageCollectionSetup; } diff --git a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts index 73d104c1d21a..8c487c85c572 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts @@ -11,12 +11,12 @@ import { fullListByIdAndLocation, } from '../status_check'; import { GetMonitorStatusResult } from '../../requests'; -import { AlertType } from '../../../../../alerting/server'; +import { AlertType } from '../../../../../alerts/server'; import { IRouter } from 'kibana/server'; import { UMServerLibs } from '../../lib'; import { UptimeCoreSetup } from '../../adapters'; import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../common/constants'; -import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks'; +import { alertsMock, AlertServicesMock } from '../../../../../alerts/server/mocks'; /** * The alert takes some dependencies as parameters; these are things like @@ -39,7 +39,7 @@ const bootstrapDependencies = (customRequests?: any) => { * This function aims to provide an easy way to give mock props that will * reduce boilerplate for tests. * @param params the params received at alert creation time - * @param services the core services provided by kibana/alerting platforms + * @param services the core services provided by kibana/alerts platforms * @param state the state the alert maintains */ const mockOptions = ( diff --git a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts index 17479bb451b1..3dd1558f5da9 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts @@ -8,7 +8,7 @@ import { schema } from '@kbn/config-schema'; import { isRight } from 'fp-ts/lib/Either'; import { ThrowReporter } from 'io-ts/lib/ThrowReporter'; import { i18n } from '@kbn/i18n'; -import { AlertExecutorOptions } from '../../../../alerting/server'; +import { AlertExecutorOptions } from '../../../../alerts/server'; import { UptimeAlertTypeFactory } from './types'; import { GetMonitorStatusResult } from '../requests'; import { StatusCheckExecutorParamsType } from '../../../common/runtime_types'; diff --git a/x-pack/plugins/uptime/server/lib/alerts/types.ts b/x-pack/plugins/uptime/server/lib/alerts/types.ts index bc1e82224f7b..a321cc124ac2 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/types.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertType } from '../../../../alerting/server'; +import { AlertType } from '../../../../alerts/server'; import { UptimeCoreSetup } from '../adapters'; import { UMServerLibs } from '../lib'; diff --git a/x-pack/plugins/uptime/server/uptime_server.ts b/x-pack/plugins/uptime/server/uptime_server.ts index 180067c0abde..fb90dfe2be6c 100644 --- a/x-pack/plugins/uptime/server/uptime_server.ts +++ b/x-pack/plugins/uptime/server/uptime_server.ts @@ -19,6 +19,6 @@ export const initUptimeServer = ( ); uptimeAlertTypeFactories.forEach((alertTypeFactory) => - plugins.alerting.registerType(alertTypeFactory(server, libs)) + plugins.alerts.registerType(alertTypeFactory(server, libs)) ); }; diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/kibana.json b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/kibana.json index 98c57db16c91..fc42e3199095 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/kibana.json +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/kibana.json @@ -3,7 +3,7 @@ "version": "1.0.0", "kibanaVersion": "kibana", "configPath": ["xpack"], - "requiredPlugins": ["taskManager", "features", "actions", "alerting"], + "requiredPlugins": ["taskManager", "features", "actions", "alerts"], "optionalPlugins": ["spaces"], "server": true, "ui": false diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts index bfabbb816939..8e3d6b6909a1 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts @@ -8,11 +8,11 @@ import { CoreSetup } from 'src/core/server'; import { schema } from '@kbn/config-schema'; import { times } from 'lodash'; import { FixtureStartDeps, FixtureSetupDeps } from './plugin'; -import { AlertType, AlertExecutorOptions } from '../../../../../../../plugins/alerting/server'; +import { AlertType, AlertExecutorOptions } from '../../../../../../../plugins/alerts/server'; export function defineAlertTypes( core: CoreSetup, - { alerting }: Pick + { alerts }: Pick ) { const clusterClient = core.elasticsearch.legacy.client; const alwaysFiringAlertType: AlertType = { @@ -286,13 +286,13 @@ export function defineAlertTypes( }, async executor(opts: AlertExecutorOptions) {}, }; - alerting.registerType(alwaysFiringAlertType); - alerting.registerType(cumulativeFiringAlertType); - alerting.registerType(neverFiringAlertType); - alerting.registerType(failingAlertType); - alerting.registerType(validationAlertType); - alerting.registerType(authorizationAlertType); - alerting.registerType(noopAlertType); - alerting.registerType(onlyContextVariablesAlertType); - alerting.registerType(onlyStateVariablesAlertType); + alerts.registerType(alwaysFiringAlertType); + alerts.registerType(cumulativeFiringAlertType); + alerts.registerType(neverFiringAlertType); + alerts.registerType(failingAlertType); + alerts.registerType(validationAlertType); + alerts.registerType(authorizationAlertType); + alerts.registerType(noopAlertType); + alerts.registerType(onlyContextVariablesAlertType); + alerts.registerType(onlyStateVariablesAlertType); } diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/plugin.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/plugin.ts index af8dd0282c57..47563f8a5f07 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/plugin.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/plugin.ts @@ -6,7 +6,7 @@ import { Plugin, CoreSetup } from 'kibana/server'; import { PluginSetupContract as ActionsPluginSetup } from '../../../../../../../plugins/actions/server/plugin'; -import { PluginSetupContract as AlertingPluginSetup } from '../../../../../../../plugins/alerting/server/plugin'; +import { PluginSetupContract as AlertingPluginSetup } from '../../../../../../../plugins/alerts/server/plugin'; import { EncryptedSavedObjectsPluginStart } from '../../../../../../../plugins/encrypted_saved_objects/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../../../../../../plugins/features/server'; import { defineAlertTypes } from './alert_types'; @@ -16,7 +16,7 @@ import { defineRoutes } from './routes'; export interface FixtureSetupDeps { features: FeaturesPluginSetup; actions: ActionsPluginSetup; - alerting: AlertingPluginSetup; + alerts: AlertingPluginSetup; } export interface FixtureStartDeps { @@ -24,17 +24,14 @@ export interface FixtureStartDeps { } export class FixturePlugin implements Plugin { - public setup( - core: CoreSetup, - { features, actions, alerting }: FixtureSetupDeps - ) { + public setup(core: CoreSetup, { features, actions, alerts }: FixtureSetupDeps) { features.registerFeature({ - id: 'alerting', - name: 'Alerting', - app: ['alerting', 'kibana'], + id: 'alerts', + name: 'Alerts', + app: ['alerts', 'kibana'], privileges: { all: { - app: ['alerting', 'kibana'], + app: ['alerts', 'kibana'], savedObject: { all: ['alert'], read: [], @@ -43,7 +40,7 @@ export class FixturePlugin implements Plugin { - const pluginPath = plugin ? `/${plugin}` : ''; return this.supertest - .delete(`${getUrlPrefix(spaceId)}/api${pluginPath}/${type}/${id}`) + .delete(`${getUrlPrefix(spaceId)}/api/${plugin}/${type}/${id}`) .set('kbn-xsrf', 'foo') .expect(204); }) diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/scenarios.ts b/x-pack/test/alerting_api_integration/security_and_spaces/scenarios.ts index d58fcd29e29f..c72ee6a192bf 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/scenarios.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/scenarios.ts @@ -47,7 +47,7 @@ const GlobalRead: User = { kibana: [ { feature: { - alerting: ['read'], + alerts: ['read'], actions: ['read'], }, spaces: ['*'], @@ -75,7 +75,7 @@ const Space1All: User = { kibana: [ { feature: { - alerting: ['all'], + alerts: ['all'], actions: ['all'], }, spaces: ['space1'], diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts index 785285f6d455..45491aa2d28f 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts @@ -121,7 +121,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { objectRemover.add(space.id, createdAction.id, 'action', 'actions'); const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -142,7 +142,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth .get(`${getUrlPrefix(space.id)}/api/actions`) diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts index 02cd661cbaf0..ab58a205f9d4 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts @@ -336,7 +336,7 @@ instanceStateValue: true const reference = alertUtils.generateReference(); const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( @@ -374,7 +374,7 @@ instanceStateValue: true case 'superuser at space1': case 'space_1_all at space1': expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', undefined); + objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); // Wait for the task to be attempted once and idle const scheduledActionTask = await retry.try(async () => { @@ -428,7 +428,7 @@ instanceStateValue: true const testStart = new Date(); const reference = alertUtils.generateReference(); const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( @@ -457,7 +457,7 @@ instanceStateValue: true break; case 'space_1_all at space1': expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', undefined); + objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); // Wait for test.authorization to index a document before disabling the alert and waiting for tasks to finish await esTestIndexTool.waitForDocs('alert:test.authorization', reference); @@ -490,7 +490,7 @@ instanceStateValue: true break; case 'superuser at space1': expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', undefined); + objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); // Wait for test.authorization to index a document before disabling the alert and waiting for tasks to finish await esTestIndexTool.waitForDocs('alert:test.authorization', reference); @@ -532,7 +532,7 @@ instanceStateValue: true .expect(200); objectRemover.add(space.id, createdAction.id, 'action', 'actions'); const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( @@ -571,7 +571,7 @@ instanceStateValue: true break; case 'space_1_all at space1': expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', undefined); + objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); // Ensure test.authorization indexed 1 document before disabling the alert and waiting for tasks to finish await esTestIndexTool.waitForDocs('action:test.authorization', reference); @@ -604,7 +604,7 @@ instanceStateValue: true break; case 'superuser at space1': expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', undefined); + objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); // Ensure test.authorization indexed 1 document before disabling the alert and waiting for tasks to finish await esTestIndexTool.waitForDocs('action:test.authorization', reference); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts index ad9fd117c360..4ca943f3e188 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts @@ -43,7 +43,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { .expect(200); const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( @@ -72,7 +72,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { case 'superuser at space1': case 'space_1_all at space1': expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', undefined); + objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); expect(response.body).to.eql({ id: response.body.id, name: 'abc', @@ -126,7 +126,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should handle create alert request appropriately when an alert is disabled ', async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send(getTestAlertData({ enabled: false })); @@ -145,7 +145,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { case 'superuser at space1': case 'space_1_all at space1': expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', undefined); + objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); expect(response.body.scheduledTaskId).to.eql(undefined); break; default: @@ -155,7 +155,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should handle create alert request appropriately when alert type is unregistered', async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( @@ -191,7 +191,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should handle create alert request appropriately when payload is empty and invalid', async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send({}); @@ -223,7 +223,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it(`should handle create alert request appropriately when params isn't valid`, async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( @@ -260,7 +260,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should handle create alert request appropriately when interval schedule is wrong syntax', async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send(getTestAlertData(getTestAlertData({ schedule: { interval: '10x' } }))); @@ -292,7 +292,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should handle create alert request appropriately when interval schedule is 0', async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send(getTestAlertData(getTestAlertData({ schedule: { interval: '0s' } }))); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/delete.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/delete.ts index 593ae574e6f3..6f8ae010b9cd 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/delete.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/delete.ts @@ -32,13 +32,13 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { describe(scenario.id, () => { it('should handle delete alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); const response = await supertestWithoutAuth - .delete(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .delete(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password); @@ -52,7 +52,7 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { error: 'Not Found', message: 'Not Found', }); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); // Ensure task still exists await getScheduledTask(createdAlert.scheduledTaskId); break; @@ -74,14 +74,14 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { it(`shouldn't delete alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth - .delete(`${getUrlPrefix('other')}/api/alert/${createdAlert.id}`) + .delete(`${getUrlPrefix('other')}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password); @@ -111,7 +111,7 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { it('should still be able to delete alert when AAD is broken', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); @@ -129,7 +129,7 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { .expect(200); const response = await supertestWithoutAuth - .delete(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .delete(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password); @@ -143,7 +143,7 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { error: 'Not Found', message: 'Not Found', }); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); // Ensure task still exists await getScheduledTask(createdAlert.scheduledTaskId); break; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/disable.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/disable.ts index dbbccba70a71..589942a7ac11 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/disable.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/disable.ts @@ -40,11 +40,11 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte describe(scenario.id, () => { it('should handle disable alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: true })) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await alertUtils.getDisableRequest(createdAlert.id); @@ -86,11 +86,11 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte it('should still be able to disable alert when AAD is broken', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: true })) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); await supertest .put( @@ -144,11 +144,11 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte it(`shouldn't disable alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix('other')}/api/alert`) + .post(`${getUrlPrefix('other')}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: true })) .expect(200); - objectRemover.add('other', createdAlert.id, 'alert', undefined); + objectRemover.add('other', createdAlert.id, 'alert', 'alerts'); const response = await alertUtils.getDisableRequest(createdAlert.id); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/enable.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/enable.ts index 611556aaf1fe..8cb0eeb0092a 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/enable.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/enable.ts @@ -40,11 +40,11 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex describe(scenario.id, () => { it('should handle enable alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await alertUtils.getEnableRequest(createdAlert.id); @@ -64,7 +64,7 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); @@ -91,11 +91,11 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex it('should still be able to enable alert when AAD is broken', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); await supertest .put( @@ -127,7 +127,7 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); @@ -154,11 +154,11 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex it(`shouldn't enable alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix('other')}/api/alert`) + .post(`${getUrlPrefix('other')}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add('other', createdAlert.id, 'alert', undefined); + objectRemover.add('other', createdAlert.id, 'alert', 'alerts'); const response = await alertUtils.getEnableRequest(createdAlert.id); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts index 1c4d684eb78d..5fe9edeb91ec 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts @@ -24,15 +24,17 @@ export default function createFindTests({ getService }: FtrProviderContext) { describe(scenario.id, () => { it('should handle find alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth .get( - `${getUrlPrefix(space.id)}/api/alert/_find?search=test.noop&search_fields=alertTypeId` + `${getUrlPrefix( + space.id + )}/api/alerts/_find?search=test.noop&search_fields=alertTypeId` ) .auth(user.username, user.password); @@ -95,7 +97,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { .expect(200); const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -110,13 +112,13 @@ export default function createFindTests({ getService }: FtrProviderContext) { }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth .get( `${getUrlPrefix( space.id - )}/api/alert/_find?filter=alert.attributes.actions:{ actionTypeId: test.noop }` + )}/api/alerts/_find?filter=alert.attributes.actions:{ actionTypeId: test.noop }` ) .auth(user.username, user.password); @@ -174,15 +176,15 @@ export default function createFindTests({ getService }: FtrProviderContext) { it(`shouldn't find alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth .get( - `${getUrlPrefix('other')}/api/alert/_find?search=test.noop&search_fields=alertTypeId` + `${getUrlPrefix('other')}/api/alerts/_find?search=test.noop&search_fields=alertTypeId` ) .auth(user.username, user.password); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts index 5800273dce75..a203b7d7c151 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts @@ -24,14 +24,14 @@ export default function createGetTests({ getService }: FtrProviderContext) { describe(scenario.id, () => { it('should handle get alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .auth(user.username, user.password); switch (scenario.id) { @@ -78,14 +78,14 @@ export default function createGetTests({ getService }: FtrProviderContext) { it(`shouldn't get alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix('other')}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix('other')}/api/alerts/alert/${createdAlert.id}`) .auth(user.username, user.password); expect(response.statusCode).to.eql(404); @@ -114,7 +114,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { it(`should handle get alert request appropriately when alert doesn't exist`, async () => { const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/1`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/1`) .auth(user.username, user.password); switch (scenario.id) { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get_alert_state.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get_alert_state.ts index 42a6b36df0f9..fd071bd55b37 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get_alert_state.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get_alert_state.ts @@ -24,14 +24,14 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont describe(scenario.id, () => { it('should handle getAlertState alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}/state`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}/state`) .auth(user.username, user.password); switch (scenario.id) { @@ -57,14 +57,14 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont it(`shouldn't getAlertState for an alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix('other')}/api/alert/${createdAlert.id}/state`) + .get(`${getUrlPrefix('other')}/api/alerts/alert/${createdAlert.id}/state`) .auth(user.username, user.password); expect(response.statusCode).to.eql(404); @@ -93,7 +93,7 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont it(`should handle getAlertState request appropriately when alert doesn't exist`, async () => { const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/1/state`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/1/state`) .auth(user.username, user.password); switch (scenario.id) { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts index 91b0ca0a37c9..f14f66f66fe2 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function alertingTests({ loadTestFile }: FtrProviderContext) { - describe('Alerting', () => { + describe('Alerts', () => { loadTestFile(require.resolve('./create')); loadTestFile(require.resolve('./delete')); loadTestFile(require.resolve('./disable')); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/list_alert_types.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/list_alert_types.ts index 4f6b26dfb67f..dd31e2dbbb5b 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/list_alert_types.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/list_alert_types.ts @@ -19,7 +19,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { describe(scenario.id, () => { it('should return 200 with list of alert types', async () => { const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/types`) + .get(`${getUrlPrefix(space.id)}/api/alerts/list_alert_types`) .auth(user.username, user.password); switch (scenario.id) { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_all.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_all.ts index 0196615629e2..2416bc2ea1d1 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_all.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_all.ts @@ -32,11 +32,11 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) describe(scenario.id, () => { it('should handle mute alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await alertUtils.getMuteAllRequest(createdAlert.id); @@ -56,7 +56,7 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_instance.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_instance.ts index 0c05dbdf5584..c59b9f4503a0 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_instance.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_instance.ts @@ -32,11 +32,11 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider describe(scenario.id, () => { it('should handle mute alert instance request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await alertUtils.getMuteInstanceRequest(createdAlert.id, '1'); @@ -56,7 +56,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); @@ -76,14 +76,16 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider it('should handle mute alert instance request appropriately and not duplicate mutedInstanceIds when muting an instance already muted', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); await supertest - .post(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}/alert_instance/1/_mute`) + .post( + `${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}/alert_instance/1/_mute` + ) .set('kbn-xsrf', 'foo') .expect(204, ''); @@ -105,7 +107,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_all.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_all.ts index ebe9f1f645ed..fd22752ccc11 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_all.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_all.ts @@ -32,14 +32,14 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex describe(scenario.id, () => { it('should handle unmute alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); await supertest - .post(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}/_mute_all`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}/_mute_all`) .set('kbn-xsrf', 'foo') .expect(204, ''); @@ -61,7 +61,7 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_instance.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_instance.ts index 7142fd7d91ad..72b524282354 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_instance.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_instance.ts @@ -32,14 +32,16 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider describe(scenario.id, () => { it('should handle unmute alert instance request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); await supertest - .post(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}/alert_instance/1/_mute`) + .post( + `${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}/alert_instance/1/_mute` + ) .set('kbn-xsrf', 'foo') .expect(204, ''); @@ -61,7 +63,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); 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 0af1e2258464..2bcc035beb7a 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 @@ -39,11 +39,11 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { describe(scenario.id, () => { it('should handle update alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const updatedData = { name: 'bcd', @@ -56,7 +56,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { throttle: '1m', }; const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send(updatedData); @@ -110,11 +110,11 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it('should still be able to update when AAD is broken', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); await supertest .put( @@ -139,7 +139,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { throttle: '1m', }; const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send(updatedData); @@ -193,14 +193,14 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it(`shouldn't update alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth - .put(`${getUrlPrefix('other')}/api/alert/${createdAlert.id}`) + .put(`${getUrlPrefix('other')}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send({ @@ -240,14 +240,14 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it('should handle update alert request appropriately when attempting to change alert type', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send({ @@ -289,7 +289,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it('should handle update alert request appropriately when payload is empty and invalid', async () => { const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alert/1`) + .put(`${getUrlPrefix(space.id)}/api/alerts/alert/1`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send({}); @@ -321,7 +321,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it(`should handle update alert request appropriately when alertTypeConfig isn't valid`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -332,10 +332,10 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send({ @@ -375,7 +375,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it('should handle update alert request appropriately when interval schedule is wrong syntax', async () => { const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alert/1`) + .put(`${getUrlPrefix(space.id)}/api/alerts/alert/1`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( @@ -413,7 +413,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it('should handle updates to an alert schedule by rescheduling the underlying task', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -421,7 +421,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); await retry.try(async () => { const alertTask = (await getAlertingTaskById(createdAlert.scheduledTaskId)).docs[0]; @@ -441,7 +441,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { throttle: '1m', }; const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send(updatedData); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update_api_key.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update_api_key.ts index 6349919c15cd..bf72b970dc0f 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update_api_key.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update_api_key.ts @@ -32,11 +32,11 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte describe(scenario.id, () => { it('should handle update alert api key request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await alertUtils.getUpdateApiKeyRequest(createdAlert.id); @@ -56,7 +56,7 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); @@ -76,11 +76,11 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte it('should still be able to update API key when AAD is broken', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); await supertest .put( @@ -112,7 +112,7 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); @@ -132,11 +132,11 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte it(`shouldn't update alert api key from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix('other')}/api/alert`) + .post(`${getUrlPrefix('other')}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add('other', createdAlert.id, 'alert', undefined); + objectRemover.add('other', createdAlert.id, 'alert', 'alerts'); const response = await alertUtils.getUpdateApiKeyRequest(createdAlert.id); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts index d3c914942bd9..8ffe65a8ebb4 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts @@ -186,7 +186,7 @@ instanceStateValue: true const reference = alertUtils.generateReference(); const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -211,7 +211,7 @@ instanceStateValue: true ); expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', undefined); + objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); const scheduledActionTask = await retry.try(async () => { const searchResult = await es.search({ index: '.kibana_task_manager', @@ -255,7 +255,7 @@ instanceStateValue: true it('should have proper callCluster and savedObjectsClient authorization for alert type executor', async () => { const reference = alertUtils.generateReference(); const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -271,7 +271,7 @@ instanceStateValue: true ); expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', undefined); + objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); const alertTestRecord = ( await esTestIndexTool.waitForDocs('alert:test.authorization', reference) )[0]; @@ -301,7 +301,7 @@ instanceStateValue: true .expect(200); objectRemover.add(space.id, createdAction.id, 'action', 'actions'); const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -327,7 +327,7 @@ instanceStateValue: true ); expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', undefined); + objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); const actionTestRecord = ( await esTestIndexTool.waitForDocs('action:test.authorization', reference) )[0]; 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 353f7d02f6b0..8412c09eefcd 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 @@ -342,7 +342,7 @@ export default function alertTests({ getService }: FtrProviderContext) { }; const { status, body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send({ name: params.name, @@ -372,7 +372,7 @@ export default function alertTests({ getService }: FtrProviderContext) { expect(status).to.be(200); const alertId = createdAlert.id; - objectRemover.add(Spaces.space1.id, alertId, 'alert', undefined); + objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); return alertId; } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts index b10c356cf40d..fa256712a012 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts @@ -39,7 +39,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { .expect(200); const response = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -54,7 +54,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { ); expect(response.status).to.eql(200); - objectRemover.add(Spaces.space1.id, response.body.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, response.body.id, 'alert', 'alerts'); expect(response.body).to.eql({ id: response.body.id, name: 'abc', @@ -104,12 +104,12 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should handle create alert request appropriately when an alert is disabled ', async () => { const response = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })); expect(response.status).to.eql(200); - objectRemover.add(Spaces.space1.id, response.body.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, response.body.id, 'alert', 'alerts'); expect(response.body.scheduledTaskId).to.eql(undefined); }); }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/delete.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/delete.ts index 3aea982f948e..e9dfe1607d32 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/delete.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/delete.ts @@ -28,13 +28,13 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { it('should handle delete alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); await supertest - .delete(`${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}`) + .delete(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .expect(204, ''); @@ -48,13 +48,13 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { it(`shouldn't delete alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); await supertest - .delete(`${getUrlPrefix(Spaces.other.id)}/api/alert/${createdAlert.id}`) + .delete(`${getUrlPrefix(Spaces.other.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .expect(404, { statusCode: 404, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts index 7152a76fa167..afa4f03e23b3 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts @@ -35,11 +35,11 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte it('should handle disable alert request appropriately', async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: true })) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); await alertUtils.disable(createdAlert.id); @@ -61,11 +61,11 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte it(`shouldn't disable alert from another space`, async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.other.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.other.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: true })) .expect(200); - objectRemover.add(Spaces.other.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.other.id, createdAlert.id, 'alert', 'alerts'); await alertUtils.getDisableRequest(createdAlert.id).expect(404, { statusCode: 404, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/enable.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/enable.ts index 3d556d093602..05b212bb064f 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/enable.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/enable.ts @@ -35,16 +35,16 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex it('should handle enable alert request appropriately', async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); await alertUtils.enable(createdAlert.id); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .expect(200); expect(typeof updatedAlert.scheduledTaskId).to.eql('string'); @@ -67,11 +67,11 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex it(`shouldn't enable alert from another space`, async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.other.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.other.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(Spaces.other.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.other.id, createdAlert.id, 'alert', 'alerts'); await alertUtils.getEnableRequest(createdAlert.id).expect(404, { statusCode: 404, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts index f57b136b9637..06f27d666c3d 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts @@ -20,16 +20,16 @@ export default function createFindTests({ getService }: FtrProviderContext) { it('should handle find alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); const response = await supertest.get( `${getUrlPrefix( Spaces.space1.id - )}/api/alert/_find?search=test.noop&search_fields=alertTypeId` + )}/api/alerts/_find?search=test.noop&search_fields=alertTypeId` ); expect(response.status).to.eql(200); @@ -63,17 +63,17 @@ export default function createFindTests({ getService }: FtrProviderContext) { it(`shouldn't find alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); await supertest .get( `${getUrlPrefix( Spaces.other.id - )}/api/alert/_find?search=test.noop&search_fields=alertTypeId` + )}/api/alerts/_find?search=test.noop&search_fields=alertTypeId` ) .expect(200, { page: 1, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts index 6b216d2ba265..ff671e16654b 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts @@ -20,14 +20,14 @@ export default function createGetTests({ getService }: FtrProviderContext) { it('should handle get alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}` + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}` ); expect(response.status).to.eql(200); @@ -57,14 +57,14 @@ export default function createGetTests({ getService }: FtrProviderContext) { it(`shouldn't find alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); await supertest - .get(`${getUrlPrefix(Spaces.other.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(Spaces.other.id)}/api/alerts/alert/${createdAlert.id}`) .expect(404, { statusCode: 404, error: 'Not Found', @@ -73,7 +73,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { }); it(`should handle get alert request appropriately when alert doesn't exist`, async () => { - await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/1`).expect(404, { + await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/1`).expect(404, { statusCode: 404, error: 'Not Found', message: 'Saved object [alert/1] not found', diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_state.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_state.ts index 06f5f5542780..d3f08d7c509a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_state.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_state.ts @@ -21,14 +21,14 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont it('should handle getAlertState request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}/state` + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/state` ); expect(response.status).to.eql(200); @@ -37,7 +37,7 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont it('should fetch updated state', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send({ enabled: true, @@ -51,12 +51,12 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont params: {}, }) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); // wait for alert to actually execute await retry.try(async () => { const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}/state` + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/state` ); expect(response.status).to.eql(200); @@ -65,7 +65,7 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont }); const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}/state` + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/state` ); expect(response.body.alertTypeState.runCount).to.greaterThan(0); @@ -79,11 +79,13 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont }); it(`should handle getAlertState request appropriately when alert doesn't exist`, async () => { - await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/1/state`).expect(404, { - statusCode: 404, - error: 'Not Found', - message: 'Saved object [alert/1] not found', - }); + await supertest + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/1/state`) + .expect(404, { + statusCode: 404, + error: 'Not Found', + message: 'Saved object [alert/1] not found', + }); }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts index 845a6f795573..aef87eefba2a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts @@ -15,7 +15,9 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { describe('list_alert_types', () => { it('should return 200 with list of alert types', async () => { - const response = await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/types`); + const response = await supertest.get( + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/list_alert_types` + ); expect(response.status).to.eql(200); const fixtureAlertType = response.body.find((alertType: any) => alertType.id === 'test.noop'); expect(fixtureAlertType).to.eql({ @@ -32,7 +34,9 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { }); it('should return actionVariables with both context and state', async () => { - const response = await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/types`); + const response = await supertest.get( + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/list_alert_types` + ); expect(response.status).to.eql(200); const fixtureAlertType = response.body.find( @@ -46,7 +50,9 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { }); it('should return actionVariables with just context', async () => { - const response = await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/types`); + const response = await supertest.get( + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/list_alert_types` + ); expect(response.status).to.eql(200); const fixtureAlertType = response.body.find( @@ -60,7 +66,9 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { }); it('should return actionVariables with just state', async () => { - const response = await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/types`); + const response = await supertest.get( + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/list_alert_types` + ); expect(response.status).to.eql(200); const fixtureAlertType = response.body.find( diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_all.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_all.ts index b2ba38ac9847..f881d0c677bb 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_all.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_all.ts @@ -27,16 +27,16 @@ export default function createMuteTests({ getService }: FtrProviderContext) { it('should handle mute alert request appropriately', async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); await alertUtils.muteAll(createdAlert.id); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .expect(200); expect(updatedAlert.muteAll).to.eql(true); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_instance.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_instance.ts index d9f52d3321e3..ca0d72aedf7a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_instance.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_instance.ts @@ -27,16 +27,16 @@ export default function createMuteInstanceTests({ getService }: FtrProviderConte it('should handle mute alert instance request appropriately', async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); await alertUtils.muteInstance(createdAlert.id, '1'); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .expect(200); expect(updatedAlert.mutedInstanceIds).to.eql(['1']); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_all.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_all.ts index 7c5f1e0a6213..1df99540903d 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_all.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_all.ts @@ -27,17 +27,17 @@ export default function createUnmuteTests({ getService }: FtrProviderContext) { it('should handle unmute alert request appropriately', async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); await alertUtils.muteAll(createdAlert.id); await alertUtils.unmuteAll(createdAlert.id); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .expect(200); expect(updatedAlert.muteAll).to.eql(false); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_instance.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_instance.ts index 86464c3d6bb6..332842ce8015 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_instance.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_instance.ts @@ -27,17 +27,17 @@ export default function createUnmuteInstanceTests({ getService }: FtrProviderCon it('should handle unmute alert instance request appropriately', async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); await alertUtils.muteInstance(createdAlert.id, '1'); await alertUtils.unmuteInstance(createdAlert.id, '1'); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .expect(200); expect(updatedAlert.mutedInstanceIds).to.eql([]); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts index fc0aeb71d906..b01a1b140f2d 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts @@ -20,11 +20,11 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it('should handle update alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); const updatedData = { name: 'bcd', @@ -37,7 +37,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { throttle: '1m', }; const response = await supertest - .put(`${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .send(updatedData) .expect(200); @@ -75,14 +75,14 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it(`shouldn't update alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); await supertest - .put(`${getUrlPrefix(Spaces.other.id)}/api/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(Spaces.other.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .send({ name: 'bcd', diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update_api_key.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update_api_key.ts index 9c7b4dcc8b1a..93f91bdc7315 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update_api_key.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update_api_key.ts @@ -31,16 +31,16 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte it('should handle update alert api key appropriately', async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); await alertUtils.updateApiKey(createdAlert.id); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .expect(200); expect(updatedAlert.apiKeyOwner).to.eql(null); @@ -56,11 +56,11 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte it(`shouldn't update alert api key from another space`, async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.other.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.other.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.other.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.other.id, createdAlert.id, 'alert', 'alerts'); await alertUtils.getUpdateApiKeyRequest(createdAlert.id).expect(404, { statusCode: 404, diff --git a/x-pack/test/case_api_integration/common/config.ts b/x-pack/test/case_api_integration/common/config.ts index 9eb62c2fe07b..45b34b7d2694 100644 --- a/x-pack/test/case_api_integration/common/config.ts +++ b/x-pack/test/case_api_integration/common/config.ts @@ -78,7 +78,6 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) 'some.non.existent.com', ])}`, `--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`, - '--xpack.alerting.enabled=true', '--xpack.eventLog.logEntries=true', ...disabledPlugins.map((key) => `--xpack.${key}.enabled=false`), `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'alerts')}`, diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts index 89ce3742adf6..13bf47676cc0 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts @@ -21,7 +21,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { async function createAlert(overwrites: Record = {}) { const { body: createdAlert } = await supertest - .post(`/api/alert`) + .post(`/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send({ enabled: true, diff --git a/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts index d78053cf926d..6cb74aff95be 100644 --- a/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts @@ -89,7 +89,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // put the fetch code in a retry block with a timeout. let alert: any; await retry.tryForTime(15000, async () => { - const apiResponse = await supertest.get('/api/alert/_find?search=uptime-test'); + const apiResponse = await supertest.get('/api/alerts/_find?search=uptime-test'); const alertsFromThisTest = apiResponse.body.data.filter( ({ name }: { name: string }) => name === 'uptime-test' ); @@ -129,7 +129,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { '"minimum_should_match":1}},{"bool":{"should":[{"match":{"monitor.type":"http"}}],"minimum_should_match":1}}]}}]}}]}}' ); } finally { - await supertest.delete(`/api/alert/${id}`).set('kbn-xsrf', 'true').expect(204); + await supertest.delete(`/api/alerts/alert/${id}`).set('kbn-xsrf', 'true').expect(204); } }); }); @@ -176,7 +176,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('has created a valid alert with expected parameters', async () => { let alert: any; await retry.tryForTime(15000, async () => { - const apiResponse = await supertest.get(`/api/alert/_find?search=${alertId}`); + const apiResponse = await supertest.get(`/api/alerts/_find?search=${alertId}`); const alertsFromThisTest = apiResponse.body.data.filter( ({ name }: { name: string }) => name === alertId ); @@ -204,7 +204,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(params).to.eql({}); expect(interval).to.eql('11m'); } finally { - await supertest.delete(`/api/alert/${id}`).set('kbn-xsrf', 'true').expect(204); + await supertest.delete(`/api/alerts/alert/${id}`).set('kbn-xsrf', 'true').expect(204); } }); }); diff --git a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/kibana.json b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/kibana.json index 1715f30b8226..74f740f52a8b 100644 --- a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/kibana.json +++ b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/kibana.json @@ -3,7 +3,7 @@ "version": "1.0.0", "kibanaVersion": "kibana", "configPath": ["xpack"], - "requiredPlugins": ["alerting", "triggers_actions_ui"], + "requiredPlugins": ["alerts", "triggers_actions_ui"], "server": true, "ui": true } diff --git a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts index 4c68a3aa15b3..2bc299ede930 100644 --- a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts +++ b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts @@ -6,21 +6,21 @@ import React from 'react'; import { Plugin, CoreSetup, AppMountParameters } from 'kibana/public'; -import { PluginSetupContract as AlertingSetup } from '../../../../../../plugins/alerting/public'; -import { AlertType, SanitizedAlert } from '../../../../../../plugins/alerting/common'; +import { PluginSetupContract as AlertingSetup } from '../../../../../../plugins/alerts/public'; +import { AlertType, SanitizedAlert } from '../../../../../../plugins/alerts/common'; import { TriggersAndActionsUIPublicPluginSetup } from '../../../../../../plugins/triggers_actions_ui/public'; export type Setup = void; export type Start = void; export interface AlertingExamplePublicSetupDeps { - alerting: AlertingSetup; + alerts: AlertingSetup; triggers_actions_ui: TriggersAndActionsUIPublicPluginSetup; } export class AlertingFixturePlugin implements Plugin { - public setup(core: CoreSetup, { alerting, triggers_actions_ui }: AlertingExamplePublicSetupDeps) { - alerting.registerNavigation( + public setup(core: CoreSetup, { alerts, triggers_actions_ui }: AlertingExamplePublicSetupDeps) { + alerts.registerNavigation( 'consumer-noop', 'test.noop', (alert: SanitizedAlert, alertType: AlertType) => `/alert/${alert.id}` 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 123c0c550e71..fb431351a382 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 @@ -8,24 +8,24 @@ import { Plugin, CoreSetup } from 'kibana/server'; import { PluginSetupContract as AlertingSetup, AlertType, -} from '../../../../../../plugins/alerting/server'; +} from '../../../../../../plugins/alerts/server'; // this plugin's dependendencies export interface AlertingExampleDeps { - alerting: AlertingSetup; + alerts: AlertingSetup; } export class AlertingFixturePlugin implements Plugin { - public setup(core: CoreSetup, { alerting }: AlertingExampleDeps) { - createNoopAlertType(alerting); - createAlwaysFiringAlertType(alerting); + public setup(core: CoreSetup, { alerts }: AlertingExampleDeps) { + createNoopAlertType(alerts); + createAlwaysFiringAlertType(alerts); } public start() {} public stop() {} } -function createNoopAlertType(alerting: AlertingSetup) { +function createNoopAlertType(alerts: AlertingSetup) { const noopAlertType: AlertType = { id: 'test.noop', name: 'Test: Noop', @@ -34,10 +34,10 @@ function createNoopAlertType(alerting: AlertingSetup) { async executor() {}, producer: 'alerting', }; - alerting.registerType(noopAlertType); + alerts.registerType(noopAlertType); } -function createAlwaysFiringAlertType(alerting: AlertingSetup) { +function createAlwaysFiringAlertType(alerts: AlertingSetup) { // Alert types const alwaysFiringAlertType: any = { id: 'test.always-firing', @@ -63,5 +63,5 @@ function createAlwaysFiringAlertType(alerting: AlertingSetup) { }; }, }; - alerting.registerType(alwaysFiringAlertType); + alerts.registerType(alwaysFiringAlertType); } diff --git a/x-pack/test/functional_with_es_ssl/services/alerting/alerts.ts b/x-pack/test/functional_with_es_ssl/services/alerting/alerts.ts index 2a0d28f24676..25f4c6a932d5 100644 --- a/x-pack/test/functional_with_es_ssl/services/alerting/alerts.ts +++ b/x-pack/test/functional_with_es_ssl/services/alerting/alerts.ts @@ -38,7 +38,7 @@ export class Alerts { ) { this.log.debug(`creating alert ${name}`); - const { data: alert, status, statusText } = await this.axios.post(`/api/alert`, { + const { data: alert, status, statusText } = await this.axios.post(`/api/alerts/alert`, { enabled: true, name, tags, @@ -63,7 +63,7 @@ export class Alerts { public async createNoOp(name: string) { this.log.debug(`creating alert ${name}`); - const { data: alert, status, statusText } = await this.axios.post(`/api/alert`, { + const { data: alert, status, statusText } = await this.axios.post(`/api/alerts/alert`, { enabled: true, name, tags: ['foo'], @@ -96,7 +96,7 @@ export class Alerts { ) { this.log.debug(`creating alert ${name}`); - const { data: alert, status, statusText } = await this.axios.post(`/api/alert`, { + const { data: alert, status, statusText } = await this.axios.post(`/api/alerts/alert`, { enabled: true, name, tags: ['foo'], @@ -132,7 +132,7 @@ export class Alerts { public async deleteAlert(id: string) { this.log.debug(`deleting alert ${id}`); - const { data: alert, status, statusText } = await this.axios.delete(`/api/alert/${id}`); + const { data: alert, status, statusText } = await this.axios.delete(`/api/alerts/alert/${id}`); if (status !== 204) { throw new Error( `Expected status code of 204, received ${status} ${statusText}: ${util.inspect(alert)}` @@ -144,7 +144,7 @@ export class Alerts { public async getAlertState(id: string) { this.log.debug(`getting alert ${id} state`); - const { data } = await this.axios.get(`/api/alert/${id}/state`); + const { data } = await this.axios.get(`/api/alerts/alert/${id}/state`); return data; } @@ -152,7 +152,7 @@ export class Alerts { this.log.debug(`muting instance ${instanceId} under alert ${id}`); const { data: alert, status, statusText } = await this.axios.post( - `/api/alert/${id}/alert_instance/${instanceId}/_mute` + `/api/alerts/alert/${id}/alert_instance/${instanceId}/_mute` ); if (status !== 204) { throw new Error( diff --git a/x-pack/typings/hapi.d.ts b/x-pack/typings/hapi.d.ts index ed86a961cd1d..6af723101fc2 100644 --- a/x-pack/typings/hapi.d.ts +++ b/x-pack/typings/hapi.d.ts @@ -9,7 +9,7 @@ import 'hapi'; import { XPackMainPlugin } from '../legacy/plugins/xpack_main/server/xpack_main'; import { SecurityPlugin } from '../legacy/plugins/security'; import { ActionsPlugin, ActionsClient } from '../plugins/actions/server'; -import { AlertingPlugin, AlertsClient } from '../plugins/alerting/server'; +import { AlertingPlugin, AlertsClient } from '../plugins/alerts/server'; import { TaskManager } from '../plugins/task_manager/server'; declare module 'hapi' { @@ -21,7 +21,7 @@ declare module 'hapi' { xpack_main: XPackMainPlugin; security?: SecurityPlugin; actions?: ActionsPlugin; - alerting?: AlertingPlugin; + alerts?: AlertingPlugin; task_manager?: TaskManager; } } From e2ddf8bc2d829e357e7f9ea06f8e3d291a86619e Mon Sep 17 00:00:00 2001 From: patrykkopycinski Date: Tue, 2 Jun 2020 11:28:25 +0200 Subject: [PATCH 16/50] [SIEM] Fix draft timeline can be attached to a case (#67844) --- .../components/flyout/header/index.tsx | 4 ++ .../timeline/properties/helpers.tsx | 63 ++++++++-------- .../timeline/properties/index.test.tsx | 71 +++++++++++++++++++ .../components/timeline/properties/index.tsx | 4 ++ .../timeline/properties/properties_right.tsx | 4 ++ .../containers/one/index.gql_query.ts | 1 + 6 files changed, 118 insertions(+), 29 deletions(-) diff --git a/x-pack/plugins/siem/public/timelines/components/flyout/header/index.tsx b/x-pack/plugins/siem/public/timelines/components/flyout/header/index.tsx index b332260597f2..ab8a24889e9b 100644 --- a/x-pack/plugins/siem/public/timelines/components/flyout/header/index.tsx +++ b/x-pack/plugins/siem/public/timelines/components/flyout/header/index.tsx @@ -39,6 +39,7 @@ const StatefulFlyoutHeader = React.memo( title, noteIds, notesById, + status, timelineId, toggleLock, updateDescription, @@ -62,6 +63,7 @@ const StatefulFlyoutHeader = React.memo( isFavorite={isFavorite} title={title} noteIds={noteIds} + status={status} timelineId={timelineId} toggleLock={toggleLock} updateDescription={updateDescription} @@ -94,6 +96,7 @@ const makeMapStateToProps = () => { kqlQuery, title = '', noteIds = emptyNotesId, + status, } = timeline; const history = emptyHistory; // TODO: get history from store via selector @@ -107,6 +110,7 @@ const makeMapStateToProps = () => { isFavorite, isDatepickerLocked: globalInput.linkTo.includes('timeline'), noteIds, + status, title, }; }; diff --git a/x-pack/plugins/siem/public/timelines/components/timeline/properties/helpers.tsx b/x-pack/plugins/siem/public/timelines/components/timeline/properties/helpers.tsx index 3b1d324f3444..3ef10d394bc7 100644 --- a/x-pack/plugins/siem/public/timelines/components/timeline/properties/helpers.tsx +++ b/x-pack/plugins/siem/public/timelines/components/timeline/properties/helpers.tsx @@ -23,6 +23,7 @@ import styled from 'styled-components'; import { useHistory } from 'react-router-dom'; import { useSelector } from 'react-redux'; +import { TimelineStatus } from '../../../../../common/types/timeline'; import { Note } from '../../../../common/lib/note'; import { Notes } from '../../notes'; import { AssociateNote, UpdateNote } from '../../notes/helpers'; @@ -119,40 +120,44 @@ Name.displayName = 'Name'; interface NewCaseProps { onClosePopover: () => void; timelineId: string; + timelineStatus: TimelineStatus; timelineTitle: string; } -export const NewCase = React.memo(({ onClosePopover, timelineId, timelineTitle }) => { - const history = useHistory(); - const { savedObjectId } = useSelector((state: State) => - timelineSelectors.selectTimeline(state, timelineId) - ); - const handleClick = useCallback(() => { - onClosePopover(); - history.push({ - pathname: `/${SiemPageName.case}/create`, - state: { - insertTimeline: { - timelineId, - timelineSavedObjectId: savedObjectId, - timelineTitle: timelineTitle.length > 0 ? timelineTitle : i18n.UNTITLED_TIMELINE, +export const NewCase = React.memo( + ({ onClosePopover, timelineId, timelineStatus, timelineTitle }) => { + const history = useHistory(); + const { savedObjectId } = useSelector((state: State) => + timelineSelectors.selectTimeline(state, timelineId) + ); + const handleClick = useCallback(() => { + onClosePopover(); + history.push({ + pathname: `/${SiemPageName.case}/create`, + state: { + insertTimeline: { + timelineId, + timelineSavedObjectId: savedObjectId, + timelineTitle: timelineTitle.length > 0 ? timelineTitle : i18n.UNTITLED_TIMELINE, + }, }, - }, - }); - }, [onClosePopover, history, timelineId, timelineTitle]); + }); + }, [onClosePopover, history, timelineId, timelineTitle]); - return ( - - {i18n.ATTACH_TIMELINE_TO_NEW_CASE} - - ); -}); + return ( + + {i18n.ATTACH_TIMELINE_TO_NEW_CASE} + + ); + } +); NewCase.displayName = 'NewCase'; interface NewTimelineProps { diff --git a/x-pack/plugins/siem/public/timelines/components/timeline/properties/index.test.tsx b/x-pack/plugins/siem/public/timelines/components/timeline/properties/index.test.tsx index bfa32fecac89..8bdec78ec8da 100644 --- a/x-pack/plugins/siem/public/timelines/components/timeline/properties/index.test.tsx +++ b/x-pack/plugins/siem/public/timelines/components/timeline/properties/index.test.tsx @@ -8,6 +8,7 @@ import { mount } from 'enzyme'; import React from 'react'; import { Provider as ReduxStoreProvider } from 'react-redux'; +import { TimelineStatus } from '../../../../../common/types/timeline'; import { mockGlobalState, apolloClientObservable, @@ -25,6 +26,24 @@ jest.mock('../../../../common/components/utils'); width: mockedWidth, })); +jest.mock('react-redux', () => { + const originalModule = jest.requireActual('react-redux'); + + return { + ...originalModule, + useSelector: jest.fn().mockReturnValue({ savedObjectId: '1' }), + }; +}); + +jest.mock('react-router-dom', () => { + const originalModule = jest.requireActual('react-router-dom'); + + return { + ...originalModule, + useHistory: jest.fn(), + }; +}); + describe('Properties', () => { const usersViewing = ['elastic']; @@ -50,6 +69,7 @@ describe('Properties', () => { description="" getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -60,7 +80,45 @@ describe('Properties', () => { /> ); + + wrapper.find('[data-test-subj="settings-gear"]').at(0).simulate('click'); + expect(wrapper.find('[data-test-subj="timeline-properties"]').exists()).toEqual(true); + expect(wrapper.find('button[data-test-subj="attach-timeline-case"]').prop('disabled')).toEqual( + false + ); + }); + + test('renders correctly draft timeline', () => { + const wrapper = mount( + + + + ); + + wrapper.find('[data-test-subj="settings-gear"]').at(0).simulate('click'); + + expect(wrapper.find('button[data-test-subj="attach-timeline-case"]').prop('disabled')).toEqual( + true + ); }); test('it renders an empty star icon when it is NOT a favorite', () => { @@ -76,6 +134,7 @@ describe('Properties', () => { description="" getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -103,6 +162,7 @@ describe('Properties', () => { description="" getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -132,6 +192,7 @@ describe('Properties', () => { description="" getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -159,6 +220,7 @@ describe('Properties', () => { description="" getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -191,6 +253,7 @@ describe('Properties', () => { description="" getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -222,6 +285,7 @@ describe('Properties', () => { description="" getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -256,6 +320,7 @@ describe('Properties', () => { description={description} getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -292,6 +357,7 @@ describe('Properties', () => { description={description} getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -326,6 +392,7 @@ describe('Properties', () => { description="" getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -360,6 +427,7 @@ describe('Properties', () => { description="" getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -392,6 +460,7 @@ describe('Properties', () => { description="" getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -421,6 +490,7 @@ describe('Properties', () => { description="" getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -448,6 +518,7 @@ describe('Properties', () => { description="" getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} diff --git a/x-pack/plugins/siem/public/timelines/components/timeline/properties/index.tsx b/x-pack/plugins/siem/public/timelines/components/timeline/properties/index.tsx index 502cc85ce907..d8966a58748e 100644 --- a/x-pack/plugins/siem/public/timelines/components/timeline/properties/index.tsx +++ b/x-pack/plugins/siem/public/timelines/components/timeline/properties/index.tsx @@ -6,6 +6,7 @@ import React, { useState, useCallback, useMemo } from 'react'; +import { TimelineStatus } from '../../../../../common/types/timeline'; import { useThrottledResizeObserver } from '../../../../common/components/utils'; import { Note } from '../../../../common/lib/note'; import { InputsModelId } from '../../../../common/store/inputs/constants'; @@ -31,6 +32,7 @@ interface Props { isFavorite: boolean; noteIds: string[]; timelineId: string; + status: TimelineStatus; title: string; toggleLock: ToggleLock; updateDescription: UpdateDescription; @@ -62,6 +64,7 @@ export const Properties = React.memo( isDatepickerLocked, isFavorite, noteIds, + status, timelineId, title, toggleLock, @@ -140,6 +143,7 @@ export const Properties = React.memo( showNotesFromWidth={width < showNotesThreshold} showTimelineModal={showTimelineModal} showUsersView={title.length > 0} + status={status} timelineId={timelineId} title={title} updateDescription={updateDescription} diff --git a/x-pack/plugins/siem/public/timelines/components/timeline/properties/properties_right.tsx b/x-pack/plugins/siem/public/timelines/components/timeline/properties/properties_right.tsx index 4149a958e889..963b67838e81 100644 --- a/x-pack/plugins/siem/public/timelines/components/timeline/properties/properties_right.tsx +++ b/x-pack/plugins/siem/public/timelines/components/timeline/properties/properties_right.tsx @@ -22,6 +22,7 @@ import { InspectButton, InspectButtonContainer } from '../../../../common/compon import * as i18n from './translations'; import { AssociateNote } from '../../notes/helpers'; import { Note } from '../../../../common/lib/note'; +import { TimelineStatus } from '../../../../../common/types/timeline'; export const PropertiesRightStyle = styled(EuiFlexGroup)` margin-right: 5px; @@ -79,6 +80,7 @@ interface Props { onCloseTimelineModal: () => void; onOpenTimelineModal: () => void; showTimelineModal: boolean; + status: TimelineStatus; title: string; updateNote: UpdateNote; } @@ -106,6 +108,7 @@ const PropertiesRightComponent: React.FC = ({ onCloseTimelineModal, onOpenTimelineModal, title, + status, }) => ( @@ -142,6 +145,7 @@ const PropertiesRightComponent: React.FC = ({ onClosePopover={onClosePopover} timelineId={timelineId} timelineTitle={title} + timelineStatus={status} /> diff --git a/x-pack/plugins/siem/public/timelines/containers/one/index.gql_query.ts b/x-pack/plugins/siem/public/timelines/containers/one/index.gql_query.ts index d70a419b99a3..47e80b005fb9 100644 --- a/x-pack/plugins/siem/public/timelines/containers/one/index.gql_query.ts +++ b/x-pack/plugins/siem/public/timelines/containers/one/index.gql_query.ts @@ -128,6 +128,7 @@ export const oneTimelineQuery = gql` updatedBy version } + status title timelineType templateTimelineId From 8373eb8680935a95c0f61e731b1f1e6332a968de Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 2 Jun 2020 12:47:13 +0300 Subject: [PATCH 17/50] Fix bug in tsvb metric add color rules (#67763) Co-authored-by: Elastic Machine --- .../public/application/components/color_rules.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/vis_type_timeseries/public/application/components/color_rules.js b/src/plugins/vis_type_timeseries/public/application/components/color_rules.js index a0cd20682222..46b2886daf36 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/color_rules.js +++ b/src/plugins/vis_type_timeseries/public/application/components/color_rules.js @@ -54,7 +54,7 @@ class ColorRulesUI extends Component { renderRow(row, i, items) { const defaults = { value: 0 }; const model = { ...defaults, ...row }; - const handleAdd = collectionActions.handleAdd.bind(null, this.props); + const handleAdd = () => collectionActions.handleAdd(this.props); const handleDelete = collectionActions.handleDelete.bind(null, this.props, model); const { intl } = this.props; const operatorOptions = [ From 7da774ff5a72dec56c70ece69cb83172e2f9f3cd Mon Sep 17 00:00:00 2001 From: Kevin Logan <56395104+kevinlog@users.noreply.github.com> Date: Tue, 2 Jun 2020 07:26:22 -0400 Subject: [PATCH 18/50] [Endpoint] add new policy fields (#67323) --- .../policy/store/policy_list/middleware.ts | 8 +- .../policy_list/mock_policy_result_list.ts | 40 ++++ .../pages/policy/view/policy_list.test.tsx | 65 ++++++ .../pages/policy/view/policy_list.tsx | 202 ++++++++++++++---- 4 files changed, 265 insertions(+), 50 deletions(-) create mode 100644 x-pack/plugins/siem/public/management/pages/policy/store/policy_list/mock_policy_result_list.ts create mode 100644 x-pack/plugins/siem/public/management/pages/policy/view/policy_list.test.tsx diff --git a/x-pack/plugins/siem/public/management/pages/policy/store/policy_list/middleware.ts b/x-pack/plugins/siem/public/management/pages/policy/store/policy_list/middleware.ts index 6054ec34b2d0..7259c0fd4d21 100644 --- a/x-pack/plugins/siem/public/management/pages/policy/store/policy_list/middleware.ts +++ b/x-pack/plugins/siem/public/management/pages/policy/store/policy_list/middleware.ts @@ -9,6 +9,7 @@ import { sendGetEndpointSpecificDatasources } from './services/ingest'; import { isOnPolicyListPage, urlSearchParams } from './selectors'; import { ImmutableMiddlewareFactory } from '../../../../../common/store'; import { Immutable } from '../../../../../../common/endpoint/types'; +import { initialPolicyListState } from './reducer'; export const policyListMiddlewareFactory: ImmutableMiddlewareFactory> = ( coreStart @@ -19,6 +20,7 @@ export const policyListMiddlewareFactory: ImmutableMiddlewareFactory GetPolicyListResponse = (options = {}) => { + const { + total = 1, + request_page_size: requestPageSize = 10, + request_page_index: requestPageIndex = 0, + } = options; + + // Skip any that are before the page we're on + const numberToSkip = requestPageSize * requestPageIndex; + + // total - numberToSkip is the count of non-skipped ones, but return no more than a pageSize, and no less than 0 + const actualCountToReturn = Math.max(Math.min(total - numberToSkip, requestPageSize), 0); + + const policies = []; + for (let index = 0; index < actualCountToReturn; index++) { + const generator = new EndpointDocGenerator('seed'); + policies.push(generator.generatePolicyDatasource()); + } + const mock: GetPolicyListResponse = { + items: policies, + total, + page: requestPageIndex, + perPage: requestPageSize, + success: true, + }; + return mock; +}; diff --git a/x-pack/plugins/siem/public/management/pages/policy/view/policy_list.test.tsx b/x-pack/plugins/siem/public/management/pages/policy/view/policy_list.test.tsx new file mode 100644 index 000000000000..a2901ab6bfe5 --- /dev/null +++ b/x-pack/plugins/siem/public/management/pages/policy/view/policy_list.test.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import * as reactTestingLibrary from '@testing-library/react'; + +import { PolicyList } from './index'; +import { mockPolicyResultList } from '../store/policy_list/mock_policy_result_list'; +import { AppContextTestRender, createAppRootMockRenderer } from '../../../../common/mock/endpoint'; +import { AppAction } from '../../../../common/store/actions'; + +describe('when on the policies page', () => { + let render: () => ReturnType; + let history: AppContextTestRender['history']; + let store: AppContextTestRender['store']; + + beforeEach(() => { + const mockedContext = createAppRootMockRenderer(); + ({ history, store } = mockedContext); + render = () => mockedContext.render(); + }); + + it('should show a table', async () => { + const renderResult = render(); + const table = await renderResult.findByTestId('policyTable'); + expect(table).not.toBeNull(); + }); + + describe('when list data loads', () => { + let firstPolicyID: string; + beforeEach(() => { + reactTestingLibrary.act(() => { + history.push('/management/policy'); + reactTestingLibrary.act(() => { + const policyListData = mockPolicyResultList({ total: 3 }); + firstPolicyID = policyListData.items[0].id; + const action: AppAction = { + type: 'serverReturnedPolicyListData', + payload: { + policyItems: policyListData.items, + total: policyListData.total, + pageSize: policyListData.perPage, + pageIndex: policyListData.page, + }, + }; + store.dispatch(action); + }); + }); + }); + it('should display rows in the table', async () => { + const renderResult = render(); + const rows = await renderResult.findAllByRole('row'); + expect(rows).toHaveLength(4); + }); + it('should display policy name value as a link', async () => { + const renderResult = render(); + const policyNameLink = (await renderResult.findAllByTestId('policyNameLink'))[0]; + expect(policyNameLink).not.toBeNull(); + expect(policyNameLink.getAttribute('href')).toContain(`policy/${firstPolicyID}`); + }); + }); +}); diff --git a/x-pack/plugins/siem/public/management/pages/policy/view/policy_list.tsx b/x-pack/plugins/siem/public/management/pages/policy/view/policy_list.tsx index 3a8004aa2ec6..2826289dab8e 100644 --- a/x-pack/plugins/siem/public/management/pages/policy/view/policy_list.tsx +++ b/x-pack/plugins/siem/public/management/pages/policy/view/policy_list.tsx @@ -4,20 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, useEffect, useMemo } from 'react'; -import { EuiBasicTable, EuiText, EuiTableFieldDataColumnType, EuiLink } from '@elastic/eui'; +import React, { useCallback, useEffect, useMemo, CSSProperties, useState } from 'react'; +import { + EuiBasicTable, + EuiText, + EuiFlexGroup, + EuiFlexItem, + EuiTableFieldDataColumnType, + EuiLink, + EuiPopover, + EuiContextMenuPanelProps, + EuiContextMenuItem, + EuiButtonIcon, + EuiContextMenuPanel, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { useDispatch } from 'react-redux'; import { useLocation, useHistory } from 'react-router-dom'; -import { - selectApiError, - selectIsLoading, - selectPageIndex, - selectPageSize, - selectPolicyItems, - selectTotal, -} from '../store/policy_list/selectors'; +import { createStructuredSelector } from 'reselect'; +import { CreateStructuredSelector } from '../../../../common/store'; +import * as selectors from '../store/policy_list/selectors'; import { usePolicyListSelector } from './policy_hooks'; import { PolicyListAction } from '../store/policy_list'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; @@ -27,11 +34,53 @@ import { LinkToApp } from '../../../../common/components/endpoint/link_to_app'; import { ManagementPageView } from '../../../components/management_page_view'; import { SpyRoute } from '../../../../common/utils/route/spy_routes'; import { getManagementUrl } from '../../../common/routing'; +import { FormattedDateAndTime } from '../../../../common/components/endpoint/formatted_date_time'; interface TableChangeCallbackArguments { page: { index: number; size: number }; } +interface PackageData { + name: string; + title: string; + version: string; +} + +const NO_WRAP_TRUNCATE_STYLE: CSSProperties = Object.freeze({ + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', +}); + +// eslint-disable-next-line react/display-name +export const TableRowActions = React.memo<{ items: EuiContextMenuPanelProps['items'] }>( + ({ items }) => { + const [isOpen, setIsOpen] = useState(false); + const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]); + const handleToggleMenu = useCallback(() => setIsOpen(!isOpen), [isOpen]); + + return ( + + } + isOpen={isOpen} + closePopover={handleCloseMenu} + > + + + ); + } +); + const PolicyLink: React.FC<{ name: string; route: string; href: string }> = ({ name, route, @@ -40,24 +89,32 @@ const PolicyLink: React.FC<{ name: string; route: string; href: string }> = ({ const clickHandler = useNavigateByRouterEventHandler(route); return ( // eslint-disable-next-line @elastic/eui/href-or-on-click - + {name} ); }; +const selector = (createStructuredSelector as CreateStructuredSelector)(selectors); export const PolicyList = React.memo(() => { const { services, notifications } = useKibana(); const history = useHistory(); const location = useLocation(); const dispatch = useDispatch<(action: PolicyListAction) => void>(); - const policyItems = usePolicyListSelector(selectPolicyItems); - const pageIndex = usePolicyListSelector(selectPageIndex); - const pageSize = usePolicyListSelector(selectPageSize); - const totalItemCount = usePolicyListSelector(selectTotal); - const loading = usePolicyListSelector(selectIsLoading); - const apiError = usePolicyListSelector(selectApiError); + const { + selectPolicyItems: policyItems, + selectPageIndex: pageIndex, + selectPageSize: pageSize, + selectTotal: totalItemCount, + selectIsLoading: loading, + selectApiError: apiError, + } = usePolicyListSelector(selector); useEffect(() => { if (apiError) { @@ -94,58 +151,110 @@ export const PolicyList = React.memo(() => { defaultMessage: 'Policy Name', }), // eslint-disable-next-line react/display-name - render: (value: string, item: Immutable) => { + render: (name: string, item: Immutable) => { const routePath = getManagementUrl({ name: 'policyDetails', policyId: item.id, excludePrefix: true, }); const routeUrl = getManagementUrl({ name: 'policyDetails', policyId: item.id }); - return ; + return ( + + + + + + + + + + + ); }, - truncateText: true, }, { - field: 'revision', - name: i18n.translate('xpack.siem.endpoint.policyList.revisionField', { - defaultMessage: 'Revision', + field: 'created_by', + name: i18n.translate('xpack.siem.endpoint.policyList.createdBy', { + defaultMessage: 'Created By', }), - dataType: 'number', + truncateText: true, }, { - field: 'package', - name: i18n.translate('xpack.siem.endpoint.policyList.versionField', { - defaultMessage: 'Version', + field: 'created_at', + name: i18n.translate('xpack.siem.endpoint.policyList.createdAt', { + defaultMessage: 'Created Date', }), - render(pkg) { - return `${pkg.title} v${pkg.version}`; + render(createdAt: string) { + return ; }, }, { - field: 'description', - name: i18n.translate('xpack.siem.endpoint.policyList.descriptionField', { - defaultMessage: 'Description', + field: 'updated_by', + name: i18n.translate('xpack.siem.endpoint.policyList.updatedBy', { + defaultMessage: 'Last Updated By', }), truncateText: true, }, { - field: 'config_id', - name: i18n.translate('xpack.siem.endpoint.policyList.agentConfigField', { - defaultMessage: 'Agent Configuration', + field: 'updated_at', + name: i18n.translate('xpack.siem.endpoint.policyList.updatedAt', { + defaultMessage: 'Last Updated', }), - render(version: string) { - return ( - - {version} - - ); + render(updatedAt: string) { + return ; }, }, + { + field: 'package', + name: i18n.translate('xpack.siem.endpoint.policyList.versionFieldLabel', { + defaultMessage: 'Version', + }), + render(pkg: Immutable) { + return i18n.translate('xpack.siem.endpoint.policyList.versionField', { + defaultMessage: '{title} v{version}', + values: { + title: pkg.title, + version: pkg.version, + }, + }); + }, + }, + { + field: '', + name: 'Actions', + actions: [ + { + // eslint-disable-next-line react/display-name + render: (item: Immutable) => { + return ( + + + + + , + ]} + /> + ); + }, + }, + ], + }, ], [services.application] ); @@ -174,6 +283,7 @@ export const PolicyList = React.memo(() => { pagination={paginationSetup} onChange={handleTableChange} data-test-subj="policyTable" + hasActions={false} /> From 7d0ffb53bcb9d302e9f1f675b70d36b235f78e8d Mon Sep 17 00:00:00 2001 From: Sandra Gonzales Date: Tue, 2 Jun 2020 08:32:24 -0400 Subject: [PATCH 19/50] install default packages in parallel (#67893) --- .../server/services/epm/packages/install.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) 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 7c0d5d571f6a..736711f9152e 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 @@ -52,22 +52,22 @@ export async function ensureInstalledDefaultPackages( const installations = []; for (const pkgName in DefaultPackages) { if (!DefaultPackages.hasOwnProperty(pkgName)) continue; - const installation = await ensureInstalledPackage({ + const installation = ensureInstalledPackage({ savedObjectsClient, pkgName, callCluster, }); - if (installation) installations.push(installation); + installations.push(installation); } - return installations; + return Promise.all(installations); } export async function ensureInstalledPackage(options: { savedObjectsClient: SavedObjectsClientContract; pkgName: string; callCluster: CallESAsCurrentUser; -}): Promise { +}): Promise { const { savedObjectsClient, pkgName, callCluster } = options; const installedPackage = await getInstallation({ savedObjectsClient, pkgName }); if (installedPackage) { @@ -79,7 +79,9 @@ export async function ensureInstalledPackage(options: { pkgName, callCluster, }); - return await getInstallation({ savedObjectsClient, pkgName }); + const installation = await getInstallation({ savedObjectsClient, pkgName }); + if (!installation) throw new Error(`could not get installation ${pkgName}`); + return installation; } export async function installPackage(options: { From a091124fab89573e6744582a09d4eea19e4c28f7 Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Tue, 2 Jun 2020 15:15:21 +0200 Subject: [PATCH 20/50] Move application.applications$ to public contract (#67463) * expose applications$ on public contract * review comments --- ...e-public.applicationstart.applications_.md | 18 ++++++ ...ana-plugin-core-public.applicationstart.md | 1 + ...ana-plugin-core-public.legacyapp.appurl.md | 11 ++++ ...-public.legacyapp.disablesuburltracking.md | 11 ++++ ...-core-public.legacyapp.linktolastsuburl.md | 11 ++++ .../kibana-plugin-core-public.legacyapp.md | 22 +++++++ ...plugin-core-public.legacyapp.suburlbase.md | 11 ++++ .../core/public/kibana-plugin-core-public.md | 3 + ...kibana-plugin-core-public.publicappinfo.md | 15 +++++ ...-plugin-core-public.publiclegacyappinfo.md | 15 +++++ .../application/application_service.mock.ts | 7 ++- .../application/application_service.test.ts | 15 ++++- .../application/application_service.tsx | 7 ++- src/core/public/application/index.ts | 4 +- src/core/public/application/types.ts | 42 +++++++++----- src/core/public/application/utils.test.ts | 57 ++++++++++++++++++- src/core/public/application/utils.ts | 18 +++++- src/core/public/chrome/chrome_service.test.ts | 7 ++- .../chrome/nav_links/to_nav_link.test.ts | 9 +-- .../public/chrome/nav_links/to_nav_link.ts | 9 ++- src/core/public/index.ts | 3 + src/core/public/legacy/legacy_service.ts | 1 + src/core/public/plugins/plugin_context.ts | 1 + src/core/public/public.api.md | 23 ++++++++ 24 files changed, 286 insertions(+), 35 deletions(-) create mode 100644 docs/development/core/public/kibana-plugin-core-public.applicationstart.applications_.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.legacyapp.appurl.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.legacyapp.disablesuburltracking.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.legacyapp.linktolastsuburl.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.legacyapp.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.legacyapp.suburlbase.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.publicappinfo.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.publiclegacyappinfo.md diff --git a/docs/development/core/public/kibana-plugin-core-public.applicationstart.applications_.md b/docs/development/core/public/kibana-plugin-core-public.applicationstart.applications_.md new file mode 100644 index 000000000000..d428faa500fa --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.applicationstart.applications_.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ApplicationStart](./kibana-plugin-core-public.applicationstart.md) > [applications$](./kibana-plugin-core-public.applicationstart.applications_.md) + +## ApplicationStart.applications$ property + +Observable emitting the list of currently registered apps and their associated status. + +Signature: + +```typescript +applications$: Observable>; +``` + +## Remarks + +Applications disabled by [Capabilities](./kibana-plugin-core-public.capabilities.md) will not be present in the map. Applications manually disabled from the client-side using an [application updater](./kibana-plugin-core-public.appupdater.md) are present, with their status properly set as `inaccessible`. + diff --git a/docs/development/core/public/kibana-plugin-core-public.applicationstart.md b/docs/development/core/public/kibana-plugin-core-public.applicationstart.md index 6f45bab3ebd2..896de2de32dd 100644 --- a/docs/development/core/public/kibana-plugin-core-public.applicationstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.applicationstart.md @@ -15,6 +15,7 @@ export interface ApplicationStart | Property | Type | Description | | --- | --- | --- | +| [applications$](./kibana-plugin-core-public.applicationstart.applications_.md) | Observable<ReadonlyMap<string, PublicAppInfo | PublicLegacyAppInfo>> | Observable emitting the list of currently registered apps and their associated status. | | [capabilities](./kibana-plugin-core-public.applicationstart.capabilities.md) | RecursiveReadonly<Capabilities> | Gets the read-only capabilities. | | [currentAppId$](./kibana-plugin-core-public.applicationstart.currentappid_.md) | Observable<string | undefined> | An observable that emits the current application id and each subsequent id update. | diff --git a/docs/development/core/public/kibana-plugin-core-public.legacyapp.appurl.md b/docs/development/core/public/kibana-plugin-core-public.legacyapp.appurl.md new file mode 100644 index 000000000000..292bf2996283 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.legacyapp.appurl.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyApp](./kibana-plugin-core-public.legacyapp.md) > [appUrl](./kibana-plugin-core-public.legacyapp.appurl.md) + +## LegacyApp.appUrl property + +Signature: + +```typescript +appUrl: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.legacyapp.disablesuburltracking.md b/docs/development/core/public/kibana-plugin-core-public.legacyapp.disablesuburltracking.md new file mode 100644 index 000000000000..af4d0eb7969d --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.legacyapp.disablesuburltracking.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyApp](./kibana-plugin-core-public.legacyapp.md) > [disableSubUrlTracking](./kibana-plugin-core-public.legacyapp.disablesuburltracking.md) + +## LegacyApp.disableSubUrlTracking property + +Signature: + +```typescript +disableSubUrlTracking?: boolean; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.legacyapp.linktolastsuburl.md b/docs/development/core/public/kibana-plugin-core-public.legacyapp.linktolastsuburl.md new file mode 100644 index 000000000000..fa1314b74fd8 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.legacyapp.linktolastsuburl.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyApp](./kibana-plugin-core-public.legacyapp.md) > [linkToLastSubUrl](./kibana-plugin-core-public.legacyapp.linktolastsuburl.md) + +## LegacyApp.linkToLastSubUrl property + +Signature: + +```typescript +linkToLastSubUrl?: boolean; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.legacyapp.md b/docs/development/core/public/kibana-plugin-core-public.legacyapp.md new file mode 100644 index 000000000000..06533aaa9917 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.legacyapp.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyApp](./kibana-plugin-core-public.legacyapp.md) + +## LegacyApp interface + + +Signature: + +```typescript +export interface LegacyApp extends AppBase +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [appUrl](./kibana-plugin-core-public.legacyapp.appurl.md) | string | | +| [disableSubUrlTracking](./kibana-plugin-core-public.legacyapp.disablesuburltracking.md) | boolean | | +| [linkToLastSubUrl](./kibana-plugin-core-public.legacyapp.linktolastsuburl.md) | boolean | | +| [subUrlBase](./kibana-plugin-core-public.legacyapp.suburlbase.md) | string | | + diff --git a/docs/development/core/public/kibana-plugin-core-public.legacyapp.suburlbase.md b/docs/development/core/public/kibana-plugin-core-public.legacyapp.suburlbase.md new file mode 100644 index 000000000000..44a1e52ccd24 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.legacyapp.suburlbase.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyApp](./kibana-plugin-core-public.legacyapp.md) > [subUrlBase](./kibana-plugin-core-public.legacyapp.suburlbase.md) + +## LegacyApp.subUrlBase property + +Signature: + +```typescript +subUrlBase?: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.md b/docs/development/core/public/kibana-plugin-core-public.md index b2524ec48c75..9e4afe0f5133 100644 --- a/docs/development/core/public/kibana-plugin-core-public.md +++ b/docs/development/core/public/kibana-plugin-core-public.md @@ -90,6 +90,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [IHttpResponseInterceptorOverrides](./kibana-plugin-core-public.ihttpresponseinterceptoroverrides.md) | Properties that can be returned by HttpInterceptor.request to override the response. | | [ImageValidation](./kibana-plugin-core-public.imagevalidation.md) | | | [IUiSettingsClient](./kibana-plugin-core-public.iuisettingsclient.md) | Client-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. [IUiSettingsClient](./kibana-plugin-core-public.iuisettingsclient.md) | +| [LegacyApp](./kibana-plugin-core-public.legacyapp.md) | | | [LegacyCoreSetup](./kibana-plugin-core-public.legacycoresetup.md) | Setup interface exposed to the legacy platform via the ui/new_platform module. | | [LegacyCoreStart](./kibana-plugin-core-public.legacycorestart.md) | Start interface exposed to the legacy platform via the ui/new_platform module. | | [LegacyNavLink](./kibana-plugin-core-public.legacynavlink.md) | | @@ -162,6 +163,8 @@ The plugin integrates with the core system via lifecycle events: `setup` | [NavType](./kibana-plugin-core-public.navtype.md) | | | [PluginInitializer](./kibana-plugin-core-public.plugininitializer.md) | The plugin export at the root of a plugin's public directory should conform to this interface. | | [PluginOpaqueId](./kibana-plugin-core-public.pluginopaqueid.md) | | +| [PublicAppInfo](./kibana-plugin-core-public.publicappinfo.md) | Public information about a registered [application](./kibana-plugin-core-public.app.md) | +| [PublicLegacyAppInfo](./kibana-plugin-core-public.publiclegacyappinfo.md) | Information about a registered [legacy application](./kibana-plugin-core-public.legacyapp.md) | | [PublicUiSettingsParams](./kibana-plugin-core-public.publicuisettingsparams.md) | A sub-set of [UiSettingsParams](./kibana-plugin-core-public.uisettingsparams.md) exposed to the client-side. | | [RecursiveReadonly](./kibana-plugin-core-public.recursivereadonly.md) | | | [SavedObjectAttribute](./kibana-plugin-core-public.savedobjectattribute.md) | Type definition for a Saved Object attribute value | diff --git a/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md b/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md new file mode 100644 index 000000000000..c70f3a97a888 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [PublicAppInfo](./kibana-plugin-core-public.publicappinfo.md) + +## PublicAppInfo type + +Public information about a registered [application](./kibana-plugin-core-public.app.md) + +Signature: + +```typescript +export declare type PublicAppInfo = Omit & { + legacy: false; +}; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.publiclegacyappinfo.md b/docs/development/core/public/kibana-plugin-core-public.publiclegacyappinfo.md new file mode 100644 index 000000000000..cc3e9de3193c --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.publiclegacyappinfo.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [PublicLegacyAppInfo](./kibana-plugin-core-public.publiclegacyappinfo.md) + +## PublicLegacyAppInfo type + +Information about a registered [legacy application](./kibana-plugin-core-public.legacyapp.md) + +Signature: + +```typescript +export declare type PublicLegacyAppInfo = Omit & { + legacy: true; +}; +``` diff --git a/src/core/public/application/application_service.mock.ts b/src/core/public/application/application_service.mock.ts index 24c0e66359af..300b09e17d15 100644 --- a/src/core/public/application/application_service.mock.ts +++ b/src/core/public/application/application_service.mock.ts @@ -25,8 +25,8 @@ import { InternalApplicationStart, ApplicationStart, InternalApplicationSetup, - App, - LegacyApp, + PublicAppInfo, + PublicLegacyAppInfo, } from './types'; import { ApplicationServiceContract } from './test_types'; @@ -47,6 +47,7 @@ const createStartContractMock = (): jest.Mocked => { const currentAppId$ = new Subject(); return { + applications$: new BehaviorSubject>(new Map()), currentAppId$: currentAppId$.asObservable(), capabilities: capabilitiesServiceMock.createStartContract().capabilities, navigateToApp: jest.fn(), @@ -60,7 +61,7 @@ const createInternalStartContractMock = (): jest.Mocked(); return { - applications$: new BehaviorSubject>(new Map()), + applications$: new BehaviorSubject>(new Map()), capabilities: capabilitiesServiceMock.createStartContract().capabilities, currentAppId$: currentAppId$.asObservable(), getComponent: jest.fn(), diff --git a/src/core/public/application/application_service.test.ts b/src/core/public/application/application_service.test.ts index b65a8581e5b5..400d1881a5af 100644 --- a/src/core/public/application/application_service.test.ts +++ b/src/core/public/application/application_service.test.ts @@ -34,7 +34,15 @@ import { httpServiceMock } from '../http/http_service.mock'; import { overlayServiceMock } from '../overlays/overlay_service.mock'; import { MockLifecycle } from './test_types'; import { ApplicationService } from './application_service'; -import { App, AppNavLinkStatus, AppStatus, AppUpdater, LegacyApp } from './types'; +import { + App, + PublicAppInfo, + AppNavLinkStatus, + AppStatus, + AppUpdater, + LegacyApp, + PublicLegacyAppInfo, +} from './types'; import { act } from 'react-dom/test-utils'; const createApp = (props: Partial): App => { @@ -366,7 +374,10 @@ describe('#setup()', () => { setup.registerAppUpdater(statusUpdater); const start = await service.start(startDeps); - let latestValue: ReadonlyMap = new Map(); + let latestValue: ReadonlyMap = new Map< + string, + PublicAppInfo | PublicLegacyAppInfo + >(); start.applications$.subscribe((apps) => { latestValue = apps; }); diff --git a/src/core/public/application/application_service.tsx b/src/core/public/application/application_service.tsx index b52b4984fb5e..2224f72e2bd9 100644 --- a/src/core/public/application/application_service.tsx +++ b/src/core/public/application/application_service.tsx @@ -46,7 +46,7 @@ import { Mounter, } from './types'; import { getLeaveAction, isConfirmAction } from './application_leave'; -import { appendAppPath, parseAppUrl, relativeToAbsolute } from './utils'; +import { appendAppPath, parseAppUrl, relativeToAbsolute, getAppInfo } from './utils'; interface SetupDeps { context: ContextSetup; @@ -291,7 +291,10 @@ export class ApplicationService { }; return { - applications$, + applications$: applications$.pipe( + map((apps) => new Map([...apps.entries()].map(([id, app]) => [id, getAppInfo(app)]))), + shareReplay(1) + ), capabilities, currentAppId$: this.currentAppId$.pipe( filter((appId) => appId !== undefined), diff --git a/src/core/public/application/index.ts b/src/core/public/application/index.ts index ec10d2bc2287..d51a4c0d69d4 100644 --- a/src/core/public/application/index.ts +++ b/src/core/public/application/index.ts @@ -39,7 +39,9 @@ export { AppLeaveAction, AppLeaveDefaultAction, AppLeaveConfirmAction, + LegacyApp, + PublicAppInfo, + PublicLegacyAppInfo, // Internal types InternalApplicationStart, - LegacyApp, } from './types'; diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts index c07d929fc5ce..2269fd0a4ca4 100644 --- a/src/core/public/application/types.ts +++ b/src/core/public/application/types.ts @@ -235,7 +235,7 @@ export interface App extends AppBase { appRoute?: string; } -/** @internal */ +/** @public */ export interface LegacyApp extends AppBase { appUrl: string; subUrlBase?: string; @@ -243,6 +243,24 @@ export interface LegacyApp extends AppBase { disableSubUrlTracking?: boolean; } +/** + * Public information about a registered {@link App | application} + * + * @public + */ +export type PublicAppInfo = Omit & { + legacy: false; +}; + +/** + * Information about a registered {@link LegacyApp | legacy application} + * + * @public + */ +export type PublicLegacyAppInfo = Omit & { + legacy: true; +}; + /** * A mount function called when the user navigates to this app's route. * @@ -649,6 +667,15 @@ export interface ApplicationStart { */ capabilities: RecursiveReadonly; + /** + * Observable emitting the list of currently registered apps and their associated status. + * + * @remarks + * Applications disabled by {@link Capabilities} will not be present in the map. Applications manually disabled from + * the client-side using an {@link AppUpdater | application updater} are present, with their status properly set as `inaccessible`. + */ + applications$: Observable>; + /** * Navigate to a given app * @@ -721,18 +748,7 @@ export interface ApplicationStart { } /** @internal */ -export interface InternalApplicationStart - extends Pick< - ApplicationStart, - 'capabilities' | 'navigateToApp' | 'navigateToUrl' | 'getUrlForApp' | 'currentAppId$' - > { - /** - * Apps available based on the current capabilities. - * Should be used to show navigation links and make routing decisions. - * Applications manually disabled from the client-side using {@link AppUpdater} - */ - applications$: Observable>; - +export interface InternalApplicationStart extends Omit { /** * Register a context provider for application mounting. Will only be available to applications that depend on the * plugin that registered this context. Deprecated, use {@link CoreSetup.getStartServices}. diff --git a/src/core/public/application/utils.test.ts b/src/core/public/application/utils.test.ts index a86a1206fc98..b41945aa4368 100644 --- a/src/core/public/application/utils.test.ts +++ b/src/core/public/application/utils.test.ts @@ -17,7 +17,8 @@ * under the License. */ -import { LegacyApp, App } from './types'; +import { of } from 'rxjs'; +import { LegacyApp, App, AppStatus, AppNavLinkStatus } from './types'; import { BasePath } from '../http/base_path'; import { removeSlashes, @@ -25,6 +26,7 @@ import { isLegacyApp, relativeToAbsolute, parseAppUrl, + getAppInfo, } from './utils'; describe('removeSlashes', () => { @@ -459,3 +461,56 @@ describe('parseAppUrl', () => { }); }); }); + +describe('getAppInfo', () => { + const createApp = (props: Partial = {}): App => ({ + mount: () => () => undefined, + updater$: of(() => undefined), + id: 'some-id', + title: 'some-title', + status: AppStatus.accessible, + navLinkStatus: AppNavLinkStatus.default, + appRoute: `/app/some-id`, + legacy: false, + ...props, + }); + + const createLegacyApp = (props: Partial = {}): LegacyApp => ({ + appUrl: '/my-app-url', + updater$: of(() => undefined), + id: 'some-id', + title: 'some-title', + status: AppStatus.accessible, + navLinkStatus: AppNavLinkStatus.default, + legacy: true, + ...props, + }); + + it('converts an application and remove sensitive properties', () => { + const app = createApp(); + const info = getAppInfo(app); + + expect(info).toEqual({ + id: 'some-id', + title: 'some-title', + status: AppStatus.accessible, + navLinkStatus: AppNavLinkStatus.default, + appRoute: `/app/some-id`, + legacy: false, + }); + }); + + it('converts a legacy application and remove sensitive properties', () => { + const app = createLegacyApp(); + const info = getAppInfo(app); + + expect(info).toEqual({ + appUrl: '/my-app-url', + id: 'some-id', + title: 'some-title', + status: AppStatus.accessible, + navLinkStatus: AppNavLinkStatus.default, + legacy: true, + }); + }); +}); diff --git a/src/core/public/application/utils.ts b/src/core/public/application/utils.ts index 8987a9402f2d..1abd71054874 100644 --- a/src/core/public/application/utils.ts +++ b/src/core/public/application/utils.ts @@ -18,7 +18,7 @@ */ import { IBasePath } from '../http'; -import { App, LegacyApp } from './types'; +import { App, LegacyApp, PublicAppInfo, PublicLegacyAppInfo } from './types'; export interface AppUrlInfo { app: string; @@ -119,3 +119,19 @@ const removeBasePath = (url: string, basePath: IBasePath, origin: string): strin } return basePath.remove(url); }; + +export function getAppInfo(app: App | LegacyApp): PublicAppInfo | PublicLegacyAppInfo { + if (isLegacyApp(app)) { + const { updater$, ...infos } = app; + return { + ...infos, + legacy: true, + }; + } else { + const { updater$, mount, ...infos } = app; + return { + ...infos, + legacy: false, + }; + } +} diff --git a/src/core/public/chrome/chrome_service.test.ts b/src/core/public/chrome/chrome_service.test.ts index 0bc305ed9e28..e39733cc10de 100644 --- a/src/core/public/chrome/chrome_service.test.ts +++ b/src/core/public/chrome/chrome_service.test.ts @@ -21,7 +21,7 @@ import { shallow } from 'enzyme'; import React from 'react'; import * as Rx from 'rxjs'; import { take, toArray } from 'rxjs/operators'; -import { App } from '../application'; +import { App, PublicAppInfo } from '../application'; import { applicationServiceMock } from '../application/application_service.mock'; import { docLinksServiceMock } from '../doc_links/doc_links_service.mock'; import { httpServiceMock } from '../http/http_service.mock'; @@ -29,6 +29,7 @@ import { injectedMetadataServiceMock } from '../injected_metadata/injected_metad import { notificationServiceMock } from '../notifications/notifications_service.mock'; import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock'; import { ChromeService } from './chrome_service'; +import { getAppInfo } from '../application/utils'; class FakeApp implements App { public title = `${this.id} App`; @@ -55,8 +56,8 @@ function defaultStartDeps(availableApps?: App[]) { }; if (availableApps) { - deps.application.applications$ = new Rx.BehaviorSubject>( - new Map(availableApps.map((app) => [app.id, app])) + deps.application.applications$ = new Rx.BehaviorSubject>( + new Map(availableApps.map((app) => [app.id, getAppInfo(app) as PublicAppInfo])) ); } diff --git a/src/core/public/chrome/nav_links/to_nav_link.test.ts b/src/core/public/chrome/nav_links/to_nav_link.test.ts index 4c319873af80..ba04dbed49cd 100644 --- a/src/core/public/chrome/nav_links/to_nav_link.test.ts +++ b/src/core/public/chrome/nav_links/to_nav_link.test.ts @@ -17,15 +17,12 @@ * under the License. */ -import { App, AppMount, AppNavLinkStatus, AppStatus, LegacyApp } from '../../application'; +import { PublicAppInfo, AppNavLinkStatus, AppStatus, PublicLegacyAppInfo } from '../../application'; import { toNavLink } from './to_nav_link'; import { httpServiceMock } from '../../mocks'; -function mount() {} - -const app = (props: Partial = {}): App => ({ - mount: (mount as unknown) as AppMount, +const app = (props: Partial = {}): PublicAppInfo => ({ id: 'some-id', title: 'some-title', status: AppStatus.accessible, @@ -35,7 +32,7 @@ const app = (props: Partial = {}): App => ({ ...props, }); -const legacyApp = (props: Partial = {}): LegacyApp => ({ +const legacyApp = (props: Partial = {}): PublicLegacyAppInfo => ({ appUrl: '/my-app-url', id: 'some-id', title: 'some-title', diff --git a/src/core/public/chrome/nav_links/to_nav_link.ts b/src/core/public/chrome/nav_links/to_nav_link.ts index 24744fe53c82..b8f97f9ddc00 100644 --- a/src/core/public/chrome/nav_links/to_nav_link.ts +++ b/src/core/public/chrome/nav_links/to_nav_link.ts @@ -17,12 +17,15 @@ * under the License. */ -import { App, AppNavLinkStatus, AppStatus, LegacyApp } from '../../application'; +import { PublicAppInfo, AppNavLinkStatus, AppStatus, PublicLegacyAppInfo } from '../../application'; import { IBasePath } from '../../http'; import { NavLinkWrapper } from './nav_link'; import { appendAppPath } from '../../application/utils'; -export function toNavLink(app: App | LegacyApp, basePath: IBasePath): NavLinkWrapper { +export function toNavLink( + app: PublicAppInfo | PublicLegacyAppInfo, + basePath: IBasePath +): NavLinkWrapper { const useAppStatus = app.navLinkStatus === AppNavLinkStatus.default; const relativeBaseUrl = isLegacyApp(app) ? basePath.prepend(app.appUrl) @@ -63,6 +66,6 @@ export function relativeToAbsolute(url: string) { return a.href; } -function isLegacyApp(app: App | LegacyApp): app is LegacyApp { +function isLegacyApp(app: PublicAppInfo | PublicLegacyAppInfo): app is PublicLegacyAppInfo { return app.legacy === true; } diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 3698fdcfe951..aa037329b1b6 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -104,6 +104,7 @@ export { ApplicationSetup, ApplicationStart, App, + PublicAppInfo, AppBase, AppMount, AppMountDeprecated, @@ -120,6 +121,8 @@ export { AppUpdatableFields, AppUpdater, ScopedHistory, + LegacyApp, + PublicLegacyAppInfo, } from './application'; export { diff --git a/src/core/public/legacy/legacy_service.ts b/src/core/public/legacy/legacy_service.ts index 810416cdbfe1..d77676b350f9 100644 --- a/src/core/public/legacy/legacy_service.ts +++ b/src/core/public/legacy/legacy_service.ts @@ -131,6 +131,7 @@ export class LegacyPlatformService { const legacyCore: LegacyCoreStart = { ...core, application: { + applications$: core.application.applications$, currentAppId$: core.application.currentAppId$, capabilities: core.application.capabilities, getUrlForApp: core.application.getUrlForApp, diff --git a/src/core/public/plugins/plugin_context.ts b/src/core/public/plugins/plugin_context.ts index c688373630a0..65c6b6ce4edb 100644 --- a/src/core/public/plugins/plugin_context.ts +++ b/src/core/public/plugins/plugin_context.ts @@ -135,6 +135,7 @@ export function createPluginStartContext< ): CoreStart { return { application: { + applications$: deps.application.applications$, currentAppId$: deps.application.currentAppId$, capabilities: deps.application.capabilities, navigateToApp: deps.application.navigateToApp, diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 90c5dbb5f655..bae0f9a2281c 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -106,6 +106,7 @@ export interface ApplicationSetup { // @public (undocumented) export interface ApplicationStart { + applications$: Observable>; capabilities: RecursiveReadonly; currentAppId$: Observable; getUrlForApp(appId: string, options?: { @@ -857,6 +858,18 @@ export interface IUiSettingsClient { set: (key: string, value: any) => Promise; } +// @public (undocumented) +export interface LegacyApp extends AppBase { + // (undocumented) + appUrl: string; + // (undocumented) + disableSubUrlTracking?: boolean; + // (undocumented) + linkToLastSubUrl?: boolean; + // (undocumented) + subUrlBase?: string; +} + // @public @deprecated export interface LegacyCoreSetup extends CoreSetup { // Warning: (ae-forgotten-export) The symbol "InjectedMetadataSetup" needs to be exported by the entry point index.d.ts @@ -993,6 +1006,16 @@ export interface PluginInitializerContext // @public (undocumented) export type PluginOpaqueId = symbol; +// @public +export type PublicAppInfo = Omit & { + legacy: false; +}; + +// @public +export type PublicLegacyAppInfo = Omit & { + legacy: true; +}; + // @public export type PublicUiSettingsParams = Omit; From d550131b9e4fdff8e43b51838cd70fa96e477de7 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 2 Jun 2020 08:38:25 -0600 Subject: [PATCH 21/50] [Maps] fix mapbox glyphs error when EMS access is turned off (#67427) * [Maps] fix mapbox glyphs error when EMS access is turned off * santize range parameter * add api test * clean up mapbox view * add jest test for getGlyphsUrl * add license file * remove unneeded font files Co-authored-by: Elastic Machine --- src/dev/precommit_hook/casing_check_config.js | 2 + x-pack/plugins/maps/common/constants.ts | 1 + .../connected_components/map/mb/view.js | 5 +- x-pack/plugins/maps/public/meta.js | 23 ++++--- x-pack/plugins/maps/public/meta.test.js | 50 +++++++++++++- .../maps/server/fonts/open_sans/0-255.pbf | Bin 0 -> 74696 bytes .../maps/server/fonts/open_sans/1024-1279.pbf | Bin 0 -> 122545 bytes .../maps/server/fonts/open_sans/256-511.pbf | Bin 0 -> 66481 bytes .../maps/server/fonts/open_sans/768-1023.pbf | Bin 0 -> 33767 bytes .../maps/server/fonts/open_sans/8192-8447.pbf | Bin 0 -> 9663 bytes .../maps/server/fonts/open_sans/license.txt | 53 +++++++++++++++ x-pack/plugins/maps/server/routes.js | 62 ++++++++++++++---- .../api_integration/apis/maps/fonts_api.js | 21 ++++++ .../test/api_integration/apis/maps/index.js | 1 + 14 files changed, 189 insertions(+), 29 deletions(-) create mode 100644 x-pack/plugins/maps/server/fonts/open_sans/0-255.pbf create mode 100644 x-pack/plugins/maps/server/fonts/open_sans/1024-1279.pbf create mode 100644 x-pack/plugins/maps/server/fonts/open_sans/256-511.pbf create mode 100644 x-pack/plugins/maps/server/fonts/open_sans/768-1023.pbf create mode 100644 x-pack/plugins/maps/server/fonts/open_sans/8192-8447.pbf create mode 100644 x-pack/plugins/maps/server/fonts/open_sans/license.txt create mode 100644 x-pack/test/api_integration/apis/maps/fonts_api.js diff --git a/src/dev/precommit_hook/casing_check_config.js b/src/dev/precommit_hook/casing_check_config.js index 2eee3b2c53bd..d968a365e7bb 100644 --- a/src/dev/precommit_hook/casing_check_config.js +++ b/src/dev/precommit_hook/casing_check_config.js @@ -64,6 +64,8 @@ export const IGNORE_FILE_GLOBS = [ 'x-pack/plugins/apm/public/**/*', 'x-pack/plugins/apm/scripts/**/*', 'x-pack/plugins/apm/e2e/**/*', + + 'x-pack/plugins/maps/server/fonts/**/*', ]; /** diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index 8fa44c512df4..d357f11f5e3e 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -30,6 +30,7 @@ export const TELEMETRY_TYPE = 'maps-telemetry'; export const MAP_APP_PATH = `app/${APP_ID}`; export const GIS_API_PATH = `api/${APP_ID}`; export const INDEX_SETTINGS_API_PATH = `${GIS_API_PATH}/indexSettings`; +export const FONTS_API_PATH = `${GIS_API_PATH}/fonts`; export const MAP_BASE_URL = `/${MAP_APP_PATH}#/${MAP_SAVED_OBJECT_TYPE}`; diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/view.js b/x-pack/plugins/maps/public/connected_components/map/mb/view.js index c4b28e33747e..42235bfd5442 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/view.js +++ b/x-pack/plugins/maps/public/connected_components/map/mb/view.js @@ -118,11 +118,8 @@ export class MBMapContainer extends React.Component { version: 8, sources: {}, layers: [], + glyphs: getGlyphUrl(), }; - const glyphUrl = getGlyphUrl(); - if (glyphUrl) { - mbStyle.glyphs = glyphUrl; - } const options = { attributionControl: false, diff --git a/x-pack/plugins/maps/public/meta.js b/x-pack/plugins/maps/public/meta.js index 3ffd0578796c..46c5e5cda361 100644 --- a/x-pack/plugins/maps/public/meta.js +++ b/x-pack/plugins/maps/public/meta.js @@ -5,15 +5,7 @@ */ import { - GIS_API_PATH, - EMS_FILES_CATALOGUE_PATH, - EMS_TILES_CATALOGUE_PATH, - EMS_GLYPHS_PATH, - EMS_APP_NAME, -} from '../common/constants'; -import { i18n } from '@kbn/i18n'; -import { EMSClient } from '@elastic/ems-client'; -import { + getHttp, getLicenseId, getIsEmsEnabled, getRegionmapLayers, @@ -25,6 +17,17 @@ import { getProxyElasticMapsServiceInMaps, getKibanaVersion, } from './kibana_services'; +import { + GIS_API_PATH, + EMS_FILES_CATALOGUE_PATH, + EMS_TILES_CATALOGUE_PATH, + EMS_GLYPHS_PATH, + EMS_APP_NAME, + FONTS_API_PATH, +} from '../common/constants'; +import { i18n } from '@kbn/i18n'; +import { EMSClient } from '@elastic/ems-client'; + import fetch from 'node-fetch'; const GIS_API_RELATIVE = `../${GIS_API_PATH}`; @@ -95,7 +98,7 @@ export function getEMSClient() { export function getGlyphUrl() { if (!getIsEmsEnabled()) { - return ''; + return getHttp().basePath.prepend(`/${FONTS_API_PATH}/{fontstack}/{range}`); } return getProxyElasticMapsServiceInMaps() ? relativeToAbsolute(`../${GIS_API_PATH}/${EMS_TILES_CATALOGUE_PATH}/${EMS_GLYPHS_PATH}`) + diff --git a/x-pack/plugins/maps/public/meta.test.js b/x-pack/plugins/maps/public/meta.test.js index 24dc65e9fc71..5c04a57c0005 100644 --- a/x-pack/plugins/maps/public/meta.test.js +++ b/x-pack/plugins/maps/public/meta.test.js @@ -5,7 +5,7 @@ */ import { EMSClient } from '@elastic/ems-client'; -import { getEMSClient } from './meta'; +import { getEMSClient, getGlyphUrl } from './meta'; jest.mock('@elastic/ems-client'); @@ -22,10 +22,56 @@ describe('default use without proxy', () => { require('./kibana_services').getEmsLandingPageUrl = () => 'http://test.com'; }); - it('should construct EMSClient with absolute file and tile API urls', async () => { + test('should construct EMSClient with absolute file and tile API urls', async () => { getEMSClient(); const mockEmsClientCall = EMSClient.mock.calls[0]; expect(mockEmsClientCall[0].fileApiUrl.startsWith('https://file-api')).toBe(true); expect(mockEmsClientCall[0].tileApiUrl.startsWith('https://tile-api')).toBe(true); }); }); + +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; + }); + + describe('EMS proxy enabled', () => { + beforeAll(() => { + require('./kibana_services').getProxyElasticMapsServiceInMaps = () => true; + }); + + test('should return proxied EMS fonts URL', async () => { + expect(getGlyphUrl()).toBe('http://localhost/api/maps/ems/tiles/fonts/{fontstack}/{range}'); + }); + }); + + describe('EMS proxy disabled', () => { + beforeAll(() => { + require('./kibana_services').getProxyElasticMapsServiceInMaps = () => false; + }); + + test('should return EMS fonts URL', async () => { + expect(getGlyphUrl()).toBe(EMS_FONTS_URL_MOCK); + }); + }); + }); + + describe('EMS disabled', () => { + beforeAll(() => { + const mockHttp = { + basePath: { + prepend: (path) => `abc${path}`, + }, + }; + require('./kibana_services').getHttp = () => mockHttp; + require('./kibana_services').getIsEmsEnabled = () => false; + }); + + test('should return kibana fonts URL', async () => { + expect(getGlyphUrl()).toBe('abc/api/maps/fonts/{fontstack}/{range}'); + }); + }); +}); diff --git a/x-pack/plugins/maps/server/fonts/open_sans/0-255.pbf b/x-pack/plugins/maps/server/fonts/open_sans/0-255.pbf new file mode 100644 index 0000000000000000000000000000000000000000..ab811ae10a2e7bd58ab67fd120cc313a03e3bc11 GIT binary patch literal 74696 zcmeFaWpGGFG4eoT(={Ozeq|;$$a2z|1A%>Wl8D+~d$;`}bnPifgNw#HXX3HRR zlJ~y%-m3X(W`4}fpQ)NZGtWMfobiQ+xb=rN}^H*g zTb6eFi$g6LsZ9$zeMQ0M?kSD)LViJ@sasP0>{d^HfQf5j-ONT;9?Qr%zGiBjm+NQf z6jwdD){*U_?+{ZtzS^GUt!E$29b0bAVCvXLmJbXxr+aFAqh@Lgh(U#X;TX4NX`v~N;m_{g94T*Fm}^L;`{i~D z221K^r)v{ky)&D;It#11x+-IxoFlXIa^utT@>2aR^j&;eEDtXh%ga{%(ht{vpq}uj z%ZDeVZb`44pwVbo<&4QZ_&T!!16j_xuP)MPKR?uTBU6$Qo`Yu&vsbxE`~K{6L*E?C zPIYPnpWl$;p?Uw@ttW5H$)k-|Ww)=+)F;zF-g~U1YiJu$HncxjTF|pKRvGK4E~9Gg z>E{<$J+akOP&&9bP!jIrq-W}voSU25IJerKTQ$Dfou8W(?r0xX&O=+7$H(jnJ{r@R zWVY7V*LOGYv9-0nzS5TI1=<+eF8SCR#|LQEA$eRbcTC9VatHSZaaa;hcMXk;ORAq) z?a09`dJ5uVBE8Ln^Qx<>TbJgWsvG83+N-O%sZ2f0z~toQ`V>A^=J7$+4QOj?YoG9M zZJ&G)S~#bpqhoZtx1(cVcL;~g*}jHe3EA0o(}K?2%CRkeVRm+Aq>F~RyH9${;$m|; zb#GrEZ)g2C3PxnErt31vy;B+bdg?MaWG#Y=dIh6gc9&ohOHB32y~{s=R@l+mS)A9_ z*;yKC_x?{Q8tu6rBQ7T=Cm|*~Cnw3rLy+k3r*yD8HL5kO#Pt3-mMiL z=_dq-dRr;^WVS4Pn(rsyNBh}pzjlc&@15TlZcX!>%hE%fG|hZd%i2fgg`YR)`s=cT ztUp-$#Id>f{nFmXa807SsjDYFkUVeO*zQV4t{*clJwB9r{`M8ffxrk(OI2|$xgT$B zy_@Y1jvwpmM89|O^K?Th6aD>d!OF&(aBGIgiLj3<;|)*EEePf&dYd`%Ohex!c3DjW z`F(L_G}BDck`WjgPhCI3$Jt2nzM_t~omXbt$VhF1i<<21vp1i}esqf~E-zp9BA$d3*YD|94V%q?^9G{#M`KJ2WDU=nU<(9z8s*xU3{Q#@oz*nV4Ttkmhgx_A*V)F+9JXKRnn{n&4?4Uev|sS42A~ zU85QMW;9N$Z*0%;3d4NT8>TkfgfVTkUtJ+|@FbyE4)2asak<5*A�)=*d|8?b+lyoy!YtJ!4}%by-1H?;T^f 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 Zcu8yF`^@02wfGfSi!Wd;wqN+@zXQQuMRx!I literal 0 HcmV?d00001 diff --git a/x-pack/plugins/maps/server/fonts/open_sans/1024-1279.pbf b/x-pack/plugins/maps/server/fonts/open_sans/1024-1279.pbf new file mode 100644 index 0000000000000000000000000000000000000000..7cda8da1d0388a7d800e57f25a5a2844722cf6ed GIT binary patch literal 122545 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 LW<3p12tWEa8bYCv literal 0 HcmV?d00001 diff --git a/x-pack/plugins/maps/server/fonts/open_sans/256-511.pbf b/x-pack/plugins/maps/server/fonts/open_sans/256-511.pbf new file mode 100644 index 0000000000000000000000000000000000000000..6e108e53a26f8579a46c9626a5e312c95c8ed83f GIT binary patch literal 66481 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 JiG}#ce*j4AU_Af; literal 0 HcmV?d00001 diff --git a/x-pack/plugins/maps/server/fonts/open_sans/768-1023.pbf b/x-pack/plugins/maps/server/fonts/open_sans/768-1023.pbf new file mode 100644 index 0000000000000000000000000000000000000000..a3efbb9361d4d603042de3f2bdb4e261b8170763 GIT binary patch literal 33767 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 literal 0 HcmV?d00001 diff --git a/x-pack/plugins/maps/server/fonts/open_sans/license.txt b/x-pack/plugins/maps/server/fonts/open_sans/license.txt new file mode 100644 index 000000000000..7783de532a33 --- /dev/null +++ b/x-pack/plugins/maps/server/fonts/open_sans/license.txt @@ -0,0 +1,53 @@ +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of this License; and +You must cause any modified files to carry prominent notices stating that You changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/x-pack/plugins/maps/server/routes.js b/x-pack/plugins/maps/server/routes.js index 63895ea8b982..ad66712eb3ad 100644 --- a/x-pack/plugins/maps/server/routes.js +++ b/x-pack/plugins/maps/server/routes.js @@ -21,12 +21,15 @@ import { GIS_API_PATH, EMS_SPRITES_PATH, INDEX_SETTINGS_API_PATH, + FONTS_API_PATH, } from '../common/constants'; import { EMSClient } from '@elastic/ems-client'; import fetch from 'node-fetch'; import { i18n } from '@kbn/i18n'; import { getIndexPatternSettings } from './lib/get_index_pattern_settings'; import { schema } from '@kbn/config-schema'; +import fs from 'fs'; +import path from 'path'; const ROOT = `/${GIS_API_PATH}`; @@ -76,7 +79,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { }), }, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -108,7 +111,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { path: `${ROOT}/${EMS_TILES_API_PATH}/${EMS_TILES_RASTER_TILE_PATH}`, validate: false, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -144,7 +147,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { path: `${ROOT}/${EMS_CATALOGUE_PATH}`, validate: false, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -180,7 +183,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { path: `${ROOT}/${EMS_FILES_CATALOGUE_PATH}/{emsVersion}/manifest`, validate: false, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -210,7 +213,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { path: `${ROOT}/${EMS_TILES_CATALOGUE_PATH}/{emsVersion}/manifest`, validate: false, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -258,7 +261,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { }), }, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -294,7 +297,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { }), }, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -344,7 +347,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { }), }, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -386,7 +389,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { }), }, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -423,7 +426,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { path: `${ROOT}/${EMS_TILES_API_PATH}/${EMS_GLYPHS_PATH}/{fontstack}/{range}`, validate: false, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -444,7 +447,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { }), }, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -481,6 +484,39 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { } ); + router.get( + { + path: `/${FONTS_API_PATH}/{fontstack}/{range}`, + validate: { + params: schema.object({ + fontstack: schema.string(), + range: schema.string(), + }), + }, + }, + (context, request, response) => { + return new Promise((resolve, reject) => { + const santizedRange = path.normalize(request.params.range); + const fontPath = path.join(__dirname, 'fonts', 'open_sans', `${santizedRange}.pbf`); + fs.readFile(fontPath, (error, data) => { + if (error) { + reject( + response.custom({ + statusCode: 404, + }) + ); + } else { + resolve( + response.ok({ + body: data, + }) + ); + } + }); + }); + } + ); + router.get( { path: `/${INDEX_SETTINGS_API_PATH}`, @@ -490,7 +526,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { }), }, }, - async (con, request, response) => { + async (context, request, response) => { const { query } = request; if (!query.indexPatternTitle) { @@ -502,7 +538,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { } try { - const resp = await con.core.elasticsearch.legacy.client.callAsCurrentUser( + const resp = await context.core.elasticsearch.legacy.client.callAsCurrentUser( 'indices.getSettings', { index: query.indexPatternTitle, diff --git a/x-pack/test/api_integration/apis/maps/fonts_api.js b/x-pack/test/api_integration/apis/maps/fonts_api.js new file mode 100644 index 000000000000..d367fb6a0610 --- /dev/null +++ b/x-pack/test/api_integration/apis/maps/fonts_api.js @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +export default function ({ getService }) { + const supertest = getService('supertest'); + + describe('fonts', () => { + it('should return fonts', async () => { + const resp = await supertest + .get(`/api/maps/fonts/Open%20Sans%20Regular,Arial%20Unicode%20MS%20Regular/0-255`) + .expect(200); + + expect(resp.body.length).to.be(74696); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/maps/index.js b/x-pack/test/api_integration/apis/maps/index.js index a6267b9fd0ce..f9dff1922964 100644 --- a/x-pack/test/api_integration/apis/maps/index.js +++ b/x-pack/test/api_integration/apis/maps/index.js @@ -13,6 +13,7 @@ export default function ({ loadTestFile, getService }) { }); describe('', () => { + loadTestFile(require.resolve('./fonts_api')); loadTestFile(require.resolve('./index_settings')); loadTestFile(require.resolve('./migrations')); }); From a1a1d5d2f79ff781d85e0bb7d845f1f7094527aa Mon Sep 17 00:00:00 2001 From: Chris Roberson Date: Tue, 2 Jun 2020 10:41:04 -0400 Subject: [PATCH 22/50] Ensure we query for more than 10 (#67172) --- .../lib/setup/collection/get_collection_status.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.js b/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.js index fcb54e92f649..607503673276 100644 --- a/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.js +++ b/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.js @@ -18,7 +18,7 @@ import { getLivesNodes } from '../../elasticsearch/nodes/get_nodes/get_live_node const NUMBER_OF_SECONDS_AGO_TO_LOOK = 30; -const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nodeUuid) => { +const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nodeUuid, size) => { const start = get(req.payload, 'timeRange.min') || `now-${NUMBER_OF_SECONDS_AGO_TO_LOOK}s`; const end = get(req.payload, 'timeRange.max') || 'now'; @@ -73,6 +73,7 @@ const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nod es_uuids: { terms: { field: 'node_stats.node_id', + size, }, aggs: { by_timestamp: { @@ -85,6 +86,7 @@ const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nod kibana_uuids: { terms: { field: 'kibana_stats.kibana.uuid', + size, }, aggs: { by_timestamp: { @@ -97,6 +99,7 @@ const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nod beats_uuids: { terms: { field: 'beats_stats.beat.uuid', + size, }, aggs: { by_timestamp: { @@ -107,11 +110,13 @@ const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nod beat_type: { terms: { field: 'beats_stats.beat.type', + size, }, }, cluster_uuid: { terms: { field: 'cluster_uuid', + size, }, }, }, @@ -119,6 +124,7 @@ const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nod logstash_uuids: { terms: { field: 'logstash_stats.logstash.uuid', + size, }, aggs: { by_timestamp: { @@ -129,6 +135,7 @@ const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nod cluster_uuid: { terms: { field: 'cluster_uuid', + size, }, }, }, @@ -348,6 +355,7 @@ export const getCollectionStatus = async ( ) => { const config = req.server.config(); const kibanaUuid = config.get('server.uuid'); + const size = config.get('monitoring.ui.max_bucket_size'); const hasPermissions = await hasNecessaryPermissions(req); if (!hasPermissions) { @@ -369,7 +377,7 @@ export const getCollectionStatus = async ( ]; const [recentDocuments, detectedProducts] = await Promise.all([ - await getRecentMonitoringDocuments(req, indexPatterns, clusterUuid, nodeUuid), + await getRecentMonitoringDocuments(req, indexPatterns, clusterUuid, nodeUuid, size), await detectProducts(req, isLiveCluster), ]); From 52c518a6aed5a46ed2d25152a70833898c134e67 Mon Sep 17 00:00:00 2001 From: Andrew Goldstein Date: Tue, 2 Jun 2020 08:57:11 -0600 Subject: [PATCH 23/50] [SIEM] Fixes column drag and drop in timeline-based views (#67799) ## Summary Fixes a bug in timeline-based views, (e.g. the Host Events table), where a column would revert back to it's original position after being dragged and dropped to a new position. Only timeline-based views were effected, not the timeline itself. To reproduce: 1) On the SIEM Overview page, click the `View events` button 2) Drag and drop any column in the Events table to a new position **Expected Result** - The column is relocated to the position where it was dropped **Actual Result** - The column reverts to it's original position ## Testing - This PR adds a Cypress test for this scenario - The new test was successfully run (at least) 10 times via `node x-pack/plugins/siem/scripts/loop_cypress_tests.js` - This fix was desk tested in: - Chrome `83.0.4103.61` - Firefox `76.0.1` - Safari `13.1` --- .../cypress/integration/events_viewer.spec.ts | 26 +++++++++++++++++++ .../plugins/siem/cypress/screens/timeline.ts | 5 ++++ .../siem/cypress/tasks/hosts/events.ts | 23 ++++++++++++++++ .../drag_drop_context_wrapper.tsx | 3 ++- .../components/drag_and_drop/helpers.test.ts | 13 ++++++++++ .../components/drag_and_drop/helpers.ts | 3 +++ 6 files changed, 72 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/siem/cypress/integration/events_viewer.spec.ts b/x-pack/plugins/siem/cypress/integration/events_viewer.spec.ts index 26ebaeb84482..82b4f4f0fbe3 100644 --- a/x-pack/plugins/siem/cypress/integration/events_viewer.spec.ts +++ b/x-pack/plugins/siem/cypress/integration/events_viewer.spec.ts @@ -17,6 +17,7 @@ import { LOAD_MORE, LOCAL_EVENTS_COUNT, } from '../screens/hosts/events'; +import { HEADERS_GROUP } from '../screens/timeline'; import { closeFieldsBrowser, filterFieldsBrowser } from '../tasks/fields_browser'; import { loginAndWaitForPage } from '../tasks/login'; @@ -25,6 +26,7 @@ import { addsHostGeoCityNameToHeader, addsHostGeoCountryNameToHeader, closeModal, + dragAndDropColumn, openEventsViewerFieldsBrowser, opensInspectQueryModal, resetFields, @@ -150,4 +152,28 @@ describe('Events Viewer', () => { cy.get(LOCAL_EVENTS_COUNT).invoke('text').should('not.equal', defaultNumberOfLoadedEvents); }); }); + + context.skip('Events columns', () => { + before(() => { + loginAndWaitForPage(HOSTS_PAGE); + openEvents(); + waitsForEventsToBeLoaded(); + }); + + afterEach(() => { + openEventsViewerFieldsBrowser(); + resetFields(); + }); + + it('re-orders columns via drag and drop', () => { + const originalColumnOrder = + '@timestampmessagehost.nameevent.moduleevent.datasetevent.actionuser.namesource.ipdestination.ip'; + const expectedOrderAfterDragAndDrop = + 'message@timestamphost.nameevent.moduleevent.datasetevent.actionuser.namesource.ipdestination.ip'; + + cy.get(HEADERS_GROUP).invoke('text').should('equal', originalColumnOrder); + dragAndDropColumn({ column: 0, newPosition: 1 }); + cy.get(HEADERS_GROUP).invoke('text').should('equal', expectedOrderAfterDragAndDrop); + }); + }); }); diff --git a/x-pack/plugins/siem/cypress/screens/timeline.ts b/x-pack/plugins/siem/cypress/screens/timeline.ts index ed1dc97454fb..bb232b752994 100644 --- a/x-pack/plugins/siem/cypress/screens/timeline.ts +++ b/x-pack/plugins/siem/cypress/screens/timeline.ts @@ -8,6 +8,11 @@ export const CLOSE_TIMELINE_BTN = '[data-test-subj="close-timeline"]'; export const CREATE_NEW_TIMELINE = '[data-test-subj="timeline-new"]'; +export const DRAGGABLE_HEADER = + '[data-test-subj="headers-group"] [data-test-subj="draggable-header"]'; + +export const HEADERS_GROUP = '[data-test-subj="headers-group"]'; + export const ID_HEADER_FIELD = '[data-test-subj="timeline"] [data-test-subj="header-text-_id"]'; export const ID_FIELD = '[data-test-subj="timeline"] [data-test-subj="field-name-_id"]'; diff --git a/x-pack/plugins/siem/cypress/tasks/hosts/events.ts b/x-pack/plugins/siem/cypress/tasks/hosts/events.ts index dff58b4b0e9e..a59365098925 100644 --- a/x-pack/plugins/siem/cypress/tasks/hosts/events.ts +++ b/x-pack/plugins/siem/cypress/tasks/hosts/events.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { drag, drop } from '../common'; import { CLOSE_MODAL, EVENTS_VIEWER_FIELDS_BUTTON, @@ -15,6 +16,7 @@ import { RESET_FIELDS, SERVER_SIDE_EVENT_COUNT, } from '../../screens/hosts/events'; +import { DRAGGABLE_HEADER } from '../../screens/timeline'; export const addsHostGeoCityNameToHeader = () => { cy.get(HOST_GEO_CITY_NAME_CHECKBOX).check({ @@ -58,3 +60,24 @@ export const resetFields = () => { export const waitsForEventsToBeLoaded = () => { cy.get(SERVER_SIDE_EVENT_COUNT).should('exist').invoke('text').should('not.equal', '0'); }; + +export const dragAndDropColumn = ({ + column, + newPosition, +}: { + column: number; + newPosition: number; +}) => { + cy.get(DRAGGABLE_HEADER).first().should('exist'); + cy.get(DRAGGABLE_HEADER) + .eq(column) + .then((header) => drag(header)); + + cy.wait(3000); // wait for DOM updates before moving + + cy.get(DRAGGABLE_HEADER) + .eq(newPosition) + .then((targetPosition) => { + drop(targetPosition); + }); +}; diff --git a/x-pack/plugins/siem/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx b/x-pack/plugins/siem/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx index 3bd2a3da1c88..c71cd8e60596 100644 --- a/x-pack/plugins/siem/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx +++ b/x-pack/plugins/siem/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx @@ -27,6 +27,7 @@ import { addFieldToTimelineColumns, addProviderToTimeline, fieldWasDroppedOnTimelineColumns, + getTimelineIdFromColumnDroppableId, IS_DRAGGING_CLASS_NAME, IS_TIMELINE_FIELD_DRAGGING_CLASS_NAME, providerWasDroppedOnTimeline, @@ -82,7 +83,7 @@ const onDragEndHandler = ({ browserFields, dispatch, result, - timelineId: ACTIVE_TIMELINE_REDUX_ID, + timelineId: getTimelineIdFromColumnDroppableId(result.destination?.droppableId ?? ''), }); } }; diff --git a/x-pack/plugins/siem/public/common/components/drag_and_drop/helpers.test.ts b/x-pack/plugins/siem/public/common/components/drag_and_drop/helpers.test.ts index 69fbedb6462c..be58381fbca1 100644 --- a/x-pack/plugins/siem/public/common/components/drag_and_drop/helpers.test.ts +++ b/x-pack/plugins/siem/public/common/components/drag_and_drop/helpers.test.ts @@ -32,6 +32,7 @@ import { getDroppableId, getFieldIdFromDraggable, getProviderIdFromDraggable, + getTimelineIdFromColumnDroppableId, getTimelineProviderDraggableId, getTimelineProviderDroppableId, providerWasDroppedOnTimeline, @@ -984,4 +985,16 @@ describe('helpers', () => { }); }); }); + + describe('getTimelineIdFromColumnDroppableId', () => { + test('it returns the expected timelineId from a column droppableId', () => { + expect(getTimelineIdFromColumnDroppableId(DROPPABLE_ID_TIMELINE_COLUMNS)).toEqual( + 'timeline-1' + ); + }); + + test('it returns an empty string when the droppableId is an empty string', () => { + expect(getTimelineIdFromColumnDroppableId('')).toEqual(''); + }); + }); }); diff --git a/x-pack/plugins/siem/public/common/components/drag_and_drop/helpers.ts b/x-pack/plugins/siem/public/common/components/drag_and_drop/helpers.ts index ad370f647738..0037fc3cae62 100644 --- a/x-pack/plugins/siem/public/common/components/drag_and_drop/helpers.ts +++ b/x-pack/plugins/siem/public/common/components/drag_and_drop/helpers.ts @@ -332,3 +332,6 @@ export const allowTopN = ({ return isWhitelistedNonBrowserField || (isAggregatable && isAllowedType); }; + +export const getTimelineIdFromColumnDroppableId = (droppableId: string) => + droppableId.slice(droppableId.lastIndexOf('.') + 1); From ce7940adc28ba53627bfa401a97b8bf4af0aa901 Mon Sep 17 00:00:00 2001 From: Corey Robertson Date: Tue, 2 Jun 2020 11:31:30 -0400 Subject: [PATCH 24/50] Allow functions to return falsy values (#67796) Co-authored-by: Elastic Machine --- .../create_streaming_batched_function.test.ts | 41 +++++++++++++++++++ .../create_streaming_batched_function.ts | 2 +- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts b/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts index 26c1d9e5033e..da6c940c48d0 100644 --- a/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts +++ b/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts @@ -303,6 +303,47 @@ describe('createStreamingBatchedFunction()', () => { expect(await promise3).toEqual({ foo: 'bar 2' }); }); + test('resolves falsy results', async () => { + const { fetchStreaming, stream } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + const promise1 = fn({ a: '1' }); + const promise2 = fn({ b: '2' }); + const promise3 = fn({ c: '3' }); + await new Promise((r) => setTimeout(r, 6)); + + stream.next( + JSON.stringify({ + id: 0, + result: false, + }) + '\n' + ); + stream.next( + JSON.stringify({ + id: 1, + result: 0, + }) + '\n' + ); + stream.next( + JSON.stringify({ + id: 2, + result: '', + }) + '\n' + ); + + expect(await isPending(promise1)).toBe(false); + expect(await isPending(promise2)).toBe(false); + expect(await isPending(promise3)).toBe(false); + expect(await promise1).toEqual(false); + expect(await promise2).toEqual(0); + expect(await promise3).toEqual(''); + }); + test('rejects promise on error response', async () => { const { fetchStreaming, stream } = setup(); const fn = createStreamingBatchedFunction({ diff --git a/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts b/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts index f80a97137d1a..89793fff6b32 100644 --- a/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts +++ b/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts @@ -106,7 +106,7 @@ export const createStreamingBatchedFunction = ( if (response.error) { responsesReceived++; items[response.id].future.reject(response.error); - } else if (response.result) { + } else if (response.result !== undefined) { responsesReceived++; items[response.id].future.resolve(response.result); } From 77e7e0bb49f38cd008e96d1e1c808e21f0732c2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez?= Date: Tue, 2 Jun 2020 17:36:47 +0200 Subject: [PATCH 25/50] [Logs UI] Ensure live stream always gets latest entries (#67935) LogPositionState doesn't always reevaluate the value of `endTimestamp` when live stream is on. The [dependencies][1] for it to change rely on the scroll position to update. If there's less than one scroll page or the previous API call didn't return any entries, the `endTimestamp` would not update. We force `Date.now()` as an `endTimestamp` on the API call to ensure it always gets the latest entries possible, regardless of the state. This introduces some inconsistency that will be fixed once work beings on #65493. [1]: https://github.com/elastic/kibana/blob/fe4c164681e92ef5bf0c28f7ab3dfe00a5aacd6f/x-pack/plugins/infra/public/containers/logs/log_position/log_position_state.ts#L160-L173 --- .../containers/logs/log_entries/index.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 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 a6d66d47975c..5fe9a45a7cee 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 @@ -194,7 +194,10 @@ const useFetchEntriesEffect = ( } }; - const runFetchMoreEntriesRequest = async (direction: ShouldFetchMoreEntries) => { + const runFetchMoreEntriesRequest = async ( + direction: ShouldFetchMoreEntries, + overrides: Partial = {} + ) => { if (!props.startTimestamp || !props.endTimestamp) { return; } @@ -209,10 +212,10 @@ const useFetchEntriesEffect = ( try { const commonFetchArgs: LogEntriesBaseRequest = { - sourceId: props.sourceId, - startTimestamp: props.startTimestamp, - endTimestamp: props.endTimestamp, - query: props.filterQuery, + sourceId: overrides.sourceId || props.sourceId, + startTimestamp: overrides.startTimestamp || props.startTimestamp, + endTimestamp: overrides.endTimestamp || props.endTimestamp, + query: overrides.filterQuery || props.filterQuery, }; const fetchArgs: LogEntriesRequest = getEntriesBefore @@ -279,10 +282,10 @@ const useFetchEntriesEffect = ( const streamEntriesEffect = () => { (async () => { if (props.isStreaming && !state.isLoadingMore && !state.isReloading) { + const endTimestamp = Date.now(); if (startedStreaming) { await new Promise((res) => setTimeout(res, LIVE_STREAM_INTERVAL)); } else { - const endTimestamp = Date.now(); props.jumpToTargetPosition({ tiebreaker: 0, time: endTimestamp }); setStartedStreaming(true); if (state.hasMoreAfterEnd) { @@ -290,7 +293,9 @@ const useFetchEntriesEffect = ( return; } } - const newEntriesEnd = await runFetchMoreEntriesRequest(ShouldFetchMoreEntries.After); + const newEntriesEnd = await runFetchMoreEntriesRequest(ShouldFetchMoreEntries.After, { + endTimestamp, + }); if (newEntriesEnd) { props.jumpToTargetPosition(newEntriesEnd); } From 13c24b0d818dd4d67073746236064e07c7546493 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 2 Jun 2020 18:43:13 +0200 Subject: [PATCH 26/50] [Uptime] Fix ping io ts type (#66926) Co-authored-by: Elastic Machine --- x-pack/plugins/uptime/common/runtime_types/ping/ping.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts b/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts index d8dc7fc89d94..ab539b38c3e4 100644 --- a/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts +++ b/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts @@ -36,9 +36,9 @@ export const MonitorType = t.intersection([ check_group: t.string, ip: t.string, name: t.string, - timespan: t.partial({ + timespan: t.type({ gte: t.string, - lte: t.string, + lt: t.string, }), }), ]); @@ -55,13 +55,13 @@ export const PingType = t.intersection([ agent: t.intersection([ t.type({ ephemeral_id: t.string, - hostname: t.string, id: t.string, type: t.string, version: t.string, }), t.partial({ name: t.string, + hostname: t.string, }), ]), container: t.partial({ From 332a92d3d537c87f6b8623a36520deb4f7c78953 Mon Sep 17 00:00:00 2001 From: Ryland Herrick Date: Tue, 2 Jun 2020 11:56:55 -0500 Subject: [PATCH 27/50] [SIEM][Detections] Allow synchronous rule actions to be updated via PATCH (#67914) * Update synchronous actions in patchRules This method was typed to accept actions, but it was not doing anything with them. This was mainly a "bug by omission" so I'm simply adding unit tests for regression purposes. * Allow synchronous actions to be patched either individually or in bulk Now that patchRules uses this field, we simply need to pass it. Co-authored-by: Garrett Spong Co-authored-by: Elastic Machine --- .../routes/rules/patch_rules_bulk_route.ts | 1 + .../routes/rules/patch_rules_route.ts | 1 + .../rules/patch_rules.test.ts | 78 +++++++++++++++++++ .../lib/detection_engine/rules/patch_rules.ts | 4 +- 4 files changed, 83 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts index 0d0cd28738c9..e9c0ca08c88e 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts @@ -122,6 +122,7 @@ export const patchRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => version, anomalyThreshold, machineLearningJobId, + actions, }); if (rule != null && rule.enabled != null && rule.name != null) { const ruleActions = await updateRulesNotifications({ diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_route.ts index ae23e0efc857..2a1ac9862e7d 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_route.ts @@ -118,6 +118,7 @@ export const patchRulesRoute = (router: IRouter, ml: SetupPlugins['ml']) => { version, anomalyThreshold, machineLearningJobId, + actions, }); if (rule != null && rule.enabled != null && rule.name != null) { const ruleActions = await updateRulesNotifications({ diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.test.ts index 3c1267c93934..0dffa665f780 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.test.ts @@ -91,4 +91,82 @@ describe('patchRules', () => { }) ); }); + + describe('regression tests', () => { + it("updates the rule's actions if provided", async () => { + const existingRule = getResult(); + + const action = { + action_type_id: '.slack', + id: '2933e581-d81c-4fe3-88fe-c57c6b8a5bfd', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} signals', + }, + group: 'default', + }; + + await patchRules({ + alertsClient, + savedObjectsClient, + actions: [action], + rule: existingRule, + }); + + expect(alertsClient.update).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ + actions: [ + { + actionTypeId: '.slack', + id: '2933e581-d81c-4fe3-88fe-c57c6b8a5bfd', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} signals', + }, + group: 'default', + }, + ], + }), + }) + ); + }); + + it('does not update actions if none are specified', async () => { + const existingRule = { + ...getResult(), + actions: [ + { + actionTypeId: '.slack', + id: '2933e581-d81c-4fe3-88fe-c57c6b8a5bfd', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} signals', + }, + group: 'default', + }, + ], + }; + + await patchRules({ + alertsClient, + savedObjectsClient, + rule: existingRule, + }); + + expect(alertsClient.update).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ + actions: [ + { + actionTypeId: '.slack', + id: '2933e581-d81c-4fe3-88fe-c57c6b8a5bfd', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} signals', + }, + group: 'default', + }, + ], + }), + }) + ); + }); + }); }); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.ts index 1e728ae7b8d0..950a3e90fb70 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.ts @@ -6,6 +6,7 @@ import { defaults } from 'lodash/fp'; import { PartialAlert } from '../../../../../alerts/server'; +import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; import { PatchRuleParams } from './types'; import { addTags } from './add_tags'; import { calculateVersion, calculateName, calculateInterval } from './utils'; @@ -44,6 +45,7 @@ export const patchRules = async ({ exceptions_list, anomalyThreshold, machineLearningJobId, + actions, }: PatchRuleParams): Promise => { if (rule == null) { return null; @@ -121,7 +123,7 @@ export const patchRules = async ({ schedule: { interval: calculateInterval(interval, rule.schedule.interval), }, - actions: rule.actions, + actions: actions?.map(transformRuleToAlertAction) ?? rule.actions, params: nextParams, }, }); From 92237517be20a23265b82c14fe104a4123e9409e Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Tue, 2 Jun 2020 20:01:39 +0300 Subject: [PATCH 28/50] [SIEM] Update cypress to 4.5.0 (#67961) --- x-pack/package.json | 2 +- yarn.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/x-pack/package.json b/x-pack/package.json index dc23602bac86..9f69cbc40bf3 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -128,7 +128,7 @@ "cheerio": "0.22.0", "commander": "3.0.2", "copy-webpack-plugin": "^5.0.4", - "cypress": "^4.4.1", + "cypress": "4.5.0", "cypress-multi-reporters": "^1.2.3", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.2", diff --git a/yarn.lock b/yarn.lock index 5d47056857bb..5506165da2d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4865,9 +4865,9 @@ "@types/sinon" "*" "@types/sinon@*": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-9.0.0.tgz#5b70a360f55645dd64f205defd2a31b749a59799" - integrity sha512-v2TkYHkts4VXshMkcmot/H+ERZ2SevKa10saGaJPGCJ8vh3lKrC4u663zYEeRZxep+VbG6YRDtQ6gVqw9dYzPA== + version "9.0.4" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-9.0.4.tgz#e934f904606632287a6e7f7ab0ce3f08a0dad4b1" + integrity sha512-sJmb32asJZY6Z2u09bl0G2wglSxDlROlAejCjsnor+LzBMz17gu8IU7vKC/vWDnv9zEq2wqADHVXFjf4eE8Gdw== dependencies: "@types/sinonjs__fake-timers" "*" @@ -10532,10 +10532,10 @@ cypress-multi-reporters@^1.2.3: debug "^4.1.1" lodash "^4.17.11" -cypress@^4.4.1: - version "4.4.1" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-4.4.1.tgz#f5aa1aa5f328f1299bff328103f7cbad89e80f29" - integrity sha512-LcskZ/PXRG9XTlEeeenKqz/KddT1x+7O7dqXsdKWPII01LxLNmNHIvHnlUqApchVbinJ5vir6J255CkELSeL0A== +cypress@4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-4.5.0.tgz#01940d085f6429cec3c87d290daa47bb976a7c7b" + integrity sha512-2A4g5FW5d2fHzq8HKUGAMVTnW6P8nlWYQALiCoGN4bqBLvgwhYM/oG9oKc2CS6LnvgHFiKivKzpm9sfk3uU3zQ== dependencies: "@cypress/listr-verbose-renderer" "0.4.1" "@cypress/request" "2.88.5" From 6481402510a9ba4e2f66d1a3da398b51797595c4 Mon Sep 17 00:00:00 2001 From: spalger Date: Tue, 2 Jun 2020 11:41:23 -0700 Subject: [PATCH 29/50] delete flaky suite with flaky beforeAll hook (#67554) (#67555) (#67556) --- .../integration_tests/ingest_coverage.test.js | 72 ------------------- 1 file changed, 72 deletions(-) delete mode 100644 src/dev/code_coverage/ingest_coverage/integration_tests/ingest_coverage.test.js diff --git a/src/dev/code_coverage/ingest_coverage/integration_tests/ingest_coverage.test.js b/src/dev/code_coverage/ingest_coverage/integration_tests/ingest_coverage.test.js deleted file mode 100644 index 03126d130e98..000000000000 --- a/src/dev/code_coverage/ingest_coverage/integration_tests/ingest_coverage.test.js +++ /dev/null @@ -1,72 +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 { resolve } from 'path'; -import execa from 'execa'; -import expect from '@kbn/expect'; - -const ROOT_DIR = resolve(__dirname, '../../../../..'); -const MOCKS_DIR = resolve(__dirname, './mocks'); -const env = { - BUILD_ID: 407, - CI_RUN_URL: 'https://kibana-ci.elastic.co/job/elastic+kibana+code-coverage/407/', - STATIC_SITE_URL_BASE: 'https://kibana-coverage.elastic.dev', - TIME_STAMP: '2020-03-02T21:11:47Z', - ES_HOST: 'https://super:changeme@some.fake.host:9243', - NODE_ENV: 'integration_test', - COVERAGE_INGESTION_KIBANA_ROOT: '/var/lib/jenkins/workspace/elastic+kibana+code-coverage/kibana', -}; -const verboseArgs = [ - 'scripts/ingest_coverage.js', - '--verbose', - '--vcsInfoPath', - 'src/dev/code_coverage/ingest_coverage/integration_tests/mocks/VCS_INFO.txt', - '--path', -]; - -// FLAKY: https://github.com/elastic/kibana/issues/67554 -// FLAKY: https://github.com/elastic/kibana/issues/67555 -// FLAKY: https://github.com/elastic/kibana/issues/67556 -describe.skip('Ingesting coverage', () => { - const summaryPath = 'jest-combined/coverage-summary-manual-mix.json'; - const resolved = resolve(MOCKS_DIR, summaryPath); - const siteUrlRegex = /"staticSiteUrl": (".+",)/; - let actualUrl = ''; - - beforeAll(async () => { - const opts = [...verboseArgs, resolved]; - const { stdout } = await execa(process.execPath, opts, { cwd: ROOT_DIR, env }); - actualUrl = siteUrlRegex.exec(stdout)[1]; - }); - - describe(`staticSiteUrl`, () => { - it('should contain the static host', () => { - const staticHost = /https:\/\/kibana-coverage\.elastic\.dev/; - expect(staticHost.test(actualUrl)).ok(); - }); - it('should contain the timestamp', () => { - const timeStamp = /\d{4}-\d{2}-\d{2}T\d{2}.*\d{2}.*\d{2}Z/; - expect(timeStamp.test(actualUrl)).ok(); - }); - it('should contain the folder structure', () => { - const folderStructure = /(?:.*|.*-combined)\//; - expect(folderStructure.test(actualUrl)).ok(); - }); - }); -}); From 9c5eb3a37bc3b8c865d19296c6199085a7178645 Mon Sep 17 00:00:00 2001 From: nnamdifrankie <56440728+nnamdifrankie@users.noreply.github.com> Date: Tue, 2 Jun 2020 15:20:05 -0400 Subject: [PATCH 30/50] [Endpoint]EMT-395: add policy configuration and reenable tests (#67967) [Endpoint]EMT-395: add policy configuration and tests --- .../siem/common/endpoint/generate_data.ts | 4 ---- x-pack/plugins/siem/common/endpoint/types.ts | 4 ---- .../scripts/endpoint/resolver_generator.ts | 2 +- .../server/endpoint/alerts/index_pattern.ts | 5 +++-- .../apis/endpoint/alerts/index.ts | 2 +- .../apis/endpoint/alerts/index_pattern.ts | 9 +++++++-- .../api_integration/apis/endpoint/metadata.ts | 2 +- .../alerts/host_api_feature/data.json.gz | Bin 849 -> 855 bytes .../alerts/host_api_feature/mappings.json | 2 +- .../endpoint/metadata/api_feature/data.json | 18 +++++++++--------- .../metadata/api_feature/mappings.json | 2 +- 11 files changed, 24 insertions(+), 26 deletions(-) diff --git a/x-pack/plugins/siem/common/endpoint/generate_data.ts b/x-pack/plugins/siem/common/endpoint/generate_data.ts index a683db86dc6a..597ad4df64df 100644 --- a/x-pack/plugins/siem/common/endpoint/generate_data.ts +++ b/x-pack/plugins/siem/common/endpoint/generate_data.ts @@ -845,10 +845,6 @@ export class EndpointDocGenerator { }, ], id: this.commonInfo.endpoint.policy.id, - policy: { - id: this.commonInfo.endpoint.policy.id, - version: policyVersion, - }, response: { configurations: { events: { diff --git a/x-pack/plugins/siem/common/endpoint/types.ts b/x-pack/plugins/siem/common/endpoint/types.ts index 6d04f1dfac38..45b5cf2526e1 100644 --- a/x-pack/plugins/siem/common/endpoint/types.ts +++ b/x-pack/plugins/siem/common/endpoint/types.ts @@ -685,10 +685,6 @@ export interface HostPolicyResponse { id: string; status: HostPolicyResponseActionStatus; actions: HostPolicyResponseAppliedAction[]; - policy: { - id: string; - version: string; - }; response: { configurations: { malware: HostPolicyResponseConfigurationStatus; diff --git a/x-pack/plugins/siem/scripts/endpoint/resolver_generator.ts b/x-pack/plugins/siem/scripts/endpoint/resolver_generator.ts index 77bf200eeb54..26c6e5ccc28a 100644 --- a/x-pack/plugins/siem/scripts/endpoint/resolver_generator.ts +++ b/x-pack/plugins/siem/scripts/endpoint/resolver_generator.ts @@ -70,7 +70,7 @@ async function main() { metadataIndex: { alias: 'mi', describe: 'index to store host metadata in', - default: 'metrics-endpoint-default-1', + default: 'metrics-endpoint.metadata-default-1', type: 'string', }, policyIndex: { diff --git a/x-pack/plugins/siem/server/endpoint/alerts/index_pattern.ts b/x-pack/plugins/siem/server/endpoint/alerts/index_pattern.ts index 1cbdf96c5bce..391aedecdd09 100644 --- a/x-pack/plugins/siem/server/endpoint/alerts/index_pattern.ts +++ b/x-pack/plugins/siem/server/endpoint/alerts/index_pattern.ts @@ -22,6 +22,7 @@ export interface IndexPatternRetriever { export class IngestIndexPatternRetriever implements IndexPatternRetriever { private static endpointPackageName = 'endpoint'; private static metadataDataset = 'metadata'; + private static policyDataset = 'policy'; private readonly log: Logger; constructor(private readonly service: ESIndexPatternService, loggerFactory: LoggerFactory) { this.log = loggerFactory.get('index-pattern-retriever'); @@ -76,7 +77,7 @@ export class IngestIndexPatternRetriever implements IndexPatternRetriever { } } - getPolicyResponseIndexPattern(ctx: RequestHandlerContext): Promise { - return Promise.resolve('metrics-endpoint.policy-default-1'); + async getPolicyResponseIndexPattern(ctx: RequestHandlerContext): Promise { + return this.getIndexPattern(ctx, IngestIndexPatternRetriever.policyDataset); } } diff --git a/x-pack/test/api_integration/apis/endpoint/alerts/index.ts b/x-pack/test/api_integration/apis/endpoint/alerts/index.ts index ecdee09ce7ed..155513aefc60 100644 --- a/x-pack/test/api_integration/apis/endpoint/alerts/index.ts +++ b/x-pack/test/api_integration/apis/endpoint/alerts/index.ts @@ -70,7 +70,7 @@ export default function ({ getService }: FtrProviderContext) { let nullableEventId = ''; - describe.skip('Endpoint alert API', () => { + describe('Endpoint alert API', () => { describe('when data is in elasticsearch', () => { before(async () => { await esArchiver.load('endpoint/alerts/api_feature'); diff --git a/x-pack/test/api_integration/apis/endpoint/alerts/index_pattern.ts b/x-pack/test/api_integration/apis/endpoint/alerts/index_pattern.ts index df1cbcfe28e7..ad9f4463c641 100644 --- a/x-pack/test/api_integration/apis/endpoint/alerts/index_pattern.ts +++ b/x-pack/test/api_integration/apis/endpoint/alerts/index_pattern.ts @@ -9,7 +9,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - describe.skip('Endpoint index pattern API', () => { + describe('Endpoint index pattern API', () => { it('should retrieve the index pattern for events', async () => { const { body } = await supertest.get('/api/endpoint/index_pattern/events').expect(200); expect(body.indexPattern).to.eql('events-endpoint-*'); @@ -17,7 +17,12 @@ export default function ({ getService }: FtrProviderContext) { it('should retrieve the index pattern for metadata', async () => { const { body } = await supertest.get('/api/endpoint/index_pattern/metadata').expect(200); - expect(body.indexPattern).to.eql('metrics-endpoint-*'); + expect(body.indexPattern).to.eql('metrics-endpoint.metadata-*'); + }); + + it('should retrieve the index pattern for policy', async () => { + const { body } = await supertest.get('/api/endpoint/index_pattern/policy').expect(200); + expect(body.indexPattern).to.eql('metrics-endpoint.policy-*'); }); it('should not retrieve the index pattern for an invalid key', async () => { diff --git a/x-pack/test/api_integration/apis/endpoint/metadata.ts b/x-pack/test/api_integration/apis/endpoint/metadata.ts index c01919f60a92..5c4bb52b8d9e 100644 --- a/x-pack/test/api_integration/apis/endpoint/metadata.ts +++ b/x-pack/test/api_integration/apis/endpoint/metadata.ts @@ -14,7 +14,7 @@ const numberOfHostsInFixture = 3; export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); - describe.skip('test metadata api', () => { + describe('test metadata api', () => { describe('POST /api/endpoint/metadata when index is empty', () => { it('metadata api should return empty result when index is empty', async () => { await esArchiver.unload('endpoint/metadata/api_feature'); diff --git a/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/data.json.gz b/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/data.json.gz index 3d4f0e11a7cc670366fa6098143b6306a1a1c72b..49082ed3bec8b0876ab3ea453581b858ce2b3d81 100644 GIT binary patch literal 855 zcmV-d1E~BTiwFppBGFy|17u-zVJ>QOZ*BnHmP>ErHW0_}{uGAK8G(->MfOy0nqm(@ z(PDuvilT?%%W-R4hAn$H4f5SP$|hMyavn$*dkA0|5;>z8@<04(u14eWq`h3{$q(a6 zYU1Ri8`#rg*Nt5913%7FH+y;h%Tm0bzDqyV=HvO_=YRZGe!nsAELJIh?u?eX-OS?_ z@+z&H`KqmWfD*L?DHqx;T99`>+h(_kU!(BIkL`S!w=FH#JSvStfvB}X<4^8PXqH#) zfzN$*nR37!5)_IVFsd2?3JcCgltiL-B1}%cdhPROJ8xE=`23$9bdAvLx@l;K{@cInS zl=Fb6VhYnKBDhnnrYP~}X+1qyGyAP*d872ld3!NF+w)rHtJ#xD?FaiEH<{X;`VtLX zFPd%7vm3d~;62|1g=M)|M)S|^P?|4`ix`rOp@6J?4Vbb7uc88luGYb8!`uA8t1Fs6 z4;L#%B?Vu#s1()@H=>)9x>ABKKbUg5ag9lV7kYr0&DP%q5Z2fi0Kz02GZxS?Vs|{bo@7!`Q_g}|hf+NUqxg$dG hpa`6Su;04ztD6WQ%D{rLX_z(6@!yl0h000txpKkyF literal 849 zcmV-X1FrlZiwFo(DwQOZ*BnHmQ8QtHV}sI{uPGL8G#%QsbNp`rYZIi z6fG9$q9}Ucr{mVP3|sbY8sxv1$|hMyaz2pmUIJK#M9yf2e20(bYBU~C+RJsC{4k!x zCQMGcfxTPox{<5$P#)*8o4q{$WhvfI-^HJ5{qg+o^FMyezu%a57OR*(cSg(9ZsuVN zX%*MaeAPls+3glB$UD<*v)hEPFE1lMw)17$wr;sDqf#>oBBeBGdva&uW@*(P_}nLz zT=JBBgzQ2B3Rr_DVSr5_>71xp_{qsvuYKBV=gq2fHI=IDT#g;YP>h;B}*3Pri>V2V}6vXXS5*Ix&rmM-*T=~8nRBtf!JwD97%XS3}qnRkoD z7e!XF`LrE>NSjicV-ymPOZVy4&{RExz1z&)jaa>x^~??nACG81y8FK#U5`drFXzHL z-{Zn;w*H=6FxtF;3p$#RD1(xLx>H%oNJfO(r6kJX(U$F_Trd^?e=cb6rS*j1HD@5= zazH5ws70t<#fR@o3d_%vq7K>S+9^PGrhIT@0`EzK5ZXE9EIEoN zNbwCi2%)U@2@NQ%3Mq`>!VFVBC|+^NKoeW_W=UcBHKYii|JAcaDTMWr4#FoELMSlS zbm$kkoHQpPTM7z2QL_43QV>_N^a6-@q=6{9WBCnd1a>`ydZY_DLJCeD2)qS!z#1a= bNFl(AQj>tC8akZ3k@n(0L$8A>b_@Uj^{t;- diff --git a/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/mappings.json b/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/mappings.json index f9d5de0d0a94..ffd64ab3bb93 100644 --- a/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/mappings.json +++ b/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/mappings.json @@ -2,7 +2,7 @@ "type": "index", "value": { "aliases": {}, - "index": "metrics-endpoint-default-1", + "index": "metrics-endpoint.metadata-default-1", "mappings": { "_meta": { "version": "1.5.0-dev" diff --git a/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/data.json b/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/data.json index d3617dc23637..0f9f86b36dec 100644 --- a/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/data.json +++ b/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/data.json @@ -2,7 +2,7 @@ "type": "doc", "value": { "id": "3KVN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint-default-1", + "index": "metrics-endpoint.metadata-default-1", "source": { "@timestamp": 1579881969541, "agent": { @@ -51,7 +51,7 @@ "type": "doc", "value": { "id": "3aVN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint-default-1", + "index": "metrics-endpoint.metadata-default-1", "source": { "@timestamp": 1579881969541, "agent": { @@ -99,7 +99,7 @@ "type": "doc", "value": { "id": "3qVN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint-default-1", + "index": "metrics-endpoint.metadata-default-1", "source": { "@timestamp": 1579881969541, "agent": { @@ -145,7 +145,7 @@ "type": "doc", "value": { "id": "36VN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint-default-1", + "index": "metrics-endpoint.metadata-default-1", "source": { "@timestamp": 1579878369541, "agent": { @@ -194,7 +194,7 @@ "type": "doc", "value": { "id": "4KVN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint-default-1", + "index": "metrics-endpoint.metadata-default-1", "source": { "@timestamp": 1579878369541, "agent": { @@ -241,7 +241,7 @@ "type": "doc", "value": { "id": "4aVN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint-default-1", + "index": "metrics-endpoint.metadata-default-1", "source": { "@timestamp": 1579878369541, "agent": { @@ -288,7 +288,7 @@ "type": "doc", "value": { "id": "4qVN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint-default-1", + "index": "metrics-endpoint.metadata-default-1", "source": { "@timestamp": 1579874769541, "agent": { @@ -336,7 +336,7 @@ "type": "doc", "value": { "id": "46VN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint-default-1", + "index": "metrics-endpoint.metadata-default-1", "source": { "@timestamp": 1579874769541, "agent": { @@ -383,7 +383,7 @@ "type": "doc", "value": { "id": "5KVN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint-default-1", + "index": "metrics-endpoint.metadata-default-1", "source": { "@timestamp": 1579874769541, "agent": { diff --git a/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/mappings.json b/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/mappings.json index f9d5de0d0a94..ffd64ab3bb93 100644 --- a/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/mappings.json +++ b/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/mappings.json @@ -2,7 +2,7 @@ "type": "index", "value": { "aliases": {}, - "index": "metrics-endpoint-default-1", + "index": "metrics-endpoint.metadata-default-1", "mappings": { "_meta": { "version": "1.5.0-dev" From b83d145813e9e74bffb93705f384806fed2f3bd9 Mon Sep 17 00:00:00 2001 From: Robert Austin Date: Tue, 2 Jun 2020 16:52:19 -0400 Subject: [PATCH 31/50] [Endpoint][SIEM] Adjust types related to subplugin's redux (#67768) --- .../siem/common/endpoint_alerts/types.ts | 21 +--- x-pack/plugins/siem/public/app/types.ts | 52 ++++---- .../drag_drop_context_wrapper.tsx | 2 +- .../error_toast_dispatcher/index.test.tsx | 2 +- .../mock/endpoint/app_context_render.tsx | 8 +- .../siem/public/common/mock/global_state.ts | 19 +-- .../plugins/siem/public/common/mock/utils.ts | 22 ++-- .../siem/public/common/store/app/selectors.ts | 4 +- .../common/store/drag_and_drop/selectors.ts | 4 +- .../plugins/siem/public/common/store/index.ts | 38 +++++- .../public/common/store/inputs/selectors.ts | 2 +- .../siem/public/common/store/reducer.ts | 79 ++++++------ .../plugins/siem/public/common/store/store.ts | 15 ++- .../siem/public/common/store/test_utils.ts | 3 +- .../plugins/siem/public/common/store/types.ts | 112 ++++++++++++------ .../common/utils/clone_http_fetch_query.ts | 3 +- .../siem/public/endpoint_alerts/index.ts | 37 ++++-- .../endpoint_alerts/models/index_pattern.ts | 2 +- .../siem/public/endpoint_alerts/routes.tsx | 2 +- .../public/endpoint_alerts/store/action.ts | 4 +- .../store/alert_details.test.ts | 3 +- .../endpoint_alerts/store/alert_list.test.ts | 3 +- .../public/endpoint_alerts/store/index.ts | 20 ---- .../endpoint_alerts/store/middleware.ts | 3 +- .../public/endpoint_alerts/store/reducer.ts | 27 ++--- .../public/endpoint_alerts/store/selectors.ts | 2 +- .../view/alert_details.test.tsx | 2 +- .../view/details/metadata/file_accordion.tsx | 3 +- .../details/metadata/general_accordion.tsx | 3 +- .../view/details/metadata/hash_accordion.tsx | 3 +- .../view/details/metadata/host_accordion.tsx | 3 +- .../metadata/source_process_accordion.tsx | 3 +- .../source_process_token_accordion.tsx | 3 +- .../view/hooks/use_alerts_selector.ts | 5 +- .../endpoint_alerts/view/index.test.tsx | 2 +- .../siem/public/endpoint_hosts/index.ts | 35 ++++-- .../siem/public/endpoint_hosts/routes.tsx | 2 +- .../public/endpoint_hosts/store/index.test.ts | 3 +- .../siem/public/endpoint_hosts/store/index.ts | 22 ---- .../endpoint_hosts/store/middleware.test.ts | 3 +- .../public/endpoint_hosts/store/middleware.ts | 6 +- .../public/endpoint_hosts/store/reducer.ts | 32 +++-- .../siem/public/endpoint_hosts/view/hooks.ts | 2 +- .../siem/public/hosts/store/selectors.ts | 2 +- .../plugins/siem/public/management/index.ts | 42 +++++-- .../policy/store/policy_details/middleware.ts | 8 +- .../policy/store/policy_details/reducer.ts | 29 ++--- .../policy/store/policy_list/index.test.ts | 4 +- .../policy/store/policy_list/middleware.ts | 8 +- .../pages/policy/store/policy_list/reducer.ts | 26 ++-- .../plugins/siem/public/management/routes.tsx | 2 +- .../siem/public/management/store/index.ts | 8 -- .../public/management/store/middleware.ts | 29 ++--- .../siem/public/management/store/reducer.ts | 28 ++--- .../siem/public/management/store/types.ts | 26 ---- .../plugins/siem/public/management/types.ts | 7 ++ .../siem/public/network/store/selectors.ts | 2 +- .../public/timelines/store/timeline/model.ts | 2 +- .../timelines/store/timeline/selectors.ts | 2 +- 59 files changed, 438 insertions(+), 408 deletions(-) delete mode 100644 x-pack/plugins/siem/public/endpoint_alerts/store/index.ts delete mode 100644 x-pack/plugins/siem/public/endpoint_hosts/store/index.ts delete mode 100644 x-pack/plugins/siem/public/management/store/index.ts delete mode 100644 x-pack/plugins/siem/public/management/store/types.ts diff --git a/x-pack/plugins/siem/common/endpoint_alerts/types.ts b/x-pack/plugins/siem/common/endpoint_alerts/types.ts index 2df92b43ab52..3fbde79414aa 100644 --- a/x-pack/plugins/siem/common/endpoint_alerts/types.ts +++ b/x-pack/plugins/siem/common/endpoint_alerts/types.ts @@ -15,28 +15,9 @@ import { AlertEvent, KbnConfigSchemaInputTypeOf, AppLocation, + Immutable, } from '../endpoint/types'; -/** - * A deep readonly type that will make all children of a given object readonly recursively - */ -export type Immutable = T extends undefined | null | boolean | string | number - ? T - : unknown extends T - ? unknown - : T extends Array - ? ImmutableArray - : T extends Map - ? ImmutableMap - : T extends Set - ? ImmutableSet - : ImmutableObject; - -type ImmutableArray = ReadonlyArray>; -type ImmutableMap = ReadonlyMap, Immutable>; -type ImmutableSet = ReadonlySet>; -type ImmutableObject = { readonly [K in keyof T]: Immutable }; - /** * Values for the Alert APIs 'order' and 'direction' parameters. */ diff --git a/x-pack/plugins/siem/public/app/types.ts b/x-pack/plugins/siem/public/app/types.ts index 444e0066c3c7..4b00dee3b751 100644 --- a/x-pack/plugins/siem/public/app/types.ts +++ b/x-pack/plugins/siem/public/app/types.ts @@ -4,17 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Reducer, AnyAction, Middleware, Dispatch } from 'redux'; +import { + Reducer, + AnyAction, + Middleware, + Dispatch, + PreloadedState, + StateFromReducersMapObject, + CombinedState, +} from 'redux'; + import { NavTab } from '../common/components/navigation/types'; -import { HostsState } from '../hosts/store'; -import { NetworkState } from '../network/store'; -import { TimelineState } from '../timelines/store/timeline/types'; -import { ImmutableReducer, State } from '../common/store'; +import { State, SubPluginsInitReducer } from '../common/store'; import { Immutable } from '../../common/endpoint/types'; -import { AlertListState } from '../../common/endpoint_alerts/types'; import { AppAction } from '../common/store/actions'; -import { HostState } from '../endpoint_hosts/types'; -import { ManagementState } from '../management/store/types'; export enum SiemPageName { overview = 'overview', @@ -38,7 +41,7 @@ export type SiemNavTabKey = export type SiemNavTab = Record; export interface SecuritySubPluginStore { - initialState: Record; + initialState: Record; reducer: Record>; middleware?: Array>>>; } @@ -54,6 +57,10 @@ type SecuritySubPluginKeyStore = | 'hostList' | 'alertList' | 'management'; + +/** + * Returned by the various 'SecuritySubPlugin' classes from the `start` method. + */ export interface SecuritySubPluginWithStore extends SecuritySubPlugin { store: SecuritySubPluginStore; @@ -61,22 +68,17 @@ export interface SecuritySubPluginWithStore; - hostList: Immutable; - management: ManagementState; - }; - reducer: { - hosts: Reducer; - network: Reducer; - timeline: Reducer; - alertList: ImmutableReducer; - hostList: ImmutableReducer; - management: ImmutableReducer; - }; + initialState: PreloadedState< + CombinedState< + StateFromReducersMapObject< + /** SubPluginsInitReducer, being an interface, will not work in `StateFromReducersMapObject`. + * Picking its keys does the trick. + **/ + Pick + > + > + >; + reducer: SubPluginsInitReducer; middlewares: Array>>>; }; } diff --git a/x-pack/plugins/siem/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx b/x-pack/plugins/siem/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx index c71cd8e60596..c33677e41db0 100644 --- a/x-pack/plugins/siem/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx +++ b/x-pack/plugins/siem/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx @@ -15,7 +15,7 @@ import { BrowserFields } from '../../containers/source'; import { dragAndDropModel, dragAndDropSelectors } from '../../store'; import { timelineSelectors } from '../../../timelines/store/timeline'; import { IdToDataProvider } from '../../store/drag_and_drop/model'; -import { State } from '../../store/reducer'; +import { State } from '../../store/types'; import { DataProvider } from '../../../timelines/components/timeline/data_providers/data_provider'; import { reArrangeProviders } from '../../../timelines/components/timeline/data_providers/helpers'; import { ACTIVE_TIMELINE_REDUX_ID } from '../top_n'; diff --git a/x-pack/plugins/siem/public/common/components/error_toast_dispatcher/index.test.tsx b/x-pack/plugins/siem/public/common/components/error_toast_dispatcher/index.test.tsx index 50b20099b17d..39b17f7008e6 100644 --- a/x-pack/plugins/siem/public/common/components/error_toast_dispatcher/index.test.tsx +++ b/x-pack/plugins/siem/public/common/components/error_toast_dispatcher/index.test.tsx @@ -12,7 +12,7 @@ import { apolloClientObservable, mockGlobalState, SUB_PLUGINS_REDUCER } from '.. import { createStore } from '../../store/store'; import { ErrorToastDispatcher } from '.'; -import { State } from '../../store/reducer'; +import { State } from '../../store/types'; describe('Error Toast Dispatcher', () => { const state: State = mockGlobalState; diff --git a/x-pack/plugins/siem/public/common/mock/endpoint/app_context_render.tsx b/x-pack/plugins/siem/public/common/mock/endpoint/app_context_render.tsx index e62f36c2ec78..3c0189625ee2 100644 --- a/x-pack/plugins/siem/public/common/mock/endpoint/app_context_render.tsx +++ b/x-pack/plugins/siem/public/common/mock/endpoint/app_context_render.tsx @@ -15,10 +15,10 @@ import { depsStartMock } from './dependencies_start_mock'; import { MiddlewareActionSpyHelper, createSpyMiddleware } from '../../store/test_utils'; import { apolloClientObservable } from '../test_providers'; import { createStore, State, substateMiddlewareFactory } from '../../store'; -import { hostMiddlewareFactory } from '../../../endpoint_hosts/store'; import { alertMiddlewareFactory } from '../../../endpoint_alerts/store/middleware'; import { AppRootProvider } from './app_root_provider'; -import { managementMiddlewareFactory } from '../../../management/store'; +import { managementMiddlewareFactory } from '../../../management/store/middleware'; +import { hostMiddlewareFactory } from '../../../endpoint_hosts/store/middleware'; import { SUB_PLUGINS_REDUCER, mockGlobalState } from '..'; type UiRender = (ui: React.ReactElement, options?: RenderOptions) => RenderResult; @@ -56,8 +56,7 @@ export const createAppRootMockRenderer = (): AppContextTestRender => { const coreStart = coreMock.createStart({ basePath: '/mock' }); const depsStart = depsStartMock(); const middlewareSpy = createSpyMiddleware(); - const state: State = mockGlobalState; - const store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, [ + const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, apolloClientObservable, [ substateMiddlewareFactory( (globalState) => globalState.hostList, hostMiddlewareFactory(coreStart, depsStart) @@ -76,7 +75,6 @@ export const createAppRootMockRenderer = (): AppContextTestRender => { ); const render: UiRender = (ui, options) => { - // @ts-ignore return reactRender(ui, { wrapper: AppWrapper as React.ComponentType, ...options, diff --git a/x-pack/plugins/siem/public/common/mock/global_state.ts b/x-pack/plugins/siem/public/common/mock/global_state.ts index c96f67a39dbf..30dffa8dbf6b 100644 --- a/x-pack/plugins/siem/public/common/mock/global_state.ts +++ b/x-pack/plugins/siem/public/common/mock/global_state.ts @@ -27,11 +27,10 @@ import { networkModel } from '../../network/store'; import { TimelineType, TimelineStatus } from '../../../common/types/timeline'; import { initialAlertListState } from '../../endpoint_alerts/store/reducer'; import { initialHostListState } from '../../endpoint_hosts/store/reducer'; -import { getManagementInitialState } from '../../management/store'; - -const alertList = initialAlertListState(); -const hostList = initialHostListState(); -const management = getManagementInitialState(); +import { mockManagementState } from '../../management/store/reducer'; +import { AlertListState } from '../../../common/endpoint_alerts/types'; +import { HostState } from '../../endpoint_hosts/types'; +import { ManagementState } from '../../management/types'; export const mockGlobalState: State = { app: { @@ -233,7 +232,11 @@ export const mockGlobalState: State = { }, }, }, - alertList, - hostList, - management, + /** + * These state's are wrapped in `Immutable`, but for compatibility with the overall app architecture, + * they are cast to mutable versions here. + */ + alertList: initialAlertListState as AlertListState, + hostList: initialHostListState as HostState, + management: mockManagementState as ManagementState, }; diff --git a/x-pack/plugins/siem/public/common/mock/utils.ts b/x-pack/plugins/siem/public/common/mock/utils.ts index 532637acab76..1ff5cb8e734e 100644 --- a/x-pack/plugins/siem/public/common/mock/utils.ts +++ b/x-pack/plugins/siem/public/common/mock/utils.ts @@ -7,9 +7,13 @@ import { hostsReducer } from '../../hosts/store'; import { networkReducer } from '../../network/store'; import { timelineReducer } from '../../timelines/store/timeline/reducer'; -import { hostListReducer } from '../../endpoint_hosts/store'; -import { alertListReducer } from '../../endpoint_alerts/store'; -import { managementReducer } from '../../management/store'; +import { managementReducer } from '../../management/store/reducer'; +import { ManagementPluginReducer } from '../../management'; +import { SubPluginsInitReducer } from '../store'; +import { EndpointAlertsPluginReducer } from '../../endpoint_alerts'; +import { EndpointHostsPluginReducer } from '../../endpoint_hosts'; +import { alertListReducer } from '../../endpoint_alerts/store/reducer'; +import { hostListReducer } from '../../endpoint_hosts/store/reducer'; interface Global extends NodeJS.Global { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -18,11 +22,15 @@ interface Global extends NodeJS.Global { export const globalNode: Global = global; -export const SUB_PLUGINS_REDUCER = { +export const SUB_PLUGINS_REDUCER: SubPluginsInitReducer = { hosts: hostsReducer, network: networkReducer, timeline: timelineReducer, - hostList: hostListReducer, - alertList: alertListReducer, - management: managementReducer, + /** + * These state's are wrapped in `Immutable`, but for compatibility with the overall app architecture, + * they are cast to mutable versions here. + */ + hostList: hostListReducer as EndpointHostsPluginReducer['hostList'], + alertList: alertListReducer as EndpointAlertsPluginReducer['alertList'], + management: managementReducer as ManagementPluginReducer['management'], }; diff --git a/x-pack/plugins/siem/public/common/store/app/selectors.ts b/x-pack/plugins/siem/public/common/store/app/selectors.ts index c37695c2ccbe..d18cb73dbcfb 100644 --- a/x-pack/plugins/siem/public/common/store/app/selectors.ts +++ b/x-pack/plugins/siem/public/common/store/app/selectors.ts @@ -7,11 +7,9 @@ import { keys } from 'lodash/fp'; import memoizeOne from 'memoize-one'; import { createSelector } from 'reselect'; - import { Note } from '../../lib/note'; -import { State } from '../reducer'; - import { ErrorModel, NotesById } from './model'; +import { State } from '../types'; const selectNotesById = (state: State): NotesById => state.app.notesById; diff --git a/x-pack/plugins/siem/public/common/store/drag_and_drop/selectors.ts b/x-pack/plugins/siem/public/common/store/drag_and_drop/selectors.ts index 99a8369783cd..5d6534f96bc7 100644 --- a/x-pack/plugins/siem/public/common/store/drag_and_drop/selectors.ts +++ b/x-pack/plugins/siem/public/common/store/drag_and_drop/selectors.ts @@ -5,10 +5,8 @@ */ import { createSelector } from 'reselect'; - -import { State } from '../reducer'; - import { IdToDataProvider } from './model'; +import { State } from '../types'; const selectDataProviders = (state: State): IdToDataProvider => state.dragAndDrop.dataProviders; diff --git a/x-pack/plugins/siem/public/common/store/index.ts b/x-pack/plugins/siem/public/common/store/index.ts index 96eb5e159909..6227931b5326 100644 --- a/x-pack/plugins/siem/public/common/store/index.ts +++ b/x-pack/plugins/siem/public/common/store/index.ts @@ -8,18 +8,46 @@ export * from './model'; export * from './reducer'; export * from './selectors'; +import { Middleware, Dispatch } from 'redux'; import { createStore, getStore } from './store'; -import { SubstateMiddlewareFactory } from './types'; +import { ImmutableMiddleware, State } from './types'; +import { AppAction } from './actions'; +import { Immutable } from '../../../common/endpoint/types'; export { createStore, getStore }; -export const substateMiddlewareFactory: SubstateMiddlewareFactory = (selector, middleware) => { +/** + * Takes a selector and an `ImmutableMiddleware`. The + * middleware's version of `getState` will receive + * the result of the selector instead of the global state. + * + * This allows middleware to have knowledge of only a subsection of state. + * + * `selector` returns an `Immutable` version of the substate. + * `middleware` must be an `ImmutableMiddleware`. + * + * Returns a regular middleware, meant to be used with `applyMiddleware`. + */ +export const substateMiddlewareFactory = ( + selector: (state: State) => Substate | Immutable, + middleware: ImmutableMiddleware +): Middleware<{}, State, Dispatch>> => { return (api) => { const substateAPI = { ...api, - // Return just the substate instead of global state. - getState() { - return selector(api.getState()); + // Return the substate instead of global state. + getState(): Immutable { + /** + * The selector will receive the basic (mutable) version of state. This is because + * the top level state shape doesn't use `Immutable`. We cast the return value as `Immutable` + * so that the middleware won't be able to mutate state. + * + * Immutable enforces nothing structural about a type so casting + * a value as `Immutable` is safe as long as nothing else is going to mutate. + * Since the state came from the return value of a reducer, the reducer will (hopefully) + * not be mutating it. + */ + return selector(api.getState()) as Immutable; }, }; return middleware(substateAPI); diff --git a/x-pack/plugins/siem/public/common/store/inputs/selectors.ts b/x-pack/plugins/siem/public/common/store/inputs/selectors.ts index 95c463776e28..0eee5ebbfbf7 100644 --- a/x-pack/plugins/siem/public/common/store/inputs/selectors.ts +++ b/x-pack/plugins/siem/public/common/store/inputs/selectors.ts @@ -6,7 +6,7 @@ import { createSelector } from 'reselect'; -import { State } from '../reducer'; +import { State } from '../types'; import { InputsModel, InputsRange, GlobalQuery } from './model'; diff --git a/x-pack/plugins/siem/public/common/store/reducer.ts b/x-pack/plugins/siem/public/common/store/reducer.ts index e06543b8d718..ba85fbef860d 100644 --- a/x-pack/plugins/siem/public/common/store/reducer.ts +++ b/x-pack/plugins/siem/public/common/store/reducer.ts @@ -4,47 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -import { combineReducers } from 'redux'; +import { combineReducers, PreloadedState, AnyAction, Reducer } from 'redux'; -import { appReducer, AppState, initialAppState } from './app'; -import { dragAndDropReducer, DragAndDropState, initialDragAndDropState } from './drag_and_drop'; -import { createInitialInputsState, initialInputsState, inputsReducer, InputsState } from './inputs'; +import { appReducer, initialAppState } from './app'; +import { dragAndDropReducer, initialDragAndDropState } from './drag_and_drop'; +import { createInitialInputsState, inputsReducer } from './inputs'; -import { HostsPluginState, HostsPluginReducer } from '../../hosts/store'; -import { NetworkPluginState, NetworkPluginReducer } from '../../network/store'; -import { TimelinePluginState, TimelinePluginReducer } from '../../timelines/store/timeline'; -import { - EndpointAlertsPluginState, - EndpointAlertsPluginReducer, -} from '../../endpoint_alerts/store'; -import { EndpointHostsPluginState, EndpointHostsPluginReducer } from '../../endpoint_hosts/store'; +import { HostsPluginReducer } from '../../hosts/store'; +import { NetworkPluginReducer } from '../../network/store'; +import { TimelinePluginReducer } from '../../timelines/store/timeline'; -import { ManagementPluginReducer, ManagementPluginState } from '../../management/store/types'; - -export interface State - extends HostsPluginState, - NetworkPluginState, - TimelinePluginState, - EndpointAlertsPluginState, - EndpointHostsPluginState, - ManagementPluginState { - app: AppState; - dragAndDrop: DragAndDropState; - inputs: InputsState; -} - -export const initialState: Pick = { - app: initialAppState, - dragAndDrop: initialDragAndDropState, - inputs: initialInputsState, -}; - -type SubPluginsInitState = HostsPluginState & - NetworkPluginState & - TimelinePluginState & - EndpointAlertsPluginState & - EndpointHostsPluginState & - ManagementPluginState; +import { SecuritySubPlugins } from '../../app/types'; +import { ManagementPluginReducer } from '../../management'; +import { EndpointAlertsPluginReducer } from '../../endpoint_alerts'; +import { EndpointHostsPluginReducer } from '../../endpoint_hosts'; +import { State } from './types'; +import { AppAction } from './actions'; export type SubPluginsInitReducer = HostsPluginReducer & NetworkPluginReducer & @@ -53,14 +28,28 @@ export type SubPluginsInitReducer = HostsPluginReducer & EndpointHostsPluginReducer & ManagementPluginReducer; -export const createInitialState = (pluginsInitState: SubPluginsInitState): State => ({ - ...initialState, - ...pluginsInitState, - inputs: createInitialInputsState(), -}); +/** + * Factory for the 'initialState' that is used to preload state into the Security App's redux store. + */ +export const createInitialState = ( + pluginsInitState: SecuritySubPlugins['store']['initialState'] +): PreloadedState => { + const preloadedState: PreloadedState = { + app: initialAppState, + dragAndDrop: initialDragAndDropState, + ...pluginsInitState, + inputs: createInitialInputsState(), + }; + return preloadedState; +}; -export const createReducer = (pluginsReducer: SubPluginsInitReducer) => - combineReducers({ +/** + * Factory for the Security app's redux reducer. + */ +export const createReducer: ( + pluginsReducer: SubPluginsInitReducer +) => Reducer = (pluginsReducer: SubPluginsInitReducer) => + combineReducers({ app: appReducer, dragAndDrop: dragAndDropReducer, inputs: inputsReducer, diff --git a/x-pack/plugins/siem/public/common/store/store.ts b/x-pack/plugins/siem/public/common/store/store.ts index 10ea61828ed3..276dcdcaedb8 100644 --- a/x-pack/plugins/siem/public/common/store/store.ts +++ b/x-pack/plugins/siem/public/common/store/store.ts @@ -12,6 +12,7 @@ import { Store, Middleware, Dispatch, + PreloadedState, } from 'redux'; import { createEpicMiddleware } from 'redux-observable'; @@ -21,11 +22,12 @@ import { telemetryMiddleware } from '../lib/telemetry'; import { appSelectors } from './app'; import { timelineSelectors } from '../../timelines/store/timeline'; import { inputsSelectors } from './inputs'; -import { State, SubPluginsInitReducer, createReducer } from './reducer'; +import { SubPluginsInitReducer, createReducer } from './reducer'; import { createRootEpic } from './epic'; import { AppApolloClient } from '../lib/lib'; import { AppAction } from './actions'; import { Immutable } from '../../../common/endpoint/types'; +import { State } from './types'; type ComposeType = typeof compose; declare global { @@ -33,10 +35,17 @@ declare global { __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: ComposeType; } } +/** + * The Redux store type for the Security app. + */ +export type SecurityAppStore = Store; let store: Store | null = null; -export { SubPluginsInitReducer }; + +/** + * Factory for Security App's redux store. + */ export const createStore = ( - state: State, + state: PreloadedState, pluginsReducer: SubPluginsInitReducer, apolloClient: Observable, additionalMiddleware?: Array>>> diff --git a/x-pack/plugins/siem/public/common/store/test_utils.ts b/x-pack/plugins/siem/public/common/store/test_utils.ts index 511cdcb6f067..89558c37909f 100644 --- a/x-pack/plugins/siem/public/common/store/test_utils.ts +++ b/x-pack/plugins/siem/public/common/store/test_utils.ts @@ -5,9 +5,8 @@ */ import { Dispatch } from 'redux'; -import { State } from './reducer'; +import { State, ImmutableMiddlewareFactory } from './types'; import { AppAction } from './actions'; -import { ImmutableMiddlewareFactory } from './types'; /** * Utilities for testing Redux middleware diff --git a/x-pack/plugins/siem/public/common/store/types.ts b/x-pack/plugins/siem/public/common/store/types.ts index a4bfdeb30b43..b9942979beb1 100644 --- a/x-pack/plugins/siem/public/common/store/types.ts +++ b/x-pack/plugins/siem/public/common/store/types.ts @@ -4,19 +4,40 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - Dispatch, - Action as ReduxAction, - AnyAction as ReduxAnyAction, - Action, - Middleware, -} from 'redux'; +import { Dispatch, Action, Middleware, CombinedState } from 'redux'; import { CoreStart } from '../../../../../../src/core/public'; -import { Immutable } from '../../../common/endpoint_alerts/types'; -import { State } from './reducer'; import { StartPlugins } from '../../types'; import { AppAction } from './actions'; +import { Immutable } from '../../../common/endpoint/types'; +import { AppState } from './app/reducer'; +import { InputsState } from './inputs/reducer'; +import { HostsPluginState } from '../../hosts/store'; +import { DragAndDropState } from './drag_and_drop/reducer'; +import { TimelinePluginState } from '../../timelines/store/timeline'; +import { NetworkPluginState } from '../../network/store'; +import { EndpointAlertsPluginState } from '../../endpoint_alerts'; +import { EndpointHostsPluginState } from '../../endpoint_hosts'; +import { ManagementPluginState } from '../../management'; + +/** + * The redux `State` type for the Security App. + * We use `CombinedState` to wrap our shape because we create our reducer using `combineReducers`. + * `combineReducers` returns a type wrapped in `CombinedState`. + * `CombinedState` is required for redux to know what keys to make optional when preloaded state into a store. + */ +export type State = CombinedState< + HostsPluginState & + NetworkPluginState & + TimelinePluginState & + EndpointAlertsPluginState & + EndpointHostsPluginState & + ManagementPluginState & { + app: AppState; + dragAndDrop: DragAndDropState; + inputs: InputsState; + } +>; export type KueryFilterQueryKind = 'kuery' | 'lucene'; @@ -67,60 +88,73 @@ export type ImmutableMiddlewareFactory = ( * Middleware will be of the `ImmutableMiddleware` variety. Not able to directly * change actions or state. */ -export type ImmutableMultipleMiddlewareFactory = ( +export type SecuritySubPluginMiddlewareFactory = ( coreStart: CoreStart, depsStart: Pick -) => Array>; +) => Array>>>; /** - * Simple type for a redux selector. + * Like `Reducer` from `redux` but it accepts immutable versions of `state` and `action`. + * Use this type for all Reducers in order to help enforce our pattern of immutable state. */ -type Selector = (state: S) => R; +export type ImmutableReducer = ( + state: Immutable | undefined, + action: Immutable +) => S | Immutable; /** - * Takes a selector and an `ImmutableMiddleware`. The - * middleware's version of `getState` will receive - * the result of the selector instead of the global state. - * - * This allows middleware to have knowledge of only a subsection of state. - * - * `selector` returns an `Immutable` version of the substate. - * `middleware` must be an `ImmutableMiddleware`. - * - * Returns a regular middleware, meant to be used with `applyMiddleware`. + * A alternate interface for `redux`'s `combineReducers`. Will work with the same underlying implementation, + * but will enforce that `Immutable` versions of `state` and `action` are received. */ -export type SubstateMiddlewareFactory = ( - selector: Selector>, - middleware: ImmutableMiddleware -) => Middleware<{}, State, Dispatch>>; +export type ImmutableCombineReducers = >( + reducers: M +) => ImmutableReducer< + CombinedState>, + ActionFromImmutableReducersMapObject +>; /** - * Like `Reducer` from `redux` but it accepts immutable versions of `state` and `action`. - * Use this type for all Reducers in order to help enforce our pattern of immutable state. + * Helper type for `ImmutableCombineReducers`. Infers the combined state type from an immutable reducer map. */ -export type ImmutableReducer = ( - state: Immutable | undefined, - action: Immutable -) => State | Immutable; +type StateFromImmutableReducersMapObject = M extends ImmutableReducersMapObject + ? { [P in keyof M]: M[P] extends ImmutableReducer ? S : never } + : never; /** - * A alternate interface for `redux`'s `combineReducers`. Will work with the same underlying implementation, - * but will enforce that `Immutable` versions of `state` and `action` are received. + * Helper type for `ImmutableCombineReducers`. Infers the combined action type from an immutable reducer map. + */ +type ActionFromImmutableReducersMapObject = M extends ImmutableReducersMapObject + ? ActionFromImmutableReducer> + : never; + +/** + * Helper type for `ImmutableCombineReducers`. Infers the combined reducer type from an immutable reducer map. + */ +type ImmutableReducerFromImmutableReducersMapObject = M extends { + [P in keyof M]: infer R; +} + ? R extends ImmutableReducer + ? R + : never + : never; + +/** + * Helper type for `ImmutableCombineReducers`. Infers the action type for an immutable reducer. */ -export type ImmutableCombineReducers = ( - reducers: ImmutableReducersMapObject -) => ImmutableReducer; +type ActionFromImmutableReducer = R extends ImmutableReducer ? A : never; /** + * Helper type for `ImmutableCombineReducers`. * Like `redux`'s `ReducersMapObject` (which is used by `combineReducers`) but enforces that * the `state` and `action` received are `Immutable` versions. */ -type ImmutableReducersMapObject = { +type ImmutableReducersMapObject = { [K in keyof S]: ImmutableReducer; }; /** * A better type for createStructuredSelector. This doesn't support the options object. + * https://github.com/reduxjs/reselect/pull/454 */ export type CreateStructuredSelector = < SelectorMap extends { [key: string]: (...args: never[]) => unknown } diff --git a/x-pack/plugins/siem/public/common/utils/clone_http_fetch_query.ts b/x-pack/plugins/siem/public/common/utils/clone_http_fetch_query.ts index bfa433dc9f9a..90b81df8bc21 100644 --- a/x-pack/plugins/siem/public/common/utils/clone_http_fetch_query.ts +++ b/x-pack/plugins/siem/public/common/utils/clone_http_fetch_query.ts @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Immutable } from '../../../common/endpoint_alerts/types'; - import { HttpFetchQuery } from '../../../../../../src/core/public'; +import { Immutable } from '../../../common/endpoint/types'; export function cloneHttpFetchQuery(query: Immutable): HttpFetchQuery { const clone: HttpFetchQuery = {}; diff --git a/x-pack/plugins/siem/public/endpoint_alerts/index.ts b/x-pack/plugins/siem/public/endpoint_alerts/index.ts index 6380edbde695..e7e45b95ceb9 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/index.ts +++ b/x-pack/plugins/siem/public/endpoint_alerts/index.ts @@ -4,15 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Reducer } from 'redux'; import { SecuritySubPluginWithStore } from '../app/types'; -import { getEndpointAlertsRoutes } from './routes'; -import { Immutable } from '../../common/endpoint/types'; -import { initialAlertListState, alertListReducer } from './store/reducer'; +import { endpointAlertsRoutes } from './routes'; +import { alertListReducer } from './store/reducer'; import { AlertListState } from '../../common/endpoint_alerts/types'; import { alertMiddlewareFactory } from './store/middleware'; import { substateMiddlewareFactory } from '../common/store'; import { CoreStart } from '../../../../../src/core/public'; import { StartPlugins } from '../types'; +import { AppAction } from '../common/store/actions'; + +/** + * Internally, our state is sometimes immutable, ignore that in our external + * interface. + */ +export interface EndpointAlertsPluginState { + alertList: AlertListState; +} + +/** + * Internally, we use `ImmutableReducer`, but we present a regular reducer + * externally for compatibility w/ regular redux. + */ +export interface EndpointAlertsPluginReducer { + alertList: Reducer; +} export class EndpointAlerts { public setup() {} @@ -20,20 +37,24 @@ export class EndpointAlerts { public start( core: CoreStart, plugins: StartPlugins - ): SecuritySubPluginWithStore<'alertList', Immutable> { + ): SecuritySubPluginWithStore<'alertList', AlertListState> { const { data, ingestManager } = plugins; const middleware = [ - substateMiddlewareFactory( + substateMiddlewareFactory( (globalState) => globalState.alertList, alertMiddlewareFactory(core, { data, ingestManager }) ), ]; return { - routes: getEndpointAlertsRoutes(), + routes: endpointAlertsRoutes(), store: { - initialState: { alertList: initialAlertListState() }, - reducer: { alertList: alertListReducer }, + initialState: { alertList: undefined }, + /** + * Cast the ImmutableReducer to a regular reducer for compatibility with + * the subplugin architecture (which expects plain redux reducers.) + */ + reducer: { alertList: alertListReducer } as EndpointAlertsPluginReducer, middleware, }, }; diff --git a/x-pack/plugins/siem/public/endpoint_alerts/models/index_pattern.ts b/x-pack/plugins/siem/public/endpoint_alerts/models/index_pattern.ts index 8daaa3fef7a2..3eb347c6cada 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/models/index_pattern.ts +++ b/x-pack/plugins/siem/public/endpoint_alerts/models/index_pattern.ts @@ -6,7 +6,7 @@ import { all } from 'deepmerge'; import { IIndexPattern } from 'src/plugins/data/public'; -import { Immutable } from '../../../common/endpoint_alerts/types'; +import { Immutable } from '../../../common/endpoint/types'; /** * Model for the `IIndexPattern` interface exported by the `data` plugin. diff --git a/x-pack/plugins/siem/public/endpoint_alerts/routes.tsx b/x-pack/plugins/siem/public/endpoint_alerts/routes.tsx index 60df7f5d4712..d62ef20c384d 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/routes.tsx +++ b/x-pack/plugins/siem/public/endpoint_alerts/routes.tsx @@ -9,7 +9,7 @@ import { Route } from 'react-router-dom'; import { AlertIndex } from './view'; -export const getEndpointAlertsRoutes = () => [ +export const endpointAlertsRoutes = () => [ , diff --git a/x-pack/plugins/siem/public/endpoint_alerts/store/action.ts b/x-pack/plugins/siem/public/endpoint_alerts/store/action.ts index ae103edaa5a2..3330cef1816a 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/store/action.ts +++ b/x-pack/plugins/siem/public/endpoint_alerts/store/action.ts @@ -5,8 +5,8 @@ */ import { IIndexPattern } from 'src/plugins/data/public'; -// import { Immutable } from '../../../common/types'; -import { AlertDetails, AlertListData, Immutable } from '../../../common/endpoint_alerts/types'; +import { Immutable } from '../../../common/endpoint/types'; +import { AlertDetails, AlertListData } from '../../../common/endpoint_alerts/types'; interface ServerReturnedAlertsData { readonly type: 'serverReturnedAlertsData'; diff --git a/x-pack/plugins/siem/public/endpoint_alerts/store/alert_details.test.ts b/x-pack/plugins/siem/public/endpoint_alerts/store/alert_details.test.ts index b634e6455d97..8e20ad089b9c 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/store/alert_details.test.ts +++ b/x-pack/plugins/siem/public/endpoint_alerts/store/alert_details.test.ts @@ -8,7 +8,7 @@ import { Store, createStore, applyMiddleware } from 'redux'; import { createBrowserHistory, History } from 'history'; import { coreMock } from '../../../../../../src/core/public/mocks'; -import { AlertListState, Immutable } from '../../../common/endpoint_alerts/types'; +import { AlertListState } from '../../../common/endpoint_alerts/types'; import { depsStartMock, DepsStartMock } from '../../common/mock/endpoint'; import { alertListReducer } from './reducer'; @@ -16,6 +16,7 @@ import { alertListReducer } from './reducer'; import { alertMiddlewareFactory } from './middleware'; import { mockAlertResultList } from './mock_alert_result_list'; +import { Immutable } from '../../../common/endpoint/types'; describe('alert details tests', () => { let store: Store; diff --git a/x-pack/plugins/siem/public/endpoint_alerts/store/alert_list.test.ts b/x-pack/plugins/siem/public/endpoint_alerts/store/alert_list.test.ts index 8f82c2522ea6..a21e44955296 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/store/alert_list.test.ts +++ b/x-pack/plugins/siem/public/endpoint_alerts/store/alert_list.test.ts @@ -7,12 +7,13 @@ import { Store, createStore, applyMiddleware } from 'redux'; import { History, createBrowserHistory } from 'history'; import { alertListReducer } from './reducer'; -import { AlertListState, AlertResultList, Immutable } from '../../../common/endpoint_alerts/types'; +import { AlertListState, AlertResultList } from '../../../common/endpoint_alerts/types'; import { alertMiddlewareFactory } from './middleware'; import { coreMock } from 'src/core/public/mocks'; import { DepsStartMock, depsStartMock } from '../../common/mock/endpoint'; import { isOnAlertPage } from './selectors'; import { mockAlertResultList } from './mock_alert_result_list'; +import { Immutable } from '../../../common/endpoint/types'; describe('alert list tests', () => { let store: Store; diff --git a/x-pack/plugins/siem/public/endpoint_alerts/store/index.ts b/x-pack/plugins/siem/public/endpoint_alerts/store/index.ts deleted file mode 100644 index dd97d60c532b..000000000000 --- a/x-pack/plugins/siem/public/endpoint_alerts/store/index.ts +++ /dev/null @@ -1,20 +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 { AlertListState, Immutable } from '../../../common/endpoint_alerts/types'; -import { ImmutableReducer } from '../../common/store'; -import { AppAction } from '../../common/store/actions'; - -export { alertListReducer } from './reducer'; -export { AlertAction } from './action'; - -export interface EndpointAlertsPluginState { - alertList: Immutable; -} - -export interface EndpointAlertsPluginReducer { - alertList: ImmutableReducer; -} diff --git a/x-pack/plugins/siem/public/endpoint_alerts/store/middleware.ts b/x-pack/plugins/siem/public/endpoint_alerts/store/middleware.ts index b8e8d36801d4..dd84b4fcff5b 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/store/middleware.ts +++ b/x-pack/plugins/siem/public/endpoint_alerts/store/middleware.ts @@ -20,9 +20,8 @@ import { uiQueryParams, isAlertPageTabChange, } from './selectors'; -import { Immutable } from '../../../common/endpoint/types'; -export const alertMiddlewareFactory: ImmutableMiddlewareFactory> = ( +export const alertMiddlewareFactory: ImmutableMiddlewareFactory = ( coreStart, depsStart ) => { diff --git a/x-pack/plugins/siem/public/endpoint_alerts/store/reducer.ts b/x-pack/plugins/siem/public/endpoint_alerts/store/reducer.ts index 3e79ad4d1c61..22fc5025656d 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/store/reducer.ts +++ b/x-pack/plugins/siem/public/endpoint_alerts/store/reducer.ts @@ -4,26 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Immutable, AlertListState } from '../../../common/endpoint_alerts/types'; +import { Immutable } from '../../../common/endpoint/types'; +import { AlertListState } from '../../../common/endpoint_alerts/types'; import { ImmutableReducer } from '../../common/store'; import { AppAction } from '../../common/store/actions'; -export const initialAlertListState = (): Immutable => { - return { - alerts: [], - alertDetails: undefined, - pageSize: 10, - pageIndex: 0, - total: 0, - location: undefined, - searchBar: { - patterns: [], - }, - }; +export const initialAlertListState: Immutable = { + alerts: [], + alertDetails: undefined, + pageSize: 10, + pageIndex: 0, + total: 0, + location: undefined, + searchBar: { + patterns: [], + }, }; export const alertListReducer: ImmutableReducer = ( - state = initialAlertListState(), + state = initialAlertListState, action ) => { if (action.type === 'serverReturnedAlertsData') { diff --git a/x-pack/plugins/siem/public/endpoint_alerts/store/selectors.ts b/x-pack/plugins/siem/public/endpoint_alerts/store/selectors.ts index bec524f948d7..ab0e4165a257 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/store/selectors.ts +++ b/x-pack/plugins/siem/public/endpoint_alerts/store/selectors.ts @@ -12,10 +12,10 @@ import { } from 'reselect'; import { encode, decode } from 'rison-node'; +import { Immutable } from '../../../common/endpoint/types'; import { Query, TimeRange, Filter } from '../../../../../../src/plugins/data/public'; import { - Immutable, AlertingIndexGetQueryInput, AlertListState, AlertingIndexUIQueryParams, diff --git a/x-pack/plugins/siem/public/endpoint_alerts/view/alert_details.test.tsx b/x-pack/plugins/siem/public/endpoint_alerts/view/alert_details.test.tsx index a4fe4811fa60..de939ad4f54c 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/view/alert_details.test.tsx +++ b/x-pack/plugins/siem/public/endpoint_alerts/view/alert_details.test.tsx @@ -11,7 +11,7 @@ import { Store } from 'redux'; import { mockAlertDetailsResult } from '../store/mock_alert_result_list'; import { alertPageTestRender } from './test_helpers/render_alert_page'; import { AppAction } from '../../common/store/actions'; -import { State } from '../../common/store/reducer'; +import { State } from '../../common/store/types'; describe('when the alert details flyout is open', () => { let render: () => reactTestingLibrary.RenderResult; diff --git a/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/file_accordion.tsx b/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/file_accordion.tsx index 1009bec0cec0..e319cbe52a69 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/file_accordion.tsx +++ b/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/file_accordion.tsx @@ -6,7 +6,8 @@ import React, { memo, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiAccordion, EuiDescriptionList } from '@elastic/eui'; -import { Immutable, AlertData } from '../../../../../common/endpoint_alerts/types'; +import { Immutable } from '../../../../../common/endpoint/types'; +import { AlertData } from '../../../../../common/endpoint_alerts/types'; import { FormattedDate } from '../../formatted_date'; export const FileAccordion = memo(({ alertData }: { alertData: Immutable }) => { diff --git a/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/general_accordion.tsx b/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/general_accordion.tsx index fc0d38188fd2..76ee46cb5a10 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/general_accordion.tsx +++ b/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/general_accordion.tsx @@ -6,8 +6,9 @@ import React, { memo, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiAccordion, EuiDescriptionList } from '@elastic/eui'; -import { Immutable, AlertData } from '../../../../../common/endpoint_alerts/types'; +import { AlertData } from '../../../../../common/endpoint_alerts/types'; import { FormattedDate } from '../../formatted_date'; +import { Immutable } from '../../../../../common/endpoint/types'; export const GeneralAccordion = memo(({ alertData }: { alertData: Immutable }) => { const columns = useMemo(() => { diff --git a/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/hash_accordion.tsx b/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/hash_accordion.tsx index ae62bd80b73b..3077a98905df 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/hash_accordion.tsx +++ b/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/hash_accordion.tsx @@ -6,7 +6,8 @@ import React, { memo, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiAccordion, EuiDescriptionList } from '@elastic/eui'; -import { Immutable, AlertData } from '../../../../../common/endpoint_alerts/types'; +import { Immutable } from '../../../../../common/endpoint/types'; +import { AlertData } from '../../../../../common/endpoint_alerts/types'; export const HashAccordion = memo(({ alertData }: { alertData: Immutable }) => { const columns = useMemo(() => { diff --git a/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/host_accordion.tsx b/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/host_accordion.tsx index 70723efd97b8..bea49cbba06b 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/host_accordion.tsx +++ b/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/host_accordion.tsx @@ -8,7 +8,8 @@ import { i18n } from '@kbn/i18n'; import { EuiAccordion, EuiDescriptionList, EuiHealth } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Immutable, AlertDetails } from '../../../../../common/endpoint_alerts/types'; +import { Immutable } from '../../../../../common/endpoint/types'; +import { AlertDetails } from '../../../../../common/endpoint_alerts/types'; export const HostAccordion = memo(({ alertData }: { alertData: Immutable }) => { const columns = useMemo(() => { diff --git a/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/source_process_accordion.tsx b/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/source_process_accordion.tsx index 607327a49de1..b2d0e369bc45 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/source_process_accordion.tsx +++ b/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/source_process_accordion.tsx @@ -6,7 +6,8 @@ import React, { memo, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiAccordion, EuiDescriptionList } from '@elastic/eui'; -import { Immutable, AlertData } from '../../../../../common/endpoint_alerts/types'; +import { Immutable } from '../../../../../common/endpoint/types'; +import { AlertData } from '../../../../../common/endpoint_alerts/types'; export const SourceProcessAccordion = memo(({ alertData }: { alertData: Immutable }) => { const columns = useMemo(() => { diff --git a/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/source_process_token_accordion.tsx b/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/source_process_token_accordion.tsx index 9be494d92a88..e559daa0a635 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/source_process_token_accordion.tsx +++ b/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/source_process_token_accordion.tsx @@ -6,7 +6,8 @@ import React, { memo, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiAccordion, EuiDescriptionList } from '@elastic/eui'; -import { Immutable, AlertData } from '../../../../../common/endpoint_alerts/types'; +import { Immutable } from '../../../../../common/endpoint/types'; +import { AlertData } from '../../../../../common/endpoint_alerts/types'; export const SourceProcessTokenAccordion = memo( ({ alertData }: { alertData: Immutable }) => { diff --git a/x-pack/plugins/siem/public/endpoint_alerts/view/hooks/use_alerts_selector.ts b/x-pack/plugins/siem/public/endpoint_alerts/view/hooks/use_alerts_selector.ts index 726f6a453cb5..95c347893b99 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/view/hooks/use_alerts_selector.ts +++ b/x-pack/plugins/siem/public/endpoint_alerts/view/hooks/use_alerts_selector.ts @@ -5,8 +5,9 @@ */ import { useSelector } from 'react-redux'; -import { Immutable, AlertListState } from '../../../../common/endpoint_alerts/types'; -import { State } from '../../../common/store/reducer'; +import { Immutable } from '../../../../common/endpoint/types'; +import { AlertListState } from '../../../../common/endpoint_alerts/types'; +import { State } from '../../../common/store/types'; export function useAlertListSelector( selector: ( diff --git a/x-pack/plugins/siem/public/endpoint_alerts/view/index.test.tsx b/x-pack/plugins/siem/public/endpoint_alerts/view/index.test.tsx index 4967d7661a08..3d056ca5c188 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/view/index.test.tsx +++ b/x-pack/plugins/siem/public/endpoint_alerts/view/index.test.tsx @@ -12,7 +12,7 @@ import { Store } from 'redux'; import { mockAlertResultList } from '../store/mock_alert_result_list'; import { alertPageTestRender } from './test_helpers/render_alert_page'; import { DepsStartMock } from '../../common/mock/endpoint'; -import { State } from '../../common/store/reducer'; +import { State } from '../../common/store/types'; import { AppAction } from '../../common/store/actions'; describe('when on the alerting page', () => { diff --git a/x-pack/plugins/siem/public/endpoint_hosts/index.ts b/x-pack/plugins/siem/public/endpoint_hosts/index.ts index 1c2649ec5cf9..bd1c5f96f8cd 100644 --- a/x-pack/plugins/siem/public/endpoint_hosts/index.ts +++ b/x-pack/plugins/siem/public/endpoint_hosts/index.ts @@ -4,15 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Reducer } from 'redux'; import { SecuritySubPluginWithStore } from '../app/types'; -import { getEndpointHostsRoutes } from './routes'; -import { initialHostListState, hostListReducer } from './store/reducer'; -import { Immutable } from '../../common/endpoint/types'; +import { endpointHostsRoutes } from './routes'; +import { hostListReducer } from './store/reducer'; import { HostState } from './types'; import { hostMiddlewareFactory } from './store/middleware'; import { CoreStart } from '../../../../../src/core/public'; import { StartPlugins } from '../types'; import { substateMiddlewareFactory } from '../common/store'; +import { AppAction } from '../common/store/actions'; + +/** + * Internally, our state is sometimes immutable, ignore that in our external + * interface. + */ +export interface EndpointHostsPluginState { + hostList: HostState; +} + +/** + * Internally, we use `ImmutableReducer`, but we present a regular reducer + * externally for compatibility w/ regular redux. + */ +export interface EndpointHostsPluginReducer { + hostList: Reducer; +} export class EndpointHosts { public setup() {} @@ -20,7 +37,7 @@ export class EndpointHosts { public start( core: CoreStart, plugins: StartPlugins - ): SecuritySubPluginWithStore<'hostList', Immutable> { + ): SecuritySubPluginWithStore<'hostList', HostState> { const { data, ingestManager } = plugins; const middleware = [ substateMiddlewareFactory( @@ -29,10 +46,14 @@ export class EndpointHosts { ), ]; return { - routes: getEndpointHostsRoutes(), + routes: endpointHostsRoutes(), store: { - initialState: { hostList: initialHostListState() }, - reducer: { hostList: hostListReducer }, + initialState: { hostList: undefined }, + /** + * Cast the ImmutableReducer to a regular reducer for compatibility with + * the subplugin architecture (which expects plain redux reducers.) + */ + reducer: { hostList: hostListReducer } as EndpointHostsPluginReducer, middleware, }, }; diff --git a/x-pack/plugins/siem/public/endpoint_hosts/routes.tsx b/x-pack/plugins/siem/public/endpoint_hosts/routes.tsx index b7e549dc4e5e..4ff9ecfaeab0 100644 --- a/x-pack/plugins/siem/public/endpoint_hosts/routes.tsx +++ b/x-pack/plugins/siem/public/endpoint_hosts/routes.tsx @@ -9,7 +9,7 @@ import { Route } from 'react-router-dom'; import { HostList } from './view'; -export const getEndpointHostsRoutes = () => [ +export const endpointHostsRoutes = () => [ , diff --git a/x-pack/plugins/siem/public/endpoint_hosts/store/index.test.ts b/x-pack/plugins/siem/public/endpoint_hosts/store/index.test.ts index 8518c37fe3f5..71452993abf0 100644 --- a/x-pack/plugins/siem/public/endpoint_hosts/store/index.test.ts +++ b/x-pack/plugins/siem/public/endpoint_hosts/store/index.test.ts @@ -5,10 +5,11 @@ */ import { createStore, Dispatch, Store } from 'redux'; -import { HostAction, hostListReducer } from './index'; import { HostState } from '../types'; import { listData } from './selectors'; import { mockHostResultList } from './mock_host_result_list'; +import { HostAction } from './action'; +import { hostListReducer } from './reducer'; describe('HostList store concerns', () => { let store: Store; diff --git a/x-pack/plugins/siem/public/endpoint_hosts/store/index.ts b/x-pack/plugins/siem/public/endpoint_hosts/store/index.ts deleted file mode 100644 index eafea5b9c740..000000000000 --- a/x-pack/plugins/siem/public/endpoint_hosts/store/index.ts +++ /dev/null @@ -1,22 +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 { HostState } from '../types'; -import { ImmutableReducer } from '../../common/store'; -import { AppAction } from '../../common/store/actions'; -import { Immutable } from '../../../common/endpoint/types'; - -export { hostListReducer } from './reducer'; -export { HostAction } from './action'; -export { hostMiddlewareFactory } from './middleware'; - -export interface EndpointHostsPluginState { - hostList: Immutable; -} - -export interface EndpointHostsPluginReducer { - hostList: ImmutableReducer; -} diff --git a/x-pack/plugins/siem/public/endpoint_hosts/store/middleware.test.ts b/x-pack/plugins/siem/public/endpoint_hosts/store/middleware.test.ts index 6c9e3dd41907..0959a3438aad 100644 --- a/x-pack/plugins/siem/public/endpoint_hosts/store/middleware.test.ts +++ b/x-pack/plugins/siem/public/endpoint_hosts/store/middleware.test.ts @@ -7,7 +7,6 @@ import { CoreStart, HttpSetup } from 'kibana/public'; import { applyMiddleware, createStore, Store } from 'redux'; import { coreMock } from '../../../../../../src/core/public/mocks'; import { History, createBrowserHistory } from 'history'; -import { hostListReducer, hostMiddlewareFactory } from './index'; import { DepsStartMock, depsStartMock } from '../../common/mock/endpoint'; @@ -17,6 +16,8 @@ import { AppAction } from '../../common/store/actions'; import { mockHostResultList } from './mock_host_result_list'; import { listData } from './selectors'; import { HostState } from '../types'; +import { hostListReducer } from './reducer'; +import { hostMiddlewareFactory } from './middleware'; describe('host list middleware', () => { let fakeCoreStart: jest.Mocked; diff --git a/x-pack/plugins/siem/public/endpoint_hosts/store/middleware.ts b/x-pack/plugins/siem/public/endpoint_hosts/store/middleware.ts index b9d847560102..dd9ab19a702e 100644 --- a/x-pack/plugins/siem/public/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/siem/public/endpoint_hosts/store/middleware.ts @@ -4,14 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { HostResultList, Immutable } from '../../../common/endpoint/types'; +import { HostResultList } from '../../../common/endpoint/types'; import { ImmutableMiddlewareFactory } from '../../common/store'; import { isOnHostPage, hasSelectedHost, uiQueryParams, listData } from './selectors'; import { HostState } from '../types'; -export const hostMiddlewareFactory: ImmutableMiddlewareFactory> = ( - coreStart -) => { +export const hostMiddlewareFactory: ImmutableMiddlewareFactory = (coreStart) => { return ({ getState, dispatch }) => (next) => async (action) => { next(action); const state = getState(); diff --git a/x-pack/plugins/siem/public/endpoint_hosts/store/reducer.ts b/x-pack/plugins/siem/public/endpoint_hosts/store/reducer.ts index 98f4a457a459..c0d5e6931db2 100644 --- a/x-pack/plugins/siem/public/endpoint_hosts/store/reducer.ts +++ b/x-pack/plugins/siem/public/endpoint_hosts/store/reducer.ts @@ -10,26 +10,24 @@ import { AppAction } from '../../common/store/actions'; import { ImmutableReducer } from '../../common/store'; import { Immutable } from '../../../common/endpoint/types'; -export const initialHostListState = (): HostState => { - return { - hosts: [], - pageSize: 10, - pageIndex: 0, - total: 0, - loading: false, - error: undefined, - details: undefined, - detailsLoading: false, - detailsError: undefined, - policyResponse: undefined, - policyResponseLoading: false, - policyResponseError: undefined, - location: undefined, - }; +export const initialHostListState: Immutable = { + hosts: [], + pageSize: 10, + pageIndex: 0, + total: 0, + loading: false, + error: undefined, + details: undefined, + detailsLoading: false, + detailsError: undefined, + policyResponse: undefined, + policyResponseLoading: false, + policyResponseError: undefined, + location: undefined, }; export const hostListReducer: ImmutableReducer = ( - state = initialHostListState(), + state = initialHostListState, action ) => { if (action.type === 'serverReturnedHostList') { diff --git a/x-pack/plugins/siem/public/endpoint_hosts/view/hooks.ts b/x-pack/plugins/siem/public/endpoint_hosts/view/hooks.ts index 6d552831c2fe..78fd679f818b 100644 --- a/x-pack/plugins/siem/public/endpoint_hosts/view/hooks.ts +++ b/x-pack/plugins/siem/public/endpoint_hosts/view/hooks.ts @@ -8,7 +8,7 @@ import { useSelector } from 'react-redux'; import { useMemo } from 'react'; import { HostState } from '../types'; import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; -import { State } from '../../common/store/reducer'; +import { State } from '../../common/store/types'; export function useHostSelector(selector: (state: HostState) => TSelected) { return useSelector(function (state: State) { diff --git a/x-pack/plugins/siem/public/hosts/store/selectors.ts b/x-pack/plugins/siem/public/hosts/store/selectors.ts index aee93441e940..61fa44a5f41d 100644 --- a/x-pack/plugins/siem/public/hosts/store/selectors.ts +++ b/x-pack/plugins/siem/public/hosts/store/selectors.ts @@ -7,7 +7,7 @@ import { get } from 'lodash/fp'; import { createSelector } from 'reselect'; -import { State } from '../../common/store/reducer'; +import { State } from '../../common/store/types'; import { GenericHostsModel, HostsType, HostsTableType } from './model'; diff --git a/x-pack/plugins/siem/public/management/index.ts b/x-pack/plugins/siem/public/management/index.ts index 86522df110df..d6a723e5340b 100644 --- a/x-pack/plugins/siem/public/management/index.ts +++ b/x-pack/plugins/siem/public/management/index.ts @@ -5,33 +5,53 @@ */ import { CoreStart } from 'kibana/public'; -import { managementReducer, getManagementInitialState, managementMiddlewareFactory } from './store'; -import { getManagementRoutes } from './routes'; +import { Reducer, CombinedState } from 'redux'; +import { managementRoutes } from './routes'; import { StartPlugins } from '../types'; -import { MANAGEMENT_STORE_GLOBAL_NAMESPACE } from './common/constants'; import { SecuritySubPluginWithStore } from '../app/types'; -import { Immutable } from '../../common/endpoint/types'; -import { ManagementStoreGlobalNamespace } from './types'; -import { ManagementState } from './store/types'; +import { managementReducer } from './store/reducer'; +import { AppAction } from '../common/store/actions'; +import { managementMiddlewareFactory } from './store/middleware'; +import { ManagementState } from './types'; export { getManagementUrl } from './common/routing'; +/** + * Internally, our state is sometimes immutable, ignore that in our external + * interface. + */ +export interface ManagementPluginState { + management: ManagementState; +} + +/** + * Internally, we use `ImmutableReducer`, but we present a regular reducer + * externally for compatibility w/ regular redux. + */ +export interface ManagementPluginReducer { + management: Reducer, AppAction>; +} + export class Management { public setup() {} public start( core: CoreStart, plugins: StartPlugins - ): SecuritySubPluginWithStore> { + ): SecuritySubPluginWithStore<'management', ManagementState> { return { - routes: getManagementRoutes(), + routes: managementRoutes(), store: { initialState: { - [MANAGEMENT_STORE_GLOBAL_NAMESPACE]: getManagementInitialState(), + management: undefined, }, + /** + * Cast the ImmutableReducer to a regular reducer for compatibility with + * the subplugin architecture (which expects plain redux reducers.) + */ reducer: { - [MANAGEMENT_STORE_GLOBAL_NAMESPACE]: managementReducer, - }, + management: managementReducer, + } as ManagementPluginReducer, middleware: managementMiddlewareFactory(core, plugins), }, }; diff --git a/x-pack/plugins/siem/public/management/pages/policy/store/policy_details/middleware.ts b/x-pack/plugins/siem/public/management/pages/policy/store/policy_details/middleware.ts index 97cdcac0fcae..ec0c526482b4 100644 --- a/x-pack/plugins/siem/public/management/pages/policy/store/policy_details/middleware.ts +++ b/x-pack/plugins/siem/public/management/pages/policy/store/policy_details/middleware.ts @@ -16,13 +16,13 @@ import { sendGetFleetAgentStatusForConfig, sendPutDatasource, } from '../policy_list/services/ingest'; -import { NewPolicyData, PolicyData, Immutable } from '../../../../../../common/endpoint/types'; +import { NewPolicyData, PolicyData } from '../../../../../../common/endpoint/types'; import { factory as policyConfigFactory } from '../../../../../../common/endpoint/models/policy_config'; import { ImmutableMiddlewareFactory } from '../../../../../common/store'; -export const policyDetailsMiddlewareFactory: ImmutableMiddlewareFactory> = (coreStart) => { +export const policyDetailsMiddlewareFactory: ImmutableMiddlewareFactory = ( + coreStart +) => { const http = coreStart.http; return ({ getState, dispatch }) => (next) => async (action) => { diff --git a/x-pack/plugins/siem/public/management/pages/policy/store/policy_details/reducer.ts b/x-pack/plugins/siem/public/management/pages/policy/store/policy_details/reducer.ts index 95daad7ae597..75e7808ea30b 100644 --- a/x-pack/plugins/siem/public/management/pages/policy/store/policy_details/reducer.ts +++ b/x-pack/plugins/siem/public/management/pages/policy/store/policy_details/reducer.ts @@ -4,24 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ import { fullPolicy, isOnPolicyDetailsPage } from './selectors'; -import { PolicyDetailsState } from '../../types'; import { Immutable, PolicyConfig, UIPolicyConfig } from '../../../../../../common/endpoint/types'; import { ImmutableReducer } from '../../../../../common/store'; import { AppAction } from '../../../../../common/store/actions'; +import { PolicyDetailsState } from '../../types'; -export const initialPolicyDetailsState = (): PolicyDetailsState => { - return { - policyItem: undefined, - isLoading: false, - agentStatusSummary: { - error: 0, - events: 0, - offline: 0, - online: 0, - total: 0, - }, - }; -}; +/** + * Return a fresh copy of initial state, since we mutate state in the reducer. + */ +export const initialPolicyDetailsState: () => Immutable = () => ({ + policyItem: undefined, + isLoading: false, + agentStatusSummary: { + error: 0, + events: 0, + offline: 0, + online: 0, + total: 0, + }, +}); export const policyDetailsReducer: ImmutableReducer = ( state = initialPolicyDetailsState(), diff --git a/x-pack/plugins/siem/public/management/pages/policy/store/policy_list/index.test.ts b/x-pack/plugins/siem/public/management/pages/policy/store/policy_list/index.test.ts index c796edff8aab..a312134bbcd2 100644 --- a/x-pack/plugins/siem/public/management/pages/policy/store/policy_list/index.test.ts +++ b/x-pack/plugins/siem/public/management/pages/policy/store/policy_list/index.test.ts @@ -10,7 +10,7 @@ import { Store, applyMiddleware, createStore } from 'redux'; import { coreMock } from '../../../../../../../../../src/core/public/mocks'; import { DATASOURCE_SAVED_OBJECT_TYPE } from '../../../../../../../ingest_manager/common'; -import { policyListReducer, initialPolicyListState } from './reducer'; +import { policyListReducer } from './reducer'; import { policyListMiddlewareFactory } from './middleware'; import { isOnPolicyListPage, selectIsLoading, urlSearchParams } from './selectors'; @@ -39,7 +39,7 @@ describe('policy list store concerns', () => { store = createStore( policyListReducer, - initialPolicyListState(), + undefined, applyMiddleware(policyListMiddlewareFactory(fakeCoreStart, depsStart), actionSpyMiddleware) ); }); diff --git a/x-pack/plugins/siem/public/management/pages/policy/store/policy_list/middleware.ts b/x-pack/plugins/siem/public/management/pages/policy/store/policy_list/middleware.ts index 7259c0fd4d21..66962c378537 100644 --- a/x-pack/plugins/siem/public/management/pages/policy/store/policy_list/middleware.ts +++ b/x-pack/plugins/siem/public/management/pages/policy/store/policy_list/middleware.ts @@ -8,10 +8,9 @@ import { GetPolicyListResponse, PolicyListState } from '../../types'; import { sendGetEndpointSpecificDatasources } from './services/ingest'; import { isOnPolicyListPage, urlSearchParams } from './selectors'; import { ImmutableMiddlewareFactory } from '../../../../../common/store'; -import { Immutable } from '../../../../../../common/endpoint/types'; import { initialPolicyListState } from './reducer'; -export const policyListMiddlewareFactory: ImmutableMiddlewareFactory> = ( +export const policyListMiddlewareFactory: ImmutableMiddlewareFactory = ( coreStart ) => { const http = coreStart.http; @@ -20,7 +19,6 @@ export const policyListMiddlewareFactory: ImmutableMiddlewareFactory { - return { - policyItems: [], - isLoading: false, - apiError: undefined, - pageIndex: 0, - pageSize: 10, - total: 0, - location: undefined, - }; -}; +/** + * Return the initial state. + * In case `state` was mutated, we return a fresh initial state object. + */ +export const initialPolicyListState: () => Immutable = () => ({ + policyItems: [], + isLoading: false, + apiError: undefined, + pageIndex: 0, + pageSize: 10, + total: 0, + location: undefined, +}); export const policyListReducer: ImmutableReducer = ( state = initialPolicyListState(), diff --git a/x-pack/plugins/siem/public/management/routes.tsx b/x-pack/plugins/siem/public/management/routes.tsx index fbcea37c7696..12727ea97458 100644 --- a/x-pack/plugins/siem/public/management/routes.tsx +++ b/x-pack/plugins/siem/public/management/routes.tsx @@ -12,7 +12,7 @@ import { MANAGEMENT_ROUTING_ROOT_PATH } from './common/constants'; /** * Returns the React Router Routes for the management area */ -export const getManagementRoutes = () => [ +export const managementRoutes = () => [ // Mounts the Management interface on `/management` , ]; diff --git a/x-pack/plugins/siem/public/management/store/index.ts b/x-pack/plugins/siem/public/management/store/index.ts deleted file mode 100644 index 50049f982808..000000000000 --- a/x-pack/plugins/siem/public/management/store/index.ts +++ /dev/null @@ -1,8 +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 { managementReducer, getManagementInitialState } from './reducer'; -export { managementMiddlewareFactory } from './middleware'; diff --git a/x-pack/plugins/siem/public/management/store/middleware.ts b/x-pack/plugins/siem/public/management/store/middleware.ts index f73736e04a5b..c8eb27e35f9d 100644 --- a/x-pack/plugins/siem/public/management/store/middleware.ts +++ b/x-pack/plugins/siem/public/management/store/middleware.ts @@ -4,30 +4,23 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ImmutableMultipleMiddlewareFactory, substateMiddlewareFactory } from '../../common/store'; +import { + substateMiddlewareFactory, + SecuritySubPluginMiddlewareFactory, + State, +} from '../../common/store'; import { policyListMiddlewareFactory } from '../pages/policy/store/policy_list'; import { policyDetailsMiddlewareFactory } from '../pages/policy/store/policy_details'; -import { - MANAGEMENT_STORE_GLOBAL_NAMESPACE, - MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE, - MANAGEMENT_STORE_POLICY_LIST_NAMESPACE, -} from '../common/constants'; -// @ts-ignore -export const managementMiddlewareFactory: ImmutableMultipleMiddlewareFactory = ( +export const managementMiddlewareFactory: SecuritySubPluginMiddlewareFactory = ( coreStart, depsStart ) => { + const listSelector = (state: State) => state.management.policyList; + const detailSelector = (state: State) => state.management.policyDetails; + return [ - substateMiddlewareFactory( - (globalState) => - globalState[MANAGEMENT_STORE_GLOBAL_NAMESPACE][MANAGEMENT_STORE_POLICY_LIST_NAMESPACE], - policyListMiddlewareFactory(coreStart, depsStart) - ), - substateMiddlewareFactory( - (globalState) => - globalState[MANAGEMENT_STORE_GLOBAL_NAMESPACE][MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE], - policyDetailsMiddlewareFactory(coreStart, depsStart) - ), + substateMiddlewareFactory(listSelector, policyListMiddlewareFactory(coreStart, depsStart)), + substateMiddlewareFactory(detailSelector, policyDetailsMiddlewareFactory(coreStart, depsStart)), ]; }; diff --git a/x-pack/plugins/siem/public/management/store/reducer.ts b/x-pack/plugins/siem/public/management/store/reducer.ts index ba7927684ad3..64b2ab5c05f9 100644 --- a/x-pack/plugins/siem/public/management/store/reducer.ts +++ b/x-pack/plugins/siem/public/management/store/reducer.ts @@ -4,42 +4,34 @@ * you may not use this file except in compliance with the Elastic License. */ -import { combineReducers as reduxCombineReducers } from 'redux'; +import { combineReducers } from 'redux'; import { - initialPolicyDetailsState, policyDetailsReducer, + initialPolicyDetailsState, } from '../pages/policy/store/policy_details/reducer'; import { - initialPolicyListState, policyListReducer, + initialPolicyListState, } from '../pages/policy/store/policy_list/reducer'; import { MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE, MANAGEMENT_STORE_POLICY_LIST_NAMESPACE, } from '../common/constants'; import { ImmutableCombineReducers } from '../../common/store'; -import { AppAction } from '../../common/store/actions'; -import { ManagementState } from './types'; +import { Immutable } from '../../../common/endpoint/types'; +import { ManagementState } from '../types'; -// Change the type of `combinerReducers` locally -const combineReducers: ImmutableCombineReducers = reduxCombineReducers; +const immutableCombineReducers: ImmutableCombineReducers = combineReducers; -/** - * Returns the initial state of the store for the SIEM Management section - */ -export const getManagementInitialState = (): ManagementState => { - return { - [MANAGEMENT_STORE_POLICY_LIST_NAMESPACE]: initialPolicyListState(), - [MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE]: initialPolicyDetailsState(), - }; +export const mockManagementState: Immutable = { + policyList: initialPolicyListState(), + policyDetails: initialPolicyDetailsState(), }; /** * Redux store reducer for the SIEM Management section */ -export const managementReducer = combineReducers({ - // @ts-ignore +export const managementReducer = immutableCombineReducers({ [MANAGEMENT_STORE_POLICY_LIST_NAMESPACE]: policyListReducer, - // @ts-ignore [MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE]: policyDetailsReducer, }); diff --git a/x-pack/plugins/siem/public/management/store/types.ts b/x-pack/plugins/siem/public/management/store/types.ts deleted file mode 100644 index 884724982fa8..000000000000 --- a/x-pack/plugins/siem/public/management/store/types.ts +++ /dev/null @@ -1,26 +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 { Immutable } from '../../../common/endpoint/types'; -import { PolicyDetailsState, PolicyListState } from '../pages/policy/types'; -import { ImmutableReducer } from '../../common/store'; -import { AppAction } from '../../common/store/actions'; - -/** - * Redux store state for the Management section - */ -export interface ManagementState { - policyDetails: Immutable; - policyList: Immutable; -} - -export interface ManagementPluginState { - management: ManagementState; -} - -export interface ManagementPluginReducer { - management: ImmutableReducer; -} diff --git a/x-pack/plugins/siem/public/management/types.ts b/x-pack/plugins/siem/public/management/types.ts index 5ee16bcd434e..eeeafb4cbe15 100644 --- a/x-pack/plugins/siem/public/management/types.ts +++ b/x-pack/plugins/siem/public/management/types.ts @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { CombinedState } from 'redux'; import { SiemPageName } from '../app/types'; +import { PolicyListState, PolicyDetailsState } from './pages/policy/types'; /** * The type for the management store global namespace. Used mostly internally to reference @@ -12,6 +14,11 @@ import { SiemPageName } from '../app/types'; */ export type ManagementStoreGlobalNamespace = 'management'; +export type ManagementState = CombinedState<{ + policyList: PolicyListState; + policyDetails: PolicyDetailsState; +}>; + /** * The management list of sub-tabs. Changes to these will impact the Router routes. */ diff --git a/x-pack/plugins/siem/public/network/store/selectors.ts b/x-pack/plugins/siem/public/network/store/selectors.ts index eac373cd63eb..cef8b139402e 100644 --- a/x-pack/plugins/siem/public/network/store/selectors.ts +++ b/x-pack/plugins/siem/public/network/store/selectors.ts @@ -8,7 +8,7 @@ import { createSelector } from 'reselect'; import { get } from 'lodash/fp'; import { FlowTargetSourceDest } from '../../graphql/types'; -import { State } from '../../common/store/reducer'; +import { State } from '../../common/store/types'; import { initialNetworkState } from './reducer'; import { IpDetailsTableType, diff --git a/x-pack/plugins/siem/public/timelines/store/timeline/model.ts b/x-pack/plugins/siem/public/timelines/store/timeline/model.ts index df5e2a99e024..bf86d6363861 100644 --- a/x-pack/plugins/siem/public/timelines/store/timeline/model.ts +++ b/x-pack/plugins/siem/public/timelines/store/timeline/model.ts @@ -14,7 +14,7 @@ import { TimelineType, TimelineStatus, } from '../../../graphql/types'; -import { KueryFilterQuery, SerializedFilterQuery } from '../../../common/store/model'; +import { KueryFilterQuery, SerializedFilterQuery } from '../../../common/store/types'; export const DEFAULT_PAGE_COUNT = 2; // Eui Pager will not render unless this is a minimum of 2 pages export type KqlMode = 'filter' | 'search'; diff --git a/x-pack/plugins/siem/public/timelines/store/timeline/selectors.ts b/x-pack/plugins/siem/public/timelines/store/timeline/selectors.ts index d4c8f3cf3a2c..af7ac075468c 100644 --- a/x-pack/plugins/siem/public/timelines/store/timeline/selectors.ts +++ b/x-pack/plugins/siem/public/timelines/store/timeline/selectors.ts @@ -7,7 +7,7 @@ import { createSelector } from 'reselect'; import { isFromKueryExpressionValid } from '../../../common/lib/keury'; -import { State } from '../../../common/store/reducer'; +import { State } from '../../../common/store/types'; import { TimelineModel } from './model'; import { AutoSavedWarningMsg, TimelineById } from './types'; From 48e3bcdaece8f1af7169a787db902642adaf855e Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 2 Jun 2020 13:53:45 -0700 Subject: [PATCH 32/50] [scripts/type_check] increase memory limit for all x-pack* projects (#68007) Co-authored-by: spalger --- src/dev/typescript/run_type_check_cli.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dev/typescript/run_type_check_cli.ts b/src/dev/typescript/run_type_check_cli.ts index 1417d3048467..5d4cf14c1cd9 100644 --- a/src/dev/typescript/run_type_check_cli.ts +++ b/src/dev/typescript/run_type_check_cli.ts @@ -88,7 +88,7 @@ export function runTypeCheckCli() { } execInProjects(log, projects, process.execPath, (project) => [ - ...(project.name === 'x-pack' ? ['--max-old-space-size=4096'] : []), + ...(project.name.startsWith('x-pack') ? ['--max-old-space-size=4096'] : []), require.resolve('typescript/bin/tsc'), ...['--project', project.tsConfigPath], ...tscArgs, From 728a4b23e6087b0d6a60448b1f84cfe22339647e Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 2 Jun 2020 14:18:37 -0700 Subject: [PATCH 33/50] share core bundle with plugins (#67892) Co-authored-by: spalger --- .../src/optimizer/optimizer_config.ts | 2 +- .../src/worker/webpack.config.ts | 66 +++++++++++-------- src/core/public/index.ts | 4 ++ .../{entry_point.ts => kbn_bootstrap.ts} | 60 +++++++++-------- src/core/public/public.api.md | 3 + .../ui/ui_render/bootstrap/template.js.hbs | 19 ++++-- src/legacy/ui/ui_render/ui_render_mixin.js | 5 +- 7 files changed, 95 insertions(+), 64 deletions(-) rename src/core/public/{entry_point.ts => kbn_bootstrap.ts} (51%) diff --git a/packages/kbn-optimizer/src/optimizer/optimizer_config.ts b/packages/kbn-optimizer/src/optimizer/optimizer_config.ts index 4ed241f3b9b2..37d8a4f5eb8a 100644 --- a/packages/kbn-optimizer/src/optimizer/optimizer_config.ts +++ b/packages/kbn-optimizer/src/optimizer/optimizer_config.ts @@ -152,7 +152,7 @@ export class OptimizerConfig { new Bundle({ type: 'entry', id: 'core', - entry: './public/entry_point', + entry: './public/index', sourceRoot: options.repoRoot, contextDir: Path.resolve(options.repoRoot, 'src/core'), outputDir: Path.resolve(options.repoRoot, 'src/core/target/public'), diff --git a/packages/kbn-optimizer/src/worker/webpack.config.ts b/packages/kbn-optimizer/src/worker/webpack.config.ts index 763f1d515804..dd003af7dc5e 100644 --- a/packages/kbn-optimizer/src/worker/webpack.config.ts +++ b/packages/kbn-optimizer/src/worker/webpack.config.ts @@ -38,11 +38,32 @@ const IS_CODE_COVERAGE = !!process.env.CODE_COVERAGE; const ISTANBUL_PRESET_PATH = require.resolve('@kbn/babel-preset/istanbul_preset'); const BABEL_PRESET_PATH = require.resolve('@kbn/babel-preset/webpack_preset'); -const STATIC_BUNDLE_PLUGINS = [ - { id: 'data', dirname: 'data' }, - { id: 'kibanaReact', dirname: 'kibana_react' }, - { id: 'kibanaUtils', dirname: 'kibana_utils' }, - { id: 'esUiShared', dirname: 'es_ui_shared' }, +const SHARED_BUNDLES = [ + { + type: 'entry', + id: 'core', + rootRelativeDir: 'src/core/public', + }, + { + type: 'plugin', + id: 'data', + rootRelativeDir: 'src/plugins/data/public', + }, + { + type: 'plugin', + id: 'kibanaReact', + rootRelativeDir: 'src/plugins/kibana_react/public', + }, + { + type: 'plugin', + id: 'kibanaUtils', + rootRelativeDir: 'src/plugins/kibana_utils/public', + }, + { + type: 'plugin', + id: 'esUiShared', + rootRelativeDir: 'src/plugins/es_ui_shared/public', + }, ]; /** @@ -57,18 +78,8 @@ const STATIC_BUNDLE_PLUGINS = [ * @param request the request for a module from a commonjs require() call or import statement */ function dynamicExternals(bundle: Bundle, context: string, request: string) { - // ignore imports that have loaders defined - if (request.includes('!')) { - return; - } - - // ignore requests that don't include a /{dirname}/public for one of our - // "static" bundles as a cheap way to avoid doing path resolution - // for paths that couldn't possibly resolve to what we're looking for - const reqToStaticBundle = STATIC_BUNDLE_PLUGINS.some((p) => - request.includes(`/${p.dirname}/public`) - ); - if (!reqToStaticBundle) { + // ignore imports that have loaders defined or are not relative seeming + if (request.includes('!') || !request.startsWith('.')) { return; } @@ -76,10 +87,15 @@ function dynamicExternals(bundle: Bundle, context: string, request: string) { const rootRelative = normalizePath( Path.relative(bundle.sourceRoot, Path.resolve(context, request)) ); - for (const { id, dirname } of STATIC_BUNDLE_PLUGINS) { - if (rootRelative === `src/plugins/${dirname}/public`) { - return `__kbnBundles__['plugin/${id}']`; + for (const sharedBundle of SHARED_BUNDLES) { + if ( + rootRelative !== sharedBundle.rootRelativeDir || + `${bundle.type}/${bundle.id}` === `${sharedBundle.type}/${sharedBundle.id}` + ) { + continue; } + + return `__kbnBundles__['${sharedBundle.type}/${sharedBundle.id}']`; } // import doesn't match a root public import @@ -112,13 +128,9 @@ export function getWebpackConfig(bundle: Bundle, worker: WorkerConfig) { info.absoluteResourcePath )}${info.query}`, jsonpFunction: `${bundle.id}_bundle_jsonpfunction`, - ...(bundle.type === 'plugin' - ? { - // When the entry point is loaded, assign it's exported `plugin` - // value to a key on the global `__kbnBundles__` object. - library: ['__kbnBundles__', `plugin/${bundle.id}`], - } - : {}), + // When the entry point is loaded, assign it's default export + // to a key on the global `__kbnBundles__` object. + library: ['__kbnBundles__', `${bundle.type}/${bundle.id}`], }, optimization: { diff --git a/src/core/public/index.ts b/src/core/public/index.ts index aa037329b1b6..bd275ca1d456 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -35,6 +35,8 @@ * @packageDocumentation */ +import './index.scss'; + import { ChromeBadge, ChromeBrand, @@ -363,3 +365,5 @@ export { UiSettingsState, NavType, }; + +export { __kbnBootstrap__ } from './kbn_bootstrap'; diff --git a/src/core/public/entry_point.ts b/src/core/public/kbn_bootstrap.ts similarity index 51% rename from src/core/public/entry_point.ts rename to src/core/public/kbn_bootstrap.ts index 25180c13ccbd..caeb95a540de 100644 --- a/src/core/public/entry_point.ts +++ b/src/core/public/kbn_bootstrap.ts @@ -25,39 +25,41 @@ * src/legacy/ui/ui_bundles/app_entry_template.js */ -import './index.scss'; import { i18n } from '@kbn/i18n'; import { CoreSystem } from './core_system'; -const injectedMetadata = JSON.parse( - document.querySelector('kbn-injected-metadata')!.getAttribute('data')! -); +/** @internal */ +export function __kbnBootstrap__() { + const injectedMetadata = JSON.parse( + document.querySelector('kbn-injected-metadata')!.getAttribute('data')! + ); -/** - * `apmConfig` would be populated with relavant APM RUM agent - * configuration if server is started with `ELASTIC_APM_ACTIVE=true` - */ -if (process.env.IS_KIBANA_DISTRIBUTABLE !== 'true' && injectedMetadata.vars.apmConfig != null) { - // @ts-ignore - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { init } = require('@elastic/apm-rum'); - init(injectedMetadata.vars.apmConfig); -} + /** + * `apmConfig` would be populated with relavant APM RUM agent + * configuration if server is started with `ELASTIC_APM_ACTIVE=true` + */ + if (process.env.IS_KIBANA_DISTRIBUTABLE !== 'true' && injectedMetadata.vars.apmConfig != null) { + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { init } = require('@elastic/apm-rum'); + init(injectedMetadata.vars.apmConfig); + } -i18n - .load(injectedMetadata.i18n.translationsUrl) - .catch((e) => e) - .then(async (i18nError) => { - const coreSystem = new CoreSystem({ - injectedMetadata, - rootDomElement: document.body, - browserSupportsCsp: !(window as any).__kbnCspNotEnforced__, - }); + i18n + .load(injectedMetadata.i18n.translationsUrl) + .catch((e) => e) + .then(async (i18nError) => { + const coreSystem = new CoreSystem({ + injectedMetadata, + rootDomElement: document.body, + browserSupportsCsp: !(window as any).__kbnCspNotEnforced__, + }); - const setup = await coreSystem.setup(); - if (i18nError && setup) { - setup.fatalErrors.add(i18nError); - } + const setup = await coreSystem.setup(); + if (i18nError && setup) { + setup.fatalErrors.add(i18nError); + } - await coreSystem.start(); - }); + await coreSystem.start(); + }); +} diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index bae0f9a2281c..74c41d010ca8 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -25,6 +25,9 @@ import { Type } from '@kbn/config-schema'; import { UnregisterCallback } from 'history'; import { UserProvidedValues as UserProvidedValues_2 } from 'src/core/server/types'; +// @internal (undocumented) +export function __kbnBootstrap__(): void; + // @public export interface App extends AppBase { appRoute?: string; diff --git a/src/legacy/ui/ui_render/bootstrap/template.js.hbs b/src/legacy/ui/ui_render/bootstrap/template.js.hbs index 1453c974c118..e8f05b46f706 100644 --- a/src/legacy/ui/ui_render/bootstrap/template.js.hbs +++ b/src/legacy/ui/ui_render/bootstrap/template.js.hbs @@ -73,12 +73,23 @@ if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) { } load([ - {{#each jsDependencyPaths}} - '{{this}}', - {{/each}} + {{#each jsDependencyPaths}} + '{{this}}', + {{/each}} ], function () { + {{#unless legacyBundlePath}} + if (!__kbnBundles__ || !__kbnBundles__['entry/core'] || typeof __kbnBundles__['entry/core'].__kbnBootstrap__ !== 'function') { + console.error('entry/core bundle did not load correctly'); + failure(); + } else { + __kbnBundles__['entry/core'].__kbnBootstrap__() + } + {{/unless}} + load([ - '{{entryBundlePath}}', + {{#if legacyBundlePath}} + '{{legacyBundlePath}}', + {{/if}} {{#each styleSheetPaths}} '{{this}}', {{/each}} diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index 10847b992852..b09d4861b343 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -173,6 +173,7 @@ export function uiRenderMixin(kbnServer, server, config) { `${regularBundlePath}/commons.bundle.js`, ]), + `${regularBundlePath}/core/core.entry.js`, ...kpPluginIds.map( (pluginId) => `${regularBundlePath}/plugin/${pluginId}/${pluginId}.plugin.js` ), @@ -199,9 +200,7 @@ export function uiRenderMixin(kbnServer, server, config) { jsDependencyPaths, styleSheetPaths, publicPathMap, - entryBundlePath: isCore - ? `${regularBundlePath}/core/core.entry.js` - : `${regularBundlePath}/${app.getId()}.bundle.js`, + legacyBundlePath: isCore ? undefined : `${regularBundlePath}/${app.getId()}.bundle.js`, }, }); From 5e2a78bb524bdaa1081a52ca71606b197927b622 Mon Sep 17 00:00:00 2001 From: Dmitry Lemeshko Date: Tue, 2 Jun 2020 23:24:06 +0200 Subject: [PATCH 34/50] [test/functional/services] convert dashboard and pie-chart to ts (#66703) Co-authored-by: Elastic Machine --- .../dashboard/{add_panel.js => add_panel.ts} | 34 +++++----- .../{expectations.js => expectations.ts} | 66 ++++++++++--------- .../services/dashboard/{index.js => index.ts} | 0 .../{panel_actions.js => panel_actions.ts} | 59 +++++++++-------- .../{replace_panel.js => replace_panel.ts} | 18 ++--- .../{visualizations.js => visualizations.ts} | 28 ++++++-- test/functional/services/index.ts | 5 +- .../{ => visualizations}/elastic_chart.ts | 2 +- .../visualizations/{index.js => index.ts} | 1 + .../{pie_chart.js => pie_chart.ts} | 37 +++++------ 10 files changed, 137 insertions(+), 113 deletions(-) rename test/functional/services/dashboard/{add_panel.js => add_panel.ts} (88%) rename test/functional/services/dashboard/{expectations.js => expectations.ts} (82%) rename test/functional/services/dashboard/{index.js => index.ts} (100%) rename test/functional/services/dashboard/{panel_actions.js => panel_actions.ts} (80%) rename test/functional/services/dashboard/{replace_panel.js => replace_panel.ts} (86%) rename test/functional/services/dashboard/{visualizations.js => visualizations.ts} (88%) rename test/functional/services/{ => visualizations}/elastic_chart.ts (97%) rename test/functional/services/visualizations/{index.js => index.ts} (93%) rename test/functional/services/visualizations/{pie_chart.js => pie_chart.ts} (78%) diff --git a/test/functional/services/dashboard/add_panel.js b/test/functional/services/dashboard/add_panel.ts similarity index 88% rename from test/functional/services/dashboard/add_panel.js rename to test/functional/services/dashboard/add_panel.ts index 625920398216..1263501aa9c1 100644 --- a/test/functional/services/dashboard/add_panel.js +++ b/test/functional/services/dashboard/add_panel.ts @@ -17,7 +17,9 @@ * under the License. */ -export function DashboardAddPanelProvider({ getService, getPageObjects }) { +import { FtrProviderContext } from '../../ftr_provider_context'; + +export function DashboardAddPanelProvider({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); const retry = getService('retry'); const testSubjects = getService('testSubjects'); @@ -39,7 +41,7 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) { await PageObjects.common.sleep(500); } - async clickAddNewEmbeddableLink(type) { + async clickAddNewEmbeddableLink(type: string) { await testSubjects.click('createNew'); await testSubjects.click(`createNew-${type}`); await testSubjects.missingOrFail(`createNew-${type}`); @@ -50,7 +52,7 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) { await testSubjects.click('savedObjectFinderFilterButton'); } - async toggleFilter(type) { + async toggleFilter(type: string) { log.debug(`DashboardAddPanel.addToFilter(${type})`); await this.waitForListLoading(); await this.toggleFilterPopover(); @@ -61,7 +63,7 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) { async addEveryEmbeddableOnCurrentPage() { log.debug('addEveryEmbeddableOnCurrentPage'); const itemList = await testSubjects.find('savedObjectFinderItemList'); - const embeddableList = []; + const embeddableList: string[] = []; await retry.try(async () => { const embeddableRows = await itemList.findAllByCssSelector('li'); for (let i = 0; i < embeddableRows.length; i++) { @@ -130,7 +132,7 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) { await flyout.ensureClosed('dashboardAddPanel'); } - async addEveryVisualization(filter) { + async addEveryVisualization(filter: string) { log.debug('DashboardAddPanel.addEveryVisualization'); await this.ensureAddPanelIsShowing(); await this.toggleFilter('visualization'); @@ -138,16 +140,16 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) { await this.filterEmbeddableNames(filter.replace('-', ' ')); } let morePages = true; - const vizList = []; + const vizList: string[][] = []; while (morePages) { vizList.push(await this.addEveryEmbeddableOnCurrentPage()); morePages = await this.clickPagerNextButton(); } await this.closeAddPanel(); - return vizList.reduce((acc, vizList) => [...acc, ...vizList], []); + return vizList.reduce((acc, list) => [...acc, ...list], []); } - async addEverySavedSearch(filter) { + async addEverySavedSearch(filter: string) { log.debug('DashboardAddPanel.addEverySavedSearch'); await this.ensureAddPanelIsShowing(); await this.toggleFilter('search'); @@ -161,20 +163,20 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) { morePages = await this.clickPagerNextButton(); } await this.closeAddPanel(); - return searchList.reduce((acc, searchList) => [...acc, ...searchList], []); + return searchList.reduce((acc, list) => [...acc, ...list], []); } - async addSavedSearch(searchName) { + async addSavedSearch(searchName: string) { return this.addEmbeddable(searchName, 'search'); } - async addSavedSearches(searches) { + async addSavedSearches(searches: string[]) { for (const name of searches) { await this.addSavedSearch(name); } } - async addVisualizations(visualizations) { + async addVisualizations(visualizations: string[]) { log.debug('DashboardAddPanel.addVisualizations'); const vizList = []; for (const vizName of visualizations) { @@ -184,11 +186,11 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) { return vizList; } - async addVisualization(vizName) { + async addVisualization(vizName: string) { return this.addEmbeddable(vizName, 'visualization'); } - async addEmbeddable(embeddableName, embeddableType) { + async addEmbeddable(embeddableName: string, embeddableType: string) { log.debug( `DashboardAddPanel.addEmbeddable, name: ${embeddableName}, type: ${embeddableType}` ); @@ -201,14 +203,14 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) { return embeddableName; } - async filterEmbeddableNames(name) { + async filterEmbeddableNames(name: string) { // The search input field may be disabled while the table is loading so wait for it await this.waitForListLoading(); await testSubjects.setValue('savedObjectFinderSearchInput', name); await this.waitForListLoading(); } - async panelAddLinkExists(name) { + async panelAddLinkExists(name: string) { log.debug(`DashboardAddPanel.panelAddLinkExists(${name})`); await this.ensureAddPanelIsShowing(); await this.filterEmbeddableNames(`"${name}"`); diff --git a/test/functional/services/dashboard/expectations.js b/test/functional/services/dashboard/expectations.ts similarity index 82% rename from test/functional/services/dashboard/expectations.js rename to test/functional/services/dashboard/expectations.ts index 66073e1043b0..77a441a772d8 100644 --- a/test/functional/services/dashboard/expectations.js +++ b/test/functional/services/dashboard/expectations.ts @@ -18,8 +18,10 @@ */ import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { WebElementWrapper } from '../lib/web_element_wrapper'; -export function DashboardExpectProvider({ getService, getPageObjects }) { +export function DashboardExpectProvider({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); const retry = getService('retry'); const testSubjects = getService('testSubjects'); @@ -29,7 +31,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { const findTimeout = 2500; return new (class DashboardExpect { - async panelCount(expectedCount) { + async panelCount(expectedCount: number) { log.debug(`DashboardExpect.panelCount(${expectedCount})`); await retry.try(async () => { const panelCount = await PageObjects.dashboard.getPanelCount(); @@ -37,7 +39,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { }); } - async visualizationsArePresent(vizList) { + async visualizationsArePresent(vizList: string[]) { log.debug('Checking all visualisations are present on dashsboard'); let notLoaded = await PageObjects.dashboard.getNotLoadedVisualizations(vizList); // TODO: Determine issue occasionally preventing 'geo map' from loading @@ -45,7 +47,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { expect(notLoaded).to.be.empty(); } - async selectedLegendColorCount(color, expectedCount) { + async selectedLegendColorCount(color: string, expectedCount: number) { log.debug(`DashboardExpect.selectedLegendColorCount(${color}, ${expectedCount})`); await retry.try(async () => { const selectedLegendColor = await testSubjects.findAll( @@ -56,7 +58,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { }); } - async docTableFieldCount(expectedCount) { + async docTableFieldCount(expectedCount: number) { log.debug(`DashboardExpect.docTableFieldCount(${expectedCount})`); await retry.try(async () => { const docTableCells = await testSubjects.findAll('docTableField', findTimeout); @@ -64,7 +66,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { }); } - async fieldSuggestions(expectedFields) { + async fieldSuggestions(expectedFields: string[]) { log.debug(`DashboardExpect.fieldSuggestions(${expectedFields})`); const fields = await filterBar.getFilterEditorFields(); expectedFields.forEach((expectedField) => { @@ -72,7 +74,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { }); } - async legendValuesToExist(legendValues) { + async legendValuesToExist(legendValues: string[]) { log.debug(`DashboardExpect.legendValuesToExist(${legendValues})`); await Promise.all( legendValues.map(async (legend) => { @@ -84,11 +86,11 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { ); } - async textWithinElementsExists(texts, getElementsFn) { + async textWithinElementsExists(texts: string[], getElementsFn: Function) { log.debug(`DashboardExpect.textWithinElementsExists(${texts})`); await retry.try(async () => { - const elements = await getElementsFn(); - const elementTexts = []; + const elements: WebElementWrapper[] = await getElementsFn(); + const elementTexts: string[] = []; await Promise.all( elements.map(async (element) => { elementTexts.push(await element.getVisibleText()); @@ -103,23 +105,23 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { }); } - async textWithinTestSubjectsExists(texts, selector) { + async textWithinTestSubjectsExists(texts: string[], selector: string) { log.debug(`DashboardExpect.textWithinTestSubjectsExists(${texts})`); log.debug(`textWithinTestSubjectsExists:(${JSON.stringify(texts)},${selector})`); await this.textWithinElementsExists(texts, async () => await testSubjects.findAll(selector)); } - async textWithinCssElementExists(texts, selector) { + async textWithinCssElementExists(texts: string[], selector: string) { log.debug(`DashboardExpect.textWithinCssElementExists(${texts})`); log.debug(`textWithinCssElementExists:(${JSON.stringify(texts)},${selector})`); await this.textWithinElementsExists(texts, async () => await find.allByCssSelector(selector)); } - async textWithinElementsDoNotExist(texts, getElementsFn) { + async textWithinElementsDoNotExist(texts: string[], getElementsFn: Function) { log.debug(`DashboardExpect.textWithinElementsDoNotExist(${texts})`); await retry.try(async () => { - const elements = await getElementsFn(); - const elementTexts = []; + const elements: WebElementWrapper[] = await getElementsFn(); + const elementTexts: string[] = []; await Promise.all( elements.map(async (element) => { elementTexts.push(await element.getVisibleText()); @@ -133,7 +135,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { }); } - async textWithinCssElementDoNotExist(texts, selector) { + async textWithinCssElementDoNotExist(texts: string[], selector: string) { log.debug(`textWithinCssElementExists:(${JSON.stringify(texts)},${selector})`); await this.textWithinElementsDoNotExist( texts, @@ -141,7 +143,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { ); } - async timelionLegendCount(expectedCount) { + async timelionLegendCount(expectedCount: number) { log.debug(`DashboardExpect.timelionLegendCount(${expectedCount})`); await retry.try(async () => { const flotLegendLabels = await testSubjects.findAll('flotLegendLabel', findTimeout); @@ -160,7 +162,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { expect(tagCloudsHaveContent.indexOf(false)).to.be.greaterThan(-1); } - async tagCloudWithValuesFound(values) { + async tagCloudWithValuesFound(values: string[]) { log.debug(`DashboardExpect.tagCloudWithValuesFound(${values})`); const tagCloudVisualizations = await testSubjects.findAll('tagCloudVisualization'); const matches = await Promise.all( @@ -177,47 +179,47 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { expect(matches.indexOf(true)).to.be.greaterThan(-1); } - async goalAndGuageLabelsExist(labels) { + async goalAndGuageLabelsExist(labels: string[]) { log.debug(`DashboardExpect.goalAndGuageLabelsExist(${labels})`); await this.textWithinCssElementExists(labels, '.chart-label'); } - async metricValuesExist(values) { + async metricValuesExist(values: string[]) { log.debug(`DashboardExpect.metricValuesExist(${values})`); await this.textWithinCssElementExists(values, '.mtrVis__value'); } - async tsvbMetricValuesExist(values) { + async tsvbMetricValuesExist(values: string[]) { log.debug(`DashboardExpect.tsvbMetricValuesExist(${values})`); await this.textWithinTestSubjectsExists(values, 'tsvbMetricValue'); } - async tsvbTopNValuesExist(values) { + async tsvbTopNValuesExist(values: string[]) { log.debug(`DashboardExpect.tsvbTopNValuesExist(${values})`); await this.textWithinTestSubjectsExists(values, 'tsvbTopNValue'); } - async vegaTextsExist(values) { + async vegaTextsExist(values: string[]) { log.debug(`DashboardExpect.vegaTextsExist(${values})`); await this.textWithinCssElementExists(values, '.vgaVis__view text'); } - async vegaTextsDoNotExist(values) { + async vegaTextsDoNotExist(values: string[]) { log.debug(`DashboardExpect.vegaTextsDoNotExist(${values})`); await this.textWithinCssElementDoNotExist(values, '.vgaVis__view text'); } - async tsvbMarkdownWithValuesExists(values) { + async tsvbMarkdownWithValuesExists(values: string[]) { log.debug(`DashboardExpect.tsvbMarkdownWithValuesExists(${values})`); await this.textWithinTestSubjectsExists(values, 'tsvbMarkdown'); } - async markdownWithValuesExists(values) { + async markdownWithValuesExists(values: string[]) { log.debug(`DashboardExpect.markdownWithValuesExists(${values})`); await this.textWithinTestSubjectsExists(values, 'markdownBody'); } - async savedSearchRowCount(expectedCount) { + async savedSearchRowCount(expectedCount: number) { log.debug(`DashboardExpect.savedSearchRowCount(${expectedCount})`); await retry.try(async () => { const savedSearchRows = await testSubjects.findAll( @@ -228,7 +230,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { }); } - async dataTableRowCount(expectedCount) { + async dataTableRowCount(expectedCount: number) { log.debug(`DashboardExpect.dataTableRowCount(${expectedCount})`); await retry.try(async () => { const dataTableRows = await find.allByCssSelector( @@ -239,7 +241,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { }); } - async seriesElementCount(expectedCount) { + async seriesElementCount(expectedCount: number) { log.debug(`DashboardExpect.seriesElementCount(${expectedCount})`); await retry.try(async () => { const seriesElements = await find.allByCssSelector('.series', findTimeout); @@ -247,7 +249,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { }); } - async inputControlItemCount(expectedCount) { + async inputControlItemCount(expectedCount: number) { log.debug(`DashboardExpect.inputControlItemCount(${expectedCount})`); await retry.try(async () => { const inputControlItems = await testSubjects.findAll('inputControlItem'); @@ -255,7 +257,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { }); } - async lineChartPointsCount(expectedCount) { + async lineChartPointsCount(expectedCount: number) { log.debug(`DashboardExpect.lineChartPointsCount(${expectedCount})`); await retry.try(async () => { const points = await find.allByCssSelector('.points', findTimeout); @@ -263,7 +265,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { }); } - async tsvbTableCellCount(expectedCount) { + async tsvbTableCellCount(expectedCount: number) { log.debug(`DashboardExpect.tsvbTableCellCount(${expectedCount})`); await retry.try(async () => { const tableCells = await testSubjects.findAll('tvbTableVis__value', findTimeout); diff --git a/test/functional/services/dashboard/index.js b/test/functional/services/dashboard/index.ts similarity index 100% rename from test/functional/services/dashboard/index.js rename to test/functional/services/dashboard/index.ts diff --git a/test/functional/services/dashboard/panel_actions.js b/test/functional/services/dashboard/panel_actions.ts similarity index 80% rename from test/functional/services/dashboard/panel_actions.js rename to test/functional/services/dashboard/panel_actions.ts index b155d747f3b9..c9a5dcfba32b 100644 --- a/test/functional/services/dashboard/panel_actions.js +++ b/test/functional/services/dashboard/panel_actions.ts @@ -17,6 +17,9 @@ * under the License. */ +import { FtrProviderContext } from '../../ftr_provider_context'; +import { WebElementWrapper } from '../lib/web_element_wrapper'; + const REMOVE_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-deletePanel'; const EDIT_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-editPanel'; const REPLACE_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-replacePanel'; @@ -26,13 +29,13 @@ const CUSTOMIZE_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-ACTION_CUSTOMIZE_P const OPEN_CONTEXT_MENU_ICON_DATA_TEST_SUBJ = 'embeddablePanelToggleMenuIcon'; const OPEN_INSPECTOR_TEST_SUBJ = 'embeddablePanelAction-openInspector'; -export function DashboardPanelActionsProvider({ getService, getPageObjects }) { +export function DashboardPanelActionsProvider({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects(['header', 'common']); return new (class DashboardPanelActions { - async findContextMenu(parent) { + async findContextMenu(parent?: WebElementWrapper) { return parent ? await testSubjects.findDescendant(OPEN_CONTEXT_MENU_ICON_DATA_TEST_SUBJ, parent) : await testSubjects.find(OPEN_CONTEXT_MENU_ICON_DATA_TEST_SUBJ); @@ -43,7 +46,7 @@ export function DashboardPanelActionsProvider({ getService, getPageObjects }) { return await testSubjects.exists(OPEN_CONTEXT_MENU_ICON_DATA_TEST_SUBJ); } - async toggleContextMenu(parent) { + async toggleContextMenu(parent?: WebElementWrapper) { log.debug('toggleContextMenu'); await (parent ? parent.moveMouseTo() : testSubjects.moveMouseTo('dashboardPanelTitle')); const toggleMenuItem = await this.findContextMenu(parent); @@ -54,7 +57,7 @@ export function DashboardPanelActionsProvider({ getService, getPageObjects }) { await testSubjects.existOrFail('embeddablePanelContextMenuOpen'); } - async openContextMenu(parent) { + async openContextMenu(parent?: WebElementWrapper) { log.debug(`openContextMenu(${parent}`); await this.toggleContextMenu(parent); await this.expectContextMenuToBeOpen(); @@ -77,43 +80,45 @@ export function DashboardPanelActionsProvider({ getService, getPageObjects }) { await testSubjects.click(REMOVE_PANEL_DATA_TEST_SUBJ); } - async removePanelByTitle(title) { + async removePanelByTitle(title: string) { const header = await this.getPanelHeading(title); await this.openContextMenu(header); await testSubjects.click(REMOVE_PANEL_DATA_TEST_SUBJ); } - async customizePanel(parent) { + async customizePanel(parent?: WebElementWrapper) { await this.openContextMenu(parent); await testSubjects.click(CUSTOMIZE_PANEL_DATA_TEST_SUBJ); } - async replacePanelByTitle(title) { + async replacePanelByTitle(title?: string) { log.debug(`replacePanel(${title})`); - let panelOptions = null; if (title) { - panelOptions = await this.getPanelHeading(title); + const panelOptions = await this.getPanelHeading(title); + await this.openContextMenu(panelOptions); + } else { + await this.openContextMenu(); } - await this.openContextMenu(panelOptions); await testSubjects.click(REPLACE_PANEL_DATA_TEST_SUBJ); } - async clonePanelByTitle(title) { + async clonePanelByTitle(title?: string) { log.debug(`clonePanel(${title})`); - let panelOptions = null; if (title) { - panelOptions = await this.getPanelHeading(title); + const panelOptions = await this.getPanelHeading(title); + await this.openContextMenu(panelOptions); + } else { + await this.openContextMenu(); } - await this.openContextMenu(panelOptions); await testSubjects.click(CLONE_PANEL_DATA_TEST_SUBJ); } - async openInspectorByTitle(title) { + async openInspectorByTitle(title: string) { const header = await this.getPanelHeading(title); await this.openInspector(header); } - async openInspector(parent) { + async openInspector(parent: WebElementWrapper) { await this.openContextMenu(parent); await testSubjects.click(OPEN_INSPECTOR_TEST_SUBJ); } @@ -163,7 +168,7 @@ export function DashboardPanelActionsProvider({ getService, getPageObjects }) { await testSubjects.existOrFail(TOGGLE_EXPAND_PANEL_DATA_TEST_SUBJ); } - async getPanelHeading(title) { + async getPanelHeading(title: string) { return await testSubjects.find(`embeddablePanelHeading-${title.replace(/\s/g, '')}`); } @@ -171,13 +176,14 @@ export function DashboardPanelActionsProvider({ getService, getPageObjects }) { await testSubjects.click('customizePanelHideTitle'); } - async toggleHidePanelTitle(originalTitle) { + async toggleHidePanelTitle(originalTitle: string) { log.debug(`hidePanelTitle(${originalTitle})`); - let panelOptions = null; if (originalTitle) { - panelOptions = await this.getPanelHeading(originalTitle); + const panelOptions = await this.getPanelHeading(originalTitle); + await this.customizePanel(panelOptions); + } else { + await this.customizePanel(); } - await this.customizePanel(panelOptions); await this.clickHidePanelTitleToggle(); await testSubjects.click('saveNewTitleButton'); } @@ -188,18 +194,19 @@ export function DashboardPanelActionsProvider({ getService, getPageObjects }) { * @param originalTitle - optional to specify which panel to change the title on. * @return {Promise} */ - async setCustomPanelTitle(customTitle, originalTitle) { + async setCustomPanelTitle(customTitle: string, originalTitle?: string) { log.debug(`setCustomPanelTitle(${customTitle}, ${originalTitle})`); - let panelOptions = null; if (originalTitle) { - panelOptions = await this.getPanelHeading(originalTitle); + const panelOptions = await this.getPanelHeading(originalTitle); + await this.customizePanel(panelOptions); + } else { + await this.customizePanel(); } - await this.customizePanel(panelOptions); await testSubjects.setValue('customEmbeddablePanelTitleInput', customTitle); await testSubjects.click('saveNewTitleButton'); } - async resetCustomPanelTitle(panel) { + async resetCustomPanelTitle(panel: WebElementWrapper) { log.debug('resetCustomPanelTitle'); await this.customizePanel(panel); await testSubjects.click('resetCustomEmbeddablePanelTitle'); diff --git a/test/functional/services/dashboard/replace_panel.js b/test/functional/services/dashboard/replace_panel.ts similarity index 86% rename from test/functional/services/dashboard/replace_panel.js rename to test/functional/services/dashboard/replace_panel.ts index faa4d404442d..d1cb4e5e697a 100644 --- a/test/functional/services/dashboard/replace_panel.js +++ b/test/functional/services/dashboard/replace_panel.ts @@ -17,7 +17,9 @@ * under the License. */ -export function DashboardReplacePanelProvider({ getService }) { +import { FtrProviderContext } from '../../ftr_provider_context'; + +export function DashboardReplacePanelProvider({ getService }: FtrProviderContext) { const log = getService('log'); const testSubjects = getService('testSubjects'); const flyout = getService('flyout'); @@ -28,7 +30,7 @@ export function DashboardReplacePanelProvider({ getService }) { await testSubjects.click('savedObjectFinderFilterButton'); } - async toggleFilter(type) { + async toggleFilter(type: string) { log.debug(`DashboardReplacePanel.replaceToFilter(${type})`); await this.waitForListLoading(); await this.toggleFilterPopover(); @@ -57,21 +59,21 @@ export function DashboardReplacePanelProvider({ getService }) { await flyout.ensureClosed('dashboardReplacePanel'); } - async replaceSavedSearch(searchName) { + async replaceSavedSearch(searchName: string) { return this.replaceEmbeddable(searchName, 'search'); } - async replaceSavedSearches(searches) { + async replaceSavedSearches(searches: string[]) { for (const name of searches) { await this.replaceSavedSearch(name); } } - async replaceVisualization(vizName) { + async replaceVisualization(vizName: string) { return this.replaceEmbeddable(vizName, 'visualization'); } - async replaceEmbeddable(embeddableName, embeddableType) { + async replaceEmbeddable(embeddableName: string, embeddableType: string) { log.debug( `DashboardReplacePanel.replaceEmbeddable, name: ${embeddableName}, type: ${embeddableType}` ); @@ -86,14 +88,14 @@ export function DashboardReplacePanelProvider({ getService }) { return embeddableName; } - async filterEmbeddableNames(name) { + async filterEmbeddableNames(name: string) { // The search input field may be disabled while the table is loading so wait for it await this.waitForListLoading(); await testSubjects.setValue('savedObjectFinderSearchInput', name); await this.waitForListLoading(); } - async panelReplaceLinkExists(name) { + async panelReplaceLinkExists(name: string) { log.debug(`DashboardReplacePanel.panelReplaceLinkExists(${name})`); await this.ensureReplacePanelIsShowing(); await this.filterEmbeddableNames(`"${name}"`); diff --git a/test/functional/services/dashboard/visualizations.js b/test/functional/services/dashboard/visualizations.ts similarity index 88% rename from test/functional/services/dashboard/visualizations.js rename to test/functional/services/dashboard/visualizations.ts index 676e4c384fe3..10747658d8c9 100644 --- a/test/functional/services/dashboard/visualizations.js +++ b/test/functional/services/dashboard/visualizations.ts @@ -17,7 +17,9 @@ * under the License. */ -export function DashboardVisualizationProvider({ getService, getPageObjects }) { +import { FtrProviderContext } from '../../ftr_provider_context'; + +export function DashboardVisualizationProvider({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); const find = getService('find'); const retry = getService('retry'); @@ -34,7 +36,7 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }) { ]); return new (class DashboardVisualizations { - async createAndAddTSVBVisualization(name) { + async createAndAddTSVBVisualization(name: string) { log.debug(`createAndAddTSVBVisualization(${name})`); const inViewMode = await PageObjects.dashboard.getIsInViewMode(); if (inViewMode) { @@ -46,7 +48,15 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }) { await PageObjects.visualize.saveVisualizationExpectSuccess(name); } - async createSavedSearch({ name, query, fields }) { + async createSavedSearch({ + name, + query, + fields, + }: { + name: string; + query?: string; + fields?: string[]; + }) { log.debug(`createSavedSearch(${name})`); await PageObjects.header.clickDiscover(); @@ -68,7 +78,15 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }) { await testSubjects.exists('saveSearchSuccess'); } - async createAndAddSavedSearch({ name, query, fields }) { + async createAndAddSavedSearch({ + name, + query, + fields, + }: { + name: string; + query?: string; + fields?: string[]; + }) { log.debug(`createAndAddSavedSearch(${name})`); await this.createSavedSearch({ name, query, fields }); @@ -106,7 +124,7 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }) { } } - async createAndAddMarkdown({ name, markdown }) { + async createAndAddMarkdown({ name, markdown }: { name: string; markdown: string }) { log.debug(`createAndAddMarkdown(${markdown})`); const inViewMode = await PageObjects.dashboard.getIsInViewMode(); if (inViewMode) { diff --git a/test/functional/services/index.ts b/test/functional/services/index.ts index cbb0c6790dbe..7891a6b00f72 100644 --- a/test/functional/services/index.ts +++ b/test/functional/services/index.ts @@ -35,10 +35,8 @@ import { DashboardExpectProvider, DashboardPanelActionsProvider, DashboardVisualizationProvider, - // @ts-ignore not TS yet } from './dashboard'; import { DocTableProvider } from './doc_table'; -import { ElasticChartProvider } from './elastic_chart'; import { EmbeddingProvider } from './embedding'; import { FilterBarProvider } from './filter_bar'; import { FlyoutProvider } from './flyout'; @@ -49,8 +47,7 @@ import { RemoteProvider } from './remote'; import { RenderableProvider } from './renderable'; import { TableProvider } from './table'; import { ToastsProvider } from './toasts'; -// @ts-ignore not TS yet -import { PieChartProvider } from './visualizations'; +import { PieChartProvider, ElasticChartProvider } from './visualizations'; import { ListingTableProvider } from './listing_table'; import { SavedQueryManagementComponentProvider } from './saved_query_management_component'; import { KibanaSupertestProvider } from './supertest'; diff --git a/test/functional/services/elastic_chart.ts b/test/functional/services/visualizations/elastic_chart.ts similarity index 97% rename from test/functional/services/elastic_chart.ts rename to test/functional/services/visualizations/elastic_chart.ts index 1c3071ac0158..3c454f0a88e2 100644 --- a/test/functional/services/elastic_chart.ts +++ b/test/functional/services/visualizations/elastic_chart.ts @@ -18,7 +18,7 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrProviderContext } from '../../ftr_provider_context'; export function ElasticChartProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); diff --git a/test/functional/services/visualizations/index.js b/test/functional/services/visualizations/index.ts similarity index 93% rename from test/functional/services/visualizations/index.js rename to test/functional/services/visualizations/index.ts index 1cc9d40abeab..1da1691b07c2 100644 --- a/test/functional/services/visualizations/index.js +++ b/test/functional/services/visualizations/index.ts @@ -18,3 +18,4 @@ */ export { PieChartProvider } from './pie_chart'; +export { ElasticChartProvider } from './elastic_chart'; diff --git a/test/functional/services/visualizations/pie_chart.js b/test/functional/services/visualizations/pie_chart.ts similarity index 78% rename from test/functional/services/visualizations/pie_chart.js rename to test/functional/services/visualizations/pie_chart.ts index edabc7ce989c..66f32d246b31 100644 --- a/test/functional/services/visualizations/pie_chart.js +++ b/test/functional/services/visualizations/pie_chart.ts @@ -18,8 +18,9 @@ */ import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; -export function PieChartProvider({ getService }) { +export function PieChartProvider({ getService }: FtrProviderContext) { const log = getService('log'); const retry = getService('retry'); const config = getService('config'); @@ -29,7 +30,7 @@ export function PieChartProvider({ getService }) { const defaultFindTimeout = config.get('timeouts.find'); return new (class PieChart { - async filterOnPieSlice(name) { + async filterOnPieSlice(name?: string) { log.debug(`PieChart.filterOnPieSlice(${name})`); if (name) { await testSubjects.click(`pieSlice-${name.split(' ').join('-')}`); @@ -43,27 +44,27 @@ export function PieChartProvider({ getService }) { } } - async filterByLegendItem(label) { + async filterByLegendItem(label: string) { log.debug(`PieChart.filterByLegendItem(${label})`); await testSubjects.click(`legend-${label}`); await testSubjects.click(`legend-${label}-filterIn`); } - async getPieSlice(name) { + async getPieSlice(name: string) { return await testSubjects.find(`pieSlice-${name.split(' ').join('-')}`); } - async getAllPieSlices(name) { + async getAllPieSlices(name: string) { return await testSubjects.findAll(`pieSlice-${name.split(' ').join('-')}`); } - async getPieSliceStyle(name) { + async getPieSliceStyle(name: string) { log.debug(`VisualizePage.getPieSliceStyle(${name})`); const pieSlice = await this.getPieSlice(name); return await pieSlice.getAttribute('style'); } - async getAllPieSliceStyles(name) { + async getAllPieSliceStyles(name: string) { log.debug(`VisualizePage.getAllPieSliceStyles(${name})`); const pieSlices = await this.getAllPieSlices(name); return await Promise.all( @@ -73,12 +74,10 @@ export function PieChartProvider({ getService }) { async getPieChartData() { const chartTypes = await find.allByCssSelector('path.slice', defaultFindTimeout * 2); - - const getChartTypesPromises = chartTypes.map(async (chart) => await chart.getAttribute('d')); - return await Promise.all(getChartTypesPromises); + return await Promise.all(chartTypes.map(async (chart) => await chart.getAttribute('d'))); } - async expectPieChartTableData(expectedTableData) { + async expectPieChartTableData(expectedTableData: Array<[]>) { await inspector.open(); await inspector.setTablePageSize(50); await inspector.expectTableData(expectedTableData); @@ -86,22 +85,18 @@ export function PieChartProvider({ getService }) { async getPieChartLabels() { const chartTypes = await find.allByCssSelector('path.slice', defaultFindTimeout * 2); - - const getChartTypesPromises = chartTypes.map( - async (chart) => await chart.getAttribute('data-label') + return await Promise.all( + chartTypes.map(async (chart) => await chart.getAttribute('data-label')) ); - return await Promise.all(getChartTypesPromises); } async getPieSliceCount() { log.debug('PieChart.getPieSliceCount'); - return await retry.try(async () => { - const slices = await find.allByCssSelector('svg > g > g.arcs > path.slice', 2500); - return slices.length; - }); + const slices = await find.allByCssSelector('svg > g > g.arcs > path.slice'); + return slices.length; } - async expectPieSliceCount(expectedCount) { + async expectPieSliceCount(expectedCount: number) { log.debug(`PieChart.expectPieSliceCount(${expectedCount})`); await retry.try(async () => { const slicesCount = await this.getPieSliceCount(); @@ -109,7 +104,7 @@ export function PieChartProvider({ getService }) { }); } - async expectPieChartLabels(expectedLabels) { + async expectPieChartLabels(expectedLabels: string[]) { log.debug(`PieChart.expectPieChartLabels(${expectedLabels.join(',')})`); await retry.try(async () => { const pieData = await this.getPieChartLabels(); From 0247df4cfd689630b174f2f094c35df0a40d16ef Mon Sep 17 00:00:00 2001 From: Michail Yasonik Date: Tue, 2 Jun 2020 17:46:21 -0400 Subject: [PATCH 35/50] New Kibana app link order (#66320) --- src/core/public/chrome/nav_links/to_nav_link.ts | 4 +--- src/core/public/chrome/ui/header/nav_link.tsx | 7 ++++++- src/plugins/dashboard/public/plugin.tsx | 2 +- src/plugins/discover/public/plugin.ts | 2 +- src/plugins/visualize/public/plugin.ts | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/core/public/chrome/nav_links/to_nav_link.ts b/src/core/public/chrome/nav_links/to_nav_link.ts index b8f97f9ddc00..2dedbfd5f36a 100644 --- a/src/core/public/chrome/nav_links/to_nav_link.ts +++ b/src/core/public/chrome/nav_links/to_nav_link.ts @@ -42,9 +42,7 @@ export function toNavLink( legacy: isLegacyApp(app), baseUrl, ...(isLegacyApp(app) - ? { - href: url && !url.startsWith(app.subUrlBase!) ? url : baseUrl, - } + ? {} : { href: url, url, diff --git a/src/core/public/chrome/ui/header/nav_link.tsx b/src/core/public/chrome/ui/header/nav_link.tsx index c09b15fac9bd..969b6728e026 100644 --- a/src/core/public/chrome/ui/header/nav_link.tsx +++ b/src/core/public/chrome/ui/header/nav_link.tsx @@ -55,7 +55,12 @@ export function createEuiListItem({ navigateToApp, dataTestSubj, }: Props) { - const { legacy, active, id, title, disabled, euiIconType, icon, tooltip, href } = link; + const { legacy, active, id, title, disabled, euiIconType, icon, tooltip } = link; + let { href } = link; + + if (legacy) { + href = link.url && !active ? link.url : link.baseUrl; + } return { label: tooltip ?? title, diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 08740b21f39a..0de398203992 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -228,7 +228,7 @@ export class DashboardPlugin const app: App = { id: DashboardConstants.DASHBOARDS_ID, title: 'Dashboard', - order: -1001, + order: 2500, euiIconType: 'dashboardApp', defaultPath: `#${DashboardConstants.LANDING_PAGE_PATH}`, updater$: this.appStateUpdater, diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts index 5a031872913c..4323e3d8deda 100644 --- a/src/plugins/discover/public/plugin.ts +++ b/src/plugins/discover/public/plugin.ts @@ -188,7 +188,7 @@ export class DiscoverPlugin id: 'discover', title: 'Discover', updater$: this.appStateUpdater.asObservable(), - order: -1004, + order: 1000, euiIconType: 'discoverApp', defaultPath: '#/', category: DEFAULT_APP_CATEGORIES.kibana, diff --git a/src/plugins/visualize/public/plugin.ts b/src/plugins/visualize/public/plugin.ts index 8a104dc51feb..8a05adc18964 100644 --- a/src/plugins/visualize/public/plugin.ts +++ b/src/plugins/visualize/public/plugin.ts @@ -102,7 +102,7 @@ export class VisualizePlugin core.application.register({ id: 'visualize', title: 'Visualize', - order: -1002, + order: 8000, euiIconType: 'visualizeApp', defaultPath: '#/', category: DEFAULT_APP_CATEGORIES.kibana, From eab74f400522faf4856b59dedddeea07c2ffe45f Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Tue, 2 Jun 2020 16:08:41 -0700 Subject: [PATCH 36/50] [Ingest Manager] Change agent config YAML view to flyout (#67918) * Consolidate context menu/row action components and usage, and some export/imports * Missed export * Move agent config yaml tab to flyout, add and consolidate agent config action menus * Fix i18n * Add download config YAML endpoint and link from flyout, add common config->yaml service * Actually remove the tab lol * Fix i18n --- .../ingest_manager/common/constants/routes.ts | 1 + .../common/services/config_to_yaml.ts | 39 +++++ .../ingest_manager/common/services/index.ts | 1 + .../ingest_manager/common/services/routes.ts | 7 + .../components/context_menu_actions.tsx | 68 +++++++++ .../ingest_manager/components/index.ts | 3 + .../components/table_row_actions_nested.tsx | 38 ----- .../ingest_manager/constants/page_paths.ts | 1 - .../ingest_manager/hooks/index.ts | 1 + .../hooks/use_request/agent_config.ts | 3 +- .../agent_config/components/actions_menu.tsx | 69 +++++++++ .../components/config_yaml_flyout.tsx | 99 +++++++++++++ .../sections/agent_config/components/index.ts | 4 +- .../components/table_row_actions.tsx | 38 ----- .../datasources/datasources_table.tsx | 10 +- .../details_page/components/index.ts | 2 + .../details_page/components/yaml/index.tsx | 56 -------- .../agent_config/details_page/index.tsx | 38 +++-- .../sections/agent_config/list_page/index.tsx | 68 ++------- .../components/data_stream_row_actions.tsx | 9 +- .../components/actions_menu.tsx | 99 ++++++------- .../sections/fleet/agent_list_page/index.tsx | 135 ++++++++---------- .../ingest_manager/services/index.ts | 1 + .../ingest_manager/types/index.ts | 1 + .../server/routes/agent_config/handlers.ts | 37 ++++- .../server/routes/agent_config/index.ts | 11 ++ .../ingest_manager/server/services/index.ts | 2 +- .../server/types/rest_spec/agent_config.ts | 3 + .../translations/translations/ja-JP.json | 5 - .../translations/translations/zh-CN.json | 5 - 30 files changed, 481 insertions(+), 373 deletions(-) create mode 100644 x-pack/plugins/ingest_manager/common/services/config_to_yaml.ts create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/context_menu_actions.tsx delete mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/table_row_actions_nested.tsx create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/actions_menu.tsx create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_yaml_flyout.tsx delete mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/table_row_actions.tsx delete mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/components/yaml/index.tsx diff --git a/x-pack/plugins/ingest_manager/common/constants/routes.ts b/x-pack/plugins/ingest_manager/common/constants/routes.ts index abb266da9f06..3309d8497f4c 100644 --- a/x-pack/plugins/ingest_manager/common/constants/routes.ts +++ b/x-pack/plugins/ingest_manager/common/constants/routes.ts @@ -46,6 +46,7 @@ export const AGENT_CONFIG_API_ROUTES = { UPDATE_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}`, DELETE_PATTERN: `${AGENT_CONFIG_API_ROOT}/delete`, FULL_INFO_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}/full`, + FULL_INFO_DOWNLOAD_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}/download`, }; // Output API routes diff --git a/x-pack/plugins/ingest_manager/common/services/config_to_yaml.ts b/x-pack/plugins/ingest_manager/common/services/config_to_yaml.ts new file mode 100644 index 000000000000..9dfd76b9ddd2 --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/services/config_to_yaml.ts @@ -0,0 +1,39 @@ +/* + * 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 { safeDump } from 'js-yaml'; +import { FullAgentConfig } from '../types'; + +const CONFIG_KEYS_ORDER = [ + 'id', + 'name', + 'revision', + 'type', + 'outputs', + 'settings', + 'datasources', + 'enabled', + 'package', + 'input', +]; + +export const configToYaml = (config: FullAgentConfig): string => { + return safeDump(config, { + skipInvalid: true, + sortKeys: (keyA: string, keyB: string) => { + const indexA = CONFIG_KEYS_ORDER.indexOf(keyA); + const indexB = CONFIG_KEYS_ORDER.indexOf(keyB); + if (indexA >= 0 && indexB < 0) { + return -1; + } + + if (indexA < 0 && indexB >= 0) { + return 1; + } + + return indexA - indexB; + }, + }); +}; diff --git a/x-pack/plugins/ingest_manager/common/services/index.ts b/x-pack/plugins/ingest_manager/common/services/index.ts index 91dbbdd515c3..c595c9a52f66 100644 --- a/x-pack/plugins/ingest_manager/common/services/index.ts +++ b/x-pack/plugins/ingest_manager/common/services/index.ts @@ -8,5 +8,6 @@ import * as AgentStatusKueryHelper from './agent_status'; export * from './routes'; export { packageToConfigDatasourceInputs, packageToConfigDatasource } from './package_to_config'; export { storedDatasourceToAgentDatasource } from './datasource_to_agent_datasource'; +export { configToYaml } from './config_to_yaml'; export { AgentStatusKueryHelper }; export { decodeCloudId } from './decode_cloud_id'; diff --git a/x-pack/plugins/ingest_manager/common/services/routes.ts b/x-pack/plugins/ingest_manager/common/services/routes.ts index 20d040ac6eae..3fc990ea9d70 100644 --- a/x-pack/plugins/ingest_manager/common/services/routes.ts +++ b/x-pack/plugins/ingest_manager/common/services/routes.ts @@ -90,6 +90,13 @@ export const agentConfigRouteService = { getInfoFullPath: (agentConfigId: string) => { return AGENT_CONFIG_API_ROUTES.FULL_INFO_PATTERN.replace('{agentConfigId}', agentConfigId); }, + + getInfoFullDownloadPath: (agentConfigId: string) => { + return AGENT_CONFIG_API_ROUTES.FULL_INFO_DOWNLOAD_PATTERN.replace( + '{agentConfigId}', + agentConfigId + ); + }, }; export const dataStreamRouteService = { diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/context_menu_actions.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/context_menu_actions.tsx new file mode 100644 index 000000000000..8a9f0553895a --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/context_menu_actions.tsx @@ -0,0 +1,68 @@ +/* + * 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, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiButtonIcon, + EuiContextMenu, + EuiContextMenuPanel, + EuiPopover, + EuiButton, +} from '@elastic/eui'; +import { EuiButtonProps } from '@elastic/eui/src/components/button/button'; +import { EuiContextMenuProps } from '@elastic/eui/src/components/context_menu/context_menu'; +import { EuiContextMenuPanelProps } from '@elastic/eui/src/components/context_menu/context_menu_panel'; + +type Props = { + button?: { + props: EuiButtonProps; + children: JSX.Element; + }; +} & ( + | { + items: EuiContextMenuPanelProps['items']; + } + | { + panels: EuiContextMenuProps['panels']; + } +); + +export const ContextMenuActions = React.memo(({ button, ...props }) => { + const [isOpen, setIsOpen] = useState(false); + const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]); + const handleToggleMenu = useCallback(() => setIsOpen(!isOpen), [isOpen]); + + return ( + + {button.children} + + ) : ( + + ) + } + isOpen={isOpen} + closePopover={handleCloseMenu} + > + {'items' in props ? ( + + ) : ( + + )} + + ); +}); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts index b0b4e79cece7..93bc0645c7ee 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts @@ -7,4 +7,7 @@ export { Loading } from './loading'; export { Error } from './error'; export { Header, HeaderProps } from './header'; export { AlphaMessaging } from './alpha_messaging'; +export { PackageIcon } from './package_icon'; +export { ContextMenuActions } from './context_menu_actions'; +export { SearchBar } from './search_bar'; export * from './settings_flyout'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/table_row_actions_nested.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/table_row_actions_nested.tsx deleted file mode 100644 index 56f010e2fa77..000000000000 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/table_row_actions_nested.tsx +++ /dev/null @@ -1,38 +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, useState } from 'react'; -import { EuiButtonIcon, EuiContextMenu, EuiPopover } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { EuiContextMenuProps } from '@elastic/eui/src/components/context_menu/context_menu'; - -export const TableRowActionsNested = React.memo<{ panels: EuiContextMenuProps['panels'] }>( - ({ panels }) => { - const [isOpen, setIsOpen] = useState(false); - const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]); - const handleToggleMenu = useCallback(() => setIsOpen(!isOpen), [isOpen]); - - return ( - - } - isOpen={isOpen} - closePopover={handleCloseMenu} - > - - - ); - } -); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts index 73771fa3cb34..5ef7f45faec4 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts @@ -43,7 +43,6 @@ export const PAGE_ROUTING_PATHS = { configurations: '/configs', configurations_list: '/configs', configuration_details: '/configs/:configId/:tabId?', - configuration_details_yaml: '/configs/:configId/yaml', configuration_details_settings: '/configs/:configId/settings', add_datasource_from_configuration: '/configs/:configId/add-datasource', add_datasource_from_integration: '/integrations/:pkgkey/add-datasource', diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts index a752ad2a8912..31a511f2d79d 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts @@ -10,6 +10,7 @@ export { useConfig, ConfigContext } from './use_config'; export { useSetupDeps, useStartDeps, DepsContext } from './use_deps'; export { useBreadcrumbs } from './use_breadcrumbs'; export { useLink } from './use_link'; +export { useKibanaLink } from './use_kibana_link'; export { usePackageIconType, UsePackageIconType } from './use_package_icon_type'; export { usePagination, Pagination } from './use_pagination'; export { useDebounce } from './use_debounce'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts index f80c468677f4..45ca6047b0d9 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts @@ -14,6 +14,7 @@ import { agentConfigRouteService } from '../../services'; import { GetAgentConfigsResponse, GetOneAgentConfigResponse, + GetFullAgentConfigResponse, CreateAgentConfigRequest, CreateAgentConfigResponse, UpdateAgentConfigRequest, @@ -39,7 +40,7 @@ export const useGetOneAgentConfig = (agentConfigId: string | undefined) => { }; export const useGetOneAgentConfigFull = (agentConfigId: string) => { - return useRequest({ + return useRequest({ path: agentConfigRouteService.getInfoFullPath(agentConfigId), method: 'get', }); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/actions_menu.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/actions_menu.tsx new file mode 100644 index 000000000000..78ed22801269 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/actions_menu.tsx @@ -0,0 +1,69 @@ +/* + * 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, { memo, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiContextMenuItem, EuiPortal } from '@elastic/eui'; +import { useCapabilities, useLink } from '../../../hooks'; +import { ContextMenuActions } from '../../../components'; +import { ConfigYamlFlyout } from './config_yaml_flyout'; + +export const AgentConfigActionMenu = memo<{ configId: string; fullButton?: boolean }>( + ({ configId, fullButton = false }) => { + const { getHref } = useLink(); + const hasWriteCapabilities = useCapabilities().write; + const [isYamlFlyoutOpen, setIsYamlFlyoutOpen] = useState(false); + return ( + <> + {isYamlFlyoutOpen ? ( + + setIsYamlFlyoutOpen(false)} /> + + ) : null} + + ), + } + : undefined + } + items={[ + setIsYamlFlyoutOpen(!isYamlFlyoutOpen)} + key="viewConfig" + > + + , + + + , + ]} + /> + + ); + } +); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_yaml_flyout.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_yaml_flyout.tsx new file mode 100644 index 000000000000..6cf60fe1dc50 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_yaml_flyout.tsx @@ -0,0 +1,99 @@ +/* + * 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, { memo } from 'react'; +import styled from 'styled-components'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutHeader, + EuiTitle, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiButtonEmpty, + EuiButton, +} from '@elastic/eui'; +import { useGetOneAgentConfigFull, useGetOneAgentConfig, useCore } from '../../../hooks'; +import { Loading } from '../../../components'; +import { configToYaml, agentConfigRouteService } from '../../../services'; + +const FlyoutBody = styled(EuiFlyoutBody)` + .euiFlyoutBody__overflowContent { + padding: 0; + } +`; + +export const ConfigYamlFlyout = memo<{ configId: string; onClose: () => void }>( + ({ configId, onClose }) => { + const core = useCore(); + const { isLoading: isLoadingYaml, data: yamlData } = useGetOneAgentConfigFull(configId); + const { data: configData } = useGetOneAgentConfig(configId); + + const body = + isLoadingYaml && !yamlData ? ( + + ) : ( + + {configToYaml(yamlData!.item)} + + ); + + const downloadLink = core.http.basePath.prepend( + agentConfigRouteService.getInfoFullDownloadPath(configId) + ); + + return ( + + + +

+ {configData?.item ? ( + + ) : ( + + )} +

+
+
+ {body} + + + + + + + + + + + + + + +
+ ); + } +); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/index.ts index c1811b99588a..f3ec15e0f477 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/index.ts @@ -3,8 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - export { AgentConfigForm, agentConfigFormValidation } from './config_form'; export { AgentConfigDeleteProvider } from './config_delete_provider'; +export { DatasourceDeleteProvider } from './datasource_delete_provider'; export { LinkedAgentCount } from './linked_agent_count'; export { ConfirmDeployConfigModal } from './confirm_deploy_modal'; +export { DangerEuiContextMenuItem } from './danger_eui_context_menu_item'; +export { AgentConfigActionMenu } from './actions_menu'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/table_row_actions.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/table_row_actions.tsx deleted file mode 100644 index 2f9a11ef7670..000000000000 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/table_row_actions.tsx +++ /dev/null @@ -1,38 +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, useState } from 'react'; -import { EuiButtonIcon, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { EuiContextMenuPanelProps } from '@elastic/eui/src/components/context_menu/context_menu_panel'; - -export const TableRowActions = React.memo<{ items: EuiContextMenuPanelProps['items'] }>( - ({ items }) => { - const [isOpen, setIsOpen] = useState(false); - const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]); - const handleToggleMenu = useCallback(() => setIsOpen(!isOpen), [isOpen]); - - return ( - - } - isOpen={isOpen} - closePopover={handleCloseMenu} - > - - - ); - } -); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/components/datasources/datasources_table.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/components/datasources/datasources_table.tsx index 316b7eed491b..01505fcf4c65 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/components/datasources/datasources_table.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/components/datasources/datasources_table.tsx @@ -17,12 +17,10 @@ import { EuiFlexItem, } from '@elastic/eui'; import { AgentConfig, Datasource } from '../../../../../types'; -import { TableRowActions } from '../../../components/table_row_actions'; -import { DangerEuiContextMenuItem } from '../../../components/danger_eui_context_menu_item'; +import { PackageIcon, ContextMenuActions } from '../../../../../components'; +import { DatasourceDeleteProvider, DangerEuiContextMenuItem } from '../../../components'; import { useCapabilities, useLink } from '../../../../../hooks'; -import { DatasourceDeleteProvider } from '../../../components/datasource_delete_provider'; -import { useConfigRefresh } from '../../hooks/use_config'; -import { PackageIcon } from '../../../../../components/package_icon'; +import { useConfigRefresh } from '../../hooks'; interface InMemoryDatasource extends Datasource { streams: { total: number; enabled: number }; @@ -197,7 +195,7 @@ export const DatasourcesTable: React.FunctionComponent = ({ actions: [ { render: (datasource: InMemoryDatasource) => ( - (({ config }) => { - const fullConfigRequest = useGetOneAgentConfigFull(config.id); - - if (fullConfigRequest.isLoading && !fullConfigRequest.data) { - return ; - } - - return ( - - - - {dump(fullConfigRequest.data.item, { - sortKeys: (keyA: string, keyB: string) => { - const indexA = CONFIG_KEYS_ORDER.indexOf(keyA); - const indexB = CONFIG_KEYS_ORDER.indexOf(keyB); - if (indexA >= 0 && indexB < 0) { - return -1; - } - - if (indexA < 0 && indexB >= 0) { - return 1; - } - - return indexA - indexB; - }, - })} - - - - ); -}); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx index 3f886645b533..1dd7e660deaa 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx @@ -27,10 +27,8 @@ import { useGetOneAgentConfig, useLink, useBreadcrumbs } from '../../../hooks'; import { Loading } from '../../../components'; import { WithHeaderLayout } from '../../../layouts'; import { ConfigRefreshContext, useGetAgentStatus, AgentStatusRefreshContext } from './hooks'; -import { LinkedAgentCount } from '../components'; -import { ConfigDatasourcesView } from './components/datasources'; -import { ConfigYamlView } from './components/yaml'; -import { ConfigSettingsView } from './components/settings'; +import { LinkedAgentCount, AgentConfigActionMenu } from '../components'; +import { ConfigDatasourcesView, ConfigSettingsView } from './components'; const Divider = styled.div` width: 0; @@ -147,21 +145,31 @@ export const AgentConfigDetailsPage: React.FunctionComponent = () => { )) || '', }, + { isDivider: true }, + { + content: agentConfig && , + }, ].map((item, index) => ( {item.isDivider ?? false ? ( - ) : ( + ) : item.label ? ( - {item.label} - {item.content} + + {item.label} + + + {item.content} + + ) : ( + item.content )} ))} ), - [agentConfig, agentStatus] + [agentConfig, configId, agentStatus] ); const headerTabs = useMemo(() => { @@ -174,14 +182,6 @@ export const AgentConfigDetailsPage: React.FunctionComponent = () => { href: getHref('configuration_details', { configId, tabId: 'datasources' }), isSelected: tabId === '' || tabId === 'datasources', }, - { - id: 'yaml', - name: i18n.translate('xpack.ingestManager.configDetails.subTabs.yamlTabText', { - defaultMessage: 'YAML', - }), - href: getHref('configuration_details', { configId, tabId: 'yaml' }), - isSelected: tabId === 'yaml', - }, { id: 'settings', name: i18n.translate('xpack.ingestManager.configDetails.subTabs.settingsTabText', { @@ -254,12 +254,6 @@ const AgentConfigDetailsContent: React.FunctionComponent<{ agentConfig: AgentCon useBreadcrumbs('configuration_details', { configName: agentConfig.name }); return ( - { - return ; - }} - /> { diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx index 5b4066e53f0c..0d43d8856c2f 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.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, { CSSProperties, memo, useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { EuiSpacer, EuiText, @@ -16,7 +16,6 @@ import { EuiTableActionsColumnType, EuiTableFieldDataColumnType, EuiTextColor, - EuiContextMenuItem, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, FormattedDate } from '@kbn/i18n/react'; @@ -33,16 +32,9 @@ import { useUrlParams, useBreadcrumbs, } from '../../../hooks'; +import { SearchBar } from '../../../components'; +import { LinkedAgentCount, AgentConfigActionMenu } from '../components'; import { CreateAgentConfigFlyout } from './components'; -import { SearchBar } from '../../../components/search_bar'; -import { LinkedAgentCount } from '../components'; -import { TableRowActions } from '../components/table_row_actions'; - -const NO_WRAP_TRUNCATE_STYLE: CSSProperties = Object.freeze({ - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', -}); const AgentConfigListPageLayout: React.FunctionComponent = ({ children }) => ( ( ); -const ConfigRowActions = memo<{ config: AgentConfig; onDelete: () => void }>( - ({ config, onDelete }) => { - const { getHref } = useLink(); - const hasWriteCapabilities = useCapabilities().write; - - return ( - - - , - - - - , - // - // - // , - ]} - /> - ); - } -); - export const AgentConfigListPage: React.FunctionComponent<{}> = () => { useBreadcrumbs('configurations_list'); const { getHref, getPath } = useLink(); @@ -172,10 +122,10 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => { width: '20%', render: (name: string, agentConfig: AgentConfig) => ( - + {name || agentConfig.id} @@ -201,7 +151,7 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => { width: '35%', truncateText: true, render: (description: AgentConfig['description']) => ( - + {description} ), @@ -239,9 +189,7 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => { }), actions: [ { - render: (config: AgentConfig) => ( - sendRequest()} /> - ), + render: (config: AgentConfig) => , }, ], }, @@ -253,7 +201,7 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => { } return cols; - }, [getHref, isFleetEnabled, sendRequest]); + }, [getHref, isFleetEnabled]); const createAgentConfigButton = useMemo( () => ( 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/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/components/data_stream_row_actions.tsx index b87ae4c4561f..cdc4f1c63a11 100644 --- 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/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/components/data_stream_row_actions.tsx @@ -5,12 +5,11 @@ */ import React, { memo } from 'react'; - -import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { useKibanaLink } from '../../../../hooks/use_kibana_link'; +import { FormattedMessage } from '@kbn/i18n/react'; import { DataStream } from '../../../../types'; -import { TableRowActionsNested } from '../../../../components/table_row_actions_nested'; +import { useKibanaLink } from '../../../../hooks'; +import { ContextMenuActions } from '../../../../components'; export const DataStreamRowActions = memo<{ datastream: DataStream }>(({ datastream }) => { const { dashboards } = datastream; @@ -78,5 +77,5 @@ export const DataStreamRowActions = memo<{ datastream: DataStream }>(({ datastre }); } - return ; + return ; }); 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/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx index 34a7ad8eb1ef..27e17f6b3df6 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx @@ -3,81 +3,70 @@ * 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, { memo, useState, useCallback } from 'react'; -import { EuiButton, EuiPopover, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui'; +import React, { memo, useState } from 'react'; +import { EuiPortal, EuiContextMenuItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { Agent } from '../../../../types'; import { useCapabilities } from '../../../../hooks'; -import { useAgentRefresh } from '../hooks'; +import { ContextMenuActions } from '../../../../components'; import { AgentUnenrollProvider, AgentReassignConfigFlyout } from '../../components'; +import { useAgentRefresh } from '../hooks'; export const AgentDetailsActionMenu: React.FunctionComponent<{ agent: Agent; }> = memo(({ agent }) => { const hasWriteCapabilites = useCapabilities().write; const refreshAgent = useAgentRefresh(); - const [isActionsPopoverOpen, setIsActionsPopoverOpen] = useState(false); - const handleCloseMenu = useCallback(() => setIsActionsPopoverOpen(false), [ - setIsActionsPopoverOpen, - ]); - const handleToggleMenu = useCallback(() => setIsActionsPopoverOpen(!isActionsPopoverOpen), [ - isActionsPopoverOpen, - ]); const [isReassignFlyoutOpen, setIsReassignFlyoutOpen] = useState(false); return ( <> {isReassignFlyoutOpen && ( - setIsReassignFlyoutOpen(false)} /> + + setIsReassignFlyoutOpen(false)} /> + )} - + - - } - isOpen={isActionsPopoverOpen} - closePopover={handleCloseMenu} - > - { - handleCloseMenu(); - setIsReassignFlyoutOpen(true); - }} - key="reassignConfig" - > - - , - - {(unenrollAgentsPrompt) => ( - { - unenrollAgentsPrompt([agent.id], 1, refreshAgent); - }} - > - - - )} - , - ]} - /> - + ), + }} + items={[ + { + setIsReassignFlyoutOpen(true); + }} + key="reassignConfig" + > + + , + + {(unenrollAgentsPrompt) => ( + { + unenrollAgentsPrompt([agent.id], 1, refreshAgent); + }} + > + + + )} + , + ]} + /> ); }); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx index d5b8b393e7ed..281a8d3a9745 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.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, { useState, useCallback } from 'react'; +import React, { useState } from 'react'; import { EuiBasicTable, EuiButton, @@ -17,10 +17,9 @@ import { EuiPopover, EuiSpacer, EuiText, - EuiButtonIcon, - EuiContextMenuPanel, EuiContextMenuItem, EuiIcon, + EuiPortal, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, FormattedRelative } from '@kbn/i18n/react'; @@ -36,12 +35,10 @@ import { useLink, useBreadcrumbs, } from '../../../hooks'; -import { AgentReassignConfigFlyout } from '../components'; -import { SearchBar } from '../../../components/search_bar'; -import { AgentHealth } from '../components/agent_health'; -import { AgentUnenrollProvider } from '../components/agent_unenroll_provider'; +import { SearchBar, ContextMenuActions } from '../../../components'; import { AgentStatusKueryHelper } from '../../../services'; import { AGENT_SAVED_OBJECT_TYPE } from '../../../constants'; +import { AgentReassignConfigFlyout, AgentHealth, AgentUnenrollProvider } from '../components'; const NO_WRAP_TRUNCATE_STYLE: CSSProperties = Object.freeze({ overflow: 'hidden', @@ -76,73 +73,53 @@ const RowActions = React.memo<{ agent: Agent; onReassignClick: () => void; refre ({ agent, refresh, onReassignClick }) => { const { getHref } = useLink(); const hasWriteCapabilites = useCapabilities().write; - const [isOpen, setIsOpen] = useState(false); - const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]); - const handleToggleMenu = useCallback(() => setIsOpen(!isOpen), [isOpen]); return ( - - } - isOpen={isOpen} - closePopover={handleCloseMenu} - > - - - , - { - handleCloseMenu(); - onReassignClick(); - }} - key="reassignConfig" - > - - , + + + , + { + onReassignClick(); + }} + key="reassignConfig" + > + + , - - {(unenrollAgentsPrompt) => ( - { - unenrollAgentsPrompt([agent.id], 1, () => { - refresh(); - }); - }} - > - - - )} - , - ]} - /> - + + {(unenrollAgentsPrompt) => ( + { + unenrollAgentsPrompt([agent.id], 1, () => { + refresh(); + }); + }} + > + + + )} + , + ]} + /> ); } ); @@ -387,13 +364,15 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { /> ) : null} {agentToReassign && ( - { - setAgentToReassignId(undefined); - agentsRequest.sendRequest(); - }} - /> + + { + setAgentToReassignId(undefined); + agentsRequest.sendRequest(); + }} + /> + )} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts index 085bad2d1837..6dbc8d67caae 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts @@ -20,5 +20,6 @@ export { appRoutesService, packageToConfigDatasourceInputs, storedDatasourceToAgentDatasource, + configToYaml, AgentStatusKueryHelper, } from '../../../../common'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts index 0a26a16d35cf..05a97fd2e2a3 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts @@ -26,6 +26,7 @@ export { GetAgentConfigsResponse, GetAgentConfigsResponseItem, GetOneAgentConfigResponse, + GetFullAgentConfigResponse, CreateAgentConfigRequest, CreateAgentConfigResponse, UpdateAgentConfigRequest, diff --git a/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts index f74f898b2baf..afc146cf9044 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ import { TypeOf } from '@kbn/config-schema'; -import { RequestHandler } from 'src/core/server'; +import { RequestHandler, ResponseHeaders } from 'src/core/server'; import bluebird from 'bluebird'; +import { configToYaml } from '../../../common/services'; import { appContextService, agentConfigService, datasourceService } from '../../services'; import { listAgents } from '../../services/agents'; import { AGENT_SAVED_OBJECT_TYPE } from '../../constants'; @@ -229,3 +230,37 @@ export const getFullAgentConfig: RequestHandler> = async (context, request, response) => { + const soClient = context.core.savedObjects.client; + const { + params: { agentConfigId }, + } = request; + + try { + const fullAgentConfig = await agentConfigService.getFullConfig(soClient, agentConfigId); + if (fullAgentConfig) { + const body = configToYaml(fullAgentConfig); + const headers: ResponseHeaders = { + 'content-type': 'text/x-yaml', + 'content-disposition': `attachment; filename="elastic-agent-config-${fullAgentConfig.id}.yml"`, + }; + return response.ok({ + body, + headers, + }); + } else { + return response.customError({ + statusCode: 404, + body: { message: 'Agent config not found' }, + }); + } + } catch (e) { + return response.customError({ + statusCode: 500, + body: { message: e.message }, + }); + } +}; diff --git a/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts b/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts index e630f3c95959..4f6cfb436b93 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts @@ -20,6 +20,7 @@ import { updateAgentConfigHandler, deleteAgentConfigsHandler, getFullAgentConfig, + downloadFullAgentConfig, } from './handlers'; export const registerRoutes = (router: IRouter) => { @@ -82,4 +83,14 @@ export const registerRoutes = (router: IRouter) => { }, getFullAgentConfig ); + + // Download one full agent config + router.get( + { + path: AGENT_CONFIG_API_ROUTES.FULL_INFO_DOWNLOAD_PATTERN, + validate: GetFullAgentConfigRequestSchema, + options: { tags: [`access:${PLUGIN_ID}-read`] }, + }, + downloadFullAgentConfig + ); }; diff --git a/x-pack/plugins/ingest_manager/server/services/index.ts b/x-pack/plugins/ingest_manager/server/services/index.ts index 483661b9de91..1a0fb262eeb7 100644 --- a/x-pack/plugins/ingest_manager/server/services/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/index.ts @@ -5,7 +5,7 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; -import { AgentStatus } from '../../common/types/models'; +import { AgentStatus } from '../types'; import * as settingsService from './settings'; export { ESIndexPatternSavedObjectService } from './es_index_pattern'; diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/agent_config.ts b/x-pack/plugins/ingest_manager/server/types/rest_spec/agent_config.ts index ab97ddc0ba72..123a413bb844 100644 --- a/x-pack/plugins/ingest_manager/server/types/rest_spec/agent_config.ts +++ b/x-pack/plugins/ingest_manager/server/types/rest_spec/agent_config.ts @@ -39,4 +39,7 @@ export const GetFullAgentConfigRequestSchema = { params: schema.object({ agentConfigId: schema.string(), }), + query: schema.object({ + download: schema.maybe(schema.boolean()), + }), }; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 0557663e9969..18186f9a1bfc 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -8121,11 +8121,9 @@ "xpack.ingestManager.agentConfigForm.systemMonitoringText": "システムメトリックを収集", "xpack.ingestManager.agentConfigForm.systemMonitoringTooltipText": "このオプションを有効にすると、システムメトリックと情報を収集するデータソースで構成をブートストラップできます。", "xpack.ingestManager.agentConfigList.actionsColumnTitle": "アクション", - "xpack.ingestManager.agentConfigList.actionsMenuText": "開く", "xpack.ingestManager.agentConfigList.addButton": "エージェント構成を作成", "xpack.ingestManager.agentConfigList.agentsColumnTitle": "エージェント", "xpack.ingestManager.agentConfigList.clearFiltersLinkText": "フィルターを消去", - "xpack.ingestManager.agentConfigList.createDatasourceActionText": "データソースを作成", "xpack.ingestManager.agentConfigList.datasourcesCountColumnTitle": "データソース", "xpack.ingestManager.agentConfigList.descriptionColumnTitle": "説明", "xpack.ingestManager.agentConfigList.loadingAgentConfigsMessage": "エージェント構成を読み込み中...", @@ -8137,7 +8135,6 @@ "xpack.ingestManager.agentConfigList.reloadAgentConfigsButtonText": "再読み込み", "xpack.ingestManager.agentConfigList.revisionNumber": "rev. {revNumber}", "xpack.ingestManager.agentConfigList.updatedOnColumnTitle": "最終更新日:", - "xpack.ingestManager.agentConfigList.viewConfigActionText": "構成を表示", "xpack.ingestManager.agentDetails.actionsButton": "アクション", "xpack.ingestManager.agentDetails.agentConfigurationLabel": "エージェント構成", "xpack.ingestManager.agentDetails.agentDetailsTitle": "エージェント'{id}'", @@ -8198,7 +8195,6 @@ "xpack.ingestManager.agentHealth.onlineStatusText": "オンライン", "xpack.ingestManager.agentHealth.warningStatusText": "エラー", "xpack.ingestManager.agentList.actionsColumnTitle": "アクション", - "xpack.ingestManager.agentList.actionsMenuText": "開く", "xpack.ingestManager.agentList.addButton": "新しいエージェントを登録", "xpack.ingestManager.agentList.clearFiltersLinkText": "フィルターを消去", "xpack.ingestManager.agentList.configColumnTitle": "構成", @@ -8266,7 +8262,6 @@ "xpack.ingestManager.configDetails.datasourcesTable.streamsCountColumnTitle": "ストリーム", "xpack.ingestManager.configDetails.subTabs.datasourcesTabText": "データソース", "xpack.ingestManager.configDetails.subTabs.settingsTabText": "設定", - "xpack.ingestManager.configDetails.subTabs.yamlTabText": "YAML", "xpack.ingestManager.configDetails.summary.datasources": "データソース", "xpack.ingestManager.configDetails.summary.lastUpdated": "最終更新日:", "xpack.ingestManager.configDetails.summary.revision": "リビジョン", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 4f62da260d43..195309f640a0 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -8125,11 +8125,9 @@ "xpack.ingestManager.agentConfigForm.systemMonitoringText": "收集系统指标", "xpack.ingestManager.agentConfigForm.systemMonitoringTooltipText": "启用此选项可使用收集系统指标和信息的数据源启动您的配置。", "xpack.ingestManager.agentConfigList.actionsColumnTitle": "操作", - "xpack.ingestManager.agentConfigList.actionsMenuText": "打开", "xpack.ingestManager.agentConfigList.addButton": "创建代理配置", "xpack.ingestManager.agentConfigList.agentsColumnTitle": "代理", "xpack.ingestManager.agentConfigList.clearFiltersLinkText": "清除筛选", - "xpack.ingestManager.agentConfigList.createDatasourceActionText": "创建数据源", "xpack.ingestManager.agentConfigList.datasourcesCountColumnTitle": "数据源", "xpack.ingestManager.agentConfigList.descriptionColumnTitle": "描述", "xpack.ingestManager.agentConfigList.loadingAgentConfigsMessage": "正在加载代理配置……", @@ -8141,7 +8139,6 @@ "xpack.ingestManager.agentConfigList.reloadAgentConfigsButtonText": "重新加载", "xpack.ingestManager.agentConfigList.revisionNumber": "修订 {revNumber}", "xpack.ingestManager.agentConfigList.updatedOnColumnTitle": "最后更新时间", - "xpack.ingestManager.agentConfigList.viewConfigActionText": "查看配置", "xpack.ingestManager.agentDetails.actionsButton": "操作", "xpack.ingestManager.agentDetails.agentConfigurationLabel": "代理配置", "xpack.ingestManager.agentDetails.agentDetailsTitle": "代理“{id}”", @@ -8202,7 +8199,6 @@ "xpack.ingestManager.agentHealth.onlineStatusText": "联机", "xpack.ingestManager.agentHealth.warningStatusText": "错误", "xpack.ingestManager.agentList.actionsColumnTitle": "操作", - "xpack.ingestManager.agentList.actionsMenuText": "打开", "xpack.ingestManager.agentList.addButton": "注册新代理", "xpack.ingestManager.agentList.clearFiltersLinkText": "清除筛选", "xpack.ingestManager.agentList.configColumnTitle": "配置", @@ -8270,7 +8266,6 @@ "xpack.ingestManager.configDetails.datasourcesTable.streamsCountColumnTitle": "流计数", "xpack.ingestManager.configDetails.subTabs.datasourcesTabText": "数据源", "xpack.ingestManager.configDetails.subTabs.settingsTabText": "设置", - "xpack.ingestManager.configDetails.subTabs.yamlTabText": "YAML", "xpack.ingestManager.configDetails.summary.datasources": "数据源", "xpack.ingestManager.configDetails.summary.lastUpdated": "最后更新时间", "xpack.ingestManager.configDetails.summary.revision": "修订", From 1d86a2abd2ff4e5e726409c4f7bfb9a31f7ed0d3 Mon Sep 17 00:00:00 2001 From: Davis Plumlee <56367316+dplumlee@users.noreply.github.com> Date: Tue, 2 Jun 2020 19:23:15 -0600 Subject: [PATCH 37/50] [SIEM][Exceptions] Add success/error toast component on alert state change (#67406) ## Summary Adds a success and error toast on open or close of alert(s). #65947 Screen Shot 2020-05-26 at 2 28 57 PM Screen Shot 2020-05-28 at 12 01 03 PM ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md) ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process) --- .../alerts/components/signals/actions.tsx | 10 ++++-- .../signals/default_config.test.tsx | 15 ++++++++ .../components/signals/default_config.tsx | 6 ++++ .../alerts/components/signals/index.tsx | 35 +++++++++++++++++++ .../alerts/components/signals/translations.ts | 28 +++++++++++++++ .../public/alerts/components/signals/types.ts | 2 ++ .../detection_engine/signals/api.ts | 3 +- 7 files changed, 95 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/siem/public/alerts/components/signals/actions.tsx b/x-pack/plugins/siem/public/alerts/components/signals/actions.tsx index c13e064bd1c3..f01b39ccaba0 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/actions.tsx +++ b/x-pack/plugins/siem/public/alerts/components/signals/actions.tsx @@ -56,17 +56,21 @@ export const updateSignalStatusAction = async ({ status, setEventsLoading, setEventsDeleted, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }: UpdateSignalStatusActionProps) => { try { setEventsLoading({ eventIds: signalIds, isLoading: true }); const queryObject = query ? { query: JSON.parse(query) } : getUpdateSignalsQuery(signalIds); - await updateSignalStatus({ query: queryObject, status }); + const response = await updateSignalStatus({ query: queryObject, status }); // TODO: Only delete those that were successfully updated from updatedRules setEventsDeleted({ eventIds: signalIds, isDeleted: true }); - } catch (e) { - // TODO: Show error toasts + + onAlertStatusUpdateSuccess(response.updated, status); + } catch (error) { + onAlertStatusUpdateFailure(status, error); } finally { setEventsLoading({ eventIds: signalIds, isLoading: false }); } diff --git a/x-pack/plugins/siem/public/alerts/components/signals/default_config.test.tsx b/x-pack/plugins/siem/public/alerts/components/signals/default_config.test.tsx index 71da68108da7..7821bfaaf957 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/default_config.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/signals/default_config.test.tsx @@ -54,11 +54,16 @@ describe('signals default_config', () => { let createTimeline: CreateTimeline; let updateTimelineIsLoading: UpdateTimelineLoading; + let onAlertStatusUpdateSuccess: (count: number, status: string) => void; + let onAlertStatusUpdateFailure: (status: string, error: Error) => void; + beforeEach(() => { setEventsLoading = jest.fn(); setEventsDeleted = jest.fn(); createTimeline = jest.fn(); updateTimelineIsLoading = jest.fn(); + onAlertStatusUpdateSuccess = jest.fn(); + onAlertStatusUpdateFailure = jest.fn(); }); describe('timeline tooltip', () => { @@ -71,6 +76,8 @@ describe('signals default_config', () => { createTimeline, status: 'open', updateTimelineIsLoading, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }); const timelineAction = signalsActions[0].getAction({ eventId: 'even-id', @@ -97,6 +104,8 @@ describe('signals default_config', () => { createTimeline, status: 'open', updateTimelineIsLoading, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }); signalOpenAction = signalsActions[1].getAction({ @@ -119,6 +128,8 @@ describe('signals default_config', () => { status: 'open', setEventsLoading, setEventsDeleted, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }); }); @@ -151,6 +162,8 @@ describe('signals default_config', () => { createTimeline, status: 'closed', updateTimelineIsLoading, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }); signalCloseAction = signalsActions[1].getAction({ @@ -173,6 +186,8 @@ describe('signals default_config', () => { status: 'closed', setEventsLoading, setEventsDeleted, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }); }); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/default_config.tsx b/x-pack/plugins/siem/public/alerts/components/signals/default_config.tsx index 05e0baba66d0..1269f31064e9 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/default_config.tsx +++ b/x-pack/plugins/siem/public/alerts/components/signals/default_config.tsx @@ -198,6 +198,8 @@ export const getSignalsActions = ({ createTimeline, status, updateTimelineIsLoading, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }: { apolloClient?: ApolloClient<{}>; canUserCRUD: boolean; @@ -207,6 +209,8 @@ export const getSignalsActions = ({ createTimeline: CreateTimeline; status: 'open' | 'closed'; updateTimelineIsLoading: UpdateTimelineLoading; + onAlertStatusUpdateSuccess: (count: number, status: string) => void; + onAlertStatusUpdateFailure: (status: string, error: Error) => void; }): TimelineAction[] => [ { getAction: ({ ecsData }: TimelineActionProps): JSX.Element => ( @@ -246,6 +250,8 @@ export const getSignalsActions = ({ status, setEventsLoading, setEventsDeleted, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }) } isDisabled={!canUserCRUD || !hasIndexWrite} diff --git a/x-pack/plugins/siem/public/alerts/components/signals/index.tsx b/x-pack/plugins/siem/public/alerts/components/signals/index.tsx index eb19cfea9732..effb6a2450dc 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/index.tsx +++ b/x-pack/plugins/siem/public/alerts/components/signals/index.tsx @@ -46,6 +46,11 @@ import { UpdateSignalsStatusProps, } from './types'; import { dispatchUpdateTimeline } from '../../../timelines/components/open_timeline/helpers'; +import { + useStateToaster, + displaySuccessToast, + displayErrorToast, +} from '../../../common/components/toasters'; export const SIGNALS_PAGE_TIMELINE_ID = 'signals-page'; @@ -91,6 +96,7 @@ export const SignalsTableComponent: React.FC = ({ signalsIndex !== '' ? [signalsIndex] : [] ); const kibana = useKibana(); + const [, dispatchToaster] = useStateToaster(); const getGlobalQuery = useCallback(() => { if (browserFields != null && indexPatterns != null) { @@ -146,6 +152,27 @@ export const SignalsTableComponent: React.FC = ({ [setEventsDeleted, SIGNALS_PAGE_TIMELINE_ID] ); + const onAlertStatusUpdateSuccess = useCallback( + (count: number, status: string) => { + const title = + status === 'closed' + ? i18n.CLOSED_ALERT_SUCCESS_TOAST(count) + : i18n.OPENED_ALERT_SUCCESS_TOAST(count); + + displaySuccessToast(title, dispatchToaster); + }, + [dispatchToaster] + ); + + const onAlertStatusUpdateFailure = useCallback( + (status: string, error: Error) => { + const title = + status === 'closed' ? i18n.CLOSED_ALERT_FAILED_TOAST : i18n.OPENED_ALERT_FAILED_TOAST; + displayErrorToast(title, [error.message], dispatchToaster); + }, + [dispatchToaster] + ); + // Catches state change isSelectAllChecked->false upon user selection change to reset utility bar useEffect(() => { if (!isSelectAllChecked) { @@ -189,6 +216,8 @@ export const SignalsTableComponent: React.FC = ({ status, setEventsDeleted: setEventsDeletedCallback, setEventsLoading: setEventsLoadingCallback, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }); refetchQuery(); }, @@ -198,6 +227,8 @@ export const SignalsTableComponent: React.FC = ({ setEventsDeletedCallback, setEventsLoadingCallback, showClearSelectionAction, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, ] ); @@ -244,6 +275,8 @@ export const SignalsTableComponent: React.FC = ({ setEventsDeleted: setEventsDeletedCallback, status: filterGroup === FILTER_OPEN ? FILTER_CLOSED : FILTER_OPEN, updateTimelineIsLoading, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }), [ apolloClient, @@ -254,6 +287,8 @@ export const SignalsTableComponent: React.FC = ({ setEventsLoadingCallback, setEventsDeletedCallback, updateTimelineIsLoading, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, ] ); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/translations.ts b/x-pack/plugins/siem/public/alerts/components/signals/translations.ts index f68dcd932bc3..e49ff9846b9b 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/translations.ts +++ b/x-pack/plugins/siem/public/alerts/components/signals/translations.ts @@ -101,3 +101,31 @@ export const ACTION_INVESTIGATE_IN_TIMELINE = i18n.translate( defaultMessage: 'Investigate in timeline', } ); + +export const CLOSED_ALERT_SUCCESS_TOAST = (totalAlerts: number) => + i18n.translate('xpack.siem.detectionEngine.signals.closedAlertSuccessToastMessage', { + values: { totalAlerts }, + defaultMessage: + 'Successfully closed {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', + }); + +export const OPENED_ALERT_SUCCESS_TOAST = (totalAlerts: number) => + i18n.translate('xpack.siem.detectionEngine.signals.openedAlertSuccessToastMessage', { + values: { totalAlerts }, + defaultMessage: + 'Successfully opened {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', + }); + +export const CLOSED_ALERT_FAILED_TOAST = i18n.translate( + 'xpack.siem.detectionEngine.signals.closedAlertFailedToastMessage', + { + defaultMessage: 'Failed to close alert(s).', + } +); + +export const OPENED_ALERT_FAILED_TOAST = i18n.translate( + 'xpack.siem.detectionEngine.signals.openedAlertFailedToastMessage', + { + defaultMessage: 'Failed to open alert(s)', + } +); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/types.ts b/x-pack/plugins/siem/public/alerts/components/signals/types.ts index b3c770415ed5..542aa61074ce 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/types.ts +++ b/x-pack/plugins/siem/public/alerts/components/signals/types.ts @@ -37,6 +37,8 @@ export interface UpdateSignalStatusActionProps { status: 'open' | 'closed'; setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; + onAlertStatusUpdateSuccess: (count: number, status: string) => void; + onAlertStatusUpdateFailure: (status: string, error: Error) => void; } export type SendSignalsToTimeline = () => void; diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.ts b/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.ts index 860305dd58e6..445f66457c59 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.ts +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ReindexResponse } from 'elasticsearch'; import { DETECTION_ENGINE_QUERY_SIGNALS_URL, DETECTION_ENGINE_SIGNALS_STATUS_URL, @@ -54,7 +55,7 @@ export const updateSignalStatus = async ({ query, status, signal, -}: UpdateSignalStatusProps): Promise => +}: UpdateSignalStatusProps): Promise => KibanaServices.get().http.fetch(DETECTION_ENGINE_SIGNALS_STATUS_URL, { method: 'POST', body: JSON.stringify({ status, ...query }), From d8b66cf7d05b0ae6cc896498c98d70c8176bceb2 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 2 Jun 2020 21:23:37 -0400 Subject: [PATCH 38/50] [Uptime] Switch from `EuiFieldNumber` to `EuiFieldText` on settings page (#66425) * Switch from `EuiFieldNumber` to `EuiFieldText` on settings page. * Add unit tests for cert form fields. * Improve validation. * Add additional server-side settings check with tests. * Only create number object for validation when value is string. * Clean up validation function. Co-authored-by: Elastic Machine --- x-pack/plugins/uptime/common/translations.ts | 6 +- .../__tests__/certificate_form.test.tsx | 108 +++++++++++++++++- .../components/settings/certificate_form.tsx | 16 +-- .../public/pages/__tests__/settings.test.tsx | 35 ++++++ .../plugins/uptime/public/pages/settings.tsx | 18 ++- .../uptime/public/pages/translations.ts | 3 + .../__tests__/dynamic_settings.test.ts | 77 +++++++++++++ .../server/rest_api/dynamic_settings.ts | 17 ++- 8 files changed, 260 insertions(+), 20 deletions(-) create mode 100644 x-pack/plugins/uptime/public/pages/__tests__/settings.test.tsx create mode 100644 x-pack/plugins/uptime/server/rest_api/__tests__/dynamic_settings.test.ts diff --git a/x-pack/plugins/uptime/common/translations.ts b/x-pack/plugins/uptime/common/translations.ts index 678fe7cb1f98..81f46df86f02 100644 --- a/x-pack/plugins/uptime/common/translations.ts +++ b/x-pack/plugins/uptime/common/translations.ts @@ -6,9 +6,13 @@ import { i18n } from '@kbn/i18n'; -export const VALUE_MUST_BE_GREATER_THEN_ZEO = i18n.translate( +export const VALUE_MUST_BE_GREATER_THAN_ZERO = i18n.translate( 'xpack.uptime.settings.invalid.error', { defaultMessage: 'Value must be greater than 0.', } ); + +export const VALUE_MUST_BE_AN_INTEGER = i18n.translate('xpack.uptime.settings.invalid.nanError', { + defaultMessage: 'Value must be an integer.', +}); diff --git a/x-pack/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx b/x-pack/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx index 8e2348a60dda..2b587d23247e 100644 --- a/x-pack/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx +++ b/x-pack/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { CertificateExpirationForm } from '../certificate_form'; -import { shallowWithRouter } from '../../../lib'; +import { shallowWithRouter, mountWithRouter } from '../../../lib'; describe('CertificateForm', () => { it('shallow renders expected elements for valid props', () => { @@ -26,4 +26,110 @@ describe('CertificateForm', () => { ) ).toMatchSnapshot(); }); + + it('submits number values for certs settings fields', () => { + const onChangeMock = jest.fn(); + const wrapper = mountWithRouter( + + ); + + const inputs = wrapper.find('input'); + + expect(inputs).toHaveLength(2); + + // expiration threshold input + inputs.at(0).simulate('change', { + target: { + value: '23', + }, + }); + + // age threshold input + inputs.at(1).simulate('change', { + target: { + value: '56', + }, + }); + + expect(onChangeMock).toHaveBeenCalledTimes(2); + + expect(onChangeMock.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "certExpirationThreshold": 23, + }, + ] + `); + + expect(onChangeMock.mock.calls[1]).toMatchInlineSnapshot(` + Array [ + Object { + "certAgeThreshold": 56, + }, + ] + `); + }); + + it('submits undefined for NaN values', () => { + const onChangeMock = jest.fn(); + const wrapper = mountWithRouter( + + ); + + const inputs = wrapper.find('input'); + + expect(inputs).toHaveLength(2); + + // expiration threshold input + inputs.at(0).simulate('change', { + target: { + value: 'A', + }, + }); + + // age threshold input + inputs.at(1).simulate('change', { + target: { + value: 'g', + }, + }); + + expect(onChangeMock).toHaveBeenCalledTimes(2); + + expect(onChangeMock.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "certExpirationThreshold": undefined, + }, + ] + `); + + expect(onChangeMock.mock.calls[1]).toMatchInlineSnapshot(` + Array [ + Object { + "certAgeThreshold": undefined, + }, + ] + `); + }); }); diff --git a/x-pack/plugins/uptime/public/components/settings/certificate_form.tsx b/x-pack/plugins/uptime/public/components/settings/certificate_form.tsx index 38c0d0663c93..dcc3b9757598 100644 --- a/x-pack/plugins/uptime/public/components/settings/certificate_form.tsx +++ b/x-pack/plugins/uptime/public/components/settings/certificate_form.tsx @@ -10,7 +10,7 @@ import { EuiDescribedFormGroup, EuiFormRow, EuiCode, - EuiFieldNumber, + EuiFieldText, EuiText, EuiTitle, EuiSpacer, @@ -80,17 +80,17 @@ export const CertificateExpirationForm: React.FC = ({ > - onChange({ - certExpirationThreshold: Number(e.target.value), + certExpirationThreshold: Number(e.target.value) || undefined, }) } /> @@ -128,17 +128,17 @@ export const CertificateExpirationForm: React.FC = ({ > - + onChange={({ target: { value } }) => onChange({ - certAgeThreshold: Number(value), + certAgeThreshold: Number(value) || undefined, }) } /> diff --git a/x-pack/plugins/uptime/public/pages/__tests__/settings.test.tsx b/x-pack/plugins/uptime/public/pages/__tests__/settings.test.tsx new file mode 100644 index 000000000000..601feee8f815 --- /dev/null +++ b/x-pack/plugins/uptime/public/pages/__tests__/settings.test.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isValidCertVal } from '../settings'; + +describe('settings', () => { + describe('isValidCertVal', () => { + it('handles NaN values', () => { + expect(isValidCertVal(NaN)).toMatchInlineSnapshot(`"Must be a number."`); + }); + + it('handles undefined', () => { + expect(isValidCertVal(undefined)).toMatchInlineSnapshot(`"Must be a number."`); + }); + + it('handles non-integer numbers', () => { + expect(isValidCertVal(23.5)).toMatchInlineSnapshot(`"Value must be an integer."`); + }); + + it('handles values less than 0', () => { + expect(isValidCertVal(-1)).toMatchInlineSnapshot(`"Value must be greater than 0."`); + }); + + it('handles 0', () => { + expect(isValidCertVal(0)).toMatchInlineSnapshot(`"Value must be greater than 0."`); + }); + + it('allows valid integer numbers', () => { + expect(isValidCertVal(67)).toBeUndefined(); + }); + }); +}); diff --git a/x-pack/plugins/uptime/public/pages/settings.tsx b/x-pack/plugins/uptime/public/pages/settings.tsx index 807da8382126..6285c0cc0f1d 100644 --- a/x-pack/plugins/uptime/public/pages/settings.tsx +++ b/x-pack/plugins/uptime/public/pages/settings.tsx @@ -31,7 +31,10 @@ import { OnFieldChangeType, } from '../components/settings/certificate_form'; import * as Translations from './translations'; -import { VALUE_MUST_BE_GREATER_THEN_ZEO } from '../../common/translations'; +import { + VALUE_MUST_BE_GREATER_THAN_ZERO, + VALUE_MUST_BE_AN_INTEGER, +} from '../../common/translations'; interface SettingsPageFieldErrors { heartbeatIndices: string | ''; @@ -47,12 +50,15 @@ export interface SettingsFormProps { isDisabled: boolean; } -const isValidCertVal = (val: string | number) => { - if (val === '') { - return Translations.BLANK_STR; +export const isValidCertVal = (val?: number): string | undefined => { + if (val === undefined || isNaN(val)) { + return Translations.settings.mustBeNumber; + } + if (val <= 0) { + return VALUE_MUST_BE_GREATER_THAN_ZERO; } - if (val === 0) { - return VALUE_MUST_BE_GREATER_THEN_ZEO; + if (val % 1) { + return VALUE_MUST_BE_AN_INTEGER; } }; diff --git a/x-pack/plugins/uptime/public/pages/translations.ts b/x-pack/plugins/uptime/public/pages/translations.ts index 8ed550323588..dae4760edc46 100644 --- a/x-pack/plugins/uptime/public/pages/translations.ts +++ b/x-pack/plugins/uptime/public/pages/translations.ts @@ -35,6 +35,9 @@ export const settings = { returnToOverviewLinkLabel: i18n.translate('xpack.uptime.settings.returnToOverviewLinkLabel', { defaultMessage: 'Return to overview', }), + mustBeNumber: i18n.translate('xpack.uptime.settings.blankNumberField.error', { + defaultMessage: 'Must be a number.', + }), }; export const BLANK_STR = i18n.translate('xpack.uptime.settings.blank.error', { diff --git a/x-pack/plugins/uptime/server/rest_api/__tests__/dynamic_settings.test.ts b/x-pack/plugins/uptime/server/rest_api/__tests__/dynamic_settings.test.ts new file mode 100644 index 000000000000..32e6385bc9fa --- /dev/null +++ b/x-pack/plugins/uptime/server/rest_api/__tests__/dynamic_settings.test.ts @@ -0,0 +1,77 @@ +/* + * 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 { validateCertsValues } from '../dynamic_settings'; + +describe('dynamic settings', () => { + describe('validateCertValues', () => { + it(`doesn't allow age threshold values less than 0`, () => { + expect( + validateCertsValues({ + certAgeThreshold: -1, + certExpirationThreshold: 2, + heartbeatIndices: 'foo', + }) + ).toMatchInlineSnapshot(` + Object { + "certAgeThreshold": "Value must be greater than 0.", + } + `); + }); + + it(`doesn't allow non-integer age threshold values`, () => { + expect( + validateCertsValues({ + certAgeThreshold: 10.2, + certExpirationThreshold: 2, + heartbeatIndices: 'foo', + }) + ).toMatchInlineSnapshot(` + Object { + "certAgeThreshold": "Value must be an integer.", + } + `); + }); + + it(`doesn't allow expiration threshold values less than 0`, () => { + expect( + validateCertsValues({ + certAgeThreshold: 2, + certExpirationThreshold: -1, + heartbeatIndices: 'foo', + }) + ).toMatchInlineSnapshot(` + Object { + "certExpirationThreshold": "Value must be greater than 0.", + } + `); + }); + + it(`doesn't allow non-integer expiration threshold values`, () => { + expect( + validateCertsValues({ + certAgeThreshold: 2, + certExpirationThreshold: 1.23, + heartbeatIndices: 'foo', + }) + ).toMatchInlineSnapshot(` + Object { + "certExpirationThreshold": "Value must be an integer.", + } + `); + }); + + it('allows valid values', () => { + expect( + validateCertsValues({ + certAgeThreshold: 2, + certExpirationThreshold: 13, + heartbeatIndices: 'foo', + }) + ).toBeUndefined(); + }); + }); +}); diff --git a/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts b/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts index c7d532d932aa..6dba36ec0613 100644 --- a/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts +++ b/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts @@ -11,7 +11,10 @@ import { UMServerLibs } from '../lib/lib'; import { DynamicSettings, DynamicSettingsType } from '../../common/runtime_types'; import { UMRestApiRouteFactory } from '.'; import { savedObjectsAdapter } from '../lib/saved_objects'; -import { VALUE_MUST_BE_GREATER_THEN_ZEO } from '../../common/translations'; +import { + VALUE_MUST_BE_GREATER_THAN_ZERO, + VALUE_MUST_BE_AN_INTEGER, +} from '../../common/translations'; export const createGetDynamicSettingsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ method: 'GET', @@ -24,13 +27,19 @@ export const createGetDynamicSettingsRoute: UMRestApiRouteFactory = (libs: UMSer }, }); -const validateCertsValues = (settings: DynamicSettings) => { +export const validateCertsValues = ( + settings: DynamicSettings +): Record | undefined => { const errors: any = {}; if (settings.certAgeThreshold <= 0) { - errors.certAgeThreshold = VALUE_MUST_BE_GREATER_THEN_ZEO; + errors.certAgeThreshold = VALUE_MUST_BE_GREATER_THAN_ZERO; + } else if (settings.certAgeThreshold % 1) { + errors.certAgeThreshold = VALUE_MUST_BE_AN_INTEGER; } if (settings.certExpirationThreshold <= 0) { - errors.certExpirationThreshold = VALUE_MUST_BE_GREATER_THEN_ZEO; + errors.certExpirationThreshold = VALUE_MUST_BE_GREATER_THAN_ZERO; + } else if (settings.certExpirationThreshold % 1) { + errors.certExpirationThreshold = VALUE_MUST_BE_AN_INTEGER; } if (errors.certAgeThreshold || errors.certExpirationThreshold) { return errors; From fdd0c47e75f80b34e3c2e50b6ce6186cb8bc0abe Mon Sep 17 00:00:00 2001 From: spalger Date: Tue, 2 Jun 2020 19:42:03 -0700 Subject: [PATCH 39/50] skip flaky suite (#53749) --- x-pack/test/functional/apps/graph/graph.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/graph/graph.ts b/x-pack/test/functional/apps/graph/graph.ts index d9e8b22c45d5..803e5e8f80d7 100644 --- a/x-pack/test/functional/apps/graph/graph.ts +++ b/x-pack/test/functional/apps/graph/graph.ts @@ -13,7 +13,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const browser = getService('browser'); - describe('graph', function () { + // FLAKY: https://github.com/elastic/kibana/issues/53749 + describe.skip('graph', function () { before(async () => { await browser.setWindowSize(1600, 1000); log.debug('load graph/secrepo data'); From 4c30d912a9c08079c8caf96f73a16f531c125a5f Mon Sep 17 00:00:00 2001 From: Garrett Spong Date: Tue, 2 Jun 2020 21:50:16 -0600 Subject: [PATCH 40/50] [SIEM] [Detections] Renames Signals to Alerts (#67731) ## Summary Resolves https://github.com/elastic/kibana/issues/65944 Renames `Signals` -> `Alerts` on the main Detection Engine page. Including: * Timeline Event Selector * Alerts Histogram * Alerts Table Does not include: * `Detections` -> `Alerts` navigation rename * `SignalsByCategory` rename as there already exists an `AlertsByCategory`, verify changing to `ExternalAlertsByCategory` * Anything server-side or related to `siemSignalsIndex` ### Checklist - [X] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md) - [X] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios --- x-pack/plugins/siem/cypress/README.md | 8 +- .../cypress/integration/detections.spec.ts | 231 +++++++++--------- .../integration/detections_timeline.spec.ts | 22 +- .../signal_detection_rules.spec.ts | 18 +- .../signal_detection_rules_custom.spec.ts | 24 +- .../signal_detection_rules_export.spec.ts | 14 +- .../signal_detection_rules_ml.spec.ts | 18 +- .../signal_detection_rules_prebuilt.spec.ts | 24 +- ...tion_rules.ts => alert_detection_rules.ts} | 0 .../siem/cypress/screens/detections.ts | 28 +-- ...tion_rules.ts => alert_detection_rules.ts} | 2 +- .../plugins/siem/cypress/tasks/detections.ts | 78 +++--- .../alerts_histogram.test.tsx} | 6 +- .../alerts_histogram.tsx} | 14 +- .../config.ts | 4 +- .../helpers.test.tsx | 8 +- .../helpers.tsx | 34 ++- .../index.test.tsx | 6 +- .../index.tsx | 106 ++++---- .../translations.ts | 48 ++-- .../types.ts | 18 +- .../alerts/components/alerts_info/index.tsx | 49 ++++ .../query.dsl.ts | 2 +- .../{signals_info => alerts_info}/types.ts | 0 .../actions.test.tsx | 44 ++-- .../{signals => alerts_table}/actions.tsx | 38 +-- .../alerts_filter_group}/index.test.tsx | 6 +- .../alerts_filter_group}/index.tsx | 16 +- .../alerts_utility_bar}/index.test.tsx | 10 +- .../alerts_utility_bar}/index.tsx | 36 +-- .../alerts_utility_bar/translations.ts | 77 ++++++ .../default_config.test.tsx | 80 +++--- .../default_config.tsx | 50 ++-- .../{signals => alerts_table}/helpers.test.ts | 0 .../{signals => alerts_table}/helpers.ts | 2 +- .../{signals => alerts_table}/index.test.tsx | 8 +- .../{signals => alerts_table}/index.tsx | 94 +++---- .../components/alerts_table/translations.ts | 128 ++++++++++ .../{signals => alerts_table}/types.ts | 18 +- .../translations.ts | 2 +- .../index.test.tsx | 6 +- .../index.tsx | 8 +- .../translations.ts | 14 +- .../rules/pre_packaged_rules/translations.ts | 2 +- .../rules/rule_actions_field/index.tsx | 2 +- .../rules/step_about_rule/schema.tsx | 2 +- .../rules/step_define_rule/schema.tsx | 2 +- .../rules/step_schedule_rule/schema.tsx | 5 +- .../signals_utility_bar/translations.ts | 77 ------ .../alerts/components/signals/translations.ts | 131 ---------- .../alerts/components/signals_info/index.tsx | 49 ---- .../components/user_info/index.test.tsx | 8 +- .../alerts/components/user_info/index.tsx | 4 +- .../{signals => alerts}/__mocks__/api.ts | 20 +- .../{signals => alerts}/api.test.ts | 58 ++--- .../{signals => alerts}/api.ts | 34 +-- .../{signals => alerts}/mock.ts | 18 +- .../{signals => alerts}/translations.ts | 14 +- .../{signals => alerts}/types.ts | 12 +- .../use_privilege_user.test.tsx | 0 .../use_privilege_user.tsx | 0 .../{signals => alerts}/use_query.test.tsx | 62 ++--- .../{signals => alerts}/use_query.tsx | 30 +-- .../use_signal_index.test.tsx | 4 +- .../{signals => alerts}/use_signal_index.tsx | 0 .../detection_engine/detection_engine.tsx | 116 +++------ .../alerts/pages/detection_engine/index.tsx | 9 +- .../rules/create/helpers.test.ts | 2 +- .../rules/create/translations.ts | 2 +- .../detection_engine/rules/details/index.tsx | 52 ++-- .../rules/details/translations.ts | 2 +- .../pages/detection_engine/rules/index.tsx | 2 +- .../detection_engine/rules/translations.ts | 11 +- .../detection_engine/rules/utils.test.ts | 2 +- .../pages/detection_engine/rules/utils.ts | 7 - .../pages/detection_engine/translations.ts | 16 +- .../alerts/pages/detection_engine/types.ts | 10 - .../components/drag_and_drop/helpers.ts | 2 +- .../common/components/link_to/link_to.tsx | 6 - .../link_to/redirect_to_detection_engine.tsx | 11 +- .../popover_description.test.tsx.snap | 2 +- .../ml_popover/popover_description.tsx | 2 +- .../common/components/top_n/helpers.test.tsx | 6 +- .../public/common/components/top_n/helpers.ts | 16 +- .../common/components/top_n/index.test.tsx | 6 +- .../public/common/components/top_n/index.tsx | 10 +- .../common/components/top_n/top_n.test.tsx | 10 +- .../common/components/top_n/translations.ts | 4 +- .../siem/public/common/mock/mock_ecs.ts | 2 +- .../public/common/mock/timeline_results.ts | 4 +- .../components/alerts_by_category/index.tsx | 11 +- .../components/signals_by_category/index.tsx | 18 +- .../public/overview/pages/translations.ts | 4 +- .../components/fields_browser/header.tsx | 10 +- .../timelines/components/timeline/index.tsx | 2 +- .../timeline/search_or_filter/pick_events.tsx | 2 +- .../public/timelines/containers/index.tsx | 4 +- .../public/timelines/store/timeline/model.ts | 2 +- .../routes/__mocks__/request_responses.ts | 2 +- .../lib/matrix_histogram/translations.ts | 2 +- .../translations/translations/ja-JP.json | 51 ---- .../translations/translations/zh-CN.json | 51 ---- .../{signals => alerts}/data.json.gz | Bin .../{signals => alerts}/mappings.json | 0 .../data.json.gz | Bin .../mappings.json | 0 .../data.json.gz | Bin .../mappings.json | 0 108 files changed, 1099 insertions(+), 1305 deletions(-) rename x-pack/plugins/siem/cypress/screens/{signal_detection_rules.ts => alert_detection_rules.ts} (100%) rename x-pack/plugins/siem/cypress/tasks/{signal_detection_rules.ts => alert_detection_rules.ts} (98%) rename x-pack/plugins/siem/public/alerts/components/{signals_histogram_panel/signals_histogram.test.tsx => alerts_histogram_panel/alerts_histogram.test.tsx} (84%) rename x-pack/plugins/siem/public/alerts/components/{signals_histogram_panel/signals_histogram.tsx => alerts_histogram_panel/alerts_histogram.tsx} (89%) rename x-pack/plugins/siem/public/alerts/components/{signals_histogram_panel => alerts_histogram_panel}/config.ts (88%) rename x-pack/plugins/siem/public/alerts/components/{signals_histogram_panel => alerts_histogram_panel}/helpers.test.tsx (89%) rename x-pack/plugins/siem/public/alerts/components/{signals_histogram_panel => alerts_histogram_panel}/helpers.tsx (67%) rename x-pack/plugins/siem/public/alerts/components/{signals_histogram_panel => alerts_histogram_panel}/index.test.tsx (85%) rename x-pack/plugins/siem/public/alerts/components/{signals_histogram_panel => alerts_histogram_panel}/index.tsx (72%) rename x-pack/plugins/siem/public/alerts/components/{signals_histogram_panel => alerts_histogram_panel}/translations.ts (50%) rename x-pack/plugins/siem/public/alerts/components/{signals_histogram_panel => alerts_histogram_panel}/types.ts (70%) create mode 100644 x-pack/plugins/siem/public/alerts/components/alerts_info/index.tsx rename x-pack/plugins/siem/public/alerts/components/{signals_info => alerts_info}/query.dsl.ts (91%) rename x-pack/plugins/siem/public/alerts/components/{signals_info => alerts_info}/types.ts (100%) rename x-pack/plugins/siem/public/alerts/components/{signals => alerts_table}/actions.test.tsx (92%) rename x-pack/plugins/siem/public/alerts/components/{signals => alerts_table}/actions.tsx (82%) rename x-pack/plugins/siem/public/alerts/components/{signals/signals_filter_group => alerts_table/alerts_filter_group}/index.test.tsx (68%) rename x-pack/plugins/siem/public/alerts/components/{signals/signals_filter_group => alerts_table/alerts_filter_group}/index.tsx (74%) rename x-pack/plugins/siem/public/alerts/components/{signals/signals_utility_bar => alerts_table/alerts_utility_bar}/index.test.tsx (76%) rename x-pack/plugins/siem/public/alerts/components/{signals/signals_utility_bar => alerts_table/alerts_utility_bar}/index.tsx (77%) create mode 100644 x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/translations.ts rename x-pack/plugins/siem/public/alerts/components/{signals => alerts_table}/default_config.test.tsx (67%) rename x-pack/plugins/siem/public/alerts/components/{signals => alerts_table}/default_config.tsx (81%) rename x-pack/plugins/siem/public/alerts/components/{signals => alerts_table}/helpers.test.ts (100%) rename x-pack/plugins/siem/public/alerts/components/{signals => alerts_table}/helpers.ts (98%) rename x-pack/plugins/siem/public/alerts/components/{signals => alerts_table}/index.test.tsx (85%) rename x-pack/plugins/siem/public/alerts/components/{signals => alerts_table}/index.tsx (81%) create mode 100644 x-pack/plugins/siem/public/alerts/components/alerts_table/translations.ts rename x-pack/plugins/siem/public/alerts/components/{signals => alerts_table}/types.ts (77%) rename x-pack/plugins/siem/public/alerts/components/{no_write_signals_callout => no_write_alerts_callout}/index.test.tsx (72%) rename x-pack/plugins/siem/public/alerts/components/{no_write_signals_callout => no_write_alerts_callout}/index.tsx (72%) rename x-pack/plugins/siem/public/alerts/components/{no_write_signals_callout => no_write_alerts_callout}/translations.ts (52%) delete mode 100644 x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/translations.ts delete mode 100644 x-pack/plugins/siem/public/alerts/components/signals/translations.ts delete mode 100644 x-pack/plugins/siem/public/alerts/components/signals_info/index.tsx rename x-pack/plugins/siem/public/alerts/containers/detection_engine/{signals => alerts}/__mocks__/api.ts (56%) rename x-pack/plugins/siem/public/alerts/containers/detection_engine/{signals => alerts}/api.test.ts (68%) rename x-pack/plugins/siem/public/alerts/containers/detection_engine/{signals => alerts}/api.ts (72%) rename x-pack/plugins/siem/public/alerts/containers/detection_engine/{signals => alerts}/mock.ts (98%) rename x-pack/plugins/siem/public/alerts/containers/detection_engine/{signals => alerts}/translations.ts (55%) rename x-pack/plugins/siem/public/alerts/containers/detection_engine/{signals => alerts}/types.ts (88%) rename x-pack/plugins/siem/public/alerts/containers/detection_engine/{signals => alerts}/use_privilege_user.test.tsx (100%) rename x-pack/plugins/siem/public/alerts/containers/detection_engine/{signals => alerts}/use_privilege_user.tsx (100%) rename x-pack/plugins/siem/public/alerts/containers/detection_engine/{signals => alerts}/use_query.test.tsx (61%) rename x-pack/plugins/siem/public/alerts/containers/detection_engine/{signals => alerts}/use_query.tsx (69%) rename x-pack/plugins/siem/public/alerts/containers/detection_engine/{signals => alerts}/use_signal_index.test.tsx (96%) rename x-pack/plugins/siem/public/alerts/containers/detection_engine/{signals => alerts}/use_signal_index.tsx (100%) delete mode 100644 x-pack/plugins/siem/public/alerts/pages/detection_engine/types.ts rename x-pack/test/siem_cypress/es_archives/{signals => alerts}/data.json.gz (100%) rename x-pack/test/siem_cypress/es_archives/{signals => alerts}/mappings.json (100%) rename x-pack/test/siem_cypress/es_archives/{closed_signals => closed_alerts}/data.json.gz (100%) rename x-pack/test/siem_cypress/es_archives/{closed_signals => closed_alerts}/mappings.json (100%) rename x-pack/test/siem_cypress/es_archives/{timeline_signals => timeline_alerts}/data.json.gz (100%) rename x-pack/test/siem_cypress/es_archives/{timeline_signals => timeline_alerts}/mappings.json (100%) diff --git a/x-pack/plugins/siem/cypress/README.md b/x-pack/plugins/siem/cypress/README.md index d84c66fec1c3..b50924532726 100644 --- a/x-pack/plugins/siem/cypress/README.md +++ b/x-pack/plugins/siem/cypress/README.md @@ -176,16 +176,16 @@ The current archives can be found in `x-pack/test/siem_cypress/es_archives/`. - siem-kibana - siem-es - jessie -- closed_signals - - Set of data with 108 closed signals linked to "Signals test" custom rule. +- closed_alerts + - Set of data with 108 closed alerts linked to "Alerts test" custom rule. - custome_rules - Set if data with just 4 custom activated rules. - empty_kibana - Empty kibana board. - prebuilt_rules_loaded - Elastic prebuilt loaded rules and deactivated. -- signals - - Set of data with 108 opened signals linked to "Signals test" custom rule. +- alerts + - Set of data with 108 opened alerts linked to "Alerts test" custom rule. ### How to generate a new archive diff --git a/x-pack/plugins/siem/cypress/integration/detections.spec.ts b/x-pack/plugins/siem/cypress/integration/detections.spec.ts index 91727595708f..23e84070e93a 100644 --- a/x-pack/plugins/siem/cypress/integration/detections.spec.ts +++ b/x-pack/plugins/siem/cypress/integration/detections.spec.ts @@ -4,24 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ import { - NUMBER_OF_SIGNALS, - OPEN_CLOSE_SIGNALS_BTN, - SELECTED_SIGNALS, - SHOWING_SIGNALS, - SIGNALS, + NUMBER_OF_ALERTS, + OPEN_CLOSE_ALERTS_BTN, + SELECTED_ALERTS, + SHOWING_ALERTS, + ALERTS, } from '../screens/detections'; import { - closeFirstSignal, - closeSignals, - goToClosedSignals, - goToOpenedSignals, - openFirstSignal, - openSignals, - selectNumberOfSignals, - waitForSignalsPanelToBeLoaded, - waitForSignals, - waitForSignalsToBeLoaded, + closeFirstAlert, + closeAlerts, + goToClosedAlerts, + goToOpenedAlerts, + openFirstAlert, + openAlerts, + selectNumberOfAlerts, + waitForAlertsPanelToBeLoaded, + waitForAlerts, + waitForAlertsToBeLoaded, } from '../tasks/detections'; import { esArchiverLoad } from '../tasks/es_archiver'; import { loginAndWaitForPage } from '../tasks/login'; @@ -29,179 +29,176 @@ import { loginAndWaitForPage } from '../tasks/login'; import { DETECTIONS } from '../urls/navigation'; describe('Detections', () => { - context('Closing signals', () => { + context('Closing alerts', () => { beforeEach(() => { - esArchiverLoad('signals'); + esArchiverLoad('alerts'); loginAndWaitForPage(DETECTIONS); }); - it('Closes and opens signals', () => { - waitForSignalsPanelToBeLoaded(); - waitForSignalsToBeLoaded(); + it('Closes and opens alerts', () => { + waitForAlertsPanelToBeLoaded(); + waitForAlertsToBeLoaded(); - cy.get(NUMBER_OF_SIGNALS) + cy.get(NUMBER_OF_ALERTS) .invoke('text') - .then((numberOfSignals) => { - cy.get(SHOWING_SIGNALS).should('have.text', `Showing ${numberOfSignals} signals`); + .then((numberOfAlerts) => { + cy.get(SHOWING_ALERTS).should('have.text', `Showing ${numberOfAlerts} alerts`); - const numberOfSignalsToBeClosed = 3; - selectNumberOfSignals(numberOfSignalsToBeClosed); + const numberOfAlertsToBeClosed = 3; + selectNumberOfAlerts(numberOfAlertsToBeClosed); - cy.get(SELECTED_SIGNALS).should( + cy.get(SELECTED_ALERTS).should( 'have.text', - `Selected ${numberOfSignalsToBeClosed} signals` + `Selected ${numberOfAlertsToBeClosed} alerts` ); - closeSignals(); - waitForSignals(); + closeAlerts(); + waitForAlerts(); cy.reload(); - waitForSignals(); + waitForAlerts(); - const expectedNumberOfSignalsAfterClosing = +numberOfSignals - numberOfSignalsToBeClosed; - cy.get(NUMBER_OF_SIGNALS).should( + const expectedNumberOfAlertsAfterClosing = +numberOfAlerts - numberOfAlertsToBeClosed; + cy.get(NUMBER_OF_ALERTS).should( 'have.text', - expectedNumberOfSignalsAfterClosing.toString() + expectedNumberOfAlertsAfterClosing.toString() ); - cy.get(SHOWING_SIGNALS).should( + cy.get(SHOWING_ALERTS).should( 'have.text', - `Showing ${expectedNumberOfSignalsAfterClosing.toString()} signals` + `Showing ${expectedNumberOfAlertsAfterClosing.toString()} alerts` ); - goToClosedSignals(); - waitForSignals(); + goToClosedAlerts(); + waitForAlerts(); - cy.get(NUMBER_OF_SIGNALS).should('have.text', numberOfSignalsToBeClosed.toString()); - cy.get(SHOWING_SIGNALS).should( + cy.get(NUMBER_OF_ALERTS).should('have.text', numberOfAlertsToBeClosed.toString()); + cy.get(SHOWING_ALERTS).should( 'have.text', - `Showing ${numberOfSignalsToBeClosed.toString()} signals` + `Showing ${numberOfAlertsToBeClosed.toString()} alerts` ); - cy.get(SIGNALS).should('have.length', numberOfSignalsToBeClosed); + cy.get(ALERTS).should('have.length', numberOfAlertsToBeClosed); - const numberOfSignalsToBeOpened = 1; - selectNumberOfSignals(numberOfSignalsToBeOpened); + const numberOfAlertsToBeOpened = 1; + selectNumberOfAlerts(numberOfAlertsToBeOpened); - cy.get(SELECTED_SIGNALS).should( - 'have.text', - `Selected ${numberOfSignalsToBeOpened} signal` - ); + cy.get(SELECTED_ALERTS).should('have.text', `Selected ${numberOfAlertsToBeOpened} alert`); - openSignals(); - waitForSignals(); + openAlerts(); + waitForAlerts(); cy.reload(); - waitForSignalsToBeLoaded(); - waitForSignals(); - goToClosedSignals(); - waitForSignals(); + waitForAlertsToBeLoaded(); + waitForAlerts(); + goToClosedAlerts(); + waitForAlerts(); - const expectedNumberOfClosedSignalsAfterOpened = 2; - cy.get(NUMBER_OF_SIGNALS).should( + const expectedNumberOfClosedAlertsAfterOpened = 2; + cy.get(NUMBER_OF_ALERTS).should( 'have.text', - expectedNumberOfClosedSignalsAfterOpened.toString() + expectedNumberOfClosedAlertsAfterOpened.toString() ); - cy.get(SHOWING_SIGNALS).should( + cy.get(SHOWING_ALERTS).should( 'have.text', - `Showing ${expectedNumberOfClosedSignalsAfterOpened.toString()} signals` + `Showing ${expectedNumberOfClosedAlertsAfterOpened.toString()} alerts` ); - cy.get(SIGNALS).should('have.length', expectedNumberOfClosedSignalsAfterOpened); + cy.get(ALERTS).should('have.length', expectedNumberOfClosedAlertsAfterOpened); - goToOpenedSignals(); - waitForSignals(); + goToOpenedAlerts(); + waitForAlerts(); - const expectedNumberOfOpenedSignals = - +numberOfSignals - expectedNumberOfClosedSignalsAfterOpened; - cy.get(SHOWING_SIGNALS).should( + const expectedNumberOfOpenedAlerts = + +numberOfAlerts - expectedNumberOfClosedAlertsAfterOpened; + cy.get(SHOWING_ALERTS).should( 'have.text', - `Showing ${expectedNumberOfOpenedSignals.toString()} signals` + `Showing ${expectedNumberOfOpenedAlerts.toString()} alerts` ); cy.get('[data-test-subj="server-side-event-count"]').should( 'have.text', - expectedNumberOfOpenedSignals.toString() + expectedNumberOfOpenedAlerts.toString() ); }); }); - it('Closes one signal when more than one opened signals are selected', () => { - waitForSignalsToBeLoaded(); + it('Closes one alert when more than one opened alerts are selected', () => { + waitForAlertsToBeLoaded(); - cy.get(NUMBER_OF_SIGNALS) + cy.get(NUMBER_OF_ALERTS) .invoke('text') - .then((numberOfSignals) => { - const numberOfSignalsToBeClosed = 1; - const numberOfSignalsToBeSelected = 3; + .then((numberOfAlerts) => { + const numberOfAlertsToBeClosed = 1; + const numberOfAlertsToBeSelected = 3; - cy.get(OPEN_CLOSE_SIGNALS_BTN).should('have.attr', 'disabled'); - selectNumberOfSignals(numberOfSignalsToBeSelected); - cy.get(OPEN_CLOSE_SIGNALS_BTN).should('not.have.attr', 'disabled'); + cy.get(OPEN_CLOSE_ALERTS_BTN).should('have.attr', 'disabled'); + selectNumberOfAlerts(numberOfAlertsToBeSelected); + cy.get(OPEN_CLOSE_ALERTS_BTN).should('not.have.attr', 'disabled'); - closeFirstSignal(); + closeFirstAlert(); cy.reload(); - waitForSignalsToBeLoaded(); - waitForSignals(); + waitForAlertsToBeLoaded(); + waitForAlerts(); - const expectedNumberOfSignals = +numberOfSignals - numberOfSignalsToBeClosed; - cy.get(NUMBER_OF_SIGNALS).invoke('text').should('eq', expectedNumberOfSignals.toString()); - cy.get(SHOWING_SIGNALS) + const expectedNumberOfAlerts = +numberOfAlerts - numberOfAlertsToBeClosed; + cy.get(NUMBER_OF_ALERTS).invoke('text').should('eq', expectedNumberOfAlerts.toString()); + cy.get(SHOWING_ALERTS) .invoke('text') - .should('eql', `Showing ${expectedNumberOfSignals.toString()} signals`); + .should('eql', `Showing ${expectedNumberOfAlerts.toString()} alerts`); - goToClosedSignals(); - waitForSignals(); + goToClosedAlerts(); + waitForAlerts(); - cy.get(NUMBER_OF_SIGNALS) + cy.get(NUMBER_OF_ALERTS) .invoke('text') - .should('eql', numberOfSignalsToBeClosed.toString()); - cy.get(SHOWING_SIGNALS) + .should('eql', numberOfAlertsToBeClosed.toString()); + cy.get(SHOWING_ALERTS) .invoke('text') - .should('eql', `Showing ${numberOfSignalsToBeClosed.toString()} signal`); - cy.get(SIGNALS).should('have.length', numberOfSignalsToBeClosed); + .should('eql', `Showing ${numberOfAlertsToBeClosed.toString()} alert`); + cy.get(ALERTS).should('have.length', numberOfAlertsToBeClosed); }); }); }); - context('Opening signals', () => { + context('Opening alerts', () => { beforeEach(() => { - esArchiverLoad('closed_signals'); + esArchiverLoad('closed_alerts'); loginAndWaitForPage(DETECTIONS); }); - it('Open one signal when more than one closed signals are selected', () => { - waitForSignals(); - goToClosedSignals(); - waitForSignalsToBeLoaded(); + it('Open one alert when more than one closed alerts are selected', () => { + waitForAlerts(); + goToClosedAlerts(); + waitForAlertsToBeLoaded(); - cy.get(NUMBER_OF_SIGNALS) + cy.get(NUMBER_OF_ALERTS) .invoke('text') - .then((numberOfSignals) => { - const numberOfSignalsToBeOpened = 1; - const numberOfSignalsToBeSelected = 3; + .then((numberOfAlerts) => { + const numberOfAlertsToBeOpened = 1; + const numberOfAlertsToBeSelected = 3; - cy.get(OPEN_CLOSE_SIGNALS_BTN).should('have.attr', 'disabled'); - selectNumberOfSignals(numberOfSignalsToBeSelected); - cy.get(OPEN_CLOSE_SIGNALS_BTN).should('not.have.attr', 'disabled'); + cy.get(OPEN_CLOSE_ALERTS_BTN).should('have.attr', 'disabled'); + selectNumberOfAlerts(numberOfAlertsToBeSelected); + cy.get(OPEN_CLOSE_ALERTS_BTN).should('not.have.attr', 'disabled'); - openFirstSignal(); + openFirstAlert(); cy.reload(); - goToClosedSignals(); - waitForSignalsToBeLoaded(); - waitForSignals(); + goToClosedAlerts(); + waitForAlertsToBeLoaded(); + waitForAlerts(); - const expectedNumberOfSignals = +numberOfSignals - numberOfSignalsToBeOpened; - cy.get(NUMBER_OF_SIGNALS).invoke('text').should('eq', expectedNumberOfSignals.toString()); - cy.get(SHOWING_SIGNALS) + const expectedNumberOfAlerts = +numberOfAlerts - numberOfAlertsToBeOpened; + cy.get(NUMBER_OF_ALERTS).invoke('text').should('eq', expectedNumberOfAlerts.toString()); + cy.get(SHOWING_ALERTS) .invoke('text') - .should('eql', `Showing ${expectedNumberOfSignals.toString()} signals`); + .should('eql', `Showing ${expectedNumberOfAlerts.toString()} alerts`); - goToOpenedSignals(); - waitForSignals(); + goToOpenedAlerts(); + waitForAlerts(); - cy.get(NUMBER_OF_SIGNALS) + cy.get(NUMBER_OF_ALERTS) .invoke('text') - .should('eql', numberOfSignalsToBeOpened.toString()); - cy.get(SHOWING_SIGNALS) + .should('eql', numberOfAlertsToBeOpened.toString()); + cy.get(SHOWING_ALERTS) .invoke('text') - .should('eql', `Showing ${numberOfSignalsToBeOpened.toString()} signal`); - cy.get(SIGNALS).should('have.length', numberOfSignalsToBeOpened); + .should('eql', `Showing ${numberOfAlertsToBeOpened.toString()} alert`); + cy.get(ALERTS).should('have.length', numberOfAlertsToBeOpened); }); }); }); diff --git a/x-pack/plugins/siem/cypress/integration/detections_timeline.spec.ts b/x-pack/plugins/siem/cypress/integration/detections_timeline.spec.ts index 6ea34f5203ad..d3ddb2ad71e3 100644 --- a/x-pack/plugins/siem/cypress/integration/detections_timeline.spec.ts +++ b/x-pack/plugins/siem/cypress/integration/detections_timeline.spec.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SIGNAL_ID } from '../screens/detections'; +import { ALERT_ID } from '../screens/detections'; import { PROVIDER_BADGE } from '../screens/timeline'; import { - expandFirstSignal, - investigateFirstSignalInTimeline, - waitForSignalsPanelToBeLoaded, + expandFirstAlert, + investigateFirstAlertInTimeline, + waitForAlertsPanelToBeLoaded, } from '../tasks/detections'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPage } from '../tasks/login'; @@ -19,22 +19,22 @@ import { DETECTIONS } from '../urls/navigation'; describe('Detections timeline', () => { beforeEach(() => { - esArchiverLoad('timeline_signals'); + esArchiverLoad('timeline_alerts'); loginAndWaitForPage(DETECTIONS); }); afterEach(() => { - esArchiverUnload('timeline_signals'); + esArchiverUnload('timeline_alerts'); }); - it('Investigate signal in default timeline', () => { - waitForSignalsPanelToBeLoaded(); - expandFirstSignal(); - cy.get(SIGNAL_ID) + it('Investigate alert in default timeline', () => { + waitForAlertsPanelToBeLoaded(); + expandFirstAlert(); + cy.get(ALERT_ID) .first() .invoke('text') .then((eventId) => { - investigateFirstSignalInTimeline(); + investigateFirstAlertInTimeline(); cy.get(PROVIDER_BADGE).invoke('text').should('eql', `_id: "${eventId}"`); }); }); diff --git a/x-pack/plugins/siem/cypress/integration/signal_detection_rules.spec.ts b/x-pack/plugins/siem/cypress/integration/signal_detection_rules.spec.ts index d07850e23f05..e8f9411c149d 100644 --- a/x-pack/plugins/siem/cypress/integration/signal_detection_rules.spec.ts +++ b/x-pack/plugins/siem/cypress/integration/signal_detection_rules.spec.ts @@ -10,12 +10,12 @@ import { RULE_SWITCH, SECOND_RULE, SEVENTH_RULE, -} from '../screens/signal_detection_rules'; +} from '../screens/alert_detection_rules'; import { - goToManageSignalDetectionRules, - waitForSignalsPanelToBeLoaded, - waitForSignalsIndexToBeCreated, + goToManageAlertDetectionRules, + waitForAlertsPanelToBeLoaded, + waitForAlertsIndexToBeCreated, } from '../tasks/detections'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; @@ -24,11 +24,11 @@ import { sortByActivatedRules, waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRuleToBeActivated, -} from '../tasks/signal_detection_rules'; +} from '../tasks/alert_detection_rules'; import { DETECTIONS } from '../urls/navigation'; -describe('Signal detection rules', () => { +describe('Detection rules', () => { before(() => { esArchiverLoad('prebuilt_rules_loaded'); }); @@ -39,9 +39,9 @@ describe('Signal detection rules', () => { it('Sorts by activated rules', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS); - waitForSignalsPanelToBeLoaded(); - waitForSignalsIndexToBeCreated(); - goToManageSignalDetectionRules(); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); cy.get(RULE_NAME) .eq(FIFTH_RULE) diff --git a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_custom.spec.ts b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_custom.spec.ts index 04762bbf352d..e5cec16c48a3 100644 --- a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_custom.spec.ts +++ b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_custom.spec.ts @@ -36,7 +36,7 @@ import { RULES_TABLE, SEVERITY, SHOWING_RULES_TEXT, -} from '../screens/signal_detection_rules'; +} from '../screens/alert_detection_rules'; import { createAndActivateRule, @@ -44,9 +44,9 @@ import { fillDefineCustomRuleWithImportedQueryAndContinue, } from '../tasks/create_new_rule'; import { - goToManageSignalDetectionRules, - waitForSignalsIndexToBeCreated, - waitForSignalsPanelToBeLoaded, + goToManageAlertDetectionRules, + waitForAlertsIndexToBeCreated, + waitForAlertsPanelToBeLoaded, } from '../tasks/detections'; import { changeToThreeHundredRowsPerPage, @@ -58,13 +58,13 @@ import { selectNumberOfRules, waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRulesToBeLoaded, -} from '../tasks/signal_detection_rules'; +} from '../tasks/alert_detection_rules'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { DETECTIONS } from '../urls/navigation'; -describe('Signal detection rules, custom', () => { +describe('Detection rules, custom', () => { before(() => { esArchiverLoad('custom_rule_with_timeline'); }); @@ -75,9 +75,9 @@ describe('Signal detection rules, custom', () => { it('Creates and activates a new custom rule', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS); - waitForSignalsPanelToBeLoaded(); - waitForSignalsIndexToBeCreated(); - goToManageSignalDetectionRules(); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); goToCreateNewRule(); fillDefineCustomRuleWithImportedQueryAndContinue(newRule); @@ -170,9 +170,9 @@ describe('Deletes custom rules', () => { beforeEach(() => { esArchiverLoad('custom_rules'); loginAndWaitForPageWithoutDateRange(DETECTIONS); - waitForSignalsPanelToBeLoaded(); - waitForSignalsIndexToBeCreated(); - goToManageSignalDetectionRules(); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); }); after(() => { diff --git a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_export.spec.ts b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_export.spec.ts index aa1a11110216..4a1299043899 100644 --- a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_export.spec.ts +++ b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_export.spec.ts @@ -5,13 +5,13 @@ */ import { - goToManageSignalDetectionRules, - waitForSignalsIndexToBeCreated, - waitForSignalsPanelToBeLoaded, + goToManageAlertDetectionRules, + waitForAlertsIndexToBeCreated, + waitForAlertsPanelToBeLoaded, } from '../tasks/detections'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; -import { exportFirstRule } from '../tasks/signal_detection_rules'; +import { exportFirstRule } from '../tasks/alert_detection_rules'; import { DETECTIONS } from '../urls/navigation'; @@ -33,9 +33,9 @@ describe('Export rules', () => { it('Exports a custom rule', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS); - waitForSignalsPanelToBeLoaded(); - waitForSignalsIndexToBeCreated(); - goToManageSignalDetectionRules(); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); exportFirstRule(); cy.wait('@export').then((xhr) => { cy.readFile(EXPECTED_EXPORTED_RULE_FILE_PATH).then(($expectedExportedJson) => { diff --git a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_ml.spec.ts b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_ml.spec.ts index cb04d8117a92..fd2dff27ad35 100644 --- a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_ml.spec.ts +++ b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_ml.spec.ts @@ -34,7 +34,7 @@ import { RULES_ROW, RULES_TABLE, SEVERITY, -} from '../screens/signal_detection_rules'; +} from '../screens/alert_detection_rules'; import { createAndActivateRule, @@ -43,9 +43,9 @@ import { selectMachineLearningRuleType, } from '../tasks/create_new_rule'; import { - goToManageSignalDetectionRules, - waitForSignalsIndexToBeCreated, - waitForSignalsPanelToBeLoaded, + goToManageAlertDetectionRules, + waitForAlertsIndexToBeCreated, + waitForAlertsPanelToBeLoaded, } from '../tasks/detections'; import { changeToThreeHundredRowsPerPage, @@ -54,13 +54,13 @@ import { goToRuleDetails, waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRulesToBeLoaded, -} from '../tasks/signal_detection_rules'; +} from '../tasks/alert_detection_rules'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { DETECTIONS } from '../urls/navigation'; -describe('Signal detection rules, machine learning', () => { +describe('Detection rules, machine learning', () => { before(() => { esArchiverLoad('prebuilt_rules_loaded'); }); @@ -71,9 +71,9 @@ describe('Signal detection rules, machine learning', () => { it('Creates and activates a new ml rule', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS); - waitForSignalsPanelToBeLoaded(); - waitForSignalsIndexToBeCreated(); - goToManageSignalDetectionRules(); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); goToCreateNewRule(); selectMachineLearningRuleType(); diff --git a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_prebuilt.spec.ts b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_prebuilt.spec.ts index 005e24dad2a1..2cd087b2ca5e 100644 --- a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_prebuilt.spec.ts +++ b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_prebuilt.spec.ts @@ -10,7 +10,7 @@ import { RELOAD_PREBUILT_RULES_BTN, RULES_ROW, RULES_TABLE, -} from '../screens/signal_detection_rules'; +} from '../screens/alert_detection_rules'; import { changeToThreeHundredRowsPerPage, @@ -22,11 +22,11 @@ import { waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForPrebuiltDetectionRulesToBeLoaded, waitForRulesToBeLoaded, -} from '../tasks/signal_detection_rules'; +} from '../tasks/alert_detection_rules'; import { - goToManageSignalDetectionRules, - waitForSignalsIndexToBeCreated, - waitForSignalsPanelToBeLoaded, + goToManageAlertDetectionRules, + waitForAlertsIndexToBeCreated, + waitForAlertsPanelToBeLoaded, } from '../tasks/detections'; import { esArchiverLoadEmptyKibana, esArchiverUnloadEmptyKibana } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; @@ -35,7 +35,7 @@ import { DETECTIONS } from '../urls/navigation'; import { totalNumberOfPrebuiltRules } from '../objects/rule'; -describe('Signal detection rules, prebuilt rules', () => { +describe('Detection rules, prebuilt rules', () => { before(() => { esArchiverLoadEmptyKibana(); }); @@ -49,9 +49,9 @@ describe('Signal detection rules, prebuilt rules', () => { const expectedElasticRulesBtnText = `Elastic rules (${expectedNumberOfRules})`; loginAndWaitForPageWithoutDateRange(DETECTIONS); - waitForSignalsPanelToBeLoaded(); - waitForSignalsIndexToBeCreated(); - goToManageSignalDetectionRules(); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); loadPrebuiltDetectionRules(); waitForPrebuiltDetectionRulesToBeLoaded(); @@ -74,9 +74,9 @@ describe('Deleting prebuilt rules', () => { esArchiverLoadEmptyKibana(); loginAndWaitForPageWithoutDateRange(DETECTIONS); - waitForSignalsPanelToBeLoaded(); - waitForSignalsIndexToBeCreated(); - goToManageSignalDetectionRules(); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); loadPrebuiltDetectionRules(); waitForPrebuiltDetectionRulesToBeLoaded(); diff --git a/x-pack/plugins/siem/cypress/screens/signal_detection_rules.ts b/x-pack/plugins/siem/cypress/screens/alert_detection_rules.ts similarity index 100% rename from x-pack/plugins/siem/cypress/screens/signal_detection_rules.ts rename to x-pack/plugins/siem/cypress/screens/alert_detection_rules.ts diff --git a/x-pack/plugins/siem/cypress/screens/detections.ts b/x-pack/plugins/siem/cypress/screens/detections.ts index d9ffa5b5a4ab..b915bcba2f88 100644 --- a/x-pack/plugins/siem/cypress/screens/detections.ts +++ b/x-pack/plugins/siem/cypress/screens/detections.ts @@ -4,30 +4,30 @@ * you may not use this file except in compliance with the Elastic License. */ -export const CLOSED_SIGNALS_BTN = '[data-test-subj="closedSignals"]'; +export const CLOSED_ALERTS_BTN = '[data-test-subj="closedAlerts"]'; -export const EXPAND_SIGNAL_BTN = '[data-test-subj="expand-event"]'; +export const EXPAND_ALERT_BTN = '[data-test-subj="expand-event"]'; -export const LOADING_SIGNALS_PANEL = '[data-test-subj="loading-signals-panel"]'; +export const LOADING_ALERTS_PANEL = '[data-test-subj="loading-alerts-panel"]'; -export const MANAGE_SIGNAL_DETECTION_RULES_BTN = '[data-test-subj="manage-signal-detection-rules"]'; +export const MANAGE_ALERT_DETECTION_RULES_BTN = '[data-test-subj="manage-alert-detection-rules"]'; -export const NUMBER_OF_SIGNALS = '[data-test-subj="server-side-event-count"] .euiBadge__text'; +export const NUMBER_OF_ALERTS = '[data-test-subj="server-side-event-count"] .euiBadge__text'; -export const OPEN_CLOSE_SIGNAL_BTN = '[data-test-subj="update-signal-status-button"]'; +export const OPEN_CLOSE_ALERT_BTN = '[data-test-subj="update-alert-status-button"]'; -export const OPEN_CLOSE_SIGNALS_BTN = '[data-test-subj="openCloseSignal"] button'; +export const OPEN_CLOSE_ALERTS_BTN = '[data-test-subj="openCloseAlert"] button'; -export const OPENED_SIGNALS_BTN = '[data-test-subj="openSignals"]'; +export const OPENED_ALERTS_BTN = '[data-test-subj="openAlerts"]'; -export const SELECTED_SIGNALS = '[data-test-subj="selectedSignals"]'; +export const SELECTED_ALERTS = '[data-test-subj="selectedAlerts"]'; -export const SEND_SIGNAL_TO_TIMELINE_BTN = '[data-test-subj="send-signal-to-timeline-button"]'; +export const SEND_ALERT_TO_TIMELINE_BTN = '[data-test-subj="send-alert-to-timeline-button"]'; -export const SHOWING_SIGNALS = '[data-test-subj="showingSignals"]'; +export const SHOWING_ALERTS = '[data-test-subj="showingAlerts"]'; -export const SIGNALS = '[data-test-subj="event"]'; +export const ALERTS = '[data-test-subj="event"]'; -export const SIGNAL_ID = '[data-test-subj="draggable-content-_id"]'; +export const ALERT_ID = '[data-test-subj="draggable-content-_id"]'; -export const SIGNAL_CHECKBOX = '[data-test-subj="select-event-container"] .euiCheckbox__input'; +export const ALERT_CHECKBOX = '[data-test-subj="select-event-container"] .euiCheckbox__input'; diff --git a/x-pack/plugins/siem/cypress/tasks/signal_detection_rules.ts b/x-pack/plugins/siem/cypress/tasks/alert_detection_rules.ts similarity index 98% rename from x-pack/plugins/siem/cypress/tasks/signal_detection_rules.ts rename to x-pack/plugins/siem/cypress/tasks/alert_detection_rules.ts index 6b2c4644a95d..9710e0e808ac 100644 --- a/x-pack/plugins/siem/cypress/tasks/signal_detection_rules.ts +++ b/x-pack/plugins/siem/cypress/tasks/alert_detection_rules.ts @@ -24,7 +24,7 @@ import { SORT_RULES_BTN, THREE_HUNDRED_ROWS, EXPORT_ACTION_BTN, -} from '../screens/signal_detection_rules'; +} from '../screens/alert_detection_rules'; export const activateRule = (rulePosition: number) => { cy.get(RULE_SWITCH).eq(rulePosition).click({ force: true }); diff --git a/x-pack/plugins/siem/cypress/tasks/detections.ts b/x-pack/plugins/siem/cypress/tasks/detections.ts index 9461dd5ff99c..f53dd83635d8 100644 --- a/x-pack/plugins/siem/cypress/tasks/detections.ts +++ b/x-pack/plugins/siem/cypress/tasks/detections.ts @@ -5,66 +5,66 @@ */ import { - CLOSED_SIGNALS_BTN, - EXPAND_SIGNAL_BTN, - LOADING_SIGNALS_PANEL, - MANAGE_SIGNAL_DETECTION_RULES_BTN, - OPEN_CLOSE_SIGNAL_BTN, - OPEN_CLOSE_SIGNALS_BTN, - OPENED_SIGNALS_BTN, - SEND_SIGNAL_TO_TIMELINE_BTN, - SIGNALS, - SIGNAL_CHECKBOX, + CLOSED_ALERTS_BTN, + EXPAND_ALERT_BTN, + LOADING_ALERTS_PANEL, + MANAGE_ALERT_DETECTION_RULES_BTN, + OPEN_CLOSE_ALERT_BTN, + OPEN_CLOSE_ALERTS_BTN, + OPENED_ALERTS_BTN, + SEND_ALERT_TO_TIMELINE_BTN, + ALERTS, + ALERT_CHECKBOX, } from '../screens/detections'; import { REFRESH_BUTTON } from '../screens/siem_header'; -export const closeFirstSignal = () => { - cy.get(OPEN_CLOSE_SIGNAL_BTN).first().click({ force: true }); +export const closeFirstAlert = () => { + cy.get(OPEN_CLOSE_ALERT_BTN).first().click({ force: true }); }; -export const closeSignals = () => { - cy.get(OPEN_CLOSE_SIGNALS_BTN).click({ force: true }); +export const closeAlerts = () => { + cy.get(OPEN_CLOSE_ALERTS_BTN).click({ force: true }); }; -export const expandFirstSignal = () => { - cy.get(EXPAND_SIGNAL_BTN).first().click({ force: true }); +export const expandFirstAlert = () => { + cy.get(EXPAND_ALERT_BTN).first().click({ force: true }); }; -export const goToClosedSignals = () => { - cy.get(CLOSED_SIGNALS_BTN).click({ force: true }); +export const goToClosedAlerts = () => { + cy.get(CLOSED_ALERTS_BTN).click({ force: true }); }; -export const goToManageSignalDetectionRules = () => { - cy.get(MANAGE_SIGNAL_DETECTION_RULES_BTN).should('exist').click({ force: true }); +export const goToManageAlertDetectionRules = () => { + cy.get(MANAGE_ALERT_DETECTION_RULES_BTN).should('exist').click({ force: true }); }; -export const goToOpenedSignals = () => { - cy.get(OPENED_SIGNALS_BTN).click({ force: true }); +export const goToOpenedAlerts = () => { + cy.get(OPENED_ALERTS_BTN).click({ force: true }); }; -export const openFirstSignal = () => { - cy.get(OPEN_CLOSE_SIGNAL_BTN).first().click({ force: true }); +export const openFirstAlert = () => { + cy.get(OPEN_CLOSE_ALERT_BTN).first().click({ force: true }); }; -export const openSignals = () => { - cy.get(OPEN_CLOSE_SIGNALS_BTN).click({ force: true }); +export const openAlerts = () => { + cy.get(OPEN_CLOSE_ALERTS_BTN).click({ force: true }); }; -export const selectNumberOfSignals = (numberOfSignals: number) => { - for (let i = 0; i < numberOfSignals; i++) { - cy.get(SIGNAL_CHECKBOX).eq(i).click({ force: true }); +export const selectNumberOfAlerts = (numberOfAlerts: number) => { + for (let i = 0; i < numberOfAlerts; i++) { + cy.get(ALERT_CHECKBOX).eq(i).click({ force: true }); } }; -export const investigateFirstSignalInTimeline = () => { - cy.get(SEND_SIGNAL_TO_TIMELINE_BTN).first().click({ force: true }); +export const investigateFirstAlertInTimeline = () => { + cy.get(SEND_ALERT_TO_TIMELINE_BTN).first().click({ force: true }); }; -export const waitForSignals = () => { +export const waitForAlerts = () => { cy.get(REFRESH_BUTTON).invoke('text').should('not.equal', 'Updating'); }; -export const waitForSignalsIndexToBeCreated = () => { +export const waitForAlertsIndexToBeCreated = () => { cy.request({ url: '/api/detection_engine/index', retryOnStatusCodeFailure: true }).then( (response) => { if (response.status !== 200) { @@ -74,12 +74,12 @@ export const waitForSignalsIndexToBeCreated = () => { ); }; -export const waitForSignalsPanelToBeLoaded = () => { - cy.get(LOADING_SIGNALS_PANEL).should('exist'); - cy.get(LOADING_SIGNALS_PANEL).should('not.exist'); +export const waitForAlertsPanelToBeLoaded = () => { + cy.get(LOADING_ALERTS_PANEL).should('exist'); + cy.get(LOADING_ALERTS_PANEL).should('not.exist'); }; -export const waitForSignalsToBeLoaded = () => { - const expectedNumberOfDisplayedSignals = 25; - cy.get(SIGNALS).should('have.length', expectedNumberOfDisplayedSignals); +export const waitForAlertsToBeLoaded = () => { + const expectedNumberOfDisplayedAlerts = 25; + cy.get(ALERTS).should('have.length', expectedNumberOfDisplayedAlerts); }; diff --git a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/signals_histogram.test.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/alerts_histogram.test.tsx similarity index 84% rename from x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/signals_histogram.test.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/alerts_histogram.test.tsx index f921c00cdafb..7f340b0bea37 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/signals_histogram.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/alerts_histogram.test.tsx @@ -7,14 +7,14 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { SignalsHistogram } from './signals_histogram'; +import { AlertsHistogram } from './alerts_histogram'; jest.mock('../../../common/lib/kibana'); -describe('SignalsHistogram', () => { +describe('AlertsHistogram', () => { it('renders correctly', () => { const wrapper = shallow( - ( +export const AlertsHistogram = React.memo( ({ chartHeight = DEFAULT_CHART_HEIGHT, data, @@ -48,9 +48,9 @@ export const SignalsHistogram = React.memo( const theme = useTheme(); const chartSize: ChartSizeArray = useMemo(() => ['100%', chartHeight], [chartHeight]); - const xAxisId = 'signalsHistogramAxisX'; - const yAxisId = 'signalsHistogramAxisY'; - const id = 'signalsHistogram'; + const xAxisId = 'alertsHistogramAxisX'; + const yAxisId = 'alertsHistogramAxisY'; + const id = 'alertsHistogram'; const yAccessors = useMemo(() => ['y'], []); const splitSeriesAccessors = useMemo(() => ['g'], []); const tickFormat = useMemo(() => histogramDateTimeFormatter([from, to]), [from, to]); @@ -59,7 +59,7 @@ export const SignalsHistogram = React.memo( <> {loading && ( ( } ); -SignalsHistogram.displayName = 'SignalsHistogram'; +AlertsHistogram.displayName = 'AlertsHistogram'; diff --git a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/config.ts b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/config.ts similarity index 88% rename from x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/config.ts rename to x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/config.ts index 2c5a1ddd9a01..513883587381 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/config.ts +++ b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/config.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SignalsHistogramOption } from './types'; +import { AlertsHistogramOption } from './types'; -export const signalsHistogramOptions: SignalsHistogramOption[] = [ +export const alertsHistogramOptions: AlertsHistogramOption[] = [ { text: 'signal.rule.risk_score', value: 'signal.rule.risk_score' }, { text: 'signal.rule.severity', value: 'signal.rule.severity' }, { text: 'signal.rule.threat.tactic.name', value: 'signal.rule.threat.tactic.name' }, diff --git a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/helpers.test.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/helpers.test.tsx similarity index 89% rename from x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/helpers.test.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/helpers.test.tsx index 2758625c0d4a..bfe4cee088a0 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/helpers.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/helpers.test.tsx @@ -9,25 +9,25 @@ import { showInitialLoadingSpinner } from './helpers'; describe('helpers', () => { describe('showInitialLoadingSpinner', () => { test('it should (only) show the spinner during initial loading, while we are fetching data', () => { - expect(showInitialLoadingSpinner({ isInitialLoading: true, isLoadingSignals: true })).toBe( + expect(showInitialLoadingSpinner({ isInitialLoading: true, isLoadingAlerts: true })).toBe( true ); }); test('it should STOP showing the spinner (during initial loading) when the first data fetch completes', () => { - expect(showInitialLoadingSpinner({ isInitialLoading: true, isLoadingSignals: false })).toBe( + expect(showInitialLoadingSpinner({ isInitialLoading: true, isLoadingAlerts: false })).toBe( false ); }); test('it should NOT show the spinner after initial loading has completed, even if the user requests more data (e.g. by clicking Refresh)', () => { - expect(showInitialLoadingSpinner({ isInitialLoading: false, isLoadingSignals: true })).toBe( + expect(showInitialLoadingSpinner({ isInitialLoading: false, isLoadingAlerts: true })).toBe( false ); }); test('it should NOT show the spinner after initial loading has completed', () => { - expect(showInitialLoadingSpinner({ isInitialLoading: false, isLoadingSignals: false })).toBe( + expect(showInitialLoadingSpinner({ isInitialLoading: false, isLoadingAlerts: false })).toBe( false ); }); diff --git a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/helpers.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/helpers.tsx similarity index 67% rename from x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/helpers.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/helpers.tsx index 0c9fa39e53d0..9d124201f022 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/helpers.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/helpers.tsx @@ -5,21 +5,19 @@ */ import { showAllOthersBucket } from '../../../../common/constants'; -import { HistogramData, SignalsAggregation, SignalsBucket, SignalsGroupBucket } from './types'; -import { SignalSearchResponse } from '../../containers/detection_engine/signals/types'; +import { HistogramData, AlertsAggregation, AlertsBucket, AlertsGroupBucket } from './types'; +import { AlertSearchResponse } from '../../containers/detection_engine/alerts/types'; import * as i18n from './translations'; -export const formatSignalsData = ( - signalsData: SignalSearchResponse<{}, SignalsAggregation> | null -) => { - const groupBuckets: SignalsGroupBucket[] = - signalsData?.aggregations?.signalsByGrouping?.buckets ?? []; - return groupBuckets.reduce((acc, { key: group, signals }) => { - const signalsBucket: SignalsBucket[] = signals.buckets ?? []; +export const formatAlertsData = (alertsData: AlertSearchResponse<{}, AlertsAggregation> | null) => { + const groupBuckets: AlertsGroupBucket[] = + alertsData?.aggregations?.alertsByGrouping?.buckets ?? []; + return groupBuckets.reduce((acc, { key: group, alerts }) => { + const alertsBucket: AlertsBucket[] = alerts.buckets ?? []; return [ ...acc, - ...signalsBucket.map(({ key, doc_count }: SignalsBucket) => ({ + ...alertsBucket.map(({ key, doc_count }: AlertsBucket) => ({ x: key, y: doc_count, g: group, @@ -28,7 +26,7 @@ export const formatSignalsData = ( }, []); }; -export const getSignalsHistogramQuery = ( +export const getAlertsHistogramQuery = ( stackByField: string, from: number, to: number, @@ -44,7 +42,7 @@ export const getSignalsHistogramQuery = ( return { aggs: { - signalsByGrouping: { + alertsByGrouping: { terms: { field: stackByField, ...missing, @@ -54,7 +52,7 @@ export const getSignalsHistogramQuery = ( size: 10, }, aggs: { - signals: { + alerts: { date_histogram: { field: '@timestamp', fixed_interval: `${Math.floor((to - from) / 32)}ms`, @@ -87,15 +85,15 @@ export const getSignalsHistogramQuery = ( }; /** - * Returns `true` when the signals histogram initial loading spinner should be shown + * Returns `true` when the alerts histogram initial loading spinner should be shown * * @param isInitialLoading The loading spinner will only be displayed if this value is `true`, because after initial load, a different, non-spinner loading indicator is displayed - * @param isLoadingSignals When `true`, IO is being performed to request signals (for rendering in the histogram) + * @param isLoadingAlerts When `true`, IO is being performed to request alerts (for rendering in the histogram) */ export const showInitialLoadingSpinner = ({ isInitialLoading, - isLoadingSignals, + isLoadingAlerts, }: { isInitialLoading: boolean; - isLoadingSignals: boolean; -}): boolean => isInitialLoading && isLoadingSignals; + isLoadingAlerts: boolean; +}): boolean => isInitialLoading && isLoadingAlerts; diff --git a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/index.test.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/index.test.tsx similarity index 85% rename from x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/index.test.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/index.test.tsx index 6578af19094d..3376df76ac6e 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/index.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/index.test.tsx @@ -7,15 +7,15 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { SignalsHistogramPanel } from './index'; +import { AlertsHistogramPanel } from './index'; jest.mock('../../../common/lib/kibana'); jest.mock('../../../common/components/navigation/use_get_url_search'); -describe('SignalsHistogramPanel', () => { +describe('AlertsHistogramPanel', () => { it('renders correctly', () => { const wrapper = shallow( - ` position: relative; `; -const defaultTotalSignalsObj: SignalsTotal = { +const defaultTotalAlertsObj: AlertsTotal = { value: 0, relation: 'eq', }; export const DETECTIONS_HISTOGRAM_ID = 'detections-histogram'; -const ViewSignalsFlexItem = styled(EuiFlexItem)` +const ViewAlertsFlexItem = styled(EuiFlexItem)` margin-left: 24px; `; -interface SignalsHistogramPanelProps { +interface AlertsHistogramPanelProps { chartHeight?: number; - defaultStackByOption?: SignalsHistogramOption; + defaultStackByOption?: AlertsHistogramOption; deleteQuery?: ({ id }: { id: string }) => void; filters?: Filter[]; from: number; @@ -66,9 +66,9 @@ interface SignalsHistogramPanelProps { panelHeight?: number; signalIndexName: string | null; setQuery: (params: RegisterQuery) => void; - showLinkToSignals?: boolean; - showTotalSignalsCount?: boolean; - stackByOptions?: SignalsHistogramOption[]; + showLinkToAlerts?: boolean; + showTotalAlertsCount?: boolean; + stackByOptions?: AlertsHistogramOption[]; title?: string; to: number; updateDateRange: UpdateDateRange; @@ -81,10 +81,10 @@ const getHistogramOption = (fieldName: string): MatrixHistogramOption => ({ const NO_LEGEND_DATA: LegendItem[] = []; -export const SignalsHistogramPanel = memo( +export const AlertsHistogramPanel = memo( ({ chartHeight, - defaultStackByOption = signalsHistogramOptions[0], + defaultStackByOption = alertsHistogramOptions[0], deleteQuery, filters, headerChildren, @@ -95,8 +95,8 @@ export const SignalsHistogramPanel = memo( panelHeight = DEFAULT_PANEL_HEIGHT, setQuery, signalIndexName, - showLinkToSignals = false, - showTotalSignalsCount = false, + showLinkToAlerts = false, + showTotalAlertsCount = false, stackByOptions, to, title = i18n.HISTOGRAM_HEADER, @@ -106,32 +106,32 @@ export const SignalsHistogramPanel = memo( const uniqueQueryId = useMemo(() => `${DETECTIONS_HISTOGRAM_ID}-${uuid.v4()}`, []); const [isInitialLoading, setIsInitialLoading] = useState(true); const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); - const [totalSignalsObj, setTotalSignalsObj] = useState(defaultTotalSignalsObj); - const [selectedStackByOption, setSelectedStackByOption] = useState( + const [totalAlertsObj, setTotalAlertsObj] = useState(defaultTotalAlertsObj); + const [selectedStackByOption, setSelectedStackByOption] = useState( onlyField == null ? defaultStackByOption : getHistogramOption(onlyField) ); const { - loading: isLoadingSignals, - data: signalsData, - setQuery: setSignalsQuery, + loading: isLoadingAlerts, + data: alertsData, + setQuery: setAlertsQuery, response, request, refetch, - } = useQuerySignals<{}, SignalsAggregation>( - getSignalsHistogramQuery(selectedStackByOption.value, from, to, []), + } = useQueryAlerts<{}, AlertsAggregation>( + getAlertsHistogramQuery(selectedStackByOption.value, from, to, []), signalIndexName ); const kibana = useKibana(); const urlSearch = useGetUrlSearch(navTabs.detections); - const totalSignals = useMemo( + const totalAlerts = useMemo( () => - i18n.SHOWING_SIGNALS( - numeral(totalSignalsObj.value).format(defaultNumberFormat), - totalSignalsObj.value, - totalSignalsObj.relation === 'gte' ? '>' : totalSignalsObj.relation === 'lte' ? '<' : '' + i18n.SHOWING_ALERTS( + numeral(totalAlertsObj.value).format(defaultNumberFormat), + totalAlertsObj.value, + totalAlertsObj.relation === 'gte' ? '>' : totalAlertsObj.relation === 'lte' ? '<' : '' ), - [totalSignalsObj] + [totalAlertsObj] ); const setSelectedOptionCallback = useCallback((event: React.ChangeEvent) => { @@ -140,12 +140,12 @@ export const SignalsHistogramPanel = memo( ); }, []); - const formattedSignalsData = useMemo(() => formatSignalsData(signalsData), [signalsData]); + const formattedAlertsData = useMemo(() => formatAlertsData(alertsData), [alertsData]); const legendItems: LegendItem[] = useMemo( () => - signalsData?.aggregations?.signalsByGrouping?.buckets != null - ? signalsData.aggregations.signalsByGrouping.buckets.map((bucket, i) => ({ + alertsData?.aggregations?.alertsByGrouping?.buckets != null + ? alertsData.aggregations.alertsByGrouping.buckets.map((bucket, i) => ({ color: i < defaultLegendColors.length ? defaultLegendColors[i] : undefined, dataProviderId: escapeDataProviderId( `draggable-legend-item-${uuid.v4()}-${selectedStackByOption.value}-${bucket.key}` @@ -154,20 +154,20 @@ export const SignalsHistogramPanel = memo( value: bucket.key, })) : NO_LEGEND_DATA, - [signalsData, selectedStackByOption.value] + [alertsData, selectedStackByOption.value] ); useEffect(() => { let canceled = false; - if (!canceled && !showInitialLoadingSpinner({ isInitialLoading, isLoadingSignals })) { + if (!canceled && !showInitialLoadingSpinner({ isInitialLoading, isLoadingAlerts })) { setIsInitialLoading(false); } return () => { canceled = true; // prevent long running data fetches from updating state after unmounting }; - }, [isInitialLoading, isLoadingSignals, setIsInitialLoading]); + }, [isInitialLoading, isLoadingAlerts, setIsInitialLoading]); useEffect(() => { return () => { @@ -185,20 +185,20 @@ export const SignalsHistogramPanel = memo( dsl: [request], response: [response], }, - loading: isLoadingSignals, + loading: isLoadingAlerts, refetch, }); } - }, [setQuery, isLoadingSignals, signalsData, response, request, refetch]); + }, [setQuery, isLoadingAlerts, alertsData, response, request, refetch]); useEffect(() => { - setTotalSignalsObj( - signalsData?.hits.total ?? { + setTotalAlertsObj( + alertsData?.hits.total ?? { value: 0, relation: 'eq', } ); - }, [signalsData]); + }, [alertsData]); useEffect(() => { const converted = esQuery.buildEsQuery( @@ -211,8 +211,8 @@ export const SignalsHistogramPanel = memo( } ); - setSignalsQuery( - getSignalsHistogramQuery( + setAlertsQuery( + getAlertsHistogramQuery( selectedStackByOption.value, from, to, @@ -222,14 +222,14 @@ export const SignalsHistogramPanel = memo( }, [selectedStackByOption.value, from, to, query, filters]); const linkButton = useMemo(() => { - if (showLinkToSignals) { + if (showLinkToAlerts) { return ( - - {i18n.VIEW_SIGNALS} - + + {i18n.VIEW_ALERTS} + ); } - }, [showLinkToSignals, urlSearch]); + }, [showLinkToAlerts, urlSearch]); const titleText = useMemo(() => (onlyField == null ? title : i18n.TOP(onlyField)), [ onlyField, @@ -237,13 +237,13 @@ export const SignalsHistogramPanel = memo( ]); return ( - + @@ -264,13 +264,13 @@ export const SignalsHistogramPanel = memo( {isInitialLoading ? ( ) : ( - @@ -281,4 +281,4 @@ export const SignalsHistogramPanel = memo( } ); -SignalsHistogramPanel.displayName = 'SignalsHistogramPanel'; +AlertsHistogramPanel.displayName = 'AlertsHistogramPanel'; diff --git a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/translations.ts b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/translations.ts similarity index 50% rename from x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/translations.ts rename to x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/translations.ts index e7b76a48c759..91345e3d989f 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/translations.ts +++ b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/translations.ts @@ -7,116 +7,116 @@ import { i18n } from '@kbn/i18n'; export const STACK_BY_LABEL = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.stackByLabel', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.stackByLabel', { defaultMessage: 'Stack by', } ); export const STACK_BY_RISK_SCORES = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.riskScoresDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.riskScoresDropDown', { defaultMessage: 'Risk scores', } ); export const STACK_BY_SEVERITIES = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.severitiesDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.severitiesDropDown', { defaultMessage: 'Severities', } ); export const STACK_BY_DESTINATION_IPS = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.destinationIpsDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.destinationIpsDropDown', { defaultMessage: 'Top destination IPs', } ); export const STACK_BY_SOURCE_IPS = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.sourceIpsDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.sourceIpsDropDown', { defaultMessage: 'Top source IPs', } ); export const STACK_BY_ACTIONS = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.eventActionsDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.eventActionsDropDown', { defaultMessage: 'Top event actions', } ); export const STACK_BY_CATEGORIES = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.eventCategoriesDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.eventCategoriesDropDown', { defaultMessage: 'Top event categories', } ); export const STACK_BY_HOST_NAMES = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.hostNamesDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.hostNamesDropDown', { defaultMessage: 'Top host names', } ); export const STACK_BY_RULE_TYPES = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.ruleTypesDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.ruleTypesDropDown', { defaultMessage: 'Top rule types', } ); export const STACK_BY_RULE_NAMES = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.rulesDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.rulesDropDown', { defaultMessage: 'Top rules', } ); export const STACK_BY_USERS = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.usersDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.usersDropDown', { defaultMessage: 'Top users', } ); export const TOP = (fieldName: string) => - i18n.translate('xpack.siem.detectionEngine.signals.histogram.topNLabel', { + i18n.translate('xpack.siem.detectionEngine.alerts.histogram.topNLabel', { values: { fieldName }, defaultMessage: `Top {fieldName}`, }); export const HISTOGRAM_HEADER = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.headerTitle', + 'xpack.siem.detectionEngine.alerts.histogram.headerTitle', { - defaultMessage: 'Signal count', + defaultMessage: 'Alert count', } ); export const ALL_OTHERS = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.allOthersGroupingLabel', + 'xpack.siem.detectionEngine.alerts.histogram.allOthersGroupingLabel', { defaultMessage: 'All others', } ); -export const VIEW_SIGNALS = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.viewSignalsButtonLabel', +export const VIEW_ALERTS = i18n.translate( + 'xpack.siem.detectionEngine.alerts.histogram.viewAlertsButtonLabel', { - defaultMessage: 'View signals', + defaultMessage: 'View alerts', } ); -export const SHOWING_SIGNALS = ( - totalSignalsFormatted: string, - totalSignals: number, +export const SHOWING_ALERTS = ( + totalAlertsFormatted: string, + totalAlerts: number, modifier: string ) => - i18n.translate('xpack.siem.detectionEngine.signals.histogram.showingSignalsTitle', { - values: { totalSignalsFormatted, totalSignals, modifier }, + i18n.translate('xpack.siem.detectionEngine.alerts.histogram.showingAlertsTitle', { + values: { totalAlertsFormatted, totalAlerts, modifier }, defaultMessage: - 'Showing: {modifier}{totalSignalsFormatted} {totalSignals, plural, =1 {signal} other {signals}}', + 'Showing: {modifier}{totalAlertsFormatted} {totalAlerts, plural, =1 {alert} other {alerts}}', }); diff --git a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/types.ts b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/types.ts similarity index 70% rename from x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/types.ts rename to x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/types.ts index 41d58a4a7391..0bf483f7ec92 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/types.ts +++ b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/types.ts @@ -6,7 +6,7 @@ import { inputsModel } from '../../../common/store'; -export interface SignalsHistogramOption { +export interface AlertsHistogramOption { text: string; value: string; } @@ -17,26 +17,26 @@ export interface HistogramData { g: string; } -export interface SignalsAggregation { - signalsByGrouping: { - buckets: SignalsGroupBucket[]; +export interface AlertsAggregation { + alertsByGrouping: { + buckets: AlertsGroupBucket[]; }; } -export interface SignalsBucket { +export interface AlertsBucket { key_as_string: string; key: number; doc_count: number; } -export interface SignalsGroupBucket { +export interface AlertsGroupBucket { key: string; - signals: { - buckets: SignalsBucket[]; + alerts: { + buckets: AlertsBucket[]; }; } -export interface SignalsTotal { +export interface AlertsTotal { value: number; relation: string; } diff --git a/x-pack/plugins/siem/public/alerts/components/alerts_info/index.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_info/index.tsx new file mode 100644 index 000000000000..7d35e429bcf5 --- /dev/null +++ b/x-pack/plugins/siem/public/alerts/components/alerts_info/index.tsx @@ -0,0 +1,49 @@ +/* + * 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 { EuiLoadingSpinner } from '@elastic/eui'; +import { FormattedRelative } from '@kbn/i18n/react'; +import React, { useState, useEffect } from 'react'; + +import { useQueryAlerts } from '../../containers/detection_engine/alerts/use_query'; +import { buildLastAlertsQuery } from './query.dsl'; +import { Aggs } from './types'; + +interface AlertInfo { + ruleId?: string | null; +} + +type Return = [React.ReactNode, React.ReactNode]; + +export const useAlertInfo = ({ ruleId = null }: AlertInfo): Return => { + const [lastAlerts, setLastAlerts] = useState( + + ); + const [totalAlerts, setTotalAlerts] = useState( + + ); + + const { loading, data: alerts } = useQueryAlerts(buildLastAlertsQuery(ruleId)); + + useEffect(() => { + if (alerts != null) { + const myAlerts = alerts; + setLastAlerts( + myAlerts.aggregations?.lastSeen.value != null ? ( + + ) : null + ); + setTotalAlerts(<>{myAlerts.hits.total.value}); + } else { + setLastAlerts(null); + setTotalAlerts(null); + } + }, [loading, alerts]); + + return [lastAlerts, totalAlerts]; +}; diff --git a/x-pack/plugins/siem/public/alerts/components/signals_info/query.dsl.ts b/x-pack/plugins/siem/public/alerts/components/alerts_info/query.dsl.ts similarity index 91% rename from x-pack/plugins/siem/public/alerts/components/signals_info/query.dsl.ts rename to x-pack/plugins/siem/public/alerts/components/alerts_info/query.dsl.ts index 8cb07a4f8e6b..a3972fd35bf2 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals_info/query.dsl.ts +++ b/x-pack/plugins/siem/public/alerts/components/alerts_info/query.dsl.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export const buildLastSignalsQuery = (ruleId: string | undefined | null) => { +export const buildLastAlertsQuery = (ruleId: string | undefined | null) => { const queryFilter = [ { bool: { should: [{ match: { 'signal.status': 'open' } }], minimum_should_match: 1 }, diff --git a/x-pack/plugins/siem/public/alerts/components/signals_info/types.ts b/x-pack/plugins/siem/public/alerts/components/alerts_info/types.ts similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/signals_info/types.ts rename to x-pack/plugins/siem/public/alerts/components/alerts_info/types.ts diff --git a/x-pack/plugins/siem/public/alerts/components/signals/actions.test.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/actions.test.tsx similarity index 92% rename from x-pack/plugins/siem/public/alerts/components/signals/actions.test.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/actions.test.tsx index d7a8a5507734..2fa7cfeedcd1 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/actions.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/actions.test.tsx @@ -6,9 +6,9 @@ import sinon from 'sinon'; import moment from 'moment'; -import { sendSignalToTimelineAction, determineToAndFrom } from './actions'; +import { sendAlertToTimelineAction, determineToAndFrom } from './actions'; import { - mockEcsDataWithSignal, + mockEcsDataWithAlert, defaultTimelineProps, apolloClient, mockTimelineApolloResult, @@ -19,7 +19,7 @@ import { TimelineType, TimelineStatus } from '../../../../common/types/timeline' jest.mock('apollo-client'); -describe('signals actions', () => { +describe('alert actions', () => { const anchor = '2020-03-01T17:59:46.349Z'; const unix = moment(anchor).valueOf(); let createTimeline: CreateTimeline; @@ -46,13 +46,13 @@ describe('signals actions', () => { clock.restore(); }); - describe('sendSignalToTimelineAction', () => { + describe('sendAlertToTimelineAction', () => { describe('timeline id is NOT empty string and apollo client exists', () => { test('it invokes updateTimelineIsLoading to set to true', async () => { - await sendSignalToTimelineAction({ + await sendAlertToTimelineAction({ apolloClient, createTimeline, - ecsData: mockEcsDataWithSignal, + ecsData: mockEcsDataWithAlert, updateTimelineIsLoading, }); @@ -61,10 +61,10 @@ describe('signals actions', () => { }); test('it invokes createTimeline with designated timeline template if "timelineTemplate" exists', async () => { - await sendSignalToTimelineAction({ + await sendAlertToTimelineAction({ apolloClient, createTimeline, - ecsData: mockEcsDataWithSignal, + ecsData: mockEcsDataWithAlert, updateTimelineIsLoading, }); const expected = { @@ -246,10 +246,10 @@ describe('signals actions', () => { }; jest.spyOn(apolloClient, 'query').mockResolvedValue(mockTimelineApolloResultModified); - await sendSignalToTimelineAction({ + await sendAlertToTimelineAction({ apolloClient, createTimeline, - ecsData: mockEcsDataWithSignal, + ecsData: mockEcsDataWithAlert, updateTimelineIsLoading, }); // @ts-ignore @@ -275,10 +275,10 @@ describe('signals actions', () => { }; jest.spyOn(apolloClient, 'query').mockResolvedValue(mockTimelineApolloResultModified); - await sendSignalToTimelineAction({ + await sendAlertToTimelineAction({ apolloClient, createTimeline, - ecsData: mockEcsDataWithSignal, + ecsData: mockEcsDataWithAlert, updateTimelineIsLoading, }); // @ts-ignore @@ -293,10 +293,10 @@ describe('signals actions', () => { throw new Error('Test error'); }); - await sendSignalToTimelineAction({ + await sendAlertToTimelineAction({ apolloClient, createTimeline, - ecsData: mockEcsDataWithSignal, + ecsData: mockEcsDataWithAlert, updateTimelineIsLoading, }); @@ -313,16 +313,16 @@ describe('signals actions', () => { describe('timelineId is empty string', () => { test('it invokes createTimeline with timelineDefaults', async () => { const ecsDataMock: Ecs = { - ...mockEcsDataWithSignal, + ...mockEcsDataWithAlert, signal: { rule: { - ...mockEcsDataWithSignal.signal?.rule!, + ...mockEcsDataWithAlert.signal?.rule!, timeline_id: null, }, }, }; - await sendSignalToTimelineAction({ + await sendAlertToTimelineAction({ apolloClient, createTimeline, ecsData: ecsDataMock, @@ -338,16 +338,16 @@ describe('signals actions', () => { describe('apolloClient is not defined', () => { test('it invokes createTimeline with timelineDefaults', async () => { const ecsDataMock: Ecs = { - ...mockEcsDataWithSignal, + ...mockEcsDataWithAlert, signal: { rule: { - ...mockEcsDataWithSignal.signal?.rule!, + ...mockEcsDataWithAlert.signal?.rule!, timeline_id: [''], }, }, }; - await sendSignalToTimelineAction({ + await sendAlertToTimelineAction({ createTimeline, ecsData: ecsDataMock, updateTimelineIsLoading, @@ -363,7 +363,7 @@ describe('signals actions', () => { describe('determineToAndFrom', () => { test('it uses ecs.Data.timestamp if one is provided', () => { const ecsDataMock: Ecs = { - ...mockEcsDataWithSignal, + ...mockEcsDataWithAlert, timestamp: '2020-03-20T17:59:46.349Z', }; const result = determineToAndFrom({ ecsData: ecsDataMock }); @@ -374,7 +374,7 @@ describe('signals actions', () => { test('it uses current time timestamp if ecsData.timestamp is not provided', () => { const { timestamp, ...ecsDataMock } = { - ...mockEcsDataWithSignal, + ...mockEcsDataWithAlert, }; const result = determineToAndFrom({ ecsData: ecsDataMock }); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/actions.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/actions.tsx similarity index 82% rename from x-pack/plugins/siem/public/alerts/components/signals/actions.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/actions.tsx index f01b39ccaba0..cde81d44bc5d 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/actions.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/actions.tsx @@ -8,8 +8,8 @@ import dateMath from '@elastic/datemath'; import { getOr, isEmpty } from 'lodash/fp'; import moment from 'moment'; -import { updateSignalStatus } from '../../containers/detection_engine/signals/api'; -import { SendSignalToTimelineActionProps, UpdateSignalStatusActionProps } from './types'; +import { updateAlertStatus } from '../../containers/detection_engine/alerts/api'; +import { SendAlertToTimelineActionProps, UpdateAlertStatusActionProps } from './types'; import { TimelineNonEcsData, GetOneTimeline, TimelineResult, Ecs } from '../../../graphql/types'; import { oneTimelineQuery } from '../../../timelines/containers/one/index.gql_query'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; @@ -24,7 +24,7 @@ import { replaceTemplateFieldFromDataProviders, } from './helpers'; -export const getUpdateSignalsQuery = (eventIds: Readonly) => { +export const getUpdateAlertsQuery = (eventIds: Readonly) => { return { query: { bool: { @@ -44,35 +44,35 @@ export const getFilterAndRuleBounds = ( const stringFilter = data?.[0].filter((d) => d.field === 'signal.rule.filters')?.[0]?.value ?? []; const eventTimes = data - .flatMap((signal) => signal.filter((d) => d.field === 'signal.original_time')?.[0]?.value ?? []) + .flatMap((alert) => alert.filter((d) => d.field === 'signal.original_time')?.[0]?.value ?? []) .map((d) => moment(d)); return [stringFilter, moment.min(eventTimes).valueOf(), moment.max(eventTimes).valueOf()]; }; -export const updateSignalStatusAction = async ({ +export const updateAlertStatusAction = async ({ query, - signalIds, + alertIds, status, setEventsLoading, setEventsDeleted, onAlertStatusUpdateSuccess, onAlertStatusUpdateFailure, -}: UpdateSignalStatusActionProps) => { +}: UpdateAlertStatusActionProps) => { try { - setEventsLoading({ eventIds: signalIds, isLoading: true }); + setEventsLoading({ eventIds: alertIds, isLoading: true }); - const queryObject = query ? { query: JSON.parse(query) } : getUpdateSignalsQuery(signalIds); + const queryObject = query ? { query: JSON.parse(query) } : getUpdateAlertsQuery(alertIds); - const response = await updateSignalStatus({ query: queryObject, status }); + const response = await updateAlertStatus({ query: queryObject, status }); // TODO: Only delete those that were successfully updated from updatedRules - setEventsDeleted({ eventIds: signalIds, isDeleted: true }); + setEventsDeleted({ eventIds: alertIds, isDeleted: true }); onAlertStatusUpdateSuccess(response.updated, status); } catch (error) { onAlertStatusUpdateFailure(status, error); } finally { - setEventsLoading({ eventIds: signalIds, isLoading: false }); + setEventsLoading({ eventIds: alertIds, isLoading: false }); } }; @@ -91,13 +91,13 @@ export const determineToAndFrom = ({ ecsData }: { ecsData: Ecs }) => { return { to, from }; }; -export const sendSignalToTimelineAction = async ({ +export const sendAlertToTimelineAction = async ({ apolloClient, createTimeline, ecsData, updateTimelineIsLoading, -}: SendSignalToTimelineActionProps) => { - let openSignalInBasicTimeline = true; +}: SendAlertToTimelineActionProps) => { + let openAlertInBasicTimeline = true; const noteContent = ecsData.signal?.rule?.note != null ? ecsData.signal?.rule?.note[0] : ''; const timelineId = ecsData.signal?.rule?.timeline_id != null ? ecsData.signal?.rule?.timeline_id[0] : ''; @@ -120,7 +120,7 @@ export const sendSignalToTimelineAction = async ({ if (!isEmpty(resultingTimeline)) { const timelineTemplate: TimelineResult = omitTypenameInTimeline(resultingTimeline); - openSignalInBasicTimeline = false; + openAlertInBasicTimeline = false; const { timeline } = formatTimelineResultToModel(timelineTemplate, true); const query = replaceTemplateFieldFromQuery( timeline.kqlQuery?.filterQuery?.kuery?.expression ?? '', @@ -162,12 +162,12 @@ export const sendSignalToTimelineAction = async ({ }); } } catch { - openSignalInBasicTimeline = true; + openAlertInBasicTimeline = true; updateTimelineIsLoading({ id: 'timeline-1', isLoading: false }); } } - if (openSignalInBasicTimeline) { + if (openAlertInBasicTimeline) { createTimeline({ from, timeline: { @@ -175,7 +175,7 @@ export const sendSignalToTimelineAction = async ({ dataProviders: [ { and: [], - id: `send-signal-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-signal-id-${ecsData._id}`, + id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-alert-id-${ecsData._id}`, name: ecsData._id, enabled: true, excluded: false, diff --git a/x-pack/plugins/siem/public/alerts/components/signals/signals_filter_group/index.test.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_filter_group/index.test.tsx similarity index 68% rename from x-pack/plugins/siem/public/alerts/components/signals/signals_filter_group/index.test.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_filter_group/index.test.tsx index dd30bb1b0a74..d7fabdabf822 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/signals_filter_group/index.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_filter_group/index.test.tsx @@ -7,11 +7,11 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { SignalsTableFilterGroup } from './index'; +import { AlertsTableFilterGroup } from './index'; -describe('SignalsTableFilterGroup', () => { +describe('AlertsTableFilterGroup', () => { it('renders correctly', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find('EuiFilterButton')).toBeTruthy(); }); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/signals_filter_group/index.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_filter_group/index.tsx similarity index 74% rename from x-pack/plugins/siem/public/alerts/components/signals/signals_filter_group/index.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_filter_group/index.tsx index a8dd22863e3c..8521170637d6 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/signals_filter_group/index.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_filter_group/index.tsx @@ -10,13 +10,13 @@ import * as i18n from '../translations'; export const FILTER_OPEN = 'open'; export const FILTER_CLOSED = 'closed'; -export type SignalFilterOption = typeof FILTER_OPEN | typeof FILTER_CLOSED; +export type AlertFilterOption = typeof FILTER_OPEN | typeof FILTER_CLOSED; interface Props { - onFilterGroupChanged: (filterGroup: SignalFilterOption) => void; + onFilterGroupChanged: (filterGroup: AlertFilterOption) => void; } -const SignalsTableFilterGroupComponent: React.FC = ({ onFilterGroupChanged }) => { +const AlertsTableFilterGroupComponent: React.FC = ({ onFilterGroupChanged }) => { const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); const onClickOpenFilterCallback = useCallback(() => { @@ -32,23 +32,23 @@ const SignalsTableFilterGroupComponent: React.FC = ({ onFilterGroupChange return ( - {i18n.OPEN_SIGNALS} + {i18n.OPEN_ALERTS} - {i18n.CLOSED_SIGNALS} + {i18n.CLOSED_ALERTS} ); }; -export const SignalsTableFilterGroup = React.memo(SignalsTableFilterGroupComponent); +export const AlertsTableFilterGroup = React.memo(AlertsTableFilterGroupComponent); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/index.test.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/index.test.tsx similarity index 76% rename from x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/index.test.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/index.test.tsx index 3b43185c2c16..543e11c9b1e6 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/index.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/index.test.tsx @@ -7,14 +7,14 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { SignalsUtilityBar } from './index'; +import { AlertsUtilityBar } from './index'; jest.mock('../../../../common/lib/kibana'); -describe('SignalsUtilityBar', () => { +describe('AlertsUtilityBar', () => { it('renders correctly', () => { const wrapper = shallow( - { isFilteredToOpen={false} selectAll={jest.fn()} showClearSelection={true} - updateSignalsStatus={jest.fn()} + updateAlertsStatus={jest.fn()} /> ); - expect(wrapper.find('[dataTestSubj="openCloseSignal"]')).toBeTruthy(); + expect(wrapper.find('[dataTestSubj="openCloseAlert"]')).toBeTruthy(); }); }); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/index.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/index.tsx similarity index 77% rename from x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/index.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/index.tsx index e23f4ebdd3d3..68b7039690db 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/index.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/index.tsx @@ -19,10 +19,10 @@ import { import * as i18n from './translations'; import { useUiSetting$ } from '../../../../common/lib/kibana'; import { TimelineNonEcsData } from '../../../../graphql/types'; -import { UpdateSignalsStatus } from '../types'; -import { FILTER_CLOSED, FILTER_OPEN } from '../signals_filter_group'; +import { UpdateAlertsStatus } from '../types'; +import { FILTER_CLOSED, FILTER_OPEN } from '../alerts_filter_group'; -interface SignalsUtilityBarProps { +interface AlertsUtilityBarProps { canUserCRUD: boolean; hasIndexWrite: boolean; areEventsLoading: boolean; @@ -32,10 +32,10 @@ interface SignalsUtilityBarProps { selectedEventIds: Readonly>; showClearSelection: boolean; totalCount: number; - updateSignalsStatus: UpdateSignalsStatus; + updateAlertsStatus: UpdateAlertsStatus; } -const SignalsUtilityBarComponent: React.FC = ({ +const AlertsUtilityBarComponent: React.FC = ({ canUserCRUD, hasIndexWrite, areEventsLoading, @@ -45,16 +45,16 @@ const SignalsUtilityBarComponent: React.FC = ({ isFilteredToOpen, selectAll, showClearSelection, - updateSignalsStatus, + updateAlertsStatus, }) => { const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); const handleUpdateStatus = useCallback(async () => { - await updateSignalsStatus({ - signalIds: Object.keys(selectedEventIds), + await updateAlertsStatus({ + alertIds: Object.keys(selectedEventIds), status: isFilteredToOpen ? FILTER_CLOSED : FILTER_OPEN, }); - }, [selectedEventIds, updateSignalsStatus, isFilteredToOpen]); + }, [selectedEventIds, updateAlertsStatus, isFilteredToOpen]); const formattedTotalCount = numeral(totalCount).format(defaultNumberFormat); const formattedSelectedEventsCount = numeral(Object.keys(selectedEventIds).length).format( @@ -66,25 +66,25 @@ const SignalsUtilityBarComponent: React.FC = ({ - - {i18n.SHOWING_SIGNALS(formattedTotalCount, totalCount)} + + {i18n.SHOWING_ALERTS(formattedTotalCount, totalCount)} {canUserCRUD && hasIndexWrite && ( <> - - {i18n.SELECTED_SIGNALS( + + {i18n.SELECTED_ALERTS( showClearSelection ? formattedTotalCount : formattedSelectedEventsCount, showClearSelection ? totalCount : Object.keys(selectedEventIds).length )} {isFilteredToOpen @@ -104,7 +104,7 @@ const SignalsUtilityBarComponent: React.FC = ({ > {showClearSelection ? i18n.CLEAR_SELECTION - : i18n.SELECT_ALL_SIGNALS(formattedTotalCount, totalCount)} + : i18n.SELECT_ALL_ALERTS(formattedTotalCount, totalCount)} )} @@ -115,8 +115,8 @@ const SignalsUtilityBarComponent: React.FC = ({ ); }; -export const SignalsUtilityBar = React.memo( - SignalsUtilityBarComponent, +export const AlertsUtilityBar = React.memo( + AlertsUtilityBarComponent, (prevProps, nextProps) => prevProps.areEventsLoading === nextProps.areEventsLoading && prevProps.selectedEventIds === nextProps.selectedEventIds && diff --git a/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/translations.ts b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/translations.ts new file mode 100644 index 000000000000..ae5070efc21e --- /dev/null +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/translations.ts @@ -0,0 +1,77 @@ +/* + * 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 SHOWING_ALERTS = (totalAlertsFormatted: string, totalAlerts: number) => + i18n.translate('xpack.siem.detectionEngine.alerts.utilityBar.showingAlertsTitle', { + values: { totalAlertsFormatted, totalAlerts }, + defaultMessage: + 'Showing {totalAlertsFormatted} {totalAlerts, plural, =1 {alert} other {alerts}}', + }); + +export const SELECTED_ALERTS = (selectedAlertsFormatted: string, selectedAlerts: number) => + i18n.translate('xpack.siem.detectionEngine.alerts.utilityBar.selectedAlertsTitle', { + values: { selectedAlertsFormatted, selectedAlerts }, + defaultMessage: + 'Selected {selectedAlertsFormatted} {selectedAlerts, plural, =1 {alert} other {alerts}}', + }); + +export const SELECT_ALL_ALERTS = (totalAlertsFormatted: string, totalAlerts: number) => + i18n.translate('xpack.siem.detectionEngine.alerts.utilityBar.selectAllAlertsTitle', { + values: { totalAlertsFormatted, totalAlerts }, + defaultMessage: + 'Select all {totalAlertsFormatted} {totalAlerts, plural, =1 {alert} other {alerts}}', + }); + +export const CLEAR_SELECTION = i18n.translate( + 'xpack.siem.detectionEngine.alerts.utilityBar.clearSelectionTitle', + { + defaultMessage: 'Clear selection', + } +); + +export const BATCH_ACTIONS = i18n.translate( + 'xpack.siem.detectionEngine.alerts.utilityBar.batchActionsTitle', + { + defaultMessage: 'Batch actions', + } +); + +export const BATCH_ACTION_VIEW_SELECTED_IN_HOSTS = i18n.translate( + 'xpack.siem.detectionEngine.alerts.utilityBar.batchActions.viewSelectedInHostsTitle', + { + defaultMessage: 'View selected in hosts', + } +); + +export const BATCH_ACTION_VIEW_SELECTED_IN_NETWORK = i18n.translate( + 'xpack.siem.detectionEngine.alerts.utilityBar.batchActions.viewSelectedInNetworkTitle', + { + defaultMessage: 'View selected in network', + } +); + +export const BATCH_ACTION_VIEW_SELECTED_IN_TIMELINE = i18n.translate( + 'xpack.siem.detectionEngine.alerts.utilityBar.batchActions.viewSelectedInTimelineTitle', + { + defaultMessage: 'View selected in timeline', + } +); + +export const BATCH_ACTION_OPEN_SELECTED = i18n.translate( + 'xpack.siem.detectionEngine.alerts.utilityBar.batchActions.openSelectedTitle', + { + defaultMessage: 'Open selected', + } +); + +export const BATCH_ACTION_CLOSE_SELECTED = i18n.translate( + 'xpack.siem.detectionEngine.alerts.utilityBar.batchActions.closeSelectedTitle', + { + defaultMessage: 'Close selected', + } +); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/default_config.test.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/default_config.test.tsx similarity index 67% rename from x-pack/plugins/siem/public/alerts/components/signals/default_config.test.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/default_config.test.tsx index 7821bfaaf957..b191464984c5 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/default_config.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/default_config.test.tsx @@ -9,23 +9,23 @@ import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import { Filter } from '../../../../../../../src/plugins/data/common/es_query'; import { TimelineAction } from '../../../timelines/components/timeline/body/actions'; -import { buildSignalsRuleIdFilter, getSignalsActions } from './default_config'; +import { buildAlertsRuleIdFilter, getAlertActions } from './default_config'; import { CreateTimeline, SetEventsDeletedProps, SetEventsLoadingProps, UpdateTimelineLoading, } from './types'; -import { mockEcsDataWithSignal } from '../../../common/mock/mock_ecs'; -import { sendSignalToTimelineAction, updateSignalStatusAction } from './actions'; +import { mockEcsDataWithAlert } from '../../../common/mock/mock_ecs'; +import { sendAlertToTimelineAction, updateAlertStatusAction } from './actions'; import * as i18n from './translations'; jest.mock('./actions'); -describe('signals default_config', () => { - describe('buildSignalsRuleIdFilter', () => { +describe('alerts default_config', () => { + describe('buildAlertsRuleIdFilter', () => { test('given a rule id this will return an array with a single filter', () => { - const filters: Filter[] = buildSignalsRuleIdFilter('rule-id-1'); + const filters: Filter[] = buildAlertsRuleIdFilter('rule-id-1'); const expectedFilter: Filter = { meta: { alias: null, @@ -48,7 +48,7 @@ describe('signals default_config', () => { }); }); - describe('getSignalsActions', () => { + describe('getAlertActions', () => { let setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; let setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; let createTimeline: CreateTimeline; @@ -67,8 +67,8 @@ describe('signals default_config', () => { }); describe('timeline tooltip', () => { - test('it invokes sendSignalToTimelineAction when button clicked', () => { - const signalsActions = getSignalsActions({ + test('it invokes sendAlertToTimelineAction when button clicked', () => { + const alertsActions = getAlertActions({ canUserCRUD: true, hasIndexWrite: true, setEventsLoading, @@ -79,24 +79,24 @@ describe('signals default_config', () => { onAlertStatusUpdateSuccess, onAlertStatusUpdateFailure, }); - const timelineAction = signalsActions[0].getAction({ + const timelineAction = alertsActions[0].getAction({ eventId: 'even-id', - ecsData: mockEcsDataWithSignal, + ecsData: mockEcsDataWithAlert, }); const wrapper = mount(timelineAction as React.ReactElement); wrapper.find(EuiButtonIcon).simulate('click'); - expect(sendSignalToTimelineAction).toHaveBeenCalled(); + expect(sendAlertToTimelineAction).toHaveBeenCalled(); }); }); - describe('signal open action', () => { - let signalsActions: TimelineAction[]; - let signalOpenAction: JSX.Element; + describe('alert open action', () => { + let alertsActions: TimelineAction[]; + let alertOpenAction: JSX.Element; let wrapper: ReactWrapper; beforeEach(() => { - signalsActions = getSignalsActions({ + alertsActions = getAlertActions({ canUserCRUD: true, hasIndexWrite: true, setEventsLoading, @@ -108,23 +108,23 @@ describe('signals default_config', () => { onAlertStatusUpdateFailure, }); - signalOpenAction = signalsActions[1].getAction({ + alertOpenAction = alertsActions[1].getAction({ eventId: 'event-id', - ecsData: mockEcsDataWithSignal, + ecsData: mockEcsDataWithAlert, }); - wrapper = mount(signalOpenAction as React.ReactElement); + wrapper = mount(alertOpenAction as React.ReactElement); }); afterEach(() => { wrapper.unmount(); }); - test('it invokes updateSignalStatusAction when button clicked', () => { + test('it invokes updateAlertStatusAction when button clicked', () => { wrapper.find(EuiButtonIcon).simulate('click'); - expect(updateSignalStatusAction).toHaveBeenCalledWith({ - signalIds: ['event-id'], + expect(updateAlertStatusAction).toHaveBeenCalledWith({ + alertIds: ['event-id'], status: 'open', setEventsLoading, setEventsDeleted, @@ -134,27 +134,27 @@ describe('signals default_config', () => { }); test('it displays expected text on hover', () => { - const openSignal = wrapper.find(EuiToolTip); - openSignal.simulate('mouseOver'); + const openAlert = wrapper.find(EuiToolTip); + openAlert.simulate('mouseOver'); const tooltip = wrapper.find('.euiToolTipPopover').text(); - expect(tooltip).toEqual(i18n.ACTION_OPEN_SIGNAL); + expect(tooltip).toEqual(i18n.ACTION_OPEN_ALERT); }); test('it displays expected icon', () => { const icon = wrapper.find(EuiButtonIcon).props().iconType; - expect(icon).toEqual('securitySignalDetected'); + expect(icon).toEqual('securityAlertDetected'); }); }); - describe('signal close action', () => { - let signalsActions: TimelineAction[]; - let signalCloseAction: JSX.Element; + describe('alert close action', () => { + let alertsActions: TimelineAction[]; + let alertCloseAction: JSX.Element; let wrapper: ReactWrapper; beforeEach(() => { - signalsActions = getSignalsActions({ + alertsActions = getAlertActions({ canUserCRUD: true, hasIndexWrite: true, setEventsLoading, @@ -166,23 +166,23 @@ describe('signals default_config', () => { onAlertStatusUpdateFailure, }); - signalCloseAction = signalsActions[1].getAction({ + alertCloseAction = alertsActions[1].getAction({ eventId: 'event-id', - ecsData: mockEcsDataWithSignal, + ecsData: mockEcsDataWithAlert, }); - wrapper = mount(signalCloseAction as React.ReactElement); + wrapper = mount(alertCloseAction as React.ReactElement); }); afterEach(() => { wrapper.unmount(); }); - test('it invokes updateSignalStatusAction when status button clicked', () => { + test('it invokes updateAlertStatusAction when status button clicked', () => { wrapper.find(EuiButtonIcon).simulate('click'); - expect(updateSignalStatusAction).toHaveBeenCalledWith({ - signalIds: ['event-id'], + expect(updateAlertStatusAction).toHaveBeenCalledWith({ + alertIds: ['event-id'], status: 'closed', setEventsLoading, setEventsDeleted, @@ -192,16 +192,16 @@ describe('signals default_config', () => { }); test('it displays expected text on hover', () => { - const closeSignal = wrapper.find(EuiToolTip); - closeSignal.simulate('mouseOver'); + const closeAlert = wrapper.find(EuiToolTip); + closeAlert.simulate('mouseOver'); const tooltip = wrapper.find('.euiToolTipPopover').text(); - expect(tooltip).toEqual(i18n.ACTION_CLOSE_SIGNAL); + expect(tooltip).toEqual(i18n.ACTION_CLOSE_ALERT); }); test('it displays expected icon', () => { const icon = wrapper.find(EuiButtonIcon).props().iconType; - expect(icon).toEqual('securitySignalResolved'); + expect(icon).toEqual('securityAlertResolved'); }); }); }); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/default_config.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/default_config.tsx similarity index 81% rename from x-pack/plugins/siem/public/alerts/components/signals/default_config.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/default_config.tsx index 1269f31064e9..6cef2e7c53c4 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/default_config.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/default_config.tsx @@ -23,8 +23,8 @@ import { import { ColumnHeaderOptions, SubsetTimelineModel } from '../../../timelines/store/timeline/model'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; -import { FILTER_OPEN } from './signals_filter_group'; -import { sendSignalToTimelineAction, updateSignalStatusAction } from './actions'; +import { FILTER_OPEN } from './alerts_filter_group'; +import { sendAlertToTimelineAction, updateAlertStatusAction } from './actions'; import * as i18n from './translations'; import { CreateTimeline, @@ -33,7 +33,7 @@ import { UpdateTimelineLoading, } from './types'; -export const signalsOpenFilters: Filter[] = [ +export const alertsOpenFilters: Filter[] = [ { meta: { alias: null, @@ -53,7 +53,7 @@ export const signalsOpenFilters: Filter[] = [ }, ]; -export const signalsClosedFilters: Filter[] = [ +export const alertsClosedFilters: Filter[] = [ { meta: { alias: null, @@ -73,7 +73,7 @@ export const signalsClosedFilters: Filter[] = [ }, ]; -export const buildSignalsRuleIdFilter = (ruleId: string): Filter[] => [ +export const buildAlertsRuleIdFilter = (ruleId: string): Filter[] => [ { meta: { alias: null, @@ -93,7 +93,7 @@ export const buildSignalsRuleIdFilter = (ruleId: string): Filter[] => [ }, ]; -export const signalsHeaders: ColumnHeaderOptions[] = [ +export const alertsHeaders: ColumnHeaderOptions[] = [ { columnHeaderType: defaultColumnHeaderType, id: '@timestamp', @@ -102,32 +102,32 @@ export const signalsHeaders: ColumnHeaderOptions[] = [ { columnHeaderType: defaultColumnHeaderType, id: 'signal.rule.name', - label: i18n.SIGNALS_HEADERS_RULE, + label: i18n.ALERTS_HEADERS_RULE, linkField: 'signal.rule.id', width: DEFAULT_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'signal.rule.version', - label: i18n.SIGNALS_HEADERS_VERSION, + label: i18n.ALERTS_HEADERS_VERSION, width: 100, }, { columnHeaderType: defaultColumnHeaderType, id: 'signal.rule.type', - label: i18n.SIGNALS_HEADERS_METHOD, + label: i18n.ALERTS_HEADERS_METHOD, width: 100, }, { columnHeaderType: defaultColumnHeaderType, id: 'signal.rule.severity', - label: i18n.SIGNALS_HEADERS_SEVERITY, + label: i18n.ALERTS_HEADERS_SEVERITY, width: 105, }, { columnHeaderType: defaultColumnHeaderType, id: 'signal.rule.risk_score', - label: i18n.SIGNALS_HEADERS_RISK_SCORE, + label: i18n.ALERTS_HEADERS_RISK_SCORE, width: 115, }, { @@ -171,9 +171,9 @@ export const signalsHeaders: ColumnHeaderOptions[] = [ }, ]; -export const signalsDefaultModel: SubsetTimelineModel = { +export const alertsDefaultModel: SubsetTimelineModel = { ...timelineDefaults, - columns: signalsHeaders, + columns: alertsHeaders, showCheckboxes: true, showRowRenderers: false, }; @@ -189,7 +189,7 @@ export const requiredFieldsForActions = [ 'signal.rule.id', ]; -export const getSignalsActions = ({ +export const getAlertActions = ({ apolloClient, canUserCRUD, hasIndexWrite, @@ -215,13 +215,13 @@ export const getSignalsActions = ({ { getAction: ({ ecsData }: TimelineActionProps): JSX.Element => ( - sendSignalToTimelineAction({ + sendAlertToTimelineAction({ apolloClient, createTimeline, ecsData, @@ -233,20 +233,20 @@ export const getSignalsActions = ({ /> ), - id: 'sendSignalToTimeline', + id: 'sendAlertToTimeline', width: 26, }, { getAction: ({ eventId }: TimelineActionProps): JSX.Element => ( - updateSignalStatusAction({ - signalIds: [eventId], + updateAlertStatusAction({ + alertIds: [eventId], status, setEventsLoading, setEventsDeleted, @@ -255,12 +255,12 @@ export const getSignalsActions = ({ }) } isDisabled={!canUserCRUD || !hasIndexWrite} - iconType={status === FILTER_OPEN ? 'securitySignalDetected' : 'securitySignalResolved'} + iconType={status === FILTER_OPEN ? 'securityAlertDetected' : 'securityAlertResolved'} aria-label="Next" /> ), - id: 'updateSignalStatus', + id: 'updateAlertStatus', width: 26, }, ]; diff --git a/x-pack/plugins/siem/public/alerts/components/signals/helpers.test.ts b/x-pack/plugins/siem/public/alerts/components/alerts_table/helpers.test.ts similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/signals/helpers.test.ts rename to x-pack/plugins/siem/public/alerts/components/alerts_table/helpers.test.ts diff --git a/x-pack/plugins/siem/public/alerts/components/signals/helpers.ts b/x-pack/plugins/siem/public/alerts/components/alerts_table/helpers.ts similarity index 98% rename from x-pack/plugins/siem/public/alerts/components/signals/helpers.ts rename to x-pack/plugins/siem/public/alerts/components/alerts_table/helpers.ts index e9b8fdda8405..11a03b042689 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/helpers.ts +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/helpers.ts @@ -19,7 +19,7 @@ interface FindValueToChangeInQuery { /** * Fields that will be replaced with the template strings from a a saved timeline template. - * This is used for the signals detection engine feature when you save a timeline template + * This is used for the alerts detection engine feature when you save a timeline template * and are the fields you can replace when creating a template. */ const templateFields = [ diff --git a/x-pack/plugins/siem/public/alerts/components/signals/index.test.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/index.test.tsx similarity index 85% rename from x-pack/plugins/siem/public/alerts/components/signals/index.test.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/index.test.tsx index b66a9fc88104..51fdd828bcdd 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/index.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/index.test.tsx @@ -7,12 +7,12 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { SignalsTableComponent } from './index'; +import { AlertsTableComponent } from './index'; -describe('SignalsTableComponent', () => { +describe('AlertsTableComponent', () => { it('renders correctly', () => { const wrapper = shallow( - { /> ); - expect(wrapper.find('[title="Signals"]')).toBeTruthy(); + expect(wrapper.find('[title="Alerts"]')).toBeTruthy(); }); }); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/index.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/index.tsx similarity index 81% rename from x-pack/plugins/siem/public/alerts/components/signals/index.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/index.tsx index effb6a2450dc..05c811d8e19b 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/index.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/index.tsx @@ -22,28 +22,28 @@ import { TimelineModel } from '../../../timelines/store/timeline/model'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; import { useApolloClient } from '../../../common/utils/apollo_context'; -import { updateSignalStatusAction } from './actions'; +import { updateAlertStatusAction } from './actions'; import { - getSignalsActions, + getAlertActions, requiredFieldsForActions, - signalsClosedFilters, - signalsDefaultModel, - signalsOpenFilters, + alertsClosedFilters, + alertsDefaultModel, + alertsOpenFilters, } from './default_config'; import { FILTER_CLOSED, FILTER_OPEN, - SignalFilterOption, - SignalsTableFilterGroup, -} from './signals_filter_group'; -import { SignalsUtilityBar } from './signals_utility_bar'; + AlertFilterOption, + AlertsTableFilterGroup, +} from './alerts_filter_group'; +import { AlertsUtilityBar } from './alerts_utility_bar'; import * as i18n from './translations'; import { CreateTimelineProps, SetEventsDeletedProps, SetEventsLoadingProps, - UpdateSignalsStatusCallback, - UpdateSignalsStatusProps, + UpdateAlertsStatusCallback, + UpdateAlertsStatusProps, } from './types'; import { dispatchUpdateTimeline } from '../../../timelines/components/open_timeline/helpers'; import { @@ -52,7 +52,7 @@ import { displayErrorToast, } from '../../../common/components/toasters'; -export const SIGNALS_PAGE_TIMELINE_ID = 'signals-page'; +export const ALERTS_TABLE_TIMELINE_ID = 'alerts-table'; interface OwnProps { canUserCRUD: boolean; @@ -64,9 +64,9 @@ interface OwnProps { to: number; } -type SignalsTableComponentProps = OwnProps & PropsFromRedux; +type AlertsTableComponentProps = OwnProps & PropsFromRedux; -export const SignalsTableComponent: React.FC = ({ +export const AlertsTableComponent: React.FC = ({ canUserCRUD, clearEventsDeleted, clearEventsLoading, @@ -91,7 +91,7 @@ export const SignalsTableComponent: React.FC = ({ const apolloClient = useApolloClient(); const [showClearSelectionAction, setShowClearSelectionAction] = useState(false); - const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); + const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); const [{ browserFields, indexPatterns }] = useFetchIndexPatterns( signalsIndex !== '' ? [signalsIndex] : [] ); @@ -140,16 +140,16 @@ export const SignalsTableComponent: React.FC = ({ const setEventsLoadingCallback = useCallback( ({ eventIds, isLoading }: SetEventsLoadingProps) => { - setEventsLoading!({ id: SIGNALS_PAGE_TIMELINE_ID, eventIds, isLoading }); + setEventsLoading!({ id: ALERTS_TABLE_TIMELINE_ID, eventIds, isLoading }); }, - [setEventsLoading, SIGNALS_PAGE_TIMELINE_ID] + [setEventsLoading, ALERTS_TABLE_TIMELINE_ID] ); const setEventsDeletedCallback = useCallback( ({ eventIds, isDeleted }: SetEventsDeletedProps) => { - setEventsDeleted!({ id: SIGNALS_PAGE_TIMELINE_ID, eventIds, isDeleted }); + setEventsDeleted!({ id: ALERTS_TABLE_TIMELINE_ID, eventIds, isDeleted }); }, - [setEventsDeleted, SIGNALS_PAGE_TIMELINE_ID] + [setEventsDeleted, ALERTS_TABLE_TIMELINE_ID] ); const onAlertStatusUpdateSuccess = useCallback( @@ -184,10 +184,10 @@ export const SignalsTableComponent: React.FC = ({ // Callback for when open/closed filter changes const onFilterGroupChangedCallback = useCallback( - (newFilterGroup: SignalFilterOption) => { - clearEventsLoading!({ id: SIGNALS_PAGE_TIMELINE_ID }); - clearEventsDeleted!({ id: SIGNALS_PAGE_TIMELINE_ID }); - clearSelected!({ id: SIGNALS_PAGE_TIMELINE_ID }); + (newFilterGroup: AlertFilterOption) => { + clearEventsLoading!({ id: ALERTS_TABLE_TIMELINE_ID }); + clearEventsDeleted!({ id: ALERTS_TABLE_TIMELINE_ID }); + clearSelected!({ id: ALERTS_TABLE_TIMELINE_ID }); setFilterGroup(newFilterGroup); }, [clearEventsLoading, clearEventsDeleted, clearSelected, setFilterGroup] @@ -195,7 +195,7 @@ export const SignalsTableComponent: React.FC = ({ // Callback for clearing entire selection from utility bar const clearSelectionCallback = useCallback(() => { - clearSelected!({ id: SIGNALS_PAGE_TIMELINE_ID }); + clearSelected!({ id: ALERTS_TABLE_TIMELINE_ID }); setSelectAll(false); setShowClearSelectionAction(false); }, [clearSelected, setSelectAll, setShowClearSelectionAction]); @@ -208,11 +208,11 @@ export const SignalsTableComponent: React.FC = ({ setShowClearSelectionAction(true); }, [setSelectAll, setShowClearSelectionAction]); - const updateSignalsStatusCallback: UpdateSignalsStatusCallback = useCallback( - async (refetchQuery: inputsModel.Refetch, { signalIds, status }: UpdateSignalsStatusProps) => { - await updateSignalStatusAction({ + const updateAlertsStatusCallback: UpdateAlertsStatusCallback = useCallback( + async (refetchQuery: inputsModel.Refetch, { alertIds, status }: UpdateAlertsStatusProps) => { + await updateAlertStatusAction({ query: showClearSelectionAction ? getGlobalQuery()?.filterQuery : undefined, - signalIds: Object.keys(selectedEventIds), + alertIds: Object.keys(selectedEventIds), status, setEventsDeleted: setEventsDeletedCallback, setEventsLoading: setEventsLoadingCallback, @@ -232,11 +232,11 @@ export const SignalsTableComponent: React.FC = ({ ] ); - // Callback for creating the SignalUtilityBar which receives totalCount from EventsViewer component + // Callback for creating the AlertsUtilityBar which receives totalCount from EventsViewer component const utilityBarCallback = useCallback( (refetchQuery: inputsModel.Refetch, totalCount: number) => { return ( - 0} clearSelection={clearSelectionCallback} @@ -246,7 +246,7 @@ export const SignalsTableComponent: React.FC = ({ selectedEventIds={selectedEventIds} showClearSelection={showClearSelectionAction} totalCount={totalCount} - updateSignalsStatus={updateSignalsStatusCallback.bind(null, refetchQuery)} + updateAlertsStatus={updateAlertsStatusCallback.bind(null, refetchQuery)} /> ); }, @@ -259,14 +259,14 @@ export const SignalsTableComponent: React.FC = ({ selectAllCallback, selectedEventIds, showClearSelectionAction, - updateSignalsStatusCallback, + updateAlertsStatusCallback, ] ); - // Send to Timeline / Update Signal Status Actions for each table row + // Send to Timeline / Update Alert Status Actions for each table row const additionalActions = useMemo( () => - getSignalsActions({ + getAlertActions({ apolloClient, canUserCRUD, hasIndexWrite, @@ -295,38 +295,38 @@ export const SignalsTableComponent: React.FC = ({ const defaultIndices = useMemo(() => [signalsIndex], [signalsIndex]); const defaultFiltersMemo = useMemo(() => { if (isEmpty(defaultFilters)) { - return filterGroup === FILTER_OPEN ? signalsOpenFilters : signalsClosedFilters; + return filterGroup === FILTER_OPEN ? alertsOpenFilters : alertsClosedFilters; } else if (defaultFilters != null && !isEmpty(defaultFilters)) { return [ ...defaultFilters, - ...(filterGroup === FILTER_OPEN ? signalsOpenFilters : signalsClosedFilters), + ...(filterGroup === FILTER_OPEN ? alertsOpenFilters : alertsClosedFilters), ]; } }, [defaultFilters, filterGroup]); const timelineTypeContext = useMemo( () => ({ - documentType: i18n.SIGNALS_DOCUMENT_TYPE, - footerText: i18n.TOTAL_COUNT_OF_SIGNALS, - loadingText: i18n.LOADING_SIGNALS, + documentType: i18n.ALERTS_DOCUMENT_TYPE, + footerText: i18n.TOTAL_COUNT_OF_ALERTS, + loadingText: i18n.LOADING_ALERTS, queryFields: requiredFieldsForActions, timelineActions: additionalActions, - title: i18n.SIGNALS_TABLE_TITLE, + title: i18n.ALERTS_TABLE_TITLE, selectAll: canUserCRUD ? selectAll : false, }), [additionalActions, canUserCRUD, selectAll] ); const headerFilterGroup = useMemo( - () => , + () => , [onFilterGroupChangedCallback] ); if (loading || isEmpty(signalsIndex)) { return ( - - + + ); } @@ -335,10 +335,10 @@ export const SignalsTableComponent: React.FC = ({ { const getGlobalInputs = inputsSelectors.globalSelector(); const mapStateToProps = (state: State) => { const timeline: TimelineModel = - getTimeline(state, SIGNALS_PAGE_TIMELINE_ID) ?? timelineDefaults; + getTimeline(state, ALERTS_TABLE_TIMELINE_ID) ?? timelineDefaults; const { deletedEventIds, isSelectAllChecked, loadingEventIds, selectedEventIds } = timeline; const globalInputs: inputsModel.InputsRange = getGlobalInputs(state); @@ -401,4 +401,4 @@ const connector = connect(makeMapStateToProps, mapDispatchToProps); type PropsFromRedux = ConnectedProps; -export const SignalsTable = connector(React.memo(SignalsTableComponent)); +export const AlertsTable = connector(React.memo(AlertsTableComponent)); diff --git a/x-pack/plugins/siem/public/alerts/components/alerts_table/translations.ts b/x-pack/plugins/siem/public/alerts/components/alerts_table/translations.ts new file mode 100644 index 000000000000..4f34e9d031ee --- /dev/null +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/translations.ts @@ -0,0 +1,128 @@ +/* + * 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 PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.pageTitle', { + defaultMessage: 'Detection engine', +}); + +export const ALERTS_TABLE_TITLE = i18n.translate('xpack.siem.detectionEngine.alerts.tableTitle', { + defaultMessage: 'Alert list', +}); + +export const ALERTS_DOCUMENT_TYPE = i18n.translate( + 'xpack.siem.detectionEngine.alerts.documentTypeTitle', + { + defaultMessage: 'Alerts', + } +); + +export const OPEN_ALERTS = i18n.translate('xpack.siem.detectionEngine.alerts.openAlertsTitle', { + defaultMessage: 'Open alerts', +}); + +export const CLOSED_ALERTS = i18n.translate('xpack.siem.detectionEngine.alerts.closedAlertsTitle', { + defaultMessage: 'Closed alerts', +}); + +export const LOADING_ALERTS = i18n.translate( + 'xpack.siem.detectionEngine.alerts.loadingAlertsTitle', + { + defaultMessage: 'Loading Alerts', + } +); + +export const TOTAL_COUNT_OF_ALERTS = i18n.translate( + 'xpack.siem.detectionEngine.alerts.totalCountOfAlertsTitle', + { + defaultMessage: 'alerts match the search criteria', + } +); + +export const ALERTS_HEADERS_RULE = i18n.translate( + 'xpack.siem.eventsViewer.alerts.defaultHeaders.ruleTitle', + { + defaultMessage: 'Rule', + } +); + +export const ALERTS_HEADERS_VERSION = i18n.translate( + 'xpack.siem.eventsViewer.alerts.defaultHeaders.versionTitle', + { + defaultMessage: 'Version', + } +); + +export const ALERTS_HEADERS_METHOD = i18n.translate( + 'xpack.siem.eventsViewer.alerts.defaultHeaders.methodTitle', + { + defaultMessage: 'Method', + } +); + +export const ALERTS_HEADERS_SEVERITY = i18n.translate( + 'xpack.siem.eventsViewer.alerts.defaultHeaders.severityTitle', + { + defaultMessage: 'Severity', + } +); + +export const ALERTS_HEADERS_RISK_SCORE = i18n.translate( + 'xpack.siem.eventsViewer.alerts.defaultHeaders.riskScoreTitle', + { + defaultMessage: 'Risk Score', + } +); + +export const ACTION_OPEN_ALERT = i18n.translate( + 'xpack.siem.detectionEngine.alerts.actions.openAlertTitle', + { + defaultMessage: 'Open alert', + } +); + +export const ACTION_CLOSE_ALERT = i18n.translate( + 'xpack.siem.detectionEngine.alerts.actions.closeAlertTitle', + { + defaultMessage: 'Close alert', + } +); + +export const ACTION_INVESTIGATE_IN_TIMELINE = i18n.translate( + 'xpack.siem.detectionEngine.alerts.actions.investigateInTimelineTitle', + { + defaultMessage: 'Investigate in timeline', + } +); + +export const CLOSED_ALERT_SUCCESS_TOAST = (totalAlerts: number) => + i18n.translate('xpack.siem.detectionEngine.alerts.closedAlertSuccessToastMessage', { + values: { totalAlerts }, + defaultMessage: + 'Successfully closed {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', + }); + +export const OPENED_ALERT_SUCCESS_TOAST = (totalAlerts: number) => + i18n.translate('xpack.siem.detectionEngine.alerts.openedAlertSuccessToastMessage', { + values: { totalAlerts }, + defaultMessage: + 'Successfully opened {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', + }); + +export const CLOSED_ALERT_FAILED_TOAST = i18n.translate( + 'xpack.siem.detectionEngine.alerts.closedAlertFailedToastMessage', + { + defaultMessage: 'Failed to close alert(s).', + } +); + +export const OPENED_ALERT_FAILED_TOAST = i18n.translate( + 'xpack.siem.detectionEngine.alerts.openedAlertFailedToastMessage', + { + defaultMessage: 'Failed to open alert(s)', + } +); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/types.ts b/x-pack/plugins/siem/public/alerts/components/alerts_table/types.ts similarity index 77% rename from x-pack/plugins/siem/public/alerts/components/signals/types.ts rename to x-pack/plugins/siem/public/alerts/components/alerts_table/types.ts index 542aa61074ce..ba342ae44185 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/types.ts +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/types.ts @@ -20,20 +20,20 @@ export interface SetEventsDeletedProps { isDeleted: boolean; } -export interface UpdateSignalsStatusProps { - signalIds: string[]; +export interface UpdateAlertsStatusProps { + alertIds: string[]; status: 'open' | 'closed'; } -export type UpdateSignalsStatusCallback = ( +export type UpdateAlertsStatusCallback = ( refetchQuery: inputsModel.Refetch, - { signalIds, status }: UpdateSignalsStatusProps + { alertIds, status }: UpdateAlertsStatusProps ) => void; -export type UpdateSignalsStatus = ({ signalIds, status }: UpdateSignalsStatusProps) => void; +export type UpdateAlertsStatus = ({ alertIds, status }: UpdateAlertsStatusProps) => void; -export interface UpdateSignalStatusActionProps { +export interface UpdateAlertStatusActionProps { query?: string; - signalIds: string[]; + alertIds: string[]; status: 'open' | 'closed'; setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; @@ -41,9 +41,7 @@ export interface UpdateSignalStatusActionProps { onAlertStatusUpdateFailure: (status: string, error: Error) => void; } -export type SendSignalsToTimeline = () => void; - -export interface SendSignalToTimelineActionProps { +export interface SendAlertToTimelineActionProps { apolloClient?: ApolloClient<{}>; createTimeline: CreateTimeline; ecsData: Ecs; diff --git a/x-pack/plugins/siem/public/alerts/components/detection_engine_header_page/translations.ts b/x-pack/plugins/siem/public/alerts/components/detection_engine_header_page/translations.ts index 303431a559e8..651faf0b1731 100644 --- a/x-pack/plugins/siem/public/alerts/components/detection_engine_header_page/translations.ts +++ b/x-pack/plugins/siem/public/alerts/components/detection_engine_header_page/translations.ts @@ -17,6 +17,6 @@ export const PAGE_BADGE_TOOLTIP = i18n.translate( 'xpack.siem.detectionEngine.headerPage.pageBadgeTooltip', { defaultMessage: - 'Detections is still in beta. Please help us improve by reporting issues or bugs in the Kibana repo.', + 'Alerts is still in beta. Please help us improve by reporting issues or bugs in the Kibana repo.', } ); diff --git a/x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/index.test.tsx b/x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/index.test.tsx similarity index 72% rename from x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/index.test.tsx rename to x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/index.test.tsx index 2e6890e60fc6..2b1173f8a484 100644 --- a/x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/index.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/index.test.tsx @@ -7,11 +7,11 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { NoWriteSignalsCallOut } from './index'; +import { NoWriteAlertsCallOut } from './index'; -describe('no_write_signals_callout', () => { +describe('no_write_alerts_callout', () => { it('renders correctly', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find('EuiCallOut')).toBeTruthy(); }); diff --git a/x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/index.tsx b/x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/index.tsx similarity index 72% rename from x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/index.tsx rename to x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/index.tsx index 195053199845..dcb50ef43a84 100644 --- a/x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/index.tsx +++ b/x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/index.tsx @@ -9,13 +9,13 @@ import React, { memo, useCallback, useState } from 'react'; import * as i18n from './translations'; -const NoWriteSignalsCallOutComponent = () => { +const NoWriteAlertsCallOutComponent = () => { const [showCallOut, setShowCallOut] = useState(true); const handleCallOut = useCallback(() => setShowCallOut(false), [setShowCallOut]); return showCallOut ? ( - -

{i18n.NO_WRITE_SIGNALS_CALLOUT_MSG}

+ +

{i18n.NO_WRITE_ALERTS_CALLOUT_MSG}

{i18n.DISMISS_CALLOUT} @@ -23,4 +23,4 @@ const NoWriteSignalsCallOutComponent = () => { ) : null; }; -export const NoWriteSignalsCallOut = memo(NoWriteSignalsCallOutComponent); +export const NoWriteAlertsCallOut = memo(NoWriteAlertsCallOutComponent); diff --git a/x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/translations.ts b/x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/translations.ts similarity index 52% rename from x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/translations.ts rename to x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/translations.ts index 065d775e1dc6..f79ede56ef9a 100644 --- a/x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/translations.ts +++ b/x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/translations.ts @@ -6,23 +6,23 @@ import { i18n } from '@kbn/i18n'; -export const NO_WRITE_SIGNALS_CALLOUT_TITLE = i18n.translate( - 'xpack.siem.detectionEngine.noWriteSignalsCallOutTitle', +export const NO_WRITE_ALERTS_CALLOUT_TITLE = i18n.translate( + 'xpack.siem.detectionEngine.noWriteAlertsCallOutTitle', { - defaultMessage: 'Signals index permissions required', + defaultMessage: 'Alerts index permissions required', } ); -export const NO_WRITE_SIGNALS_CALLOUT_MSG = i18n.translate( - 'xpack.siem.detectionEngine.noWriteSignalsCallOutMsg', +export const NO_WRITE_ALERTS_CALLOUT_MSG = i18n.translate( + 'xpack.siem.detectionEngine.noWriteAlertsCallOutMsg', { defaultMessage: - 'You are currently missing the required permissions to update signals. Please contact your administrator for further assistance.', + 'You are currently missing the required permissions to update alerts. Please contact your administrator for further assistance.', } ); export const DISMISS_CALLOUT = i18n.translate( - 'xpack.siem.detectionEngine.dismissNoWriteSignalButton', + 'xpack.siem.detectionEngine.dismissNoWriteAlertButton', { defaultMessage: 'Dismiss', } diff --git a/x-pack/plugins/siem/public/alerts/components/rules/pre_packaged_rules/translations.ts b/x-pack/plugins/siem/public/alerts/components/rules/pre_packaged_rules/translations.ts index 407dedbf27ba..9b36d96cef9c 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/pre_packaged_rules/translations.ts +++ b/x-pack/plugins/siem/public/alerts/components/rules/pre_packaged_rules/translations.ts @@ -17,7 +17,7 @@ export const PRE_BUILT_MSG = i18n.translate( 'xpack.siem.detectionEngine.rules.prePackagedRules.emptyPromptMessage', { defaultMessage: - 'Elastic SIEM comes with prebuilt detection rules that run in the background and create signals when their conditions are met. By default, all prebuilt rules are disabled and you select which rules you want to activate.', + 'Elastic SIEM comes with prebuilt detection rules that run in the background and create alerts when their conditions are met. By default, all prebuilt rules are disabled and you select which rules you want to activate.', } ); diff --git a/x-pack/plugins/siem/public/alerts/components/rules/rule_actions_field/index.tsx b/x-pack/plugins/siem/public/alerts/components/rules/rule_actions_field/index.tsx index 5823612faac1..b77de683d5f2 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/rule_actions_field/index.tsx +++ b/x-pack/plugins/siem/public/alerts/components/rules/rule_actions_field/index.tsx @@ -26,7 +26,7 @@ type ThrottleSelectField = typeof SelectField; const DEFAULT_ACTION_GROUP_ID = 'default'; const DEFAULT_ACTION_MESSAGE = - 'Rule {{context.rule.name}} generated {{state.signals_count}} signals'; + 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts'; const FieldErrorsContainer = styled.div` p { diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_about_rule/schema.tsx b/x-pack/plugins/siem/public/alerts/components/rules/step_about_rule/schema.tsx index 33aa02adf3f1..69e8ed10d1b3 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/step_about_rule/schema.tsx +++ b/x-pack/plugins/siem/public/alerts/components/rules/step_about_rule/schema.tsx @@ -183,7 +183,7 @@ export const schema: FormSchema = { }), helpText: i18n.translate('xpack.siem.detectionEngine.createRule.stepAboutRule.guideHelpText', { defaultMessage: - 'Provide helpful information for analysts that are performing a signal investigation. This guide will appear on both the rule details page and in timelines created from signals generated by this rule.', + 'Provide helpful information for analysts that are performing an alert investigation. This guide will appear on both the rule details page and in timelines created from alerts generated by this rule.', }), labelAppend: OptionalFieldLabel, }, diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_define_rule/schema.tsx b/x-pack/plugins/siem/public/alerts/components/rules/step_define_rule/schema.tsx index 14afa63ef360..0c309c8c51a1 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/step_define_rule/schema.tsx +++ b/x-pack/plugins/siem/public/alerts/components/rules/step_define_rule/schema.tsx @@ -168,7 +168,7 @@ export const schema: FormSchema = { helpText: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepAboutRule.fieldTimelineTemplateHelpText', { - defaultMessage: 'Select which timeline to use when investigating generated signals.', + defaultMessage: 'Select which timeline to use when investigating generated alerts.', } ), }, diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_schedule_rule/schema.tsx b/x-pack/plugins/siem/public/alerts/components/rules/step_schedule_rule/schema.tsx index 99ff8a672737..d010a3128b24 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/step_schedule_rule/schema.tsx +++ b/x-pack/plugins/siem/public/alerts/components/rules/step_schedule_rule/schema.tsx @@ -22,8 +22,7 @@ export const schema: FormSchema = { helpText: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldIntervalHelpText', { - defaultMessage: - 'Rules run periodically and detect signals within the specified time frame.', + defaultMessage: 'Rules run periodically and detect alerts within the specified time frame.', } ), }, @@ -38,7 +37,7 @@ export const schema: FormSchema = { helpText: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldAdditionalLookBackHelpText', { - defaultMessage: 'Adds time to the look-back period to prevent missed signals.', + defaultMessage: 'Adds time to the look-back period to prevent missed alerts.', } ), }, diff --git a/x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/translations.ts b/x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/translations.ts deleted file mode 100644 index b876177d5e4d..000000000000 --- a/x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/translations.ts +++ /dev/null @@ -1,77 +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'; - -export const SHOWING_SIGNALS = (totalSignalsFormatted: string, totalSignals: number) => - i18n.translate('xpack.siem.detectionEngine.signals.utilityBar.showingSignalsTitle', { - values: { totalSignalsFormatted, totalSignals }, - defaultMessage: - 'Showing {totalSignalsFormatted} {totalSignals, plural, =1 {signal} other {signals}}', - }); - -export const SELECTED_SIGNALS = (selectedSignalsFormatted: string, selectedSignals: number) => - i18n.translate('xpack.siem.detectionEngine.signals.utilityBar.selectedSignalsTitle', { - values: { selectedSignalsFormatted, selectedSignals }, - defaultMessage: - 'Selected {selectedSignalsFormatted} {selectedSignals, plural, =1 {signal} other {signals}}', - }); - -export const SELECT_ALL_SIGNALS = (totalSignalsFormatted: string, totalSignals: number) => - i18n.translate('xpack.siem.detectionEngine.signals.utilityBar.selectAllSignalsTitle', { - values: { totalSignalsFormatted, totalSignals }, - defaultMessage: - 'Select all {totalSignalsFormatted} {totalSignals, plural, =1 {signal} other {signals}}', - }); - -export const CLEAR_SELECTION = i18n.translate( - 'xpack.siem.detectionEngine.signals.utilityBar.clearSelectionTitle', - { - defaultMessage: 'Clear selection', - } -); - -export const BATCH_ACTIONS = i18n.translate( - 'xpack.siem.detectionEngine.signals.utilityBar.batchActionsTitle', - { - defaultMessage: 'Batch actions', - } -); - -export const BATCH_ACTION_VIEW_SELECTED_IN_HOSTS = i18n.translate( - 'xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInHostsTitle', - { - defaultMessage: 'View selected in hosts', - } -); - -export const BATCH_ACTION_VIEW_SELECTED_IN_NETWORK = i18n.translate( - 'xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInNetworkTitle', - { - defaultMessage: 'View selected in network', - } -); - -export const BATCH_ACTION_VIEW_SELECTED_IN_TIMELINE = i18n.translate( - 'xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInTimelineTitle', - { - defaultMessage: 'View selected in timeline', - } -); - -export const BATCH_ACTION_OPEN_SELECTED = i18n.translate( - 'xpack.siem.detectionEngine.signals.utilityBar.batchActions.openSelectedTitle', - { - defaultMessage: 'Open selected', - } -); - -export const BATCH_ACTION_CLOSE_SELECTED = i18n.translate( - 'xpack.siem.detectionEngine.signals.utilityBar.batchActions.closeSelectedTitle', - { - defaultMessage: 'Close selected', - } -); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/translations.ts b/x-pack/plugins/siem/public/alerts/components/signals/translations.ts deleted file mode 100644 index e49ff9846b9b..000000000000 --- a/x-pack/plugins/siem/public/alerts/components/signals/translations.ts +++ /dev/null @@ -1,131 +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'; - -export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.pageTitle', { - defaultMessage: 'Detection engine', -}); - -export const SIGNALS_TABLE_TITLE = i18n.translate('xpack.siem.detectionEngine.signals.tableTitle', { - defaultMessage: 'Signals', -}); - -export const SIGNALS_DOCUMENT_TYPE = i18n.translate( - 'xpack.siem.detectionEngine.signals.documentTypeTitle', - { - defaultMessage: 'Signals', - } -); - -export const OPEN_SIGNALS = i18n.translate('xpack.siem.detectionEngine.signals.openSignalsTitle', { - defaultMessage: 'Open signals', -}); - -export const CLOSED_SIGNALS = i18n.translate( - 'xpack.siem.detectionEngine.signals.closedSignalsTitle', - { - defaultMessage: 'Closed signals', - } -); - -export const LOADING_SIGNALS = i18n.translate( - 'xpack.siem.detectionEngine.signals.loadingSignalsTitle', - { - defaultMessage: 'Loading Signals', - } -); - -export const TOTAL_COUNT_OF_SIGNALS = i18n.translate( - 'xpack.siem.detectionEngine.signals.totalCountOfSignalsTitle', - { - defaultMessage: 'signals match the search criteria', - } -); - -export const SIGNALS_HEADERS_RULE = i18n.translate( - 'xpack.siem.eventsViewer.signals.defaultHeaders.ruleTitle', - { - defaultMessage: 'Rule', - } -); - -export const SIGNALS_HEADERS_VERSION = i18n.translate( - 'xpack.siem.eventsViewer.signals.defaultHeaders.versionTitle', - { - defaultMessage: 'Version', - } -); - -export const SIGNALS_HEADERS_METHOD = i18n.translate( - 'xpack.siem.eventsViewer.signals.defaultHeaders.methodTitle', - { - defaultMessage: 'Method', - } -); - -export const SIGNALS_HEADERS_SEVERITY = i18n.translate( - 'xpack.siem.eventsViewer.signals.defaultHeaders.severityTitle', - { - defaultMessage: 'Severity', - } -); - -export const SIGNALS_HEADERS_RISK_SCORE = i18n.translate( - 'xpack.siem.eventsViewer.signals.defaultHeaders.riskScoreTitle', - { - defaultMessage: 'Risk Score', - } -); - -export const ACTION_OPEN_SIGNAL = i18n.translate( - 'xpack.siem.detectionEngine.signals.actions.openSignalTitle', - { - defaultMessage: 'Open signal', - } -); - -export const ACTION_CLOSE_SIGNAL = i18n.translate( - 'xpack.siem.detectionEngine.signals.actions.closeSignalTitle', - { - defaultMessage: 'Close signal', - } -); - -export const ACTION_INVESTIGATE_IN_TIMELINE = i18n.translate( - 'xpack.siem.detectionEngine.signals.actions.investigateInTimelineTitle', - { - defaultMessage: 'Investigate in timeline', - } -); - -export const CLOSED_ALERT_SUCCESS_TOAST = (totalAlerts: number) => - i18n.translate('xpack.siem.detectionEngine.signals.closedAlertSuccessToastMessage', { - values: { totalAlerts }, - defaultMessage: - 'Successfully closed {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', - }); - -export const OPENED_ALERT_SUCCESS_TOAST = (totalAlerts: number) => - i18n.translate('xpack.siem.detectionEngine.signals.openedAlertSuccessToastMessage', { - values: { totalAlerts }, - defaultMessage: - 'Successfully opened {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', - }); - -export const CLOSED_ALERT_FAILED_TOAST = i18n.translate( - 'xpack.siem.detectionEngine.signals.closedAlertFailedToastMessage', - { - defaultMessage: 'Failed to close alert(s).', - } -); - -export const OPENED_ALERT_FAILED_TOAST = i18n.translate( - 'xpack.siem.detectionEngine.signals.openedAlertFailedToastMessage', - { - defaultMessage: 'Failed to open alert(s)', - } -); diff --git a/x-pack/plugins/siem/public/alerts/components/signals_info/index.tsx b/x-pack/plugins/siem/public/alerts/components/signals_info/index.tsx deleted file mode 100644 index b1d7f2cfe7eb..000000000000 --- a/x-pack/plugins/siem/public/alerts/components/signals_info/index.tsx +++ /dev/null @@ -1,49 +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 { EuiLoadingSpinner } from '@elastic/eui'; -import { FormattedRelative } from '@kbn/i18n/react'; -import React, { useState, useEffect } from 'react'; - -import { useQuerySignals } from '../../containers/detection_engine/signals/use_query'; -import { buildLastSignalsQuery } from './query.dsl'; -import { Aggs } from './types'; - -interface SignalInfo { - ruleId?: string | null; -} - -type Return = [React.ReactNode, React.ReactNode]; - -export const useSignalInfo = ({ ruleId = null }: SignalInfo): Return => { - const [lastSignals, setLastSignals] = useState( - - ); - const [totalSignals, setTotalSignals] = useState( - - ); - - const { loading, data: signals } = useQuerySignals(buildLastSignalsQuery(ruleId)); - - useEffect(() => { - if (signals != null) { - const mySignals = signals; - setLastSignals( - mySignals.aggregations?.lastSeen.value != null ? ( - - ) : null - ); - setTotalSignals(<>{mySignals.hits.total.value}); - } else { - setLastSignals(null); - setTotalSignals(null); - } - }, [loading, signals]); - - return [lastSignals, totalSignals]; -}; diff --git a/x-pack/plugins/siem/public/alerts/components/user_info/index.test.tsx b/x-pack/plugins/siem/public/alerts/components/user_info/index.test.tsx index 81b2c4347e17..b01edac2605e 100644 --- a/x-pack/plugins/siem/public/alerts/components/user_info/index.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/user_info/index.test.tsx @@ -7,11 +7,11 @@ import { renderHook } from '@testing-library/react-hooks'; import { useUserInfo } from './index'; -import { usePrivilegeUser } from '../../containers/detection_engine/signals/use_privilege_user'; -import { useSignalIndex } from '../../containers/detection_engine/signals/use_signal_index'; +import { usePrivilegeUser } from '../../containers/detection_engine/alerts/use_privilege_user'; +import { useSignalIndex } from '../../containers/detection_engine/alerts/use_signal_index'; import { useKibana } from '../../../common/lib/kibana'; -jest.mock('../../containers/detection_engine/signals/use_privilege_user'); -jest.mock('../../containers/detection_engine/signals/use_signal_index'); +jest.mock('../../containers/detection_engine/alerts/use_privilege_user'); +jest.mock('../../containers/detection_engine/alerts/use_signal_index'); jest.mock('../../../common/lib/kibana'); describe('useUserInfo', () => { diff --git a/x-pack/plugins/siem/public/alerts/components/user_info/index.tsx b/x-pack/plugins/siem/public/alerts/components/user_info/index.tsx index faf901629255..049463d4066d 100644 --- a/x-pack/plugins/siem/public/alerts/components/user_info/index.tsx +++ b/x-pack/plugins/siem/public/alerts/components/user_info/index.tsx @@ -7,8 +7,8 @@ import { noop } from 'lodash/fp'; import React, { useEffect, useReducer, Dispatch, createContext, useContext } from 'react'; -import { usePrivilegeUser } from '../../containers/detection_engine/signals/use_privilege_user'; -import { useSignalIndex } from '../../containers/detection_engine/signals/use_signal_index'; +import { usePrivilegeUser } from '../../containers/detection_engine/alerts/use_privilege_user'; +import { useSignalIndex } from '../../containers/detection_engine/alerts/use_signal_index'; import { useKibana } from '../../../common/lib/kibana'; export interface State { diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/__mocks__/api.ts b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/__mocks__/api.ts similarity index 56% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/__mocks__/api.ts rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/__mocks__/api.ts index 7cb1d7d574cf..64a55a8ec6eb 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/__mocks__/api.ts +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/__mocks__/api.ts @@ -4,26 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - QuerySignals, - SignalSearchResponse, - BasicSignals, - SignalsIndex, - Privilege, -} from '../types'; -import { signalsMock, mockSignalIndex, mockUserPrivilege } from '../mock'; +import { QueryAlerts, AlertSearchResponse, BasicSignals, AlertsIndex, Privilege } from '../types'; +import { alertsMock, mockSignalIndex, mockUserPrivilege } from '../mock'; -export const fetchQuerySignals = async ({ +export const fetchQueryAlerts = async ({ query, signal, -}: QuerySignals): Promise> => - Promise.resolve(signalsMock as SignalSearchResponse); +}: QueryAlerts): Promise> => + Promise.resolve(alertsMock as AlertSearchResponse); -export const getSignalIndex = async ({ signal }: BasicSignals): Promise => +export const getSignalIndex = async ({ signal }: BasicSignals): Promise => Promise.resolve(mockSignalIndex); export const getUserPrivilege = async ({ signal }: BasicSignals): Promise => Promise.resolve(mockUserPrivilege); -export const createSignalIndex = async ({ signal }: BasicSignals): Promise => +export const createSignalIndex = async ({ signal }: BasicSignals): Promise => Promise.resolve(mockSignalIndex); diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.test.ts b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/api.test.ts similarity index 68% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.test.ts rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/api.test.ts index 67d81d19faa7..3cd819b55685 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.test.ts +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/api.test.ts @@ -6,15 +6,15 @@ import { KibanaServices } from '../../../../common/lib/kibana'; import { - signalsMock, - mockSignalsQuery, - mockStatusSignalQuery, + alertsMock, + mockAlertsQuery, + mockStatusAlertQuery, mockSignalIndex, mockUserPrivilege, } from './mock'; import { - fetchQuerySignals, - updateSignalStatus, + fetchQueryAlerts, + updateAlertStatus, getSignalIndex, getUserPrivilege, createSignalIndex, @@ -27,41 +27,41 @@ jest.mock('../../../../common/lib/kibana'); const fetchMock = jest.fn(); mockKibanaServices.mockReturnValue({ http: { fetch: fetchMock } }); -describe('Detections Signals API', () => { - describe('fetchQuerySignals', () => { +describe('Detections Alerts API', () => { + describe('fetchQueryAlerts', () => { beforeEach(() => { fetchMock.mockClear(); - fetchMock.mockResolvedValue(signalsMock); + fetchMock.mockResolvedValue(alertsMock); }); test('check parameter url, body', async () => { - await fetchQuerySignals({ query: mockSignalsQuery, signal: abortCtrl.signal }); + await fetchQueryAlerts({ query: mockAlertsQuery, signal: abortCtrl.signal }); expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/signals/search', { body: - '{"aggs":{"signalsByGrouping":{"terms":{"field":"signal.rule.risk_score","missing":"All others","order":{"_count":"desc"},"size":10},"aggs":{"signals":{"date_histogram":{"field":"@timestamp","fixed_interval":"81000000ms","min_doc_count":0,"extended_bounds":{"min":1579644343954,"max":1582236343955}}}}}},"query":{"bool":{"filter":[{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}},{"range":{"@timestamp":{"gte":1579644343954,"lte":1582236343955}}}]}}}', + '{"aggs":{"alertsByGrouping":{"terms":{"field":"signal.rule.risk_score","missing":"All others","order":{"_count":"desc"},"size":10},"aggs":{"alerts":{"date_histogram":{"field":"@timestamp","fixed_interval":"81000000ms","min_doc_count":0,"extended_bounds":{"min":1579644343954,"max":1582236343955}}}}}},"query":{"bool":{"filter":[{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}},{"range":{"@timestamp":{"gte":1579644343954,"lte":1582236343955}}}]}}}', method: 'POST', signal: abortCtrl.signal, }); }); test('happy path', async () => { - const signalsResp = await fetchQuerySignals({ - query: mockSignalsQuery, + const signalsResp = await fetchQueryAlerts({ + query: mockAlertsQuery, signal: abortCtrl.signal, }); - expect(signalsResp).toEqual(signalsMock); + expect(signalsResp).toEqual(alertsMock); }); }); - describe('updateSignalStatus', () => { + describe('updateAlertStatus', () => { beforeEach(() => { fetchMock.mockClear(); fetchMock.mockResolvedValue({}); }); - test('check parameter url, body when closing a signal', async () => { - await updateSignalStatus({ - query: mockStatusSignalQuery, + test('check parameter url, body when closing an alert', async () => { + await updateAlertStatus({ + query: mockStatusAlertQuery, signal: abortCtrl.signal, status: 'closed', }); @@ -73,9 +73,9 @@ describe('Detections Signals API', () => { }); }); - test('check parameter url, body when opening a signal', async () => { - await updateSignalStatus({ - query: mockStatusSignalQuery, + test('check parameter url, body when opening an alert', async () => { + await updateAlertStatus({ + query: mockStatusAlertQuery, signal: abortCtrl.signal, status: 'open', }); @@ -88,12 +88,12 @@ describe('Detections Signals API', () => { }); test('happy path', async () => { - const signalsResp = await updateSignalStatus({ - query: mockStatusSignalQuery, + const alertsResp = await updateAlertStatus({ + query: mockStatusAlertQuery, signal: abortCtrl.signal, status: 'open', }); - expect(signalsResp).toEqual({}); + expect(alertsResp).toEqual({}); }); }); @@ -112,10 +112,10 @@ describe('Detections Signals API', () => { }); test('happy path', async () => { - const signalsResp = await getSignalIndex({ + const alertsResp = await getSignalIndex({ signal: abortCtrl.signal, }); - expect(signalsResp).toEqual(mockSignalIndex); + expect(alertsResp).toEqual(mockSignalIndex); }); }); @@ -134,10 +134,10 @@ describe('Detections Signals API', () => { }); test('happy path', async () => { - const signalsResp = await getUserPrivilege({ + const alertsResp = await getUserPrivilege({ signal: abortCtrl.signal, }); - expect(signalsResp).toEqual(mockUserPrivilege); + expect(alertsResp).toEqual(mockUserPrivilege); }); }); @@ -156,10 +156,10 @@ describe('Detections Signals API', () => { }); test('happy path', async () => { - const signalsResp = await createSignalIndex({ + const alertsResp = await createSignalIndex({ signal: abortCtrl.signal, }); - expect(signalsResp).toEqual(mockSignalIndex); + expect(alertsResp).toEqual(mockSignalIndex); }); }); }); diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.ts b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/api.ts similarity index 72% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.ts rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/api.ts index 445f66457c59..ccf35c967183 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.ts +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/api.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ReindexResponse } from 'elasticsearch'; +import { UpdateDocumentByQueryResponse } from 'elasticsearch'; import { DETECTION_ENGINE_QUERY_SIGNALS_URL, DETECTION_ENGINE_SIGNALS_STATUS_URL, @@ -15,25 +15,25 @@ import { KibanaServices } from '../../../../common/lib/kibana'; import { BasicSignals, Privilege, - QuerySignals, - SignalSearchResponse, - SignalsIndex, - UpdateSignalStatusProps, + QueryAlerts, + AlertSearchResponse, + AlertsIndex, + UpdateAlertStatusProps, } from './types'; /** - * Fetch Signals by providing a query + * Fetch Alerts by providing a query * * @param query String to match a dsl * @param signal to cancel request * * @throws An error if response is not OK */ -export const fetchQuerySignals = async ({ +export const fetchQueryAlerts = async ({ query, signal, -}: QuerySignals): Promise> => - KibanaServices.get().http.fetch>( +}: QueryAlerts): Promise> => + KibanaServices.get().http.fetch>( DETECTION_ENGINE_QUERY_SIGNALS_URL, { method: 'POST', @@ -43,19 +43,19 @@ export const fetchQuerySignals = async ({ ); /** - * Update signal status by query + * Update alert status by query * - * @param query of signals to update + * @param query of alerts to update * @param status to update to('open' / 'closed') * @param signal AbortSignal for cancelling request * * @throws An error if response is not OK */ -export const updateSignalStatus = async ({ +export const updateAlertStatus = async ({ query, status, signal, -}: UpdateSignalStatusProps): Promise => +}: UpdateAlertStatusProps): Promise => KibanaServices.get().http.fetch(DETECTION_ENGINE_SIGNALS_STATUS_URL, { method: 'POST', body: JSON.stringify({ status, ...query }), @@ -69,8 +69,8 @@ export const updateSignalStatus = async ({ * * @throws An error if response is not OK */ -export const getSignalIndex = async ({ signal }: BasicSignals): Promise => - KibanaServices.get().http.fetch(DETECTION_ENGINE_INDEX_URL, { +export const getSignalIndex = async ({ signal }: BasicSignals): Promise => + KibanaServices.get().http.fetch(DETECTION_ENGINE_INDEX_URL, { method: 'GET', signal, }); @@ -95,8 +95,8 @@ export const getUserPrivilege = async ({ signal }: BasicSignals): Promise => - KibanaServices.get().http.fetch(DETECTION_ENGINE_INDEX_URL, { +export const createSignalIndex = async ({ signal }: BasicSignals): Promise => + KibanaServices.get().http.fetch(DETECTION_ENGINE_INDEX_URL, { method: 'POST', signal, }); diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/mock.ts b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/mock.ts similarity index 98% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/mock.ts rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/mock.ts index 6b0c7e007826..cd2cc1fe390b 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/mock.ts +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/mock.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SignalSearchResponse, SignalsIndex, Privilege } from './types'; +import { AlertSearchResponse, AlertsIndex, Privilege } from './types'; -export const signalsMock: SignalSearchResponse = { +export const alertsMock: AlertSearchResponse = { took: 7, timeout: false, _shards: { @@ -902,14 +902,14 @@ export const signalsMock: SignalSearchResponse = { ], }, aggregations: { - signalsByGrouping: { + alertsByGrouping: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [ { key: '4', doc_count: 12600, - signals: { + alerts: { buckets: [ { key_as_string: '2020-01-21T04:30:00.000Z', @@ -939,9 +939,9 @@ export const signalsMock: SignalSearchResponse = { }, }; -export const mockSignalsQuery: object = { +export const mockAlertsQuery: object = { aggs: { - signalsByGrouping: { + alertsByGrouping: { terms: { field: 'signal.rule.risk_score', missing: 'All others', @@ -949,7 +949,7 @@ export const mockSignalsQuery: object = { size: 10, }, aggs: { - signals: { + alerts: { date_histogram: { field: '@timestamp', fixed_interval: '81000000ms', @@ -970,7 +970,7 @@ export const mockSignalsQuery: object = { }, }; -export const mockStatusSignalQuery: object = { +export const mockStatusAlertQuery: object = { bool: { filter: { terms: { _id: ['b4ee5c32e3a321057edcc953ca17228c6fdfe5ba43fdbbdaffa8cefa11605cc5'] }, @@ -978,7 +978,7 @@ export const mockStatusSignalQuery: object = { }, }; -export const mockSignalIndex: SignalsIndex = { +export const mockSignalIndex: AlertsIndex = { name: 'mock-signal-index', }; diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/translations.ts b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/translations.ts similarity index 55% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/translations.ts rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/translations.ts index 2b8f54e5438d..2f3ebccdb14c 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/translations.ts +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/translations.ts @@ -6,29 +6,29 @@ import { i18n } from '@kbn/i18n'; -export const SIGNAL_FETCH_FAILURE = i18n.translate( - 'xpack.siem.containers.detectionEngine.signals.errorFetchingSignalsDescription', +export const ALERT_FETCH_FAILURE = i18n.translate( + 'xpack.siem.containers.detectionEngine.alerts.errorFetchingAlertsDescription', { - defaultMessage: 'Failed to query signals', + defaultMessage: 'Failed to query alerts', } ); export const PRIVILEGE_FETCH_FAILURE = i18n.translate( - 'xpack.siem.containers.detectionEngine.signals.errorFetchingSignalsDescription', + 'xpack.siem.containers.detectionEngine.alerts.errorFetchingAlertsDescription', { - defaultMessage: 'Failed to query signals', + defaultMessage: 'Failed to query alerts', } ); export const SIGNAL_GET_NAME_FAILURE = i18n.translate( - 'xpack.siem.containers.detectionEngine.signals.errorGetSignalDescription', + 'xpack.siem.containers.detectionEngine.alerts.errorGetAlertDescription', { defaultMessage: 'Failed to get signal index name', } ); export const SIGNAL_POST_FAILURE = i18n.translate( - 'xpack.siem.containers.detectionEngine.signals.errorPostSignalDescription', + 'xpack.siem.containers.detectionEngine.alerts.errorPostAlertDescription', { defaultMessage: 'Failed to create signal index', } diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/types.ts b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/types.ts similarity index 88% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/types.ts rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/types.ts index 4e97c597546a..b425cfd54a7f 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/types.ts +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/types.ts @@ -7,17 +7,17 @@ export interface BasicSignals { signal: AbortSignal; } -export interface QuerySignals extends BasicSignals { +export interface QueryAlerts extends BasicSignals { query: object; } -export interface SignalsResponse { +export interface AlertsResponse { took: number; timeout: boolean; } -export interface SignalSearchResponse - extends SignalsResponse { +export interface AlertSearchResponse + extends AlertsResponse { _shards: { total: number; successful: number; @@ -34,13 +34,13 @@ export interface SignalSearchResponse }; } -export interface UpdateSignalStatusProps { +export interface UpdateAlertStatusProps { query: object; status: 'open' | 'closed'; signal?: AbortSignal; // TODO: implement cancelling } -export interface SignalsIndex { +export interface AlertsIndex { name: string; } diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_privilege_user.test.tsx b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_privilege_user.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_privilege_user.test.tsx rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_privilege_user.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_privilege_user.tsx b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_privilege_user.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_privilege_user.tsx rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_privilege_user.tsx diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_query.test.tsx b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_query.test.tsx similarity index 61% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_query.test.tsx rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_query.test.tsx index c577f291f037..8627b953c8da 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_query.test.tsx +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_query.test.tsx @@ -5,13 +5,13 @@ */ import { renderHook, act } from '@testing-library/react-hooks'; -import { useQuerySignals, ReturnQuerySignals } from './use_query'; +import { useQueryAlerts, ReturnQueryAlerts } from './use_query'; import * as api from './api'; -import { mockSignalsQuery, signalsMock } from './mock'; +import { mockAlertsQuery, alertsMock } from './mock'; jest.mock('./api'); -describe('useQuerySignals', () => { +describe('useQueryAlerts', () => { const indexName = 'mock-index-name'; beforeEach(() => { jest.resetAllMocks(); @@ -20,8 +20,8 @@ describe('useQuerySignals', () => { await act(async () => { const { result, waitForNextUpdate } = renderHook< [object, string], - ReturnQuerySignals - >(() => useQuerySignals(mockSignalsQuery, indexName)); + ReturnQueryAlerts + >(() => useQueryAlerts(mockAlertsQuery, indexName)); await waitForNextUpdate(); expect(result.current).toEqual({ loading: true, @@ -34,72 +34,72 @@ describe('useQuerySignals', () => { }); }); - test('fetch signals data', async () => { + test('fetch alerts data', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook< [object, string], - ReturnQuerySignals - >(() => useQuerySignals(mockSignalsQuery, indexName)); + ReturnQueryAlerts + >(() => useQueryAlerts(mockAlertsQuery, indexName)); await waitForNextUpdate(); await waitForNextUpdate(); expect(result.current).toEqual({ loading: false, - data: signalsMock, - response: JSON.stringify(signalsMock, null, 2), - request: JSON.stringify({ index: [indexName] ?? [''], body: mockSignalsQuery }, null, 2), + data: alertsMock, + response: JSON.stringify(alertsMock, null, 2), + request: JSON.stringify({ index: [indexName] ?? [''], body: mockAlertsQuery }, null, 2), setQuery: result.current.setQuery, refetch: result.current.refetch, }); }); }); - test('re-fetch signals data', async () => { - const spyOnfetchQuerySignals = jest.spyOn(api, 'fetchQuerySignals'); + test('re-fetch alerts data', async () => { + const spyOnfetchQueryAlerts = jest.spyOn(api, 'fetchQueryAlerts'); await act(async () => { const { result, waitForNextUpdate } = renderHook< [object, string], - ReturnQuerySignals - >(() => useQuerySignals(mockSignalsQuery, indexName)); + ReturnQueryAlerts + >(() => useQueryAlerts(mockAlertsQuery, indexName)); await waitForNextUpdate(); await waitForNextUpdate(); if (result.current.refetch) { result.current.refetch(); } await waitForNextUpdate(); - expect(spyOnfetchQuerySignals).toHaveBeenCalledTimes(2); + expect(spyOnfetchQueryAlerts).toHaveBeenCalledTimes(2); }); }); - test('fetch signal when index name changed', async () => { - const spyOnfetchRules = jest.spyOn(api, 'fetchQuerySignals'); + test('fetch alert when index name changed', async () => { + const spyOnfetchRules = jest.spyOn(api, 'fetchQueryAlerts'); await act(async () => { const { rerender, waitForNextUpdate } = renderHook< [object, string], - ReturnQuerySignals - >((args) => useQuerySignals(args[0], args[1]), { - initialProps: [mockSignalsQuery, indexName], + ReturnQueryAlerts + >((args) => useQueryAlerts(args[0], args[1]), { + initialProps: [mockAlertsQuery, indexName], }); await waitForNextUpdate(); await waitForNextUpdate(); - rerender([mockSignalsQuery, 'new-mock-index-name']); + rerender([mockAlertsQuery, 'new-mock-index-name']); await waitForNextUpdate(); expect(spyOnfetchRules).toHaveBeenCalledTimes(2); }); }); - test('fetch signal when query object changed', async () => { - const spyOnfetchRules = jest.spyOn(api, 'fetchQuerySignals'); + test('fetch alert when query object changed', async () => { + const spyOnfetchRules = jest.spyOn(api, 'fetchQueryAlerts'); await act(async () => { const { result, waitForNextUpdate } = renderHook< [object, string], - ReturnQuerySignals - >((args) => useQuerySignals(args[0], args[1]), { - initialProps: [mockSignalsQuery, indexName], + ReturnQueryAlerts + >((args) => useQueryAlerts(args[0], args[1]), { + initialProps: [mockAlertsQuery, indexName], }); await waitForNextUpdate(); await waitForNextUpdate(); if (result.current.setQuery) { - result.current.setQuery({ ...mockSignalsQuery }); + result.current.setQuery({ ...mockAlertsQuery }); } await waitForNextUpdate(); expect(spyOnfetchRules).toHaveBeenCalledTimes(2); @@ -107,13 +107,13 @@ describe('useQuerySignals', () => { }); test('if there is an error when fetching data, we should get back the init value for every properties', async () => { - const spyOnGetUserPrivilege = jest.spyOn(api, 'fetchQuerySignals'); + const spyOnGetUserPrivilege = jest.spyOn(api, 'fetchQueryAlerts'); spyOnGetUserPrivilege.mockImplementation(() => { throw new Error('Something went wrong, let see what happen'); }); await act(async () => { - const { result, waitForNextUpdate } = renderHook>( - () => useQuerySignals(mockSignalsQuery, 'mock-index-name') + const { result, waitForNextUpdate } = renderHook>( + () => useQueryAlerts(mockAlertsQuery, 'mock-index-name') ); await waitForNextUpdate(); await waitForNextUpdate(); diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_query.tsx b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_query.tsx similarity index 69% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_query.tsx rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_query.tsx index 531e080ed7d1..9c992fa87270 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_query.tsx +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_query.tsx @@ -6,14 +6,14 @@ import React, { SetStateAction, useEffect, useState } from 'react'; -import { fetchQuerySignals } from './api'; -import { SignalSearchResponse } from './types'; +import { fetchQueryAlerts } from './api'; +import { AlertSearchResponse } from './types'; type Func = () => void; -export interface ReturnQuerySignals { +export interface ReturnQueryAlerts { loading: boolean; - data: SignalSearchResponse | null; + data: AlertSearchResponse | null; setQuery: React.Dispatch>; response: string; request: string; @@ -21,18 +21,18 @@ export interface ReturnQuerySignals { } /** - * Hook for using to get a Signals from the Detection Engine API + * Hook for fetching Alerts from the Detection Engine API * * @param initialQuery query dsl object * */ -export const useQuerySignals = ( +export const useQueryAlerts = ( initialQuery: object, indexName?: string | null -): ReturnQuerySignals => { +): ReturnQueryAlerts => { const [query, setQuery] = useState(initialQuery); - const [signals, setSignals] = useState< - Pick, 'data' | 'setQuery' | 'response' | 'request' | 'refetch'> + const [alerts, setAlerts] = useState< + Pick, 'data' | 'setQuery' | 'response' | 'request' | 'refetch'> >({ data: null, response: '', @@ -49,15 +49,15 @@ export const useQuerySignals = ( async function fetchData() { try { setLoading(true); - const signalResponse = await fetchQuerySignals({ + const alertResponse = await fetchQueryAlerts({ query, signal: abortCtrl.signal, }); if (isSubscribed) { - setSignals({ - data: signalResponse, - response: JSON.stringify(signalResponse, null, 2), + setAlerts({ + data: alertResponse, + response: JSON.stringify(alertResponse, null, 2), request: JSON.stringify({ index: [indexName] ?? [''], body: query }, null, 2), setQuery, refetch: fetchData, @@ -65,7 +65,7 @@ export const useQuerySignals = ( } } catch (error) { if (isSubscribed) { - setSignals({ + setAlerts({ data: null, response: '', request: '', @@ -86,5 +86,5 @@ export const useQuerySignals = ( }; }, [query, indexName]); - return { loading, ...signals }; + return { loading, ...alerts }; }; diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_signal_index.test.tsx b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_signal_index.test.tsx similarity index 96% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_signal_index.test.tsx rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_signal_index.test.tsx index c834e4ab14be..d0571bfca5b2 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_signal_index.test.tsx +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_signal_index.test.tsx @@ -30,7 +30,7 @@ describe('useSignalIndex', () => { }); }); - test('fetch signals info', async () => { + test('fetch alerts info', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook(() => useSignalIndex() @@ -105,7 +105,7 @@ describe('useSignalIndex', () => { }); }); - test('if there is an error when fetching signals info, signalIndexExists === false && signalIndexName == null', async () => { + test('if there is an error when fetching alerts info, signalIndexExists === false && signalIndexName == null', async () => { const spyOnGetSignalIndex = jest.spyOn(api, 'getSignalIndex'); spyOnGetSignalIndex.mockImplementation(() => { throw new Error('Something went wrong, let see what happen'); diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_signal_index.tsx b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_signal_index.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_signal_index.tsx rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_signal_index.tsx diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/siem/public/alerts/pages/detection_engine/detection_engine.tsx index a83a85678bd0..e3eb4666522a 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/detection_engine.tsx +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/detection_engine.tsx @@ -6,7 +6,6 @@ import { EuiButton, EuiSpacer } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; -import { useParams } from 'react-router-dom'; import { StickyContainer } from 'react-sticky'; import { connect, ConnectedProps } from 'react-redux'; @@ -15,60 +14,34 @@ import { indicesExistOrDataTemporarilyUnavailable, WithSource, } from '../../../common/containers/source'; -import { AlertsTable } from '../../../common/components/alerts_viewer/alerts_table'; import { UpdateDateRange } from '../../../common/components/charts/common'; import { FiltersGlobal } from '../../../common/components/filters_global'; -import { - getDetectionEngineTabUrl, - getRulesUrl, -} from '../../../common/components/link_to/redirect_to_detection_engine'; +import { getRulesUrl } from '../../../common/components/link_to/redirect_to_detection_engine'; import { SiemSearchBar } from '../../../common/components/search_bar'; import { WrapperPage } from '../../../common/components/wrapper_page'; -import { SiemNavigation } from '../../../common/components/navigation'; -import { NavTab } from '../../../common/components/navigation/types'; import { State } from '../../../common/store'; import { inputsSelectors } from '../../../common/store/inputs'; import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../../common/store/inputs/actions'; import { SpyRoute } from '../../../common/utils/route/spy_routes'; import { InputsRange } from '../../../common/store/inputs/model'; -import { AlertsByCategory } from '../../../overview/components/alerts_by_category'; -import { useSignalInfo } from '../../components/signals_info'; -import { SignalsTable } from '../../components/signals'; +import { useAlertInfo } from '../../components/alerts_info'; +import { AlertsTable } from '../../components/alerts_table'; import { NoApiIntegrationKeyCallOut } from '../../components/no_api_integration_callout'; -import { NoWriteSignalsCallOut } from '../../components/no_write_signals_callout'; -import { SignalsHistogramPanel } from '../../components/signals_histogram_panel'; -import { signalsHistogramOptions } from '../../components/signals_histogram_panel/config'; +import { NoWriteAlertsCallOut } from '../../components/no_write_alerts_callout'; +import { AlertsHistogramPanel } from '../../components/alerts_histogram_panel'; +import { alertsHistogramOptions } from '../../components/alerts_histogram_panel/config'; import { useUserInfo } from '../../components/user_info'; import { DetectionEngineEmptyPage } from './detection_engine_empty_page'; import { DetectionEngineNoIndex } from './detection_engine_no_signal_index'; import { DetectionEngineHeaderPage } from '../../components/detection_engine_header_page'; import { DetectionEngineUserUnauthenticated } from './detection_engine_user_unauthenticated'; import * as i18n from './translations'; -import { DetectionEngineTab } from './types'; - -const detectionsTabs: Record = { - [DetectionEngineTab.signals]: { - id: DetectionEngineTab.signals, - name: i18n.SIGNAL, - href: getDetectionEngineTabUrl(DetectionEngineTab.signals), - disabled: false, - urlKey: 'detections', - }, - [DetectionEngineTab.alerts]: { - id: DetectionEngineTab.alerts, - name: i18n.ALERT, - href: getDetectionEngineTabUrl(DetectionEngineTab.alerts), - disabled: false, - urlKey: 'detections', - }, -}; export const DetectionEnginePageComponent: React.FC = ({ filters, query, setAbsoluteRangeDatePicker, }) => { - const { tabName = DetectionEngineTab.signals } = useParams(); const { loading, isSignalIndexExists, @@ -79,7 +52,7 @@ export const DetectionEnginePageComponent: React.FC = ({ hasIndexWrite, } = useUserInfo(); - const [lastSignals] = useSignalInfo({}); + const [lastAlerts] = useAlertInfo({}); const updateDateRangeCallback = useCallback( ({ x }) => { @@ -116,7 +89,7 @@ export const DetectionEnginePageComponent: React.FC = ({ return ( <> {hasEncryptionKey != null && !hasEncryptionKey && } - {hasIndexWrite != null && !hasIndexWrite && } + {hasIndexWrite != null && !hasIndexWrite && } {({ indicesExist, indexPattern }) => { return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( @@ -127,11 +100,11 @@ export const DetectionEnginePageComponent: React.FC = ({ - {i18n.LAST_SIGNAL} + {i18n.LAST_ALERT} {': '} - {lastSignals} + {lastAlerts} ) } @@ -141,7 +114,7 @@ export const DetectionEnginePageComponent: React.FC = ({ fill href={getRulesUrl()} iconType="gear" - data-test-subj="manage-signal-detection-rules" + data-test-subj="manage-alert-detection-rules" > {i18n.BUTTON_MANAGE_RULES} @@ -150,48 +123,29 @@ export const DetectionEnginePageComponent: React.FC = ({ {({ to, from, deleteQuery, setQuery }) => ( <> - - - {tabName === DetectionEngineTab.signals && ( - <> - - - - - )} - {tabName === DetectionEngineTab.alerts && ( - <> - - - - )} + <> + + + + )} diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/index.tsx b/x-pack/plugins/siem/public/alerts/pages/detection_engine/index.tsx index 756e222c0295..1f9b1373d404 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/index.tsx +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/index.tsx @@ -13,7 +13,6 @@ import { DetectionEnginePage } from './detection_engine'; import { EditRulePage } from './rules/edit'; import { RuleDetailsPage } from './rules/details'; import { RulesPage } from './rules'; -import { DetectionEngineTab } from './types'; const detectionEnginePath = `/:pageName(detections)`; @@ -22,11 +21,7 @@ type Props = Partial> & { url: string }; const DetectionEngineContainerComponent: React.FC = () => ( - + @@ -44,7 +39,7 @@ const DetectionEngineContainerComponent: React.FC = () => ( ( - + )} /> diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/helpers.test.ts b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/helpers.test.ts index 1894d0ab1a9e..d9cbcfc8979a 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/helpers.test.ts +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/helpers.test.ts @@ -611,7 +611,7 @@ describe('helpers', () => { const mockAction = { group: 'default', id: '99403909-ca9b-49ba-9d7a-7e5320e68d05', - params: { message: 'ML Rule generated {{state.signals_count}} signals' }, + params: { message: 'ML Rule generated {{state.signals_count}} alerts' }, actionTypeId: '.slack', }; diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/translations.ts b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/translations.ts index f35b6c8d7b00..615882d4a7e3 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/translations.ts +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/translations.ts @@ -13,7 +13,7 @@ export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.createRule. export const BACK_TO_RULES = i18n.translate( 'xpack.siem.detectionEngine.createRule.backToRulesDescription', { - defaultMessage: 'Back to signal detection rules', + defaultMessage: 'Back to detection rules', } ); diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/details/index.tsx index 74110e25cc94..7197ed397717 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/details/index.tsx @@ -42,15 +42,15 @@ import { SpyRoute } from '../../../../../common/utils/route/spy_routes'; import { StepAboutRuleToggleDetails } from '../../../../components/rules/step_about_rule_details'; import { DetectionEngineHeaderPage } from '../../../../components/detection_engine_header_page'; -import { SignalsHistogramPanel } from '../../../../components/signals_histogram_panel'; -import { SignalsTable } from '../../../../components/signals'; +import { AlertsHistogramPanel } from '../../../../components/alerts_histogram_panel'; +import { AlertsTable } from '../../../../components/alerts_table'; import { useUserInfo } from '../../../../components/user_info'; import { DetectionEngineEmptyPage } from '../../detection_engine_empty_page'; -import { useSignalInfo } from '../../../../components/signals_info'; +import { useAlertInfo } from '../../../../components/alerts_info'; import { StepDefineRule } from '../../../../components/rules/step_define_rule'; import { StepScheduleRule } from '../../../../components/rules/step_schedule_rule'; -import { buildSignalsRuleIdFilter } from '../../../../components/signals/default_config'; -import { NoWriteSignalsCallOut } from '../../../../components/no_write_signals_callout'; +import { buildAlertsRuleIdFilter } from '../../../../components/alerts_table/default_config'; +import { NoWriteAlertsCallOut } from '../../../../components/no_write_alerts_callout'; import * as detectionI18n from '../../translations'; import { ReadOnlyCallOut } from '../../../../components/rules/read_only_callout'; import { RuleSwitch } from '../../../../components/rules/rule_switch'; @@ -59,7 +59,7 @@ import { getStepsData, redirectToDetections, userHasNoPermissions } from '../hel import * as ruleI18n from '../translations'; import * as i18n from './translations'; import { GlobalTime } from '../../../../../common/containers/global_time'; -import { signalsHistogramOptions } from '../../../../components/signals_histogram_panel/config'; +import { alertsHistogramOptions } from '../../../../components/alerts_histogram_panel/config'; import { inputsSelectors } from '../../../../../common/store/inputs'; import { State } from '../../../../../common/store'; import { InputsRange } from '../../../../../common/store/inputs/model'; @@ -72,14 +72,14 @@ import { useMlCapabilities } from '../../../../../common/components/ml_popover/h import { hasMlAdminPermissions } from '../../../../../../common/machine_learning/has_ml_admin_permissions'; enum RuleDetailTabs { - signals = 'signals', + alerts = 'alerts', failures = 'failures', } const ruleDetailTabs = [ { - id: RuleDetailTabs.signals, - name: detectionI18n.SIGNAL, + id: RuleDetailTabs.alerts, + name: detectionI18n.ALERT, disabled: false, }, { @@ -107,7 +107,7 @@ export const RuleDetailsPageComponent: FC = ({ const [isLoading, rule] = useRule(ruleId); // This is used to re-trigger api rule status when user de/activate rule const [ruleEnabled, setRuleEnabled] = useState(null); - const [ruleDetailTab, setRuleDetailTab] = useState(RuleDetailTabs.signals); + const [ruleDetailTab, setRuleDetailTab] = useState(RuleDetailTabs.alerts); const { aboutRuleData, modifiedAboutRuleDetailsData, defineRuleData, scheduleRuleData } = rule != null ? getStepsData({ rule, detailsView: true }) @@ -117,7 +117,7 @@ export const RuleDetailsPageComponent: FC = ({ defineRuleData: null, scheduleRuleData: null, }; - const [lastSignals] = useSignalInfo({ ruleId }); + const [lastAlerts] = useAlertInfo({ ruleId }); const mlCapabilities = useMlCapabilities(); // TODO: Refactor license check + hasMlAdminPermissions to common check @@ -166,13 +166,13 @@ export const RuleDetailsPageComponent: FC = ({ [isLoading, rule] ); - const signalDefaultFilters = useMemo( - () => (ruleId != null ? buildSignalsRuleIdFilter(ruleId) : []), + const alertDefaultFilters = useMemo( + () => (ruleId != null ? buildAlertsRuleIdFilter(ruleId) : []), [ruleId] ); - const signalMergedFilters = useMemo(() => [...signalDefaultFilters, ...filters], [ - signalDefaultFilters, + const alertMergedFilters = useMemo(() => [...alertDefaultFilters, ...filters], [ + alertDefaultFilters, filters, ]); @@ -196,7 +196,7 @@ export const RuleDetailsPageComponent: FC = ({ const ruleError = useMemo( () => rule?.status === 'failed' && - ruleDetailTab === RuleDetailTabs.signals && + ruleDetailTab === RuleDetailTabs.alerts && rule?.last_failure_at != null ? ( = ({ return ( <> - {hasIndexWrite != null && !hasIndexWrite && } + {hasIndexWrite != null && !hasIndexWrite && } {userHasNoPermissions(canUserCRUD) && } {({ indicesExist, indexPattern }) => { @@ -257,12 +257,12 @@ export const RuleDetailsPageComponent: FC = ({ border subtitle={subTitle} subtitle2={[ - ...(lastSignals != null + ...(lastAlerts != null ? [ <> - {detectionI18n.LAST_SIGNAL} + {detectionI18n.LAST_ALERT} {': '} - {lastSignals} + {lastAlerts} , ] : []), @@ -358,24 +358,24 @@ export const RuleDetailsPageComponent: FC = ({ {tabs} - {ruleDetailTab === RuleDetailTabs.signals && ( + {ruleDetailTab === RuleDetailTabs.alerts && ( <> - {ruleId != null && ( - { diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/translations.ts b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/translations.ts index fc0a79fa652f..0fe110617105 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/translations.ts +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/translations.ts @@ -6,12 +6,9 @@ import { i18n } from '@kbn/i18n'; -export const BACK_TO_DETECTION_ENGINE = i18n.translate( - 'xpack.siem.detectionEngine.rules.backOptionsHeader', - { - defaultMessage: 'Back to detections', - } -); +export const BACK_TO_ALERTS = i18n.translate('xpack.siem.detectionEngine.rules.backOptionsHeader', { + defaultMessage: 'Back to alerts', +}); export const IMPORT_RULE = i18n.translate('xpack.siem.detectionEngine.rules.importRuleTitle', { defaultMessage: 'Import rule…', @@ -22,7 +19,7 @@ export const ADD_NEW_RULE = i18n.translate('xpack.siem.detectionEngine.rules.add }); export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.rules.pageTitle', { - defaultMessage: 'Signal detection rules', + defaultMessage: 'Detection rules', }); export const ADD_PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.rules.addPageTitle', { diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/utils.test.ts b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/utils.test.ts index 34a521ed32b1..3d2f2dc03946 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/utils.test.ts +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/utils.test.ts @@ -19,6 +19,6 @@ describe('getBreadcrumbs', () => { }, [] ) - ).toEqual([{ href: '#/link-to/detections', text: 'Detections' }]); + ).toEqual([{ href: '#/link-to/detections', text: 'Alerts' }]); }); }); diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/utils.ts b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/utils.ts index 159301a07de7..e5cdbd7123ff 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/utils.ts +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/utils.ts @@ -30,13 +30,6 @@ const getTabBreadcrumb = (pathname: string, search: string[]) => { }; } - if (tabPath === 'signals') { - return { - text: i18nDetections.SIGNAL, - href: `${getDetectionEngineTabUrl(tabPath)}${!isEmpty(search[0]) ? search[0] : ''}`, - }; - } - if (tabPath === 'rules') { return { text: i18nRules.PAGE_TITLE, diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/translations.ts b/x-pack/plugins/siem/public/alerts/pages/detection_engine/translations.ts index 008d660be9d8..067399f68d51 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/translations.ts +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/translations.ts @@ -7,27 +7,23 @@ import { i18n } from '@kbn/i18n'; export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.detectionsPageTitle', { - defaultMessage: 'Detections', + defaultMessage: 'Alerts', }); -export const LAST_SIGNAL = i18n.translate('xpack.siem.detectionEngine.lastSignalTitle', { - defaultMessage: 'Last signal', +export const LAST_ALERT = i18n.translate('xpack.siem.detectionEngine.lastAlertTitle', { + defaultMessage: 'Last alert', }); -export const TOTAL_SIGNAL = i18n.translate('xpack.siem.detectionEngine.totalSignalTitle', { +export const TOTAL_ALERT = i18n.translate('xpack.siem.detectionEngine.totalAlertTitle', { defaultMessage: 'Total', }); -export const SIGNAL = i18n.translate('xpack.siem.detectionEngine.signalTitle', { - defaultMessage: 'Detected signals', -}); - export const ALERT = i18n.translate('xpack.siem.detectionEngine.alertTitle', { - defaultMessage: 'External alerts', + defaultMessage: 'Detected alerts', }); export const BUTTON_MANAGE_RULES = i18n.translate('xpack.siem.detectionEngine.buttonManageRules', { - defaultMessage: 'Manage signal detection rules', + defaultMessage: 'Manage detection rules', }); export const PANEL_SUBTITLE_SHOWING = i18n.translate( diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/types.ts b/x-pack/plugins/siem/public/alerts/pages/detection_engine/types.ts deleted file mode 100644 index d529d99ad3ad..000000000000 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export enum DetectionEngineTab { - signals = 'signals', - alerts = 'alerts', -} diff --git a/x-pack/plugins/siem/public/common/components/drag_and_drop/helpers.ts b/x-pack/plugins/siem/public/common/components/drag_and_drop/helpers.ts index 0037fc3cae62..4fb4e5d30ca7 100644 --- a/x-pack/plugins/siem/public/common/components/drag_and_drop/helpers.ts +++ b/x-pack/plugins/siem/public/common/components/drag_and_drop/helpers.ts @@ -258,7 +258,7 @@ export const allowTopN = ({ 'string', ].includes(fieldType); - // TODO: remove this explicit whitelist when the ECS documentation includes signals + // TODO: remove this explicit whitelist when the ECS documentation includes alerts const isWhitelistedNonBrowserField = [ 'signal.ancestors.depth', 'signal.ancestors.id', diff --git a/x-pack/plugins/siem/public/common/components/link_to/link_to.tsx b/x-pack/plugins/siem/public/common/components/link_to/link_to.tsx index 8151291679e3..0294d175aef1 100644 --- a/x-pack/plugins/siem/public/common/components/link_to/link_to.tsx +++ b/x-pack/plugins/siem/public/common/components/link_to/link_to.tsx @@ -25,7 +25,6 @@ import { RedirectToCreatePage, RedirectToConfigureCasesPage, } from './redirect_to_case'; -import { DetectionEngineTab } from '../../../alerts/pages/detection_engine/types'; import { TimelineType } from '../../../../common/types/timeline'; import { RedirectToManagementPage } from './redirect_to_management'; @@ -89,11 +88,6 @@ export const LinkToPage = React.memo(({ match }) => ( exact path={`${match.url}/:pageName(${SiemPageName.detections})`} /> - ; @@ -20,14 +18,9 @@ export type DetectionEngineComponentProps = RouteComponentProps<{ export const DETECTION_ENGINE_PAGE_NAME = 'detections'; export const RedirectToDetectionEnginePage = ({ - match: { - params: { tabName }, - }, location: { search }, }: DetectionEngineComponentProps) => { - const defaultSelectedTab = DetectionEngineTab.signals; - const selectedTab = tabName ? tabName : defaultSelectedTab; - const to = `/${DETECTION_ENGINE_PAGE_NAME}/${selectedTab}${search}`; + const to = `/${DETECTION_ENGINE_PAGE_NAME}${search}`; return ; }; @@ -66,8 +59,6 @@ const baseDetectionEngineUrl = `#/link-to/${DETECTION_ENGINE_PAGE_NAME}`; export const getDetectionEngineUrl = (search?: string) => `${baseDetectionEngineUrl}${appendSearch(search)}`; -export const getDetectionEngineAlertUrl = (search?: string) => - `${baseDetectionEngineUrl}/${DetectionEngineTab.alerts}${appendSearch(search)}`; export const getDetectionEngineTabUrl = (tabPath: string) => `${baseDetectionEngineUrl}/${tabPath}`; export const getRulesUrl = () => `${baseDetectionEngineUrl}/rules`; export const getCreateRuleUrl = () => `${baseDetectionEngineUrl}/rules/create`; diff --git a/x-pack/plugins/siem/public/common/components/ml_popover/__snapshots__/popover_description.test.tsx.snap b/x-pack/plugins/siem/public/common/components/ml_popover/__snapshots__/popover_description.test.tsx.snap index 46e61f9e939e..78b05a00cef9 100644 --- a/x-pack/plugins/siem/public/common/components/ml_popover/__snapshots__/popover_description.test.tsx.snap +++ b/x-pack/plugins/siem/public/common/components/ml_popover/__snapshots__/popover_description.test.tsx.snap @@ -5,7 +5,7 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = ` size="s" > ( diff --git a/x-pack/plugins/siem/public/common/components/top_n/helpers.test.tsx b/x-pack/plugins/siem/public/common/components/top_n/helpers.test.tsx index da0f6f59b533..a9a0b85202fe 100644 --- a/x-pack/plugins/siem/public/common/components/top_n/helpers.test.tsx +++ b/x-pack/plugins/siem/public/common/components/top_n/helpers.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { allEvents, defaultOptions, getOptions, rawEvents, signalEvents } from './helpers'; +import { allEvents, defaultOptions, getOptions, rawEvents, alertEvents } from './helpers'; describe('getOptions', () => { test(`it returns the default options when 'activeTimelineEventType' is undefined`, () => { @@ -19,7 +19,7 @@ describe('getOptions', () => { expect(getOptions('raw')).toEqual(rawEvents); }); - test(`it returns 'signalEvents' when 'activeTimelineEventType' is 'signal'`, () => { - expect(getOptions('signal')).toEqual(signalEvents); + test(`it returns 'alertEvents' when 'activeTimelineEventType' is 'alert'`, () => { + expect(getOptions('alert')).toEqual(alertEvents); }); }); diff --git a/x-pack/plugins/siem/public/common/components/top_n/helpers.ts b/x-pack/plugins/siem/public/common/components/top_n/helpers.ts index a4226cc58530..b654eaf17b47 100644 --- a/x-pack/plugins/siem/public/common/components/top_n/helpers.ts +++ b/x-pack/plugins/siem/public/common/components/top_n/helpers.ts @@ -32,17 +32,17 @@ export const rawEvents: TopNOption[] = [ }, ]; -/** A (stable) array containing only the 'Signal events' option */ -export const signalEvents: TopNOption[] = [ +/** A (stable) array containing only the 'Alert events' option */ +export const alertEvents: TopNOption[] = [ { - value: 'signal', - inputDisplay: i18n.SIGNAL_EVENTS, - 'data-test-subj': 'option-signal', + value: 'alert', + inputDisplay: i18n.ALERT_EVENTS, + 'data-test-subj': 'option-alert', }, ]; /** A (stable) array containing the default Top N options */ -export const defaultOptions = [...rawEvents, ...signalEvents]; +export const defaultOptions = [...rawEvents, ...alertEvents]; /** * Returns the options to be displayed in a Top N view select. When @@ -58,8 +58,8 @@ export const getOptions = (activeTimelineEventType?: EventType): TopNOption[] => return allEvents; case 'raw': return rawEvents; - case 'signal': - return signalEvents; + case 'alert': + return alertEvents; default: return defaultOptions; } diff --git a/x-pack/plugins/siem/public/common/components/top_n/index.test.tsx b/x-pack/plugins/siem/public/common/components/top_n/index.test.tsx index d2b38a062091..0505f55d507b 100644 --- a/x-pack/plugins/siem/public/common/components/top_n/index.test.tsx +++ b/x-pack/plugins/siem/public/common/components/top_n/index.test.tsx @@ -310,13 +310,13 @@ describe('StatefulTopN', () => { }); }); - test(`defaults to the 'Signals events' option when rendering in a NON-active timeline context (e.g. the Signals table on the Detections page) when 'documentType' from 'useTimelineTypeContext()' is 'signals'`, () => { + test(`defaults to the 'Alert events' option when rendering in a NON-active timeline context (e.g. the Alerts table on the Detections page) when 'documentType' from 'useTimelineTypeContext()' is 'alerts'`, () => { const filterManager = new FilterManager(mockUiSettingsForFilterManager); const wrapper = mount( { const props = wrapper.find('[data-test-subj="top-n"]').first().props() as Props; - expect(props.defaultView).toEqual('signal'); + expect(props.defaultView).toEqual('alert'); }); }); diff --git a/x-pack/plugins/siem/public/common/components/top_n/index.tsx b/x-pack/plugins/siem/public/common/components/top_n/index.tsx index a71b27e0bd9c..f6dc8e7c66ed 100644 --- a/x-pack/plugins/siem/public/common/components/top_n/index.tsx +++ b/x-pack/plugins/siem/public/common/components/top_n/index.tsx @@ -94,14 +94,14 @@ const StatefulTopNComponent: React.FC = ({ const kibana = useKibana(); // Regarding data from useTimelineTypeContext: - // * `documentType` (e.g. 'signals') may only be populated in some views, - // e.g. the `Signals` view on the `Detections` page. + // * `documentType` (e.g. 'alerts') may only be populated in some views, + // e.g. the `Alerts` view on the `Detections` page. // * `id` (`timelineId`) may only be populated when we are rendered in the // context of the active timeline. - // * `indexToAdd`, which enables the signals index to be appended to + // * `indexToAdd`, which enables the alerts index to be appended to // the `indexPattern` returned by `WithSource`, may only be populated when // this component is rendered in the context of the active timeline. This - // behavior enables the 'All events' view by appending the signals index + // behavior enables the 'All events' view by appending the alerts index // to the index pattern. const { documentType, id: timelineId, indexToAdd } = useTimelineTypeContext(); @@ -135,7 +135,7 @@ const StatefulTopNComponent: React.FC = ({ } data-test-subj="top-n" defaultView={ - documentType?.toLocaleLowerCase() === 'signals' ? 'signal' : options[0].value + documentType?.toLocaleLowerCase() === 'alerts' ? 'alert' : options[0].value } deleteQuery={timelineId === ACTIVE_TIMELINE_REDUX_ID ? undefined : deleteQuery} field={field} diff --git a/x-pack/plugins/siem/public/common/components/top_n/top_n.test.tsx b/x-pack/plugins/siem/public/common/components/top_n/top_n.test.tsx index 0a35b9e25754..5e1476f62216 100644 --- a/x-pack/plugins/siem/public/common/components/top_n/top_n.test.tsx +++ b/x-pack/plugins/siem/public/common/components/top_n/top_n.test.tsx @@ -163,11 +163,11 @@ describe('TopN', () => { }); test(`it does NOT render SignalsByCategory when defaultView is 'raw'`, () => { - expect(wrapper.find('[data-test-subj="signals-histogram-panel"]').exists()).toBe(false); + expect(wrapper.find('[data-test-subj="alerts-histogram-panel"]').exists()).toBe(false); }); }); - describe('signals view', () => { + describe('alerts view', () => { let toggleTopN: () => void; let wrapper: ReactWrapper; @@ -176,7 +176,7 @@ describe('TopN', () => { wrapper = mount( { }); test(`it renders SignalsByCategory when defaultView is 'signal'`, () => { - expect(wrapper.find('[data-test-subj="signals-histogram-panel"]').exists()).toBe(true); + expect(wrapper.find('[data-test-subj="alerts-histogram-panel"]').exists()).toBe(true); }); test(`it does NOT render EventsByDataset when defaultView is 'signal'`, () => { @@ -242,7 +242,7 @@ describe('TopN', () => { }); test(`it does NOT render SignalsByCategory when defaultView is 'all'`, () => { - expect(wrapper.find('[data-test-subj="signals-histogram-panel"]').exists()).toBe(false); + expect(wrapper.find('[data-test-subj="alerts-histogram-panel"]').exists()).toBe(false); }); }); }); diff --git a/x-pack/plugins/siem/public/common/components/top_n/translations.ts b/x-pack/plugins/siem/public/common/components/top_n/translations.ts index 7db55fa94d42..a6873ab0ece7 100644 --- a/x-pack/plugins/siem/public/common/components/top_n/translations.ts +++ b/x-pack/plugins/siem/public/common/components/top_n/translations.ts @@ -18,6 +18,6 @@ export const RAW_EVENTS = i18n.translate('xpack.siem.topN.rawEventsSelectLabel', defaultMessage: 'Raw events', }); -export const SIGNAL_EVENTS = i18n.translate('xpack.siem.topN.signalEventsSelectLabel', { - defaultMessage: 'Signal events', +export const ALERT_EVENTS = i18n.translate('xpack.siem.topN.alertEventsSelectLabel', { + defaultMessage: 'Alert events', }); diff --git a/x-pack/plugins/siem/public/common/mock/mock_ecs.ts b/x-pack/plugins/siem/public/common/mock/mock_ecs.ts index 7fbbabb29da1..c897da69caba 100644 --- a/x-pack/plugins/siem/public/common/mock/mock_ecs.ts +++ b/x-pack/plugins/siem/public/common/mock/mock_ecs.ts @@ -1281,7 +1281,7 @@ export const mockEcsData: Ecs[] = [ }, ]; -export const mockEcsDataWithSignal: Ecs = { +export const mockEcsDataWithAlert: Ecs = { _id: '1', timestamp: '2018-11-05T19:03:25.937Z', host: { diff --git a/x-pack/plugins/siem/public/common/mock/timeline_results.ts b/x-pack/plugins/siem/public/common/mock/timeline_results.ts index 42d14daa11a6..4eb66acdfad6 100644 --- a/x-pack/plugins/siem/public/common/mock/timeline_results.ts +++ b/x-pack/plugins/siem/public/common/mock/timeline_results.ts @@ -10,7 +10,7 @@ import { TimelineType, TimelineStatus } from '../../../common/types/timeline'; import { OpenTimelineResult } from '../../timelines/components/open_timeline/types'; import { GetAllTimeline, SortFieldTimeline, TimelineResult, Direction } from '../../graphql/types'; import { allTimelinesQuery } from '../../timelines/containers/all/index.gql_query'; -import { CreateTimelineProps } from '../../alerts/components/signals/types'; +import { CreateTimelineProps } from '../../alerts/components/alerts_table/types'; import { TimelineModel } from '../../timelines/store/timeline/model'; import { timelineDefaults } from '../../timelines/store/timeline/defaults'; export interface MockedProvidedQuery { @@ -2206,7 +2206,7 @@ export const defaultTimelineProps: CreateTimelineProps = { enabled: true, excluded: false, id: - 'send-signal-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-signal-id-1', + 'send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-alert-id-1', kqlQuery: '', name: '1', queryMatch: { field: '_id', operator: ':', value: '1' }, diff --git a/x-pack/plugins/siem/public/overview/components/alerts_by_category/index.tsx b/x-pack/plugins/siem/public/overview/components/alerts_by_category/index.tsx index 9ba150a82ec9..574260a81907 100644 --- a/x-pack/plugins/siem/public/overview/components/alerts_by_category/index.tsx +++ b/x-pack/plugins/siem/public/overview/components/alerts_by_category/index.tsx @@ -11,7 +11,6 @@ import { Position } from '@elastic/charts'; import { DEFAULT_NUMBER_FORMAT } from '../../../../common/constants'; import { SHOWING, UNIT } from '../../../common/components/alerts_viewer/translations'; -import { getDetectionEngineAlertUrl } from '../../../common/components/link_to/redirect_to_detection_engine'; import { MatrixHistogramContainer } from '../../../common/components/matrix_histogram'; import { useKibana, useUiSetting$ } from '../../../common/lib/kibana'; import { convertToBuildEsQuery } from '../../../common/lib/keury'; @@ -22,7 +21,7 @@ import { Query, } from '../../../../../../../src/plugins/data/public'; import { inputsModel } from '../../../common/store'; -import { HostsType } from '../../../hosts/store/model'; +import { HostsTableType, HostsType } from '../../../hosts/store/model'; import * as i18n from '../../pages/translations'; import { @@ -32,6 +31,7 @@ import { import { MatrixHisrogramConfigs } from '../../../common/components/matrix_histogram/types'; import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search'; import { navTabs } from '../../../app/home/home_navigations'; +import { getTabsOnHostsUrl } from '../../../common/components/link_to/redirect_to_hosts'; const ID = 'alertsByCategoryOverview'; @@ -75,11 +75,14 @@ const AlertsByCategoryComponent: React.FC = ({ const kibana = useKibana(); const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); - const urlSearch = useGetUrlSearch(navTabs.detections); + const urlSearch = useGetUrlSearch(navTabs.hosts); const alertsCountViewAlertsButton = useMemo( () => ( - + {i18n.VIEW_ALERTS} ), diff --git a/x-pack/plugins/siem/public/overview/components/signals_by_category/index.tsx b/x-pack/plugins/siem/public/overview/components/signals_by_category/index.tsx index d569fd61afc9..f2ad45be9352 100644 --- a/x-pack/plugins/siem/public/overview/components/signals_by_category/index.tsx +++ b/x-pack/plugins/siem/public/overview/components/signals_by_category/index.tsx @@ -6,9 +6,9 @@ import React, { useCallback } from 'react'; -import { SignalsHistogramPanel } from '../../../alerts/components/signals_histogram_panel'; -import { signalsHistogramOptions } from '../../../alerts/components/signals_histogram_panel/config'; -import { useSignalIndex } from '../../../alerts/containers/detection_engine/signals/use_signal_index'; +import { AlertsHistogramPanel } from '../../../alerts/components/alerts_histogram_panel'; +import { alertsHistogramOptions } from '../../../alerts/components/alerts_histogram_panel/config'; +import { useSignalIndex } from '../../../alerts/containers/detection_engine/alerts/use_signal_index'; import { SetAbsoluteRangeDatePicker } from '../../../network/pages/types'; import { Filter, IIndexPattern, Query } from '../../../../../../../src/plugins/data/public'; import { inputsModel } from '../../../common/store'; @@ -65,10 +65,10 @@ const SignalsByCategoryComponent: React.FC = ({ ); const defaultStackByOption = - signalsHistogramOptions.find((o) => o.text === DEFAULT_STACK_BY) ?? signalsHistogramOptions[0]; + alertsHistogramOptions.find((o) => o.text === DEFAULT_STACK_BY) ?? alertsHistogramOptions[0]; return ( - = ({ query={query} signalIndexName={signalIndexName} setQuery={setQuery} - showTotalSignalsCount={true} - showLinkToSignals={onlyField == null ? true : false} - stackByOptions={onlyField == null ? signalsHistogramOptions : undefined} + showTotalAlertsCount={true} + showLinkToAlerts={onlyField == null ? true : false} + stackByOptions={onlyField == null ? alertsHistogramOptions : undefined} legendPosition={'right'} to={to} - title={i18n.SIGNAL_COUNT} + title={i18n.ALERT_COUNT} updateDateRange={updateDateRangeCallback} /> ); diff --git a/x-pack/plugins/siem/public/overview/pages/translations.ts b/x-pack/plugins/siem/public/overview/pages/translations.ts index b7bee15e4c5b..7c0c00029266 100644 --- a/x-pack/plugins/siem/public/overview/pages/translations.ts +++ b/x-pack/plugins/siem/public/overview/pages/translations.ts @@ -34,8 +34,8 @@ export const RECENT_TIMELINES = i18n.translate('xpack.siem.overview.recentTimeli defaultMessage: 'Recent timelines', }); -export const SIGNAL_COUNT = i18n.translate('xpack.siem.overview.signalCountTitle', { - defaultMessage: 'Signal count', +export const ALERT_COUNT = i18n.translate('xpack.siem.overview.alertCountTitle', { + defaultMessage: 'Alert count', }); export const TOP = (fieldName: string) => diff --git a/x-pack/plugins/siem/public/timelines/components/fields_browser/header.tsx b/x-pack/plugins/siem/public/timelines/components/fields_browser/header.tsx index 1136b7c8d0dc..d018487a1ccd 100644 --- a/x-pack/plugins/siem/public/timelines/components/fields_browser/header.tsx +++ b/x-pack/plugins/siem/public/timelines/components/fields_browser/header.tsx @@ -16,8 +16,8 @@ import React, { useCallback } from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../../common/containers/source'; -import { signalsHeaders } from '../../../alerts/components/signals/default_config'; -import { alertsHeaders } from '../../../common/components/alerts_viewer/default_headers'; +import { alertsHeaders } from '../../../alerts/components/alerts_table/default_config'; +import { alertsHeaders as externalAlertsHeaders } from '../../../common/components/alerts_viewer/default_headers'; import { defaultHeaders as eventsDefaultHeaders } from '../../../common/components/events_viewer/default_headers'; import { defaultHeaders } from '../timeline/body/column_headers/default_headers'; import { OnUpdateColumns } from '../timeline/events'; @@ -104,10 +104,10 @@ const TitleRow = React.memo<{ const handleResetColumns = useCallback(() => { let resetDefaultHeaders = defaultHeaders; if (isEventViewer) { - if (timelineTypeContext.documentType?.toLocaleLowerCase() === 'alerts') { + if (timelineTypeContext.documentType?.toLocaleLowerCase() === 'externalAlerts') { + resetDefaultHeaders = externalAlertsHeaders; + } else if (timelineTypeContext.documentType?.toLocaleLowerCase() === 'alerts') { resetDefaultHeaders = alertsHeaders; - } else if (timelineTypeContext.documentType?.toLocaleLowerCase() === 'signals') { - resetDefaultHeaders = signalsHeaders; } else { resetDefaultHeaders = eventsDefaultHeaders; } diff --git a/x-pack/plugins/siem/public/timelines/components/timeline/index.tsx b/x-pack/plugins/siem/public/timelines/components/timeline/index.tsx index 3d1a9075386c..c52be64f94bf 100644 --- a/x-pack/plugins/siem/public/timelines/components/timeline/index.tsx +++ b/x-pack/plugins/siem/public/timelines/components/timeline/index.tsx @@ -9,7 +9,7 @@ import { connect, ConnectedProps } from 'react-redux'; import deepEqual from 'fast-deep-equal'; import { WithSource } from '../../../common/containers/source'; -import { useSignalIndex } from '../../../alerts/containers/detection_engine/signals/use_signal_index'; +import { useSignalIndex } from '../../../alerts/containers/detection_engine/alerts/use_signal_index'; import { inputsModel, inputsSelectors, State } from '../../../common/store'; import { timelineActions, timelineSelectors } from '../../store/timeline'; import { ColumnHeaderOptions, TimelineModel } from '../../../timelines/store/timeline/model'; diff --git a/x-pack/plugins/siem/public/timelines/components/timeline/search_or_filter/pick_events.tsx b/x-pack/plugins/siem/public/timelines/components/timeline/search_or_filter/pick_events.tsx index 85097f93464b..5a3805af0ca4 100644 --- a/x-pack/plugins/siem/public/timelines/components/timeline/search_or_filter/pick_events.tsx +++ b/x-pack/plugins/siem/public/timelines/components/timeline/search_or_filter/pick_events.tsx @@ -60,7 +60,7 @@ export const eventTypeOptions: EventTypeOptionItem[] = [ inputDisplay: {i18n.RAW_EVENT}, }, { - value: 'signal', + value: 'alert', inputDisplay: {i18n.SIGNAL_EVENT}, }, ]; diff --git a/x-pack/plugins/siem/public/timelines/containers/index.tsx b/x-pack/plugins/siem/public/timelines/containers/index.tsx index 76f6bdd36ece..5efcb8453912 100644 --- a/x-pack/plugins/siem/public/timelines/containers/index.tsx +++ b/x-pack/plugins/siem/public/timelines/containers/index.tsx @@ -27,7 +27,7 @@ import { QueryTemplate, QueryTemplateProps } from '../../common/containers/query import { EventType } from '../../timelines/store/timeline/model'; import { timelineQuery } from './index.gql_query'; import { timelineActions } from '../../timelines/store/timeline'; -import { SIGNALS_PAGE_TIMELINE_ID } from '../../alerts/components/signals'; +import { ALERTS_TABLE_TIMELINE_ID } from '../../alerts/components/alerts_table'; export interface TimelineArgs { events: TimelineItem[]; @@ -182,7 +182,7 @@ const makeMapStateToProps = () => { const mapDispatchToProps = (dispatch: Dispatch) => ({ clearSignalsState: ({ id }: { id?: string }) => { - if (id != null && id === SIGNALS_PAGE_TIMELINE_ID) { + if (id != null && id === ALERTS_TABLE_TIMELINE_ID) { dispatch(timelineActions.clearEventsLoading({ id })); dispatch(timelineActions.clearEventsDeleted({ id })); } diff --git a/x-pack/plugins/siem/public/timelines/store/timeline/model.ts b/x-pack/plugins/siem/public/timelines/store/timeline/model.ts index bf86d6363861..f7e848e8a9e1 100644 --- a/x-pack/plugins/siem/public/timelines/store/timeline/model.ts +++ b/x-pack/plugins/siem/public/timelines/store/timeline/model.ts @@ -18,7 +18,7 @@ import { KueryFilterQuery, SerializedFilterQuery } from '../../../common/store/t export const DEFAULT_PAGE_COUNT = 2; // Eui Pager will not render unless this is a minimum of 2 pages export type KqlMode = 'filter' | 'search'; -export type EventType = 'all' | 'raw' | 'signal'; +export type EventType = 'all' | 'raw' | 'alert'; export type ColumnHeaderType = 'not-filtered' | 'text-filter'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index cce23d3a0d59..9246d8cb3519 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -641,7 +641,7 @@ export const getEmptySignalsResponse = (): SignalSearchResponse => ({ _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, hits: { total: { value: 0, relation: 'eq' }, max_score: 0, hits: [] }, aggregations: { - signalsByGrouping: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, + alertsByGrouping: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, }, }); diff --git a/x-pack/plugins/siem/server/lib/matrix_histogram/translations.ts b/x-pack/plugins/siem/server/lib/matrix_histogram/translations.ts index 413acaa2d4b0..9fdfc9ff7be0 100644 --- a/x-pack/plugins/siem/server/lib/matrix_histogram/translations.ts +++ b/x-pack/plugins/siem/server/lib/matrix_histogram/translations.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; export const ALL_OTHERS = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.allOthersGroupingLabel', + 'xpack.siem.detectionEngine.alerts.histogram.allOthersGroupingLabel', { defaultMessage: 'All others', } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 18186f9a1bfc..dbb2a85eb2a2 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -13853,9 +13853,6 @@ "xpack.siem.containers.detectionEngine.createPrePackagedRuleFailDescription": "Elasticから事前にパッケージ化されているルールをインストールすることができませんでした", "xpack.siem.containers.detectionEngine.createPrePackagedRuleSuccesDescription": "Elasticから事前にパッケージ化されているルールをインストールしました", "xpack.siem.containers.detectionEngine.rules": "ルールを取得できませんでした", - "xpack.siem.containers.detectionEngine.signals.errorFetchingSignalsDescription": "シグナルをクエリできませんでした", - "xpack.siem.containers.detectionEngine.signals.errorGetSignalDescription": "シグナルインデックス名を取得できませんでした", - "xpack.siem.containers.detectionEngine.signals.errorPostSignalDescription": "シグナルインデックスを作成できませんでした", "xpack.siem.containers.detectionEngine.tagFetchFailDescription": "タグを取得できませんでした", "xpack.siem.containers.errors.dataFetchFailureTitle": "データの取得に失敗", "xpack.siem.containers.errors.networkFailureTitle": "ネットワーク障害", @@ -13972,7 +13969,6 @@ "xpack.siem.detectionEngine.detectionsPageTitle": "検出", "xpack.siem.detectionEngine.dismissButton": "閉じる", "xpack.siem.detectionEngine.dismissNoApiIntegrationKeyButton": "閉じる", - "xpack.siem.detectionEngine.dismissNoWriteSignalButton": "閉じる", "xpack.siem.detectionEngine.editRule.backToDescription": "戻る", "xpack.siem.detectionEngine.editRule.cancelTitle": "キャンセル", "xpack.siem.detectionEngine.editRule.errorMsgDescription": "申し訳ありません", @@ -13984,7 +13980,6 @@ "xpack.siem.detectionEngine.goToDocumentationButton": "ドキュメンテーションを表示", "xpack.siem.detectionEngine.headerPage.pageBadgeLabel": "ベータ", "xpack.siem.detectionEngine.headerPage.pageBadgeTooltip": "検出はまだベータ段階です。Kibana repoで問題やバグを報告して、製品の改善にご協力ください。", - "xpack.siem.detectionEngine.lastSignalTitle": "前回のシグナル", "xpack.siem.detectionEngine.mitreAttack.addTitle": "MITRE ATT&CK\\u2122脅威を追加", "xpack.siem.detectionEngine.mitreAttack.tacticPlaceHolderDescription": "Tacticを追加...", "xpack.siem.detectionEngine.mitreAttack.tacticsDescription": "Tactic", @@ -14274,8 +14269,6 @@ "xpack.siem.detectionEngine.noApiIntegrationKeyCallOutTitle": "API統合キーが必要です", "xpack.siem.detectionEngine.noIndexMsgBody": "検出エンジンを使用するには、必要なクラスターとインデックス権限のユーザーが最初にこのページにアクセスする必要があります。ヘルプについては、管理者にお問い合わせください。", "xpack.siem.detectionEngine.noIndexTitle": "検出エンジンを設定しましょう", - "xpack.siem.detectionEngine.noWriteSignalsCallOutMsg": "現在、シグナルを更新するための必要な権限がありません。サポートについては、管理者にお問い合わせください。", - "xpack.siem.detectionEngine.noWriteSignalsCallOutTitle": "シグナルインデックス権限が必要です", "xpack.siem.detectionEngine.pageTitle": "検出エンジン", "xpack.siem.detectionEngine.panelSubtitleShowing": "表示中", "xpack.siem.detectionEngine.readOnlyCallOutMsg": "現在、検出エンジンルールを作成/編集するための必要な権限がありません。サポートについては、管理者にお問い合わせください。", @@ -14380,43 +14373,6 @@ "xpack.siem.detectionEngine.ruleStatus.statusDateDescription": "ステータス日付", "xpack.siem.detectionEngine.ruleStatus.statusDescription": "前回の応答", "xpack.siem.detectionEngine.signalRuleAlert.actionGroups.default": "デフォルト", - "xpack.siem.detectionEngine.signals.actions.closeSignalTitle": "シグナルを閉じる", - "xpack.siem.detectionEngine.signals.actions.investigateInTimelineTitle": "タイムラインで調査", - "xpack.siem.detectionEngine.signals.actions.openSignalTitle": "シグナルを開く", - "xpack.siem.detectionEngine.signals.closedSignalsTitle": "閉じたシグナル", - "xpack.siem.detectionEngine.signals.documentTypeTitle": "シグナル", - "xpack.siem.detectionEngine.signals.histogram.allOthersGroupingLabel": "その他すべて", - "xpack.siem.detectionEngine.signals.histogram.headerTitle": "シグナル数", - "xpack.siem.detectionEngine.signals.histogram.showingSignalsTitle": "表示中: {modifier}{totalSignalsFormatted} {totalSignals, plural, =1 {シグナル} other {シグナル}}", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.destinationIpsDropDown": "上位のデスティネーションIP", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.eventActionsDropDown": "上位のイベントアクション", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.eventCategoriesDropDown": "上位のイベントカテゴリー", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.hostNamesDropDown": "上位のホスト名", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.riskScoresDropDown": "リスクスコア", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.rulesDropDown": "上位のルール", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.ruleTypesDropDown": "上位のルールタイプ", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.severitiesDropDown": "重要度", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.sourceIpsDropDown": "上位のソースIP", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.stackByLabel": "積み上げ", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.usersDropDown": "上位のユーザー", - "xpack.siem.detectionEngine.signals.histogram.topNLabel": "トップ{fieldName}", - "xpack.siem.detectionEngine.signals.histogram.viewSignalsButtonLabel": "シグナルの表示", - "xpack.siem.detectionEngine.signals.loadingSignalsTitle": "シグナルの読み込み中", - "xpack.siem.detectionEngine.signals.openSignalsTitle": "シグナルを開く", - "xpack.siem.detectionEngine.signals.tableTitle": "シグナル", - "xpack.siem.detectionEngine.signals.totalCountOfSignalsTitle": "シグナルが検索条件に一致します", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.closeSelectedTitle": "選択した項目を閉じる", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.openSelectedTitle": "選択した項目を開く", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInHostsTitle": "ホストで選択した項目を表示", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInNetworkTitle": "ネットワークで選択した項目を表示", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInTimelineTitle": "タイムラインで選択した項目を表示", - "xpack.siem.detectionEngine.signals.utilityBar.batchActionsTitle": "バッチ処理", - "xpack.siem.detectionEngine.signals.utilityBar.clearSelectionTitle": "選択した項目をクリア", - "xpack.siem.detectionEngine.signals.utilityBar.selectAllSignalsTitle": "すべての{totalSignalsFormatted} {totalSignals, plural, =1 {シグナル} other {シグナル}}を選択", - "xpack.siem.detectionEngine.signals.utilityBar.selectedSignalsTitle": "{selectedSignalsFormatted} {selectedSignals, plural, =1 {シグナル} other {シグナル}}を選択しました", - "xpack.siem.detectionEngine.signals.utilityBar.showingSignalsTitle": "{totalSignalsFormatted} {totalSignals, plural, =1 {シグナル} other {シグナル}}を表示中", - "xpack.siem.detectionEngine.signalTitle": "検出したシグナル", - "xpack.siem.detectionEngine.totalSignalTitle": "合計", "xpack.siem.detectionEngine.userUnauthenticatedMsgBody": "検出エンジンを表示するための必要なアクセス権がありません。ヘルプについては、管理者にお問い合わせください。", "xpack.siem.detectionEngine.userUnauthenticatedTitle": "検出エンジンアクセス権が必要です", "xpack.siem.dragAndDrop.addToTimeline": "タイムライン調査に追加", @@ -14456,11 +14412,6 @@ "xpack.siem.eventsViewer.eventsLabel": "イベント", "xpack.siem.eventsViewer.footer.loadingEventsDataLabel": "イベントを読み込み中", "xpack.siem.eventsViewer.showingLabel": "表示中", - "xpack.siem.eventsViewer.signals.defaultHeaders.methodTitle": "メソド", - "xpack.siem.eventsViewer.signals.defaultHeaders.riskScoreTitle": "リスクスコア", - "xpack.siem.eventsViewer.signals.defaultHeaders.ruleTitle": "ルール", - "xpack.siem.eventsViewer.signals.defaultHeaders.severityTitle": "深刻度", - "xpack.siem.eventsViewer.signals.defaultHeaders.versionTitle": "バージョン", "xpack.siem.eventsViewer.unit": "{totalCount, plural, =1 {event} other {events}}", "xpack.siem.featureCatalogue.description": "セキュリティメトリクスとログのイベントとアラートを確認します", "xpack.siem.featureCatalogue.title": "SIEM", @@ -14793,7 +14744,6 @@ "xpack.siem.overview.recentlyCreatedCasesButtonLabel": "最近作成したケース", "xpack.siem.overview.recentTimelinesSidebarTitle": "最近のタイムライン", "xpack.siem.overview.showTopTooltip": "上位の{fieldName}を表示", - "xpack.siem.overview.signalCountTitle": "シグナル数", "xpack.siem.overview.startedText": "セキュリティ情報およびイベント管理(SIEM)へようこそ。はじめに{docs}や{data}をご参照ください。今後の機能に関する情報やチュートリアルは、{siemSolution} ページをお見逃しなく。", "xpack.siem.overview.startedText.dataLinkText": "投入データ", "xpack.siem.overview.startedText.docsLinkText": "ドキュメンテーション", @@ -14974,7 +14924,6 @@ "xpack.siem.topN.allEventsSelectLabel": "すべてのイベント", "xpack.siem.topN.closeButtonLabel": "閉じる", "xpack.siem.topN.rawEventsSelectLabel": "未加工イベント", - "xpack.siem.topN.signalEventsSelectLabel": "シグナルイベント", "xpack.siem.uiSettings.defaultAnomalyScoreDescription": "

機械学習ジョブの異常がこの値を超えると SIEM アプリに表示されます。

有効な値:0 ~ 100。

", "xpack.siem.uiSettings.defaultAnomalyScoreLabel": "デフォルトの異常しきい値", "xpack.siem.uiSettings.defaultIndexDescription": "

SIEM アプリがイベントを収集する Elasticsearch インデックスのコンマ区切りのリストです。

", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 195309f640a0..5527385c752e 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -13858,9 +13858,6 @@ "xpack.siem.containers.detectionEngine.createPrePackagedRuleFailDescription": "无法安装 elastic 的预打包规则", "xpack.siem.containers.detectionEngine.createPrePackagedRuleSuccesDescription": "已安装 elastic 的预打包规则", "xpack.siem.containers.detectionEngine.rules": "无法提取规则", - "xpack.siem.containers.detectionEngine.signals.errorFetchingSignalsDescription": "无法查询信号", - "xpack.siem.containers.detectionEngine.signals.errorGetSignalDescription": "无法获取信号索引名称", - "xpack.siem.containers.detectionEngine.signals.errorPostSignalDescription": "无法创建信号索引", "xpack.siem.containers.detectionEngine.tagFetchFailDescription": "无法提取标记", "xpack.siem.containers.errors.dataFetchFailureTitle": "数据提取失败", "xpack.siem.containers.errors.networkFailureTitle": "网络故障", @@ -13977,7 +13974,6 @@ "xpack.siem.detectionEngine.detectionsPageTitle": "检测", "xpack.siem.detectionEngine.dismissButton": "关闭", "xpack.siem.detectionEngine.dismissNoApiIntegrationKeyButton": "关闭", - "xpack.siem.detectionEngine.dismissNoWriteSignalButton": "关闭", "xpack.siem.detectionEngine.editRule.backToDescription": "返回到", "xpack.siem.detectionEngine.editRule.cancelTitle": "取消", "xpack.siem.detectionEngine.editRule.errorMsgDescription": "抱歉", @@ -13989,7 +13985,6 @@ "xpack.siem.detectionEngine.goToDocumentationButton": "查看文档", "xpack.siem.detectionEngine.headerPage.pageBadgeLabel": "公测版", "xpack.siem.detectionEngine.headerPage.pageBadgeTooltip": "“检测”仍为公测版。请通过在 Kibana 存储库中报告问题或错误,帮助我们改进产品。", - "xpack.siem.detectionEngine.lastSignalTitle": "上一信号", "xpack.siem.detectionEngine.mitreAttack.addTitle": "添加 MITRE ATT&CK\\u2122 威胁", "xpack.siem.detectionEngine.mitreAttack.tacticPlaceHolderDescription": "选择策略......", "xpack.siem.detectionEngine.mitreAttack.tacticsDescription": "策略", @@ -14279,8 +14274,6 @@ "xpack.siem.detectionEngine.noApiIntegrationKeyCallOutTitle": "需要 API 集成密钥", "xpack.siem.detectionEngine.noIndexMsgBody": "要使用检测引擎,具有所需集群和索引权限的用户必须首先访问此页面。若需要更多帮助,请联系您的管理员。", "xpack.siem.detectionEngine.noIndexTitle": "让我们来设置您的检测引擎", - "xpack.siem.detectionEngine.noWriteSignalsCallOutMsg": "您当前缺少所需的权限,无法更新信号。有关进一步帮助,请联系您的管理员。", - "xpack.siem.detectionEngine.noWriteSignalsCallOutTitle": "需要信号索引权限", "xpack.siem.detectionEngine.pageTitle": "检测引擎", "xpack.siem.detectionEngine.panelSubtitleShowing": "正在显示", "xpack.siem.detectionEngine.readOnlyCallOutMsg": "您当前缺少所需的权限,无法创建/编辑检测引擎规则。有关进一步帮助,请联系您的管理员。", @@ -14385,43 +14378,6 @@ "xpack.siem.detectionEngine.ruleStatus.statusDateDescription": "状态日期", "xpack.siem.detectionEngine.ruleStatus.statusDescription": "上次响应", "xpack.siem.detectionEngine.signalRuleAlert.actionGroups.default": "默认值", - "xpack.siem.detectionEngine.signals.actions.closeSignalTitle": "关闭信号", - "xpack.siem.detectionEngine.signals.actions.investigateInTimelineTitle": "在时间线中调查", - "xpack.siem.detectionEngine.signals.actions.openSignalTitle": "打开信号", - "xpack.siem.detectionEngine.signals.closedSignalsTitle": "已关闭信号", - "xpack.siem.detectionEngine.signals.documentTypeTitle": "信号", - "xpack.siem.detectionEngine.signals.histogram.allOthersGroupingLabel": "所有其他", - "xpack.siem.detectionEngine.signals.histogram.headerTitle": "信号计数", - "xpack.siem.detectionEngine.signals.histogram.showingSignalsTitle": "正在显示:{modifier}{totalSignalsFormatted} 个{totalSignals, plural, =1 {信号} other {信号}}", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.destinationIpsDropDown": "排名靠前的目标 IP", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.eventActionsDropDown": "排名靠前的事件操作", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.eventCategoriesDropDown": "排名靠前的事件类别", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.hostNamesDropDown": "排名靠前的主机名", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.riskScoresDropDown": "风险分数", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.rulesDropDown": "排名靠前的规则", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.ruleTypesDropDown": "排名靠前的规则类型", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.severitiesDropDown": "严重性", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.sourceIpsDropDown": "排名靠前的源 IP", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.stackByLabel": "堆叠依据", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.usersDropDown": "排名靠前的用户", - "xpack.siem.detectionEngine.signals.histogram.topNLabel": "热门{fieldName}", - "xpack.siem.detectionEngine.signals.histogram.viewSignalsButtonLabel": "查看信号", - "xpack.siem.detectionEngine.signals.loadingSignalsTitle": "正在加载信号", - "xpack.siem.detectionEngine.signals.openSignalsTitle": "打开信号", - "xpack.siem.detectionEngine.signals.tableTitle": "信号", - "xpack.siem.detectionEngine.signals.totalCountOfSignalsTitle": "个信号匹配搜索条件", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.closeSelectedTitle": "关闭选定", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.openSelectedTitle": "打开选定", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInHostsTitle": "查看主机中所选", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInNetworkTitle": "查看网络中所选", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInTimelineTitle": "查看时间线中所选", - "xpack.siem.detectionEngine.signals.utilityBar.batchActionsTitle": "批量操作", - "xpack.siem.detectionEngine.signals.utilityBar.clearSelectionTitle": "清除选择", - "xpack.siem.detectionEngine.signals.utilityBar.selectAllSignalsTitle": "选择所有 {totalSignalsFormatted} 个{totalSignals, plural, =1 {信号} other {信号}}", - "xpack.siem.detectionEngine.signals.utilityBar.selectedSignalsTitle": "已选择 {selectedSignalsFormatted} 个{selectedSignals, plural, =1 {信号} other {信号}}", - "xpack.siem.detectionEngine.signals.utilityBar.showingSignalsTitle": "正在显示 {totalSignalsFormatted} 个{totalSignals, plural, =1 {信号} other {信号}}", - "xpack.siem.detectionEngine.signalTitle": "检测到的信号", - "xpack.siem.detectionEngine.totalSignalTitle": "合计", "xpack.siem.detectionEngine.userUnauthenticatedMsgBody": "您没有所需的权限,无法查看检测引擎。若需要更多帮助,请联系您的管理员。", "xpack.siem.detectionEngine.userUnauthenticatedTitle": "需要检测引擎权限", "xpack.siem.dragAndDrop.addToTimeline": "添加到时间线调查", @@ -14461,11 +14417,6 @@ "xpack.siem.eventsViewer.eventsLabel": "事件", "xpack.siem.eventsViewer.footer.loadingEventsDataLabel": "正在加载事件", "xpack.siem.eventsViewer.showingLabel": "显示", - "xpack.siem.eventsViewer.signals.defaultHeaders.methodTitle": "方法", - "xpack.siem.eventsViewer.signals.defaultHeaders.riskScoreTitle": "风险分数", - "xpack.siem.eventsViewer.signals.defaultHeaders.ruleTitle": "规则", - "xpack.siem.eventsViewer.signals.defaultHeaders.severityTitle": "严重性", - "xpack.siem.eventsViewer.signals.defaultHeaders.versionTitle": "版本", "xpack.siem.eventsViewer.unit": "{totalCount, plural, =1 {个事件} other {个事件}}", "xpack.siem.featureCatalogue.description": "浏览安全指标和日志以了解事件和告警", "xpack.siem.featureCatalogue.title": "SIEM", @@ -14798,7 +14749,6 @@ "xpack.siem.overview.recentlyCreatedCasesButtonLabel": "最近创建的案例", "xpack.siem.overview.recentTimelinesSidebarTitle": "最近的时间线", "xpack.siem.overview.showTopTooltip": "显示热门{fieldName}", - "xpack.siem.overview.signalCountTitle": "信号计数", "xpack.siem.overview.startedText": "欢迎使用安全信息和事件管理 (SIEM)。首先,查看我们的 {docs} 或 {data}。有关即将推出的功能和教程,确保查看我们的{siemSolution}页。", "xpack.siem.overview.startedText.dataLinkText": "正在采集数据", "xpack.siem.overview.startedText.docsLinkText": "文档", @@ -14979,7 +14929,6 @@ "xpack.siem.topN.allEventsSelectLabel": "所有事件", "xpack.siem.topN.closeButtonLabel": "关闭", "xpack.siem.topN.rawEventsSelectLabel": "原始事件", - "xpack.siem.topN.signalEventsSelectLabel": "信号事件", "xpack.siem.uiSettings.defaultAnomalyScoreDescription": "

在显示异常之前要超过的默认异常分数阈值。

有效值:0 到 100。

", "xpack.siem.uiSettings.defaultAnomalyScoreLabel": "默认异常阈值", "xpack.siem.uiSettings.defaultIndexDescription": "

SIEM 应用要从其中搜索事件的 Elasticsearch 索引逗号分隔列表。

", diff --git a/x-pack/test/siem_cypress/es_archives/signals/data.json.gz b/x-pack/test/siem_cypress/es_archives/alerts/data.json.gz similarity index 100% rename from x-pack/test/siem_cypress/es_archives/signals/data.json.gz rename to x-pack/test/siem_cypress/es_archives/alerts/data.json.gz diff --git a/x-pack/test/siem_cypress/es_archives/signals/mappings.json b/x-pack/test/siem_cypress/es_archives/alerts/mappings.json similarity index 100% rename from x-pack/test/siem_cypress/es_archives/signals/mappings.json rename to x-pack/test/siem_cypress/es_archives/alerts/mappings.json diff --git a/x-pack/test/siem_cypress/es_archives/closed_signals/data.json.gz b/x-pack/test/siem_cypress/es_archives/closed_alerts/data.json.gz similarity index 100% rename from x-pack/test/siem_cypress/es_archives/closed_signals/data.json.gz rename to x-pack/test/siem_cypress/es_archives/closed_alerts/data.json.gz diff --git a/x-pack/test/siem_cypress/es_archives/closed_signals/mappings.json b/x-pack/test/siem_cypress/es_archives/closed_alerts/mappings.json similarity index 100% rename from x-pack/test/siem_cypress/es_archives/closed_signals/mappings.json rename to x-pack/test/siem_cypress/es_archives/closed_alerts/mappings.json diff --git a/x-pack/test/siem_cypress/es_archives/timeline_signals/data.json.gz b/x-pack/test/siem_cypress/es_archives/timeline_alerts/data.json.gz similarity index 100% rename from x-pack/test/siem_cypress/es_archives/timeline_signals/data.json.gz rename to x-pack/test/siem_cypress/es_archives/timeline_alerts/data.json.gz diff --git a/x-pack/test/siem_cypress/es_archives/timeline_signals/mappings.json b/x-pack/test/siem_cypress/es_archives/timeline_alerts/mappings.json similarity index 100% rename from x-pack/test/siem_cypress/es_archives/timeline_signals/mappings.json rename to x-pack/test/siem_cypress/es_archives/timeline_alerts/mappings.json From 36c6cd941549baecacfa8b86fd30c7d7a8edd61d Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Wed, 3 Jun 2020 06:41:18 +0200 Subject: [PATCH 41/50] [Discover] Fix missing histogram time range update when query is refreshed (#67582) --- .../public/application/angular/discover.js | 1 + test/functional/apps/discover/_discover.js | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index daea8b593804..f9ccdbf5ab53 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -870,6 +870,7 @@ function discoverController( if ($scope.vis.data.aggs.aggs[1]) { $scope.bucketInterval = $scope.vis.data.aggs.aggs[1].buckets.getInterval(); } + $scope.updateTime(); } $scope.hits = resp.hits.total; diff --git a/test/functional/apps/discover/_discover.js b/test/functional/apps/discover/_discover.js index 163022e32c82..9f3ce667d64b 100644 --- a/test/functional/apps/discover/_discover.js +++ b/test/functional/apps/discover/_discover.js @@ -233,5 +233,19 @@ export default function ({ getService, getPageObjects }) { expect(await PageObjects.discover.getNrOfFetches()).to.be(1); }); }); + + describe('empty query', function () { + it('should update the histogram timerange when the query is resubmitted', async function () { + await kibanaServer.uiSettings.update({ + 'timepicker:timeDefaults': '{ "from": "2015-09-18T19:37:13.000Z", "to": "now"}', + }); + await PageObjects.common.navigateToApp('discover'); + await PageObjects.header.awaitKibanaChrome(); + const initialTimeString = await PageObjects.discover.getChartTimespan(); + await queryBar.submitQuery(); + const refreshedTimeString = await PageObjects.discover.getChartTimespan(); + expect(refreshedTimeString).not.to.be(initialTimeString); + }); + }); }); } From fbcb74ce285a1d35af153fe8422fcfa6bd860557 Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Wed, 3 Jun 2020 09:35:44 +0300 Subject: [PATCH 42/50] Add error if filter index pattern is missing (#66979) * add error if filter index pattern is gone * docs change - why? * Fix i18n * Added a functional test for broken filters (field + index pattern) * Clarify readme * Moved readme * New warning status * Remove getAll * git pull upstream master * Fix translation files * Fix merge * added filterbar texts * disabled correction * Disable check in maps test until #64861 is resolved * Fix tests, warning state is not disabled. * Adjust warning filter - ignore filters from "foreign" index pattern, that are still applicable * Add an additional unrelaeted filter test * Update src/plugins/data/public/ui/filter_bar/_global_filter_item.scss Co-authored-by: Caroline Horn <549577+cchaos@users.noreply.github.com> * Update src/plugins/data/public/ui/filter_bar/_global_filter_item.scss Co-authored-by: Caroline Horn <549577+cchaos@users.noreply.github.com> * Fixed test data * Revert mapping * Update data with missing test * Update test to match data * Code review Co-authored-by: Caroline Horn <549577+cchaos@users.noreply.github.com> Co-authored-by: Elastic Machine --- .../es_query/filters/get_display_value.ts | 4 +- .../filter_manager/compare_filters.test.ts | 16 + .../query/filter_manager/compare_filters.ts | 4 + .../ui/filter_bar/_global_filter_item.scss | 15 +- .../data/public/ui/filter_bar/filter_bar.tsx | 1 + .../ui/filter_bar/filter_editor/index.tsx | 7 +- .../data/public/ui/filter_bar/filter_item.tsx | 322 +++++++++++------- .../ui/filter_bar/filter_view/index.tsx | 12 +- .../apps/dashboard/dashboard_filter_bar.js | 32 ++ .../functional/fixtures/es_archiver/README.md | 10 + .../dashboard/current/kibana/data.json.gz | Bin 16643 -> 20510 bytes .../dashboard/current/kibana/mappings.json | 230 ++++++++++++- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../maps/embeddable/tooltip_filter_actions.js | 5 +- 15 files changed, 525 insertions(+), 135 deletions(-) create mode 100644 test/functional/fixtures/es_archiver/README.md diff --git a/src/plugins/data/common/es_query/filters/get_display_value.ts b/src/plugins/data/common/es_query/filters/get_display_value.ts index 03167f308041..10b4dab3f46e 100644 --- a/src/plugins/data/common/es_query/filters/get_display_value.ts +++ b/src/plugins/data/common/es_query/filters/get_display_value.ts @@ -25,6 +25,7 @@ import { Filter } from '../filters'; function getValueFormatter(indexPattern?: IIndexPattern, key?: string) { if (!indexPattern || !key) return; + let format = get(indexPattern, ['fields', 'byName', key, 'format']); if (!format && (indexPattern.fields as any).getByName) { // TODO: Why is indexPatterns sometimes a map and sometimes an array? @@ -43,9 +44,8 @@ function getValueFormatter(indexPattern?: IIndexPattern, key?: string) { } export function getDisplayValueFromFilter(filter: Filter, indexPatterns: IIndexPattern[]): string { - const indexPattern = getIndexPatternFromFilter(filter, indexPatterns); - if (typeof filter.meta.value === 'function') { + const indexPattern = getIndexPatternFromFilter(filter, indexPatterns); const valueFormatter: any = getValueFormatter(indexPattern, filter.meta.key); return filter.meta.value(valueFormatter); } else { diff --git a/src/plugins/data/common/query/filter_manager/compare_filters.test.ts b/src/plugins/data/common/query/filter_manager/compare_filters.test.ts index 0c3947ade822..1e5391332e6b 100644 --- a/src/plugins/data/common/query/filter_manager/compare_filters.test.ts +++ b/src/plugins/data/common/query/filter_manager/compare_filters.test.ts @@ -228,5 +228,21 @@ describe('filter manager utilities', () => { expect(compareFilters([f1], [f2], COMPARE_ALL_OPTIONS)).toBeFalsy(); }); + + test('should compare index with index true', () => { + const f1 = { + $state: { store: FilterStateStore.GLOBAL_STATE }, + ...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index', ''), + }; + const f2 = { + $state: { store: FilterStateStore.GLOBAL_STATE }, + ...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index', ''), + }; + + f2.meta.index = 'wassup'; + f2.meta.index = 'dog'; + + expect(compareFilters([f1], [f2], { index: true })).toBeFalsy(); + }); }); }); diff --git a/src/plugins/data/common/query/filter_manager/compare_filters.ts b/src/plugins/data/common/query/filter_manager/compare_filters.ts index 3be52a9a6097..65df6e26a25b 100644 --- a/src/plugins/data/common/query/filter_manager/compare_filters.ts +++ b/src/plugins/data/common/query/filter_manager/compare_filters.ts @@ -21,6 +21,7 @@ import { defaults, isEqual, omit, map } from 'lodash'; import { FilterMeta, Filter } from '../../es_query'; export interface FilterCompareOptions { + index?: boolean; disabled?: boolean; negate?: boolean; state?: boolean; @@ -31,6 +32,7 @@ export interface FilterCompareOptions { * Include disabled, negate and store when comparing filters */ export const COMPARE_ALL_OPTIONS: FilterCompareOptions = { + index: true, disabled: true, negate: true, state: true, @@ -44,6 +46,7 @@ const mapFilter = ( ) => { const cleaned: FilterMeta = omit(filter, excludedAttributes); + if (comparators.index) cleaned.index = filter.meta?.index; if (comparators.negate) cleaned.negate = filter.meta && Boolean(filter.meta.negate); if (comparators.disabled) cleaned.disabled = filter.meta && Boolean(filter.meta.disabled); if (comparators.alias) cleaned.alias = filter.meta?.alias; @@ -81,6 +84,7 @@ export const compareFilters = ( const excludedAttributes: string[] = ['$$hashKey', 'meta']; comparators = defaults(comparatorOptions || {}, { + index: false, state: false, negate: false, disabled: false, diff --git a/src/plugins/data/public/ui/filter_bar/_global_filter_item.scss b/src/plugins/data/public/ui/filter_bar/_global_filter_item.scss index 24adf0093af9..73ec14de82b4 100644 --- a/src/plugins/data/public/ui/filter_bar/_global_filter_item.scss +++ b/src/plugins/data/public/ui/filter_bar/_global_filter_item.scss @@ -32,15 +32,26 @@ font-style: italic; } -.globalFilterItem-isInvalid { +.globalFilterItem-isError, .globalFilterItem-isWarning { text-decoration: none; .globalFilterLabel__value { - color: $euiColorDanger; font-weight: $euiFontWeightBold; } } +.globalFilterItem-isError { + .globalFilterLabel__value { + color: makeHighContrastColor($euiColorDangerText, $euiColorLightShade); + } +} + +.globalFilterItem-isWarning { + .globalFilterLabel__value { + color: makeHighContrastColor($euiColorWarningText, $euiColorLightShade); + } +} + .globalFilterItem-isPinned { position: relative; diff --git a/src/plugins/data/public/ui/filter_bar/filter_bar.tsx b/src/plugins/data/public/ui/filter_bar/filter_bar.tsx index d89cf01eedd4..a54a25acc591 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_bar.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_bar.tsx @@ -64,6 +64,7 @@ function FilterBarUI(props: Props) { onUpdate(i, newFilter)} onRemove={() => onRemove(i)} diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/index.tsx b/src/plugins/data/public/ui/filter_bar/filter_editor/index.tsx index fd228a221379..0e2bcc758195 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_editor/index.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/index.tsx @@ -198,9 +198,14 @@ class FilterEditorUI extends Component { if ( this.props.indexPatterns.length <= 1 && this.props.indexPatterns.find( - (indexPattern) => indexPattern === this.state.selectedIndexPattern + (indexPattern) => indexPattern === this.getIndexPatternFromFilter() ) ) { + /** + * Don't render the index pattern selector if there's just one \ zero index patterns + * and if the index pattern the filter was LOADED with is in the indexPatterns list. + **/ + return ''; } const { selectedIndexPattern } = this.state; diff --git a/src/plugins/data/public/ui/filter_bar/filter_item.tsx b/src/plugins/data/public/ui/filter_bar/filter_item.tsx index 528ec4800e7b..c44e1faeb8e7 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_item.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_item.tsx @@ -18,9 +18,9 @@ */ import { EuiContextMenu, EuiPopover } from '@elastic/eui'; -import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; +import { InjectedIntl } from '@kbn/i18n/react'; import classNames from 'classnames'; -import React, { Component, MouseEvent } from 'react'; +import React, { MouseEvent, useState, useEffect } from 'react'; import { IUiSettingsClient } from 'src/core/public'; import { FilterEditor } from './filter_editor'; import { FilterView } from './filter_view'; @@ -32,8 +32,9 @@ import { toggleFilterNegated, toggleFilterPinned, toggleFilterDisabled, + getIndexPatternFromFilter, } from '../../../common'; -import { getNotifications } from '../../services'; +import { getIndexPatterns } from '../../services'; interface Props { id: string; @@ -46,95 +47,123 @@ interface Props { uiSettings: IUiSettingsClient; } -interface State { - isPopoverOpen: boolean; +interface LabelOptions { + title: string; + status: string; + message?: string; } -class FilterItemUI extends Component { - public state = { - isPopoverOpen: false, - }; +const FILTER_ITEM_OK = ''; +const FILTER_ITEM_WARNING = 'warn'; +const FILTER_ITEM_ERROR = 'error'; - private handleBadgeClick = (e: MouseEvent) => { - if (e.shiftKey) { - this.onToggleDisabled(); +export function FilterItem(props: Props) { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [indexPatternExists, setIndexPatternExists] = useState(undefined); + const { id, filter, indexPatterns } = props; + + useEffect(() => { + const index = props.filter.meta.index; + if (index) { + getIndexPatterns() + .get(index) + .then((indexPattern) => { + setIndexPatternExists(!!indexPattern); + }) + .catch(() => { + setIndexPatternExists(false); + }); } else { - this.togglePopover(); + setIndexPatternExists(false); } - }; - public render() { - const { filter, id } = this.props; - const { negate, disabled } = filter.meta; - let hasError: boolean = false; + }, [props.filter.meta.index]); - let valueLabel; - try { - valueLabel = getDisplayValueFromFilter(filter, this.props.indexPatterns); - } catch (e) { - getNotifications().toasts.addError(e, { - title: this.props.intl.formatMessage({ - id: 'data.filter.filterBar.labelErrorMessage', - defaultMessage: 'Failed to display filter', - }), - }); - valueLabel = this.props.intl.formatMessage({ - id: 'data.filter.filterBar.labelErrorText', - defaultMessage: 'Error', - }); - hasError = true; + function handleBadgeClick(e: MouseEvent) { + if (e.shiftKey) { + onToggleDisabled(); + } else { + setIsPopoverOpen(!isPopoverOpen); } - const dataTestSubjKey = filter.meta.key ? `filter-key-${filter.meta.key}` : ''; - const dataTestSubjValue = filter.meta.value ? `filter-value-${valueLabel}` : ''; - const dataTestSubjDisabled = `filter-${ - this.props.filter.meta.disabled ? 'disabled' : 'enabled' - }`; - const dataTestSubjPinned = `filter-${isFilterPinned(filter) ? 'pinned' : 'unpinned'}`; + } - const classes = classNames( + function onSubmit(f: Filter) { + setIsPopoverOpen(false); + props.onUpdate(f); + } + + function onTogglePinned() { + const f = toggleFilterPinned(filter); + props.onUpdate(f); + } + + function onToggleNegated() { + const f = toggleFilterNegated(filter); + props.onUpdate(f); + } + + function onToggleDisabled() { + const f = toggleFilterDisabled(filter); + props.onUpdate(f); + } + + function isValidLabel(labelConfig: LabelOptions) { + return labelConfig.status === FILTER_ITEM_OK; + } + + function isDisabled(labelConfig: LabelOptions) { + const { disabled } = filter.meta; + return disabled || labelConfig.status === FILTER_ITEM_ERROR; + } + + function getClasses(negate: boolean, labelConfig: LabelOptions) { + return classNames( 'globalFilterItem', { - 'globalFilterItem-isDisabled': disabled || hasError, - 'globalFilterItem-isInvalid': hasError, + 'globalFilterItem-isDisabled': isDisabled(labelConfig), + 'globalFilterItem-isError': labelConfig.status === FILTER_ITEM_ERROR, + 'globalFilterItem-isWarning': labelConfig.status === FILTER_ITEM_WARNING, 'globalFilterItem-isPinned': isFilterPinned(filter), 'globalFilterItem-isExcluded': negate, }, - this.props.className + props.className ); + } - const badge = ( - this.props.onRemove()} - onClick={this.handleBadgeClick} - data-test-subj={`filter ${dataTestSubjDisabled} ${dataTestSubjKey} ${dataTestSubjValue} ${dataTestSubjPinned}`} - /> - ); + function getDataTestSubj(labelConfig: LabelOptions) { + const dataTestSubjKey = filter.meta.key ? `filter-key-${filter.meta.key}` : ''; + const dataTestSubjValue = filter.meta.value + ? `filter-value-${isValidLabel(labelConfig) ? labelConfig.title : labelConfig.status}` + : ''; + const dataTestSubjDisabled = `filter-${isDisabled(labelConfig) ? 'disabled' : 'enabled'}`; + const dataTestSubjPinned = `filter-${isFilterPinned(filter) ? 'pinned' : 'unpinned'}`; + return `filter ${dataTestSubjDisabled} ${dataTestSubjKey} ${dataTestSubjValue} ${dataTestSubjPinned}`; + } - const panelTree = [ + function getPanels() { + const { negate, disabled } = filter.meta; + return [ { id: 0, items: [ { name: isFilterPinned(filter) - ? this.props.intl.formatMessage({ + ? props.intl.formatMessage({ id: 'data.filter.filterBar.unpinFilterButtonLabel', defaultMessage: 'Unpin', }) - : this.props.intl.formatMessage({ + : props.intl.formatMessage({ id: 'data.filter.filterBar.pinFilterButtonLabel', defaultMessage: 'Pin across all apps', }), icon: 'pin', onClick: () => { - this.closePopover(); - this.onTogglePinned(); + setIsPopoverOpen(false); + onTogglePinned(); }, 'data-test-subj': 'pinFilter', }, { - name: this.props.intl.formatMessage({ + name: props.intl.formatMessage({ id: 'data.filter.filterBar.editFilterButtonLabel', defaultMessage: 'Edit filter', }), @@ -144,47 +173,47 @@ class FilterItemUI extends Component { }, { name: negate - ? this.props.intl.formatMessage({ + ? props.intl.formatMessage({ id: 'data.filter.filterBar.includeFilterButtonLabel', defaultMessage: 'Include results', }) - : this.props.intl.formatMessage({ + : props.intl.formatMessage({ id: 'data.filter.filterBar.excludeFilterButtonLabel', defaultMessage: 'Exclude results', }), icon: negate ? 'plusInCircle' : 'minusInCircle', onClick: () => { - this.closePopover(); - this.onToggleNegated(); + setIsPopoverOpen(false); + onToggleNegated(); }, 'data-test-subj': 'negateFilter', }, { name: disabled - ? this.props.intl.formatMessage({ + ? props.intl.formatMessage({ id: 'data.filter.filterBar.enableFilterButtonLabel', defaultMessage: 'Re-enable', }) - : this.props.intl.formatMessage({ + : props.intl.formatMessage({ id: 'data.filter.filterBar.disableFilterButtonLabel', defaultMessage: 'Temporarily disable', }), icon: `${disabled ? 'eye' : 'eyeClosed'}`, onClick: () => { - this.closePopover(); - this.onToggleDisabled(); + setIsPopoverOpen(false); + onToggleDisabled(); }, 'data-test-subj': 'disableFilter', }, { - name: this.props.intl.formatMessage({ + name: props.intl.formatMessage({ id: 'data.filter.filterBar.deleteFilterButtonLabel', defaultMessage: 'Delete', }), icon: 'trash', onClick: () => { - this.closePopover(); - this.props.onRemove(); + setIsPopoverOpen(false); + props.onRemove(); }, 'data-test-subj': 'deleteFilter', }, @@ -197,63 +226,124 @@ class FilterItemUI extends Component {
{ + setIsPopoverOpen(false); + }} />
), }, ]; - - return ( - - - - ); } - private closePopover = () => { - this.setState({ - isPopoverOpen: false, - }); - }; + /** + * Checks if filter field exists in any of the index patterns provided, + * Because if so, a filter for the wrong index pattern may still be applied. + * This function makes this behavior explicit, but it needs to be revised. + */ + function isFilterApplicable() { + const ip = getIndexPatternFromFilter(filter, indexPatterns); + if (ip) return true; - private togglePopover = () => { - this.setState({ - isPopoverOpen: !this.state.isPopoverOpen, + const allFields = indexPatterns.map((indexPattern) => { + return indexPattern.fields.map((field) => field.name); }); - }; + const flatFields = allFields.reduce((acc: string[], it: string[]) => [...acc, ...it], []); + return flatFields.includes(filter.meta?.key || ''); + } - private onSubmit = (filter: Filter) => { - this.closePopover(); - this.props.onUpdate(filter); - }; + function getValueLabel(): LabelOptions { + const label = { + title: '', + message: '', + status: FILTER_ITEM_OK, + }; + if (indexPatternExists === false) { + label.status = FILTER_ITEM_ERROR; + label.title = props.intl.formatMessage({ + id: 'data.filter.filterBar.labelErrorText', + defaultMessage: `Error`, + }); + label.message = props.intl.formatMessage( + { + id: 'data.filter.filterBar.labelErrorInfo', + defaultMessage: 'Index pattern {indexPattern} not found', + }, + { + indexPattern: filter.meta.index, + } + ); + } else if (isFilterApplicable()) { + try { + label.title = getDisplayValueFromFilter(filter, indexPatterns); + } catch (e) { + label.status = FILTER_ITEM_ERROR; + label.title = props.intl.formatMessage({ + id: 'data.filter.filterBar.labelErrorText', + defaultMessage: `Error`, + }); + label.message = e.message; + } + } else { + label.status = FILTER_ITEM_WARNING; + label.title = props.intl.formatMessage({ + id: 'data.filter.filterBar.labelWarningText', + defaultMessage: `Warning`, + }); + label.message = props.intl.formatMessage( + { + id: 'data.filter.filterBar.labelWarningInfo', + defaultMessage: 'Field {fieldName} does not exist in current view', + }, + { + fieldName: filter.meta.key, + } + ); + } - private onTogglePinned = () => { - const filter = toggleFilterPinned(this.props.filter); - this.props.onUpdate(filter); - }; + return label; + } - private onToggleNegated = () => { - const filter = toggleFilterNegated(this.props.filter); - this.props.onUpdate(filter); - }; + // Don't render until we know if the index pattern is valid + if (indexPatternExists === undefined) return null; + const valueLabelConfig = getValueLabel(); - private onToggleDisabled = () => { - const filter = toggleFilterDisabled(this.props.filter); - this.props.onUpdate(filter); - }; -} + // Disable errored filters and re-render + if (valueLabelConfig.status === FILTER_ITEM_ERROR && !filter.meta.disabled) { + filter.meta.disabled = true; + props.onUpdate(filter); + return null; + } -export const FilterItem = injectI18n(FilterItemUI); + const badge = ( + props.onRemove()} + onClick={handleBadgeClick} + data-test-subj={getDataTestSubj(valueLabelConfig)} + /> + ); + + return ( + { + setIsPopoverOpen(false); + }} + button={badge} + anchorPosition="downLeft" + withTitle={true} + panelPaddingSize="none" + > + + + ); +} diff --git a/src/plugins/data/public/ui/filter_bar/filter_view/index.tsx b/src/plugins/data/public/ui/filter_bar/filter_view/index.tsx index 6ff261e3cfb8..f9328875cc91 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_view/index.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_view/index.tsx @@ -26,6 +26,7 @@ import { Filter, isFilterPinned } from '../../../../common'; interface Props { filter: Filter; valueLabel: string; + errorMessage?: string; [propName: string]: any; } @@ -34,14 +35,17 @@ export const FilterView: FC = ({ iconOnClick, onClick, valueLabel, + errorMessage, ...rest }: Props) => { const [ref, innerText] = useInnerText(); - let title = i18n.translate('data.filter.filterBar.moreFilterActionsMessage', { - defaultMessage: 'Filter: {innerText}. Select for more filter actions.', - values: { innerText }, - }); + let title = + errorMessage || + i18n.translate('data.filter.filterBar.moreFilterActionsMessage', { + defaultMessage: 'Filter: {innerText}. Select for more filter actions.', + values: { innerText }, + }); if (isFilterPinned(filter)) { title = `${i18n.translate('data.filter.filterBar.pinnedFilterPrefix', { diff --git a/test/functional/apps/dashboard/dashboard_filter_bar.js b/test/functional/apps/dashboard/dashboard_filter_bar.js index 417b6fb06617..6bc34a8b998a 100644 --- a/test/functional/apps/dashboard/dashboard_filter_bar.js +++ b/test/functional/apps/dashboard/dashboard_filter_bar.js @@ -186,5 +186,37 @@ export default function ({ getService, getPageObjects }) { expect(filterCount).to.equal(1); }); }); + + describe('bad filters are loaded properly', function () { + before(async () => { + await filterBar.ensureFieldEditorModalIsClosed(); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.dashboard.loadSavedDashboard('dashboard with bad filters'); + }); + + it('filter with non-existent index pattern renders in error mode', async function () { + const hasBadFieldFilter = await filterBar.hasFilter('name', 'error', false); + expect(hasBadFieldFilter).to.be(true); + }); + + it('filter with non-existent field renders in error mode', async function () { + const hasBadFieldFilter = await filterBar.hasFilter('baad-field', 'error', false); + expect(hasBadFieldFilter).to.be(true); + }); + + it('filter from unrelated index pattern is still applicable if field name is found', async function () { + const hasUnrelatedIndexPatternFilterPhrase = await filterBar.hasFilter( + '@timestamp', + '123', + true + ); + expect(hasUnrelatedIndexPatternFilterPhrase).to.be(true); + }); + + it('filter from unrelated index pattern is rendred as a warning if field name is not found', async function () { + const hasWarningFieldFilter = await filterBar.hasFilter('extension', 'warn', true); + expect(hasWarningFieldFilter).to.be(true); + }); + }); }); } diff --git a/test/functional/fixtures/es_archiver/README.md b/test/functional/fixtures/es_archiver/README.md new file mode 100644 index 000000000000..ca0f612fad06 --- /dev/null +++ b/test/functional/fixtures/es_archiver/README.md @@ -0,0 +1,10 @@ +## Changing test data sets + +If you need to update these datasets use: + + * Run the dev server `node scripts/functional_tests_server.js` + * When it starts, use `es_archiver` to load the dataset you want to change + * Make the changes you want + * Export the data by running `node scripts/es_archiver.js save data .kibana` + * Move the mapping and data files to the correct location and commit your changes + diff --git a/test/functional/fixtures/es_archiver/dashboard/current/kibana/data.json.gz b/test/functional/fixtures/es_archiver/dashboard/current/kibana/data.json.gz index e83e34a2e07fab31fd8be22de647fdcd646dba12..a052aad9450f564426820c0354e43489a3e7e0c5 100644 GIT binary patch literal 20510 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ßgoDwAB@36~bC6353>h5$CVMgPw@{Q5YWJnSQ)Do=-_^`)3L8?$|wh6VTw zy7lrfFQ1X@sj{Ln5z`{2rbk4rKVhJX($yir{J@KUBgbyV&)^;b@Drb|0nTgjCJ@C# zR=BOp^+?1hPq^~nH(l0%iSw6cZq){24JmA-$_r5ZI?_a1N1R{5RnXxy98eSBldlY{ zi0q-3#>m|Hwba}Zt@x(v&+}{YV(~Q~M6M;Q;c``Q_?r1x#hUGi9C%V@1Ae> za~=1VO(aj;1b4&i2+Ol$U6+b)s;&&{ir(CnMW%>e$K^nl^$#?IY=NWiDyzO(JjxF8 z5)-|&ATTH}=Nvk{QvvhxO$IzLn9*T#KemLj4~t)Bb$`EdxuW)Y5U?C%JnvlSyLn9; zsN40X9~VB<28O?_P14Im>P&p!6YKEr%lJvO#oHn<_l;Fw-C1(WL{MLmWKsj$2;g|g zVS2wp#)&swyqMR%HX8pm2DufYv3X_%IxsTCd-LCxQJ$>0RC{j*ozjLr*Ix4;4gxb$ zB2OHTqs$Ws!I(L?o!@4v+H!^9BSC~&a`L|DtwF++;w<{!VLd{m%dl~Nu&SHsJQ)HpZ3QY>l~G`jEVq=N z6HnX#&c$<`V$noU<;{&qE{sZr@i`=;ksr&q^EPeHYvn;tnGEC;F&uHLZw?Ai3G-@^hh3wv8cz#0i7GcL zr!O@^X;eR4v>K}dm}Yr9Jy-%%(n>b3o3>?>*uv84aI3iX=HhnQDG>_E zvv>N~*ax7x+0_I%O(lu{>$A=yRaP7=CcWR=WMDuwjpV7I?lDMs3->$5XpfEFZyExfM=E%cW^ z96t3numu+tB188ew;8Hcc(70b%w|ySjYN*196fe{>P3&3XV4I4R->uxtk`2l0lUEp z&XL$r6BiSvzM4L&U{ajwx^1VqD3iSsJ(4C} zj~{g< z`f~3R@Xw$ERa4>&UvK9?#>ofxGsdi`4xHM8GGO7?P-u4SRX>dvT#aGuQ5jDE@E@AyEr z5|<>BVH#_ShW}Z(p>eYI${$B%b6LOglD%{NIiQF=2N`~*~&6I*2D~XWoN!Cmo4E4?$WBT zbP3#-HZSsMX5GL4>Z}pDBY_@Z9GhFcsIy)FOP`Yw){;3};%C^#WK*3_n|ni?G4Ihn z>2@Bw%Ijt>hIc-bt<+PYZRPME993378RC{$<7O%S2Pd+|$Zx=180~V@#;w-a26-{YHmuy4AuHik?ww@9weH zg|Fr5d)*PH@kko>yc@)X=y`?0m6Y3QQJk(vQ z_EGuu;`mpGbGI9neefMlL(*G?!7hC}j;4${H&bA#j@DDu)lCJ#m`xUr=a4HyQervSH(~p!3ii!R$-%pG+$1$DK5DMNg-WeO zbGH%iC01+cveX)c*`dy*3`;rfH}uMm3lGk+2{*4;>wT=&_K<0wgRjy0QhjAB_ditDsL@7xi>>{`Z==Zjo@ctuUy<%oM< zG7FTGFey+!AwbA$MJBF!1BxPMmKBXy$a2=_cO06w6Gg=WE(S)ie z@}RVBEDz!FV{bCNxC7^T=dx1>+?d_J)Y*x3RxbQqJFSh*uYyRFdTw${q~OvZT3 z#sdq1%2i_4=mouj*XR5Az)MR&?2q1b$8VdFf+HXCjE~-=;Y**S>RwTdx+q6*AJ|6k z_2QJi_oEE0!oA3?0NV7|fW({sos(buWPpzPwfw!?QMwo#RXl zpVb)5X%u)<)xFI4KIm1@%ADP7s-)Vitnzb=z*JQ?qur9!)X*T;_!P^OnPGvXSd5iu zl`%&LUwJe1(Pqj=Jz8U>d0*^$VT{O_eur8;cCtZTzMzDAF{JVWBioM=q1Ek!QY{)# z-yW^$$yv79&wi9h zYocmkX)>|PkM~DfVQ!bMc|OXuTUc^yLonz;k zC{{*Oi=o%-g%ynM9JU3y^#}=NZp#&#T%qB{SKlw-wx5djJK~YrKc;<=Zj~1Bl?7N! z?mQ;h69Rexfv+gM$(-`E_)fIjL<4*M{Leip~MHvjvTp9QW)@XifMV5 z_>qN$!T~MCJ8?^gjwh4hCV@C0mRT745eca5Q$u02h-05mW3|U{#@Wfx#u!E1ilXGp zcBEn`+z=Kn$IE=j~Z@espUr3BBNceqscDOVFfHZdN z?xbPr(ug^b+jIae2!h^W@TA?RLWVJTm$wgZ~!aFTp1jzGvJv zy>26?6>GPdeqmu2AvReIG|k-&f0gplgtb|ZWEI4jG;elLV9md8<<@4%8lukNfftY$K)L7y7_vs3!^WAwCrheq z(30OnbSGpl{UTP1mn8Zv`N{S{7c&RM`x`Cz;EaeoOj@28!HJW#qK=us*W(TQp`n1c z%=?9W$OyRWm|M;3jcbs_#}wCAQCYK*?8>oPceEjACR|>tGwoDRthI*gVhv=x!O!#@ zw1ykQxSb!^71Okh$h)nfvLmBX8G6nrcZOl+hU>&v=}5yWt;DROavM3l`K(!9`UT~4 zAUY}17mjgQbb)A7eP%R`r!I8-$J5x{BeM4L^`LiQu8VqGlGcp5BiMpwZVEJz%#5e0 zH@5?t?X<`(V#Q%Q9A{8Cc4Wg|Kl4ODebY6RXqZQCnwMndJ8;VTVfHAG_w(BB_uB2~ z_dDWkI3K`gIIyPEYM}Ky07MIs3I$ zgQDB8nEmwJVbZc_N13(faY5}+2735nB${DH8xg*$A@EA1+kwj7AIWATrV0jD16E@k zQ=U;q7Mn9lGt%$obY(u)q)hHLV|?;8W<}>pnHlL13x)~@Z&H<(fKpuXB+d(SL+t;0 zxx@{aR7lDreYa>>$(oTJl$VE*q&aG)CFG>@5k#mZ-s(|T1`q=u6kA(Dlhm1D^PzRDkAUV-ou(oJTDjYxY9hQRCK_6?$ zZv64dK@g2WY7yI=CwpLY74f7t@e)%eB99ji$Q|J?QNV93Zr&%XQ$9Jk;{4Pc_cn*q zFWJ!d%ru4j5?=ZUuU9BE9xi4o4_1!HlhVPwKk4~>#!@pl(Ivg~1!umf&J?JA>6VOz zoc7A7aaPxbWXa=$w-C64zUoAK~Zn{ClSUVmuj&lcFNT?Wy;-B6jWV>Z|I zF`aAYGFGno60O!HS&DGomCImRlOou#N+W4y+O8L@bszS1bGR(`*kc@oEU#%%Cseh( zHSFh$fTsEE<0to0F|QgdBa^Y!68PkOBhIDe^YElGj)wD!Yd7@SbTYP{*YLTGb(Vqx zvGy6RnDLF0WOz0cDpISend_8k&1nFZ^l~nYo)*$7Tf|$#>Xr0yFe`VQrjh08?QobO zX>nE1l7J=TJkVV4TdnsEydyd+jS4~~Qg2ZXTYxr2^j)Mk$ngYb^6#6l0Q|_b9C9cO z*a+Ap1{@CAj8#bI9KvYlwjl>%Fsg%y`JTX>{rnU=5 z&DmRQ7R+*P_5!TxQ((wlQ7m@HS!Xr%7B2 zFDWh$l#5LijE`Kwq}<(2 z%5m%^{0VV^DNV1^6MKp99qcI=(~}+-DPxd+SB)NCVu=OKJ4Fka!Nl@7eE|gB;Us3=VB)^!m%sCV? zMV);4GDKEG?oR!~>mjD|EB}ZXF}OgN11q`R3$|dsA$AhV7EwaM4~tX!Ao~4 ziEPa%jH~QHvr1&iCnv;m`T}oILdF4Ii z>Mpz%S6+fPAfbIyzf&AG=ta&n{ZS=Ss@0zx?2}=YJ1wt1Z%sEbi{e^uXO2xoU$mN; z->rYjY`7~MgI`>LLIwJqirdb&+^qz$4s=B=+wQ9TL({Nef>R?MkonsVdg&$FXU?C0 zlWI2zz!BxSb4d=R=%?@i#eEB!)i_6G3;Oj@))+jDA&J5sI3mqxFDu7@h2=OjEshqdAamI#}QA-hqQ+dVXsGVrm z@!!v{qX^?$EO6jc>c8PmsT$IzS0Mb-wF37(*!lu$9 zjDQW~Iu4q{Vg#}X0a}okWR+>$dC{gL!Pkx+^W>i!s~HMH5tHXUNio%9;eZ zoPLmNXkTp;)P#szm4j!lme}}&agSwIQ{mB%_UdffPG42qcPdQS=BOq zCc*11Ae9nnr*0*6JeMpzeLLfJ*Vq7FgUnKjGI_fT)!F|%(FPtYi9DD~f|+C8K z)aJU?XUC%GbizX1xWKdSFV6|uTf;JGbxkDLI3XzC5Y4qZC88h1<_Psg3U1|u)S|aM zC6yMfti&!$D75hnth77oaLo<8#QF&4EaMm7yr8fi5w|*}B*&VA`<%LXZ`5dg6wnr(SR3q_8LU9)hv^};b=620CE zSplzM#a-DzOR4c4Fehqic|^uBRb0)%K~K7d8!%Pg;DDfIsStCEAl?B?smTraoA7tT z1B`5ov(V-hhSg|khROVIw=+O=s|Q$SXTti`w`=@W$5Zp8U}m4Fs7ng&%EA-!H7{=- z<#zg>=C;Wsde1@zKAuBfV+d84`jHOy+66CF*fNUxv@5p=aEn#>3%!xM%B@$a?Rrr} z5jHE23KX>%V!0jAtP$>~Y;hmuj4+$X_sR69Vw>uU`S!wu@5;BNm9z%4xZ%8W!{YNg zJQXJMFk`uQyA(}KcUmh=OI`kay3)Z`F*SjnpNKN(Pqk@!H?p1WG|dmZx1BT^FHxZc z?fjQj50~%r(3?;xdfVk+Q9(bUK!=w*1P$-@K~C+`k~TFZ9VXvyN;~MiI@0r!sb0+_ zK<_pb{*JPOHXf2I;tF4ObHa)nU&XKX-#OCJTLcRG<(Oofr}cd{Mc=TD=5wje_(tY3 zS`1xQ|9KB3*)STLAPV>#YjyIYT*f!hSLzXPoUz?f4;HvZq=_5QgG7|(B zo1lf>66Z|kaA^qHSMy+x1&SQHN(2vWiCnzg$JC>LBhF0ffthJLD}P?L#GjpAM~Oe= zvoWILc@f|cj5mI)?p)Br$54IkGdV;8P4YY#bW-2~3weKPHU{7iUoq7c2s|Bnd)|4^ zAK*CmE;M!m?se&Iu07`nK_++T&YSqdbb%$7iYyT^+!mb*kU8fVK^L=^A4y-F&6I72 zdUp04?3!uUUtxI=-?v(!iLju>mV^mHT7@tOmA)kv%lkKFwx#*(=_V+I9DmXh;OdI` z3`8M+L$tYODVI!k<;mdTpa^`jiuPTeJ^GtosLgMORm0XZw60O>frF34nQw-!qenA^ zbVKjJk&0U|YwkF1zXPUQB%4^d6VbaoA7H>_AXP2RsO66i&F|H zde`jCd1r0Ok~3<7Dt*_y(w}ans1Pdv??28sv+4>4<%37b_Pi*Sx}Un7m%Iub>Dy%% z2W3}{T08Gxf4b!4GN_hyG#vr(0dV8GSiATz0jqD695BIkND2K{9k5DQc}ewSbUc+2 zUW<$|$G`<=!}hw_`abx}H*MH@fR2F+$UXn$kdXtu^PjETuvGw=j8bfvAn!_i{=^+S zAd9<_VuF`@IZL7`q&rKBf(q*tDL3ZZ^2A)}#{KTyImbxmC@SGDNlh+Y&qdCC77M|^ z_Brb}t4=)mhH2UQ0{n4Um1$rpbO5$Rkl8n2y!3M@Co&y6QF4H*Z<*!j3uHE0e0m~S zhVO1rW!BwBJN^&*IM6N8o8Z#&81QgGW|e!b-I++NjnYZ9lX~La?s#!qBi$^ewX>VP znTN4qBo*?>#{z~O@k!1BoUe~*UQL1RVc;iIR&VWX6F;DHeXWcXTnI1!Yi(!k0l&q( z85l8EdZ5;TtHc^hH?GRoyKDG+llk2yfl}rYYm@9T9y0xvaT#RycA~MxK=m^7K_C$i zAED3IP~+-CWtTodm179t;q4I(T;^NpiV_+|#E~)=alt14qJHgpZ`-PN zfmd6fnObCYT>(ZNvJp8vuK*V~(`l*YO-)&3BxN1S!8rIGufVgKRWCF#y&Mg{6Wc!mm3@($+b`aLI_m;=)@nmD&ohxD&od}SbGN?8VXpLOE9Tw0vQIr> z&q}Z~!$XeBJ^dAkCNKh^+BerX)xYLjoGSP7MuYp2ZaMOJb!%Tpa+XzZ?B2R0;T_kfI#lEW_>Tb9lA4%oxmRxNfIA zz{5I|b_5-Lb#*l;zH}0UTL`O+Y0sF`zUUn^PgKVqb}5e3C!OR#O|Q2Y4_WGgFTC!@ zU+?=T&{yUsX=zi=7#s$*?pCl1{b7|?p6r~AUgue7{tkZ#_yGwoUhM#o_46}oc=h*- zOXXd$SkE{@o=?nSFTt$bt=|uI<3{s_0r&99dmsvS6$Y{0#ZN9Cul&RVYp30x8L$^q zwCz^Jf)AL9-xKc4FoC)-d2tw$@N@vDveagiGMsZN()m*F_%p2nzg{Y0#RQ8)Z{A{R zjc?YDwjf4o++GhA5J;g)9V@FaUl9p%QGOi@2wqoDj95P%6?9RX%v7!CY10ZcBT?M$ z2QM_N#e!fLRKrd{{wp*b-95(51L2LNj5Rl`1nzU9zOL$P4cMf519-C)Mk!RdoW9ns zNnSiBRl5veIS<+>RRIl?wCGCT*Rt zaF(WeOgui6<51Wfrp-f}y0}H!2#YiyubpMKOx8V`2mSEKVoF!|j~x!8ck&GKeep%k z(4Atf?S*y>4rF5L!B1i$j;;kdUe=sRysr1#^UJ}Y*PELY7r>n617qSdl|j-<6Pxs( zl1|m3Ed|~Cg;Q84D3I;OUhR1z#Ta2|G*XJbc%a50@?72a`2*2%S!Dvgfk(A34syiW z#4U0ZA7KYar0-;ET)Ir91j&a=IcnbXa`CL9;9C>Rg1j=ctWq>VO<7b7ig8m~o0x}A9;`%2 zF(t0`x-V@QFe0SF1b0l2<7lzF_1|EP{$uUY59Ncne8<5DX~E_%7`>$FzaCFQD5hF; zd#(>;8J5wW)<~zMgfYS}A`0#6R^gMzo^$X46BM^aFk1hrL=C9NCoVn&$vrm>yk@3j zy=2}!oh7?KEWhcUGEi)pNcWbKtsRyDA&-QsJ~=zhio;O%DTU-jgU+d@F7!&48A*#I z#mxR>3#)qIGgAKe9bH;_dnp2+z;(DYLD`&H7NfOA>6$Xk6J)qj%K>Qjg?w*;mh0To zOP=MGA!yE4eH5DVN>KRyJWeD!jI}_O=KL%kl{T-`Z{y>k93GcaVo?=~R6AC%lu5c~ z7;5PIAKp$wRGLPLt*KWQV{L+^UHC=-5;-T!rLu(2U4q)>D{Urj zpIJr{)hI8ioEP-_)V!xjf%RkjY@08d%AZ&rAF}fS~4PG8>~cp4g)mB;P(% zeOKI1imFQsHQk_4HW{Vz(YQV_U;6%6B#Ni5ctb3)NtJtUIka{i)6By2$_<__YhPy9 zj%C&&oohzBh7uK5tCKa9GL1sP&p|gLA-|D+%mW+d6-QXX?tDM980QS>&XLZnlGphk zN*)yk&(h}G^-QpCc4liYivzo$cbmqA2cw%u zrPpmL4p8^Uvo4a4UmH{EO)`>1b?xJEGtBQsHA&&4CUjrpxHJ|`uvShMPl9Rvmu5gf z-(MHenleR_T84L%CQWs5GJp+;Y1JHwiSxS9`6*{>s3?s*bmqgNAO1f`qD7Dy>0G+&og8ZS9BaZEiGAmTeCt4UXtN4ARL4mI><`t$P|50MRErnUxs| ze|jB=2@E_gVEGp{DFEtSnkHng-f)dXGD>*$hj(-y%%%^hcE_o|t9TOf#61t_Miw^S z85-2>ZV^~bQ}1GTc`$ZIdneSQ|cS zEiGtUwjzejV|Mn674H;NcI3}3Fg!gMgg+KAFJg?-aI3Vqe$2;oa}g!hM)j(^bNd+h zm;E&UsBx91S4~Vl2yBS3B6%QA+81v8GGme!4t}mRC*tRCDo;?{66Fkv! zIBAbe=F`s9Ru=H6==KVWg>OcH7dU6qU+?%*i3s?Yid01O3@Hz=v{D^cI_WP zcjw)HyaRXr&g4vqP+5U!e*3!5p6hPg#mhoxk#pe>>B88G2 zj!%)T(jLdxnGj3k_Ea^Jd#~n=U}f3HsHiN^XjM?J%aa_j3j0%7EvMG{1_YJhokn+K zGHw4Sao@4QV#WQ*J_Y%y2Na9I`81NUXL*ho7dkg^s`=iMA zLa*Qac58oAw~B=qAtcRTaoC;3)(QwOSM*+nB0)s(m)b9ptgc#u?5gH!??@me1y)t7 zNypaf+4a$dw#S!PXBR$YxELnLx|Z$$3qKN^ezLMyFLh-`vO%mzegt)6M4K4#;)?G;LaP)^K^QJQ?HK%50*WD~jrV zm@FI~;F9#VvFB?c?Q4cmx7t!jxW(yT5Iu3$V;ZNKTm}r8me5X@xeCtX#oO(Z3!oh2 z>7`pf{RYV|rzgWOLd}01M}2gKB{{&Bo4Cexxb#U`6Xvth(b2a|anQ0~`{!vei*0`x z{SN=@I|I~~*(@FP4gD=#`79F#;yrgbdP`X9U?CpB;@cuTjUl2|$kwgyOAoRX3@M*8 zlza?{EF*Ye(iUJl=y60GjmKXVDBbzK$CEfRB9D~#{xr&e7zADDJ?RIUJ192(w7?tj zGq1lKG2J9_oDsq?2Ulk7Il6QJ_zoHA&SsYfsMe6Y?_dDx8o577B5tZ4Ncf$oCkMxC zl&;~9&rUHu&_Dffh=m|u4}c8-&pbpHp%0G!hr_^lBoOp|JDf~S_x(z9F7ad?(m5=+b0nM20$cs2K!7yHd99*ElzjVqd*OJDA}gy+f-; zF~*p4!;t`K_szLwBt25@Lwxv1e}g~T?izt0kSlVkC%0+5@DQX9t1alcZcv%r~52_`y;c`oy|(0rK2@fn(E)56p0Q z?NkaFTk#~7{=_kaaqMhIeC@i2 z-7428ua1KV2s5}ifzh^W?PF|xwTxgA5ODbTtJDR6>Z}r}P9PlULXweB5CgDGv3zRK z-7t|DAdt&26$0{~{vdrSXP_p6k%D-{fHy)}Q|yA`E3GkY{=oQXr5%tvr=H*A2>FkZQ&FTOt02;srYw7HUZrP#XvEAypGcX-m$? zRl>yj`M>7UqR~qZcWLlFV9(L#=OLw@?0S>#zDXy)P`8m(`vJ}lxb5~opH?-#2ulaB z%SDu{>~Q;z+X;Y|Y@xxin0=~6Cj{iLoys|e?z#HMEvLt`ECZKH=^sI(nVNtI6QLip z7ew_gQc=P<=FxsZSD4W5z4D&1hib;bnx1pN;qD|J>td6CJWh!{hbzvo*uNCI2khb} zNzH+z7{re{Nx<%HB|oR?Yneysad8(yVr4=@IG%AL0;-y!=p*XEJp5#jbx|Zk{RoiN zc!u50(3+S%n`tvNNMZCa1L5-O`OGv3CI!$Xeu*UJ2!$4b`duD$EI{>Y9R{cP@;^AT z@VB2re1TT37(s`cQcpQkArmGtkTTvO+t$j*@@Lm4P(%&4m$-+~@@MV_QRSK@qdV&X z5?$hp-(!}ln5-(w+Gg$j6A}!{K0O0CID?q z`->r^`(xn6>%qKWCCP=nV$T8hJIl8MErgDO{0a+zEXqFi__0L#}CcWP3wU_M0(`5J?#v-EQ$zZDn*96(kXH) zgfN({;vR)D0vGBiUCIQ$=tIB4{i~B~IE8HJ#a~3{%P#^x(T}=Ai>MTSD+mx3)~2XI z?=S$pmQ7Zkw%*S+mqXT_<>Y(09)t?7;LJf<2@OQzhU654oYd04rXFU??&!6jXA`ov zdJw4fN|wO%b`nZ!gDDsU{T}g;!NO5H3K}whX3*3p;};y? zT~U0?uO-;}#*Cb)E>FXD0F<5_b^x6I_jtUw^*>*BCGmg%mlu$$D+MuQKZr-C$H#|A zPfiyEo}kxUcYb1hFpN-- zq9VtGR@(ErHF+92nUf>-F>{o{MtpJyz1jHqIBL@Ja5P{YF^%5)o#0haCnnDEn_9cT zaovtgg8t)KxmGo{^UB{Yi+KT%`AQRYHee}59r9*I1>m>$xGC?lWbJlLu8VLbE8eI? z2C$)--9VlnmnkHBX;3)p)UN(j^zhQjxnX%8WlLS_SwxhAH5qbaHy9s-Q@2zQ=CCAE<$s92#n4k;z}x|KJpEQe1^$)IGUiTqH6vJ_T1H>u30Mqlif7w(H&ItpXU zkDn~<;6${s_4YY^kRNUMqTV>xcj8v5bet=>?p;$FhJIc9x9{U>R{6N!bD0g;H{a{;8@680@LY*;yv+#6S$^fLf{ z6vwN)CFc&Fo~}<@a^-Az^S-=zTiy?GyKFgv@+HieGsf5WTb^A{X}T1K?*o7jAdO?m0m!f@PMrZ7$XAP5PhAFA9 z_c>k5?$qtw7FTSLOZ-Jjc*Ri4>>7dGCOzJ?390@H&yMEk1fJflTpJ9ca~!eklJu&8 zx%408fz&KjldsF6kiIg%DLLy@Y=Fz@d;73?Wb(oWqO14UJa~&`nzY zEZH)yC@m(nlte?-?(o}fEIsa&Oolb1sH@@hV!QH!U8dGjG&WC_Xl$J@RVtV?bCi&u z2~dTaS)p`eZ8B6sohcNzY?wXKuq}fcblu!kuWfGoYk6TQqZ}(e$uzK;2ezYr(XIs6 zr5S4S`aU{wVY$dyImzbBN5{8lx~|<^o^~%Zuh}ma_3T zvd9Q(2nPx<42j*OT+9306*th8WlU1I`R=B{W5J7|e%Ng*RM{o(X)-hY0%`Ll?QT2@ z5z`e}C1=fjKb|a`!Q=4UW6Lzy$}F@fdZnA)SbM~MC9mKUrPQir6}w>o zw?&CX`_-%so94mgHC0wOF!QIQWcoi@9KylWtV42r=Aq1qcHZy*j^a?0{!bLA5lEjg zz47Uh?5L`4Y&orM9o)X?m*qV^1ro&f=h&e|{`asASiu&C*ZWFjadgiwVxT|5{qAUT z%LbV_AK;Ias#9GR#LYRSE|c;B9ZtN45J&zlImC1$VMq58_U*Is%%LWLC2w2rga8#h zya8c?-=N8Pw?CK~DKfOeP2HDeqVB|^-HN+=PGen6Lg=-r%d>QtobCLeGPRJ~8}r)Y zu|sI4(W}htleSBFW~D{mRy!u}@{Q!sKHSJP@#4`#1tG}O$oR?FbL{NJb@dKs)lR6; zG6N~)>-Kf++sz2SQnHtdo!GnzWZ*-j+gmm~s0xi-%Qfr#mFk3Sdn}|XurC=E#^a7` z^~XwpgKDsjN-&1<*U)qRF&tSWSYz3SsHk4c+6-kl2G*M*R#B`qq^hZvrTV3xxy4W-7voMi z7E6Q2Fujg2s{Tn94X=_$-YRdRpsI35e{iGSK}Wk~n>9(^da5|p3x%;)%g^q#ib=Z^ zy$X=(We-G^uA#!`cS6Ak@JhhL8g&V~5(tbslLqQe8zmLYMw;v(6Dr9q)_Z9&mNuu& z{tJ*Xyk)t_UN-pF9|yGx#p;9_t4-#O7fO{*5mZ)Y&AV5HI8poau@)4#kag6yK^$|< zv`ISFwa<%&{t~4!xihEgkb+A87mi+cV=W@3dFc{_y@&C?T*SZ0ayK-n@Ooa>41|)t zQ>>_|_Z(xGf4vt626>Cs`s2s#*J?09A|HH26>qqM`VKdO?V2z8kZ;KM2#hk!+#WK9 zZTE1!MjS4nWJxk=*-eHy?bnmKl{C7-Zi=pXz&+gIwi0If+fXd225yx&i*=v>iHvQt zlUoBM$xJ1`-!DrMB{Tu|_d9@!-b<~{-lbizN2apdl7@Sshf}p;I!DJ2$}Y0rHX893 zk2em5w`_9ncH#XrOypcjjrJvj+i4C1_v=U$JpV1zjH>a?PjaSk_**OvPHf8D1)kM% zs`obB^|Wn$u7G_(csc%;wG=wp(UC|6r%r^_q`LMABBNVfhbB9H3r6ZL3d!Uck$stI zj2C95ON&s7*bw<+E7fC`cdr!+45{{;fUW^yMWkKT> z!E9_O+gfG(8e-Ap>{!j6AuRmj{?)YuPVt*>s=BC5RY5oG1y)Kt5TsAe7bR7?zZ#D4 zGK(l4Aa0Eo5*PoRyquxhZH6MFZ(4)({&^=3ETPW7mW-n$hHlxxaB3>rlZc`U*O3a% zV3{s?ptty;3e=>@-8_UTNCLawvyF=#u#~7dqEaEVQv>vwrtrT3TqJo}Et$)eCNIgk zyquw+o58*+RcOnl*w-re?IkT6^81}499H``rmCb>ldg8h)whbBA0f;PRjiMLmiLy( zW<|q3?NhlhR&k*vX5Ay_3Rb$SO*pG5-L^_6A7+@KYGAH)+W4RE*?lGHhp(0?jZ832w^4u9$a0JDj7a7e&xQ_5r7jcC zJ7;L+a4B>S@q+e|AoZ2fNOGAPn=vE`-0{Ye z2-%V_|4*rZ7N)023hVLCi@r@zi7jI3*L4cPl=k8Tr@%J;=vfmpxf9=4tv$>#BD_fV zKj+d?aog0r2Aug29dF$eZyZVX2=h;yFF$>{lA zz(IeNkcj`)vfH6II@D=ezJY`H;zOaf=dGy-!LeB(U}CSR-WeKO-Wl6ZcXy9XQGh|qESu5HBf77NyYA%>+~VZ&a0+dA=wZBJv;Y%x_nCXvO+2*L6Yj)rvu>quNIqw#eBNNv@GJBsOI7SY166 z#$rXWp?nv_%sWYnjOMb^v&qd~GupF(< zFLAc_EMlYwY5B@z!*q}|=g?MA^*>fWoCs!sl#CR_flv%%ZA>)kDJ~BC-?g+W@gZU{ z^68>f!ClzM%JgatLfb6w!Xfo$Kp{B9Z?}KRX{=r|e@y7qH^M#C&oq z?Zt=T;Ia2tdvfdB1PmmZ;j)rtn#j40GgrB-b`EoyWVdJ{bAI%*EQBVMFNGHEzh#EfJj)n##(*y{=ddenCaBefuTguNdrBrPjYdrAPC!_;35f8^_;P z3bO@o#Mm<_M@SO;Xr+UAR;09G8lA57M~MlBRJoVV$H}Pt4$Lb{0X}steK-1fwfa3S zf=0pH|KyNwStFl)|Ip7B{LyrlJSE6O0K`J{JLX-w>G%Od|JB8jWh5WO#qa+Kbw{VK zj_Br~D`3PJ{~URl2mCkG?F)ZfM4&T5(ivotIZyee>Ib#qKEpBdFph{!kau5)1=v~Y zH6(sS%})V%;=QdBD@=d05Jd9pf$)dGDB6?zp!-S|*qs%6Cw$=x>L<;w9S3<*^xctW zCRk<4bQBNIi}$x1Izz1YF93+j>jnBpbtfd^{6RxPCGqcJ@z%I}BcOx3B?O=bfI?fz z`X6@uv$9DIuzjKahv@+&1_&3L21@ z;Fq}iBi+~wIKB;B$-X0XrZ0|m$jG-1o)@#LeF4l*j_{rVJ&WOeP$gE9aHKt0W@UaD z^gUJPOCBLDu2~=82c=Fx`0X~B`jB7SzQP?VG5iw6oKXVvIevWaGTDq;&K@COB$;Fj zJdkjBSdkt{=va*0UP}C5Y@DOy1n=0N8+I6qhV{lv8pdu#Z=emDu0D|aDPr4RbfUOS zESrQIG6=g-cwTmad|=u70bl!s!%l^3cpH@-B-klz4S@7KG$yK@%r%8UJkaEIsNFO` zMSmS6oA1AoE zq%B2AV?5}Oc)cQ{6CrgARnVmh*eGA0mmE4YM#;f;HU2xSImVnEqT>O`99;#2(NE`nJ#iW*z}{DZQHCW?YkH~E{@c4$ zZpMy)LcUpV7V-nY|4*vhZ=w1O zx1XakF>^87Zb-T8^nU`~6p8-%z+RVoZHtiI8ixT%zCRu!2QY5z0loaop4RMpyH2SezEEhhGQRAF7%B*0VsL<53;T_$49M@ zH)&6}LLoqVfix(%2OGN7CK{dKKOoZVYH|ic??xpxniQY^o8`*ZGrMIwJ_iu^|KBW^ z4;c>IbhbZE1qO0O(&i_)$L^;%K^n+X8I@kKa17)<8@3eKcmD7+gB!62gp-ml$TFhu z=k5Oub1NLB`_jo<&@1Os>Lz-Dhw9MIf~nE7*BGSG_KFyjEp3s%=U`Lu+<2uH0O|t3 z)043V-aRqHtCKdRQB`foM`=yZ8E3GOcVhE#WWz%$DW5Z&B%7K~*?h+X$emWWT%Gi+R_gTy57LXamPWs2H$VMDx!!2?N~<3$wXj7xtM7I} z0;N2=4|#7#stAuK z4dpWwj_8pHcv%M&6i8esV+?7M_8=B8sgd}l3rPnPPJcW>q`doorM%<6jK;UI7<~5d zV9L$Sv17im;JfYrIs`ffd^d_ z13`v>Dx!m>uv6R_@c2wO+Yw=)*+Tq+14vB5P}s#U@El=!=Kp`7T!#xvuJ+QmaKfe?tf}C-xPD<;W<=OgnjX~6MyAf*Y5m_p&eM>w)gd<{O4Fj(4!P&U7 z!s&O|p28vGAgLxw$e*T>rg=(b!wRUg$@*%cGQ(|B+Q0IUvI=7aLzfM%U|b^%`%S)N zK9+XDE)gqVwaXYiE{*|QhCITLZ&egv>r8Tg5I^zNg=sKB; zq9=M%9-(Y>%$gR97Af;l$bJ$hj|Z*n+@7L;KfW^msS7`nhy0Ap|HpuN$VI&x_U*s1 z4*jvLCkSc8(FNrDpQ6xl{fULyy!g5vLN&b|Q~bV|pH=US++L9A_sPRbyZg6m{$TRG z!BJgi9~e&^YF5&N{vreZ6q)c3h!hi-KOpUL3v zCtg=%SdDuunNf{mJ<@l7b}GmpNk1rBdiv}hMiMX`qDI=@BZkwhYl{+3%_Fv@%I9d_ z-gSSDmTTTuJp03JOxqJ1=iVQ)kFC6lC~d>`da`xx@b1Y$Cl;W_Fx@fF*4h&TGKvU~ z>kGzJ`lf9Bk2h6Xy`0*oi@5>*t6|k73}|&Zjx4sPzyIBVu_EWKF7J-TI7 zY?e!(DsvqZNCw6w#!gjsPsVGX!=a$(XKq>Ot)}K)DJ(`(-1Kf5u-X*%y0CjB@l_5- zVFSjfy(Q2U)sng67E~t}L~A5b@|$_2&neU~k}lc#I*vXiBx%HJXoAHz!bV0>ZHOFE z7-f=f^{cK`s&FP~+U6sS8dA23m8CQ4=Lzc*yc!%!3Ov!>Cxljh|8<2aZa>S1o+_qh zQ|GKt$l+QY<65a&ly>iR^wefu_EwFmG-ossKijq<1l*qTi?xAL>8Him813 zl7V`-ao&K;H#|uNPRcrYJ{o_4HAQBrfY0aynHr+8C zi%$Qb$E|$CsDM}H+?vP28s%4w{2bfUY%A+L&fV|nOzb;CshnbJFIBcVir&f!Y^?w$ z8KDf-grve_?{nBQo2fXVDdbq-=zwFhR-(_ktyk(^I)ssOsj#V88|)%n@hRtQX*cv$ zuVY}G-06>lkIXUXOf}YL)DX?i|BNkZz|dH0799$KY3OTepm1JTPHyguHjt`RPuk#B zFX-bN@(e38n-(mymxH*u@*L~*_<6rBqNx&{hD9Z+9geJHGA{Ew^V~&CmtpcCW~e%F z$OaeExE;_>x_n-kmp{X!D?Psz90sJp%Vv-~P diff --git a/test/functional/fixtures/es_archiver/dashboard/current/kibana/mappings.json b/test/functional/fixtures/es_archiver/dashboard/current/kibana/mappings.json index 7f4cdd2906d4..9f5edaad0fe7 100644 --- a/test/functional/fixtures/es_archiver/dashboard/current/kibana/mappings.json +++ b/test/functional/fixtures/es_archiver/dashboard/current/kibana/mappings.json @@ -1,13 +1,74 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "application_usage_totals": "c897e4310c5f24b07caaff3db53ae2c1", + "application_usage_transactional": "965839e75f809fefe04f92dc4d99722a", + "config": "ae24d22d5986d04124cc6568f771066f", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "181661168bbadd1eff5902361e2a0d5c", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "url": "b675c3be8d76ecf029294d51dc7ec65d", + "visualization": "52d7a13ad68a150c4525b292d23e12cc" + } + }, "dynamic": "strict", "properties": { + "application_usage_totals": { + "properties": { + "appId": { + "type": "keyword" + }, + "minutesOnScreen": { + "type": "float" + }, + "numberOfClicks": { + "type": "long" + } + } + }, + "application_usage_transactional": { + "properties": { + "appId": { + "type": "keyword" + }, + "minutesOnScreen": { + "type": "float" + }, + "numberOfClicks": { + "type": "long" + }, + "timestamp": { + "type": "date" + } + } + }, "config": { "dynamic": "true", "properties": { + "accessibility:disableAnimations": { + "type": "boolean" + }, "buildNum": { "type": "keyword" }, @@ -40,6 +101,9 @@ }, "notifications:lifetime:warning": { "type": "long" + }, + "xPackMonitoring:showBanner": { + "type": "boolean" } } }, @@ -92,9 +156,6 @@ "title": { "type": "text" }, - "uiStateJSON": { - "type": "text" - }, "version": { "type": "integer" } @@ -122,6 +183,122 @@ }, "title": { "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "dashboard": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "index-pattern": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "search": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "visualization": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "namespace": { + "type": "keyword" + }, + "namespaces": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" } } }, @@ -161,6 +338,34 @@ } } }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, "timelion-sheet": { "properties": { "description": { @@ -202,9 +407,23 @@ } } }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, "type": { "type": "keyword" }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, "updated_at": { "type": "date" }, @@ -222,7 +441,6 @@ "url": { "fields": { "keyword": { - "ignore_above": 2048, "type": "keyword" } }, @@ -242,7 +460,7 @@ } } }, - "savedSearchId": { + "savedSearchRefName": { "type": "keyword" }, "title": { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index dbb2a85eb2a2..bbb622f45296 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -565,7 +565,6 @@ "data.filter.filterBar.filterItemBadgeIconAriaLabel": "削除", "data.filter.filterBar.includeFilterButtonLabel": "結果を含める", "data.filter.filterBar.indexPatternSelectPlaceholder": "インデックスパターンの選択", - "data.filter.filterBar.labelErrorMessage": "フィルターを表示できませんでした", "data.filter.filterBar.labelErrorText": "エラー", "data.filter.filterBar.moreFilterActionsMessage": "フィルター:{innerText}。他のフィルターアクションを使用するには選択してください。", "data.filter.filterBar.negatedFilterPrefix": "NOT ", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 5527385c752e..07169a2b73da 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -565,7 +565,6 @@ "data.filter.filterBar.filterItemBadgeIconAriaLabel": "删除", "data.filter.filterBar.includeFilterButtonLabel": "包括结果", "data.filter.filterBar.indexPatternSelectPlaceholder": "选择索引模式", - "data.filter.filterBar.labelErrorMessage": "无法显示筛选", "data.filter.filterBar.labelErrorText": "错误", "data.filter.filterBar.moreFilterActionsMessage": "筛选:{innerText}。选择以获取更多筛选操作。", "data.filter.filterBar.negatedFilterPrefix": "非 ", diff --git a/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js b/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js index 9b5b82efe6f5..a996910d4787 100644 --- a/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js +++ b/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js @@ -32,8 +32,9 @@ export default function ({ getPageObjects, getService }) { await testSubjects.click('mapTooltipCreateFilterButton'); await testSubjects.click('applyFiltersPopoverButton'); - const hasSourceFilter = await filterBar.hasFilter('name', 'charlie'); - expect(hasSourceFilter).to.be(true); + // TODO: Fix me #64861 + // const hasSourceFilter = await filterBar.hasFilter('name', 'charlie'); + // expect(hasSourceFilter).to.be(true); const hasJoinFilter = await filterBar.hasFilter('shape_name', 'charlie'); expect(hasJoinFilter).to.be(true); From 4a1b05c843c39beb3ea2d4db35f444e4b120c428 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 3 Jun 2020 11:35:55 +0200 Subject: [PATCH 43/50] Lens editor auto refresh (#65868) --- .../public/react_expression_renderer.test.tsx | 25 ++++++++++++++ .../public/react_expression_renderer.tsx | 16 ++++++++- .../editor_frame/editor_frame.tsx | 1 + .../editor_frame/suggestion_panel.test.tsx | 2 ++ .../editor_frame/suggestion_panel.tsx | 19 +++++++++-- .../editor_frame/workspace_panel.test.tsx | 34 ++++++++++++------- .../editor_frame/workspace_panel.tsx | 9 ++++- 7 files changed, 88 insertions(+), 18 deletions(-) diff --git a/src/plugins/expressions/public/react_expression_renderer.test.tsx b/src/plugins/expressions/public/react_expression_renderer.test.tsx index 702f88d78575..7c1711f056d6 100644 --- a/src/plugins/expressions/public/react_expression_renderer.test.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.test.tsx @@ -88,6 +88,31 @@ describe('ExpressionRenderer', () => { expect(instance.find(EuiProgress)).toHaveLength(0); }); + it('updates the expression loader when refresh subject emits', () => { + const refreshSubject = new Subject(); + const loaderUpdate = jest.fn(); + + (ExpressionLoader as jest.Mock).mockImplementation(() => { + return { + render$: new Subject(), + data$: new Subject(), + loading$: new Subject(), + update: loaderUpdate, + destroy: jest.fn(), + }; + }); + + const instance = mount(); + + act(() => { + refreshSubject.next(); + }); + + expect(loaderUpdate).toHaveBeenCalled(); + + instance.unmount(); + }); + it('should display a custom error message if the user provides one and then remove it after successful render', () => { const dataSubject = new Subject(); const data$ = dataSubject.asObservable().pipe(share()); diff --git a/src/plugins/expressions/public/react_expression_renderer.tsx b/src/plugins/expressions/public/react_expression_renderer.tsx index a83c63443906..bf716a3b9b1e 100644 --- a/src/plugins/expressions/public/react_expression_renderer.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.tsx @@ -19,7 +19,7 @@ import React, { useRef, useEffect, useState, useLayoutEffect } from 'react'; import classNames from 'classnames'; -import { Subscription } from 'rxjs'; +import { Observable, Subscription } from 'rxjs'; import { filter } from 'rxjs/operators'; import useShallowCompareEffect from 'react-use/lib/useShallowCompareEffect'; import { EuiLoadingChart, EuiProgress } from '@elastic/eui'; @@ -38,6 +38,10 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams { renderError?: (error?: string | null) => React.ReactElement | React.ReactElement[]; padding?: 'xs' | 's' | 'm' | 'l' | 'xl'; onEvent?: (event: ExpressionRendererEvent) => void; + /** + * An observable which can be used to re-run the expression without destroying the component + */ + reload$?: Observable; } export type ReactExpressionRendererType = React.ComponentType; @@ -63,6 +67,7 @@ export const ReactExpressionRenderer = ({ renderError, expression, onEvent, + reload$, ...expressionLoaderOptions }: ReactExpressionRendererProps) => { const mountpoint: React.MutableRefObject = useRef(null); @@ -135,6 +140,15 @@ export const ReactExpressionRenderer = ({ }; }, [hasCustomRenderErrorHandler, onEvent]); + useEffect(() => { + const subscription = reload$?.subscribe(() => { + if (expressionLoaderRef.current) { + expressionLoaderRef.current.update(expression, expressionLoaderOptions); + } + }); + return () => subscription?.unsubscribe(); + }, [reload$, expression, ...Object.values(expressionLoaderOptions)]); + // Re-fetch data automatically when the inputs change useShallowCompareEffect( () => { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx index 90405b98afe6..07c76a81ed62 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx @@ -305,6 +305,7 @@ export function EditorFrame(props: EditorFrameProps) { dispatch={dispatch} ExpressionRenderer={props.ExpressionRenderer} stagedPreview={state.stagedPreview} + plugins={props.plugins} /> ) } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx index 6b0f0338d401..fd509c0046e1 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx @@ -21,6 +21,7 @@ import { SuggestionPanel, SuggestionPanelProps } from './suggestion_panel'; import { getSuggestions, Suggestion } from './suggestion_helpers'; import { EuiIcon, EuiPanel, EuiToolTip } from '@elastic/eui'; import chartTableSVG from '../../..assets/chart_datatable.svg'; +import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; jest.mock('./suggestion_helpers'); @@ -85,6 +86,7 @@ describe('suggestion_panel', () => { dispatch: dispatchMock, ExpressionRenderer: expressionRendererMock, frame: createMockFramePublicAPI(), + plugins: { data: dataPluginMock.createStartContract() }, }; }); 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 0f0885d696ba..b06b316ec79a 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 @@ -24,10 +24,14 @@ import classNames from 'classnames'; import { Action, PreviewState } from './state_management'; import { Datasource, Visualization, FramePublicAPI, DatasourcePublicAPI } from '../../types'; import { getSuggestions, switchToSuggestion } from './suggestion_helpers'; -import { ReactExpressionRendererType } from '../../../../../../src/plugins/expressions/public'; +import { + ReactExpressionRendererProps, + ReactExpressionRendererType, +} from '../../../../../../src/plugins/expressions/public'; import { prependDatasourceExpression, prependKibanaContext } from './expression_helpers'; import { debouncedComponent } from '../../debounced_component'; import { trackUiEvent, trackSuggestionEvent } from '../../lens_ui_telemetry'; +import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public'; const MAX_SUGGESTIONS_DISPLAYED = 5; @@ -52,6 +56,7 @@ export interface SuggestionPanelProps { ExpressionRenderer: ReactExpressionRendererType; frame: FramePublicAPI; stagedPreview?: PreviewState; + plugins: { data: DataPublicPluginStart }; } const PreviewRenderer = ({ @@ -154,6 +159,7 @@ export function SuggestionPanel({ frame, ExpressionRenderer: ExpressionRendererComponent, stagedPreview, + plugins, }: SuggestionPanelProps) { const currentDatasourceStates = stagedPreview ? stagedPreview.datasourceStates : datasourceStates; const currentVisualizationState = stagedPreview @@ -204,6 +210,13 @@ export function SuggestionPanel({ visualizationMap, ]); + const AutoRefreshExpressionRenderer = useMemo(() => { + const autoRefreshFetch$ = plugins.data.query.timefilter.timefilter.getAutoRefreshFetch$(); + return (props: ReactExpressionRendererProps) => ( + + ); + }, [plugins.data.query.timefilter.timefilter.getAutoRefreshFetch$, ExpressionRendererComponent]); + const [lastSelectedSuggestion, setLastSelectedSuggestion] = useState(-1); useEffect(() => { @@ -296,7 +309,7 @@ export function SuggestionPanel({ defaultMessage: 'Current', }), }} - ExpressionRenderer={ExpressionRendererComponent} + ExpressionRenderer={AutoRefreshExpressionRenderer} onSelect={rollbackToCurrentVisualization} selected={lastSelectedSuggestion === -1} showTitleAsLabel @@ -312,7 +325,7 @@ export function SuggestionPanel({ icon: suggestion.previewIcon, title: suggestion.title, }} - ExpressionRenderer={ExpressionRendererComponent} + ExpressionRenderer={AutoRefreshExpressionRenderer} key={index} onSelect={() => { trackUiEvent('suggestion_clicked'); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx index 59b5f358e190..49d12e9f4144 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx @@ -21,11 +21,17 @@ import { ReactWrapper } from 'enzyme'; import { DragDrop, ChildDragDropProvider } from '../../drag_drop'; import { Ast } from '@kbn/interpreter/common'; import { coreMock } from 'src/core/public/mocks'; -import { esFilters, IFieldType, IIndexPattern } from '../../../../../../src/plugins/data/public'; +import { + DataPublicPluginStart, + esFilters, + IFieldType, + IIndexPattern, +} from '../../../../../../src/plugins/data/public'; import { TriggerId, UiActionsStart } from '../../../../../../src/plugins/ui_actions/public'; import { uiActionsPluginMock } from '../../../../../../src/plugins/ui_actions/public/mocks'; import { TriggerContract } from '../../../../../../src/plugins/ui_actions/public/triggers'; import { VIS_EVENT_TO_TRIGGER } from '../../../../../../src/plugins/visualizations/public/embeddable'; +import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; describe('workspace_panel', () => { let mockVisualization: jest.Mocked; @@ -34,6 +40,7 @@ describe('workspace_panel', () => { let expressionRendererMock: jest.Mock; let uiActionsMock: jest.Mocked; + let dataMock: jest.Mocked; let trigger: jest.Mocked>; let instance: ReactWrapper; @@ -41,6 +48,7 @@ describe('workspace_panel', () => { beforeEach(() => { trigger = ({ exec: jest.fn() } as unknown) as jest.Mocked>; uiActionsMock = uiActionsPluginMock.createStartContract(); + dataMock = dataPluginMock.createStartContract(); uiActionsMock.getTrigger.mockReturnValue(trigger); mockVisualization = createMockVisualization(); mockVisualization2 = createMockVisualization(); @@ -69,7 +77,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); @@ -92,7 +100,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); @@ -115,7 +123,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); @@ -152,7 +160,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); @@ -240,7 +248,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); @@ -292,7 +300,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); @@ -372,7 +380,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); }); @@ -427,7 +435,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); }); @@ -482,7 +490,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); @@ -520,7 +528,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); }); @@ -564,7 +572,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); }); @@ -620,7 +628,7 @@ describe('workspace_panel', () => { dispatch={mockDispatch} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx index 44dd9f836487..76da38ead652 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx @@ -36,6 +36,7 @@ import { debouncedComponent } from '../../debounced_component'; import { trackUiEvent } from '../../lens_ui_telemetry'; import { UiActionsStart } from '../../../../../../src/plugins/ui_actions/public'; import { VIS_EVENT_TO_TRIGGER } from '../../../../../../src/plugins/visualizations/public'; +import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public'; export interface WorkspacePanelProps { activeVisualizationId: string | null; @@ -54,7 +55,7 @@ export interface WorkspacePanelProps { dispatch: (action: Action) => void; ExpressionRenderer: ReactExpressionRendererType; core: CoreStart | CoreSetup; - plugins: { uiActions?: UiActionsStart }; + plugins: { uiActions?: UiActionsStart; data: DataPublicPluginStart }; } export const WorkspacePanel = debouncedComponent(InnerWorkspacePanel); @@ -135,6 +136,11 @@ export function InnerWorkspacePanel({ framePublicAPI.filters, ]); + const autoRefreshFetch$ = useMemo( + () => plugins.data.query.timefilter.timefilter.getAutoRefreshFetch$(), + [plugins.data.query.timefilter.timefilter.getAutoRefreshFetch$] + ); + useEffect(() => { // reset expression error if component attempts to run it again if (expression && localState.expressionBuildError) { @@ -224,6 +230,7 @@ export function InnerWorkspacePanel({ className="lnsExpressionRenderer__component" padding="m" expression={expression!} + reload$={autoRefreshFetch$} onEvent={(event: ExpressionRendererEvent) => { if (!plugins.uiActions) { // ui actions not available, not handling event... From 0a23bfb08cb695f93a62854806bea604bfe4e321 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 3 Jun 2020 13:29:54 +0200 Subject: [PATCH 44/50] TSVB: handle division by zero in math agg (#67111) --- .../response_processors/series/math.js | 36 +++-- .../response_processors/series/math.test.js | 148 ++++++++++++++++++ 2 files changed, 170 insertions(+), 14 deletions(-) create mode 100644 src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.test.js diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.js index 6c8fe7ca1661..f8752ce8fa3a 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.js @@ -91,21 +91,29 @@ export function mathAgg(resp, panel, series, meta) { // a safety check for the user const someNull = values(params).some((v) => v == null); if (someNull) return [ts, null]; - // calculate the result based on the user's script and return the value - const result = evaluate(mathMetric.script, { - params: { - ...params, - _index: index, - _timestamp: ts, - _all: all, - _interval: split.meta.bucketSize * 1000, - }, - }); - // if the result is an object (usually when the user is working with maps and functions) flatten the results and return the last value. - if (typeof result === 'object') { - return [ts, last(flatten(result.valueOf()))]; + try { + // calculate the result based on the user's script and return the value + const result = evaluate(mathMetric.script, { + params: { + ...params, + _index: index, + _timestamp: ts, + _all: all, + _interval: split.meta.bucketSize * 1000, + }, + }); + // if the result is an object (usually when the user is working with maps and functions) flatten the results and return the last value. + if (typeof result === 'object') { + return [ts, last(flatten(result.valueOf()))]; + } + return [ts, result]; + } catch (e) { + if (e.message === 'Cannot divide by 0') { + // Drop division by zero errors and treat as null value + return [ts, null]; + } + throw e; } - return [ts, result]; }); return { id: split.id, diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.test.js new file mode 100644 index 000000000000..79cfd2ddd54b --- /dev/null +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.test.js @@ -0,0 +1,148 @@ +/* + * 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 { mathAgg } from './math'; +import { stdMetric } from './std_metric'; + +describe('math(resp, panel, series)', () => { + let panel; + let series; + let resp; + beforeEach(() => { + panel = { + time_field: 'timestamp', + }; + series = { + chart_type: 'line', + stacked: false, + line_width: 1, + point_size: 1, + fill: 0, + id: 'test', + label: 'Math', + split_mode: 'terms', + split_color_mode: 'gradient', + color: '#F00', + metrics: [ + { + id: 'avgcpu', + type: 'avg', + field: 'cpu', + }, + { + id: 'mincpu', + type: 'min', + field: 'cpu', + }, + { + id: 'mathagg', + type: 'math', + script: 'divide(params.a, params.b)', + variables: [ + { name: 'a', field: 'avgcpu' }, + { name: 'b', field: 'mincpu' }, + ], + }, + ], + }; + resp = { + aggregations: { + test: { + meta: { + bucketSize: 5, + }, + buckets: [ + { + key: 'example-01', + timeseries: { + buckets: [ + { + key: 1, + avgcpu: { value: 0.25 }, + mincpu: { value: 0.125 }, + }, + { + key: 2, + avgcpu: { value: 0.25 }, + mincpu: { value: 0.25 }, + }, + ], + }, + }, + ], + }, + }, + }; + }); + + test('calls next when finished', () => { + const next = jest.fn(); + mathAgg(resp, panel, series)(next)([]); + expect(next.mock.calls.length).toEqual(1); + }); + + test('creates a series', () => { + const next = mathAgg(resp, panel, series)((results) => results); + const results = stdMetric(resp, panel, series)(next)([]); + expect(results).toHaveLength(1); + + expect(results[0]).toEqual({ + id: 'test:example-01', + label: 'example-01', + color: 'rgb(255, 0, 0)', + stack: false, + seriesId: 'test', + lines: { show: true, fill: 0, lineWidth: 1, steps: false }, + points: { show: true, radius: 1, lineWidth: 1 }, + bars: { fill: 0, lineWidth: 1, show: false }, + data: [ + [1, 2], + [2, 1], + ], + }); + }); + + test('turns division by zero into null values', () => { + resp.aggregations.test.buckets[0].timeseries.buckets[0].mincpu = 0; + const next = mathAgg(resp, panel, series)((results) => results); + const results = stdMetric(resp, panel, series)(next)([]); + expect(results).toHaveLength(1); + + expect(results[0]).toEqual( + expect.objectContaining({ + data: [ + [1, null], + [2, 1], + ], + }) + ); + }); + + test('throws on actual tinymath expression errors', () => { + series.metrics[2].script = 'notExistingFn(params.a)'; + expect(() => + stdMetric(resp, panel, series)(mathAgg(resp, panel, series)((results) => results))([]) + ).toThrow(); + + series.metrics[2].script = 'divide(params.a, params.b'; + expect(() => + stdMetric(resp, panel, series)(mathAgg(resp, panel, series)((results) => results))([]) + ).toThrow(); + }); +}); From f8759d4571101d4410373b2fb01b9226f8ae66db Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 3 Jun 2020 13:34:14 +0200 Subject: [PATCH 45/50] Deprecate kibana.defaultAppId setting (#67635) --- docs/setup/docker.asciidoc | 2 +- docs/setup/settings.asciidoc | 4 +++- src/plugins/kibana_legacy/server/index.ts | 20 +++++++++++++++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/docs/setup/docker.asciidoc b/docs/setup/docker.asciidoc index 12ee96b21b0c..e8029ed1bbe9 100644 --- a/docs/setup/docker.asciidoc +++ b/docs/setup/docker.asciidoc @@ -93,7 +93,7 @@ Some example translations are shown here: [horizontal] **Environment Variable**:: **Kibana Setting** `SERVER_NAME`:: `server.name` -`KIBANA_DEFAULTAPPID`:: `kibana.defaultAppId` +`SERVER_BASEPATH`:: `server.basePath` `MONITORING_ENABLED`:: `monitoring.enabled` In general, any setting listed in <> can be diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 42d616c80119..1be9d5b1ef35 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -216,7 +216,9 @@ on the {kib} index at startup. {kib} users still need to authenticate with | Enables use of interpreter in Visualize. *Default: `true`* | `kibana.defaultAppId:` - | The default application to load. *Default: `"home"`* + | *deprecated* This setting is deprecated and will get removed in Kibana 8.0. +Please use the `defaultRoute` advanced setting instead. +The default application to load. *Default: `"home"`* | `kibana.index:` | {kib} uses an index in {es} to store saved searches, visualizations, and diff --git a/src/plugins/kibana_legacy/server/index.ts b/src/plugins/kibana_legacy/server/index.ts index 98c754795e94..0188f9b1ec51 100644 --- a/src/plugins/kibana_legacy/server/index.ts +++ b/src/plugins/kibana_legacy/server/index.ts @@ -17,7 +17,13 @@ * under the License. */ -import { CoreSetup, CoreStart, PluginConfigDescriptor } from 'kibana/server'; +import { + ConfigDeprecationLogger, + CoreSetup, + CoreStart, + PluginConfigDescriptor, +} from 'kibana/server'; +import { get } from 'lodash'; import { configSchema, ConfigSchema } from '../config'; @@ -29,6 +35,18 @@ export const config: PluginConfigDescriptor = { deprecations: ({ renameFromRoot }) => [ // TODO: Remove deprecation once defaultAppId is deleted renameFromRoot('kibana.defaultAppId', 'kibana_legacy.defaultAppId', true), + (completeConfig: Record, rootPath: string, log: ConfigDeprecationLogger) => { + if ( + get(completeConfig, 'kibana.defaultAppId') === undefined && + get(completeConfig, 'kibana_legacy.defaultAppId') === undefined + ) { + return completeConfig; + } + log( + `kibana.defaultAppId is deprecated and will be removed in 8.0. Please use the \`defaultRoute\` advanced setting instead` + ); + return completeConfig; + }, ], }; From 884704d847ef170928bd59c725a2ef95a2ee1200 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 3 Jun 2020 15:15:30 +0200 Subject: [PATCH 46/50] relax color rule validation (#67759) --- .../vis_type_timeseries/server/routes/post_vis_schema.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/vis_type_timeseries/server/routes/post_vis_schema.ts b/src/plugins/vis_type_timeseries/server/routes/post_vis_schema.ts index c74d1dd72d76..e8838f57ae36 100644 --- a/src/plugins/vis_type_timeseries/server/routes/post_vis_schema.ts +++ b/src/plugins/vis_type_timeseries/server/routes/post_vis_schema.ts @@ -66,6 +66,7 @@ const backgroundColorRulesItems = schema.object({ id: stringOptionalNullable, background_color: stringOptionalNullable, color: stringOptionalNullable, + operator: stringOptionalNullable, }); const gaugeColorRulesItems = schema.object({ @@ -73,7 +74,7 @@ const gaugeColorRulesItems = schema.object({ text: stringOptionalNullable, id: stringOptionalNullable, operator: stringOptionalNullable, - value: schema.number(), + value: schema.maybe(schema.nullable(schema.number())), }); const metricsItems = schema.object({ field: stringOptionalNullable, From 6f3e1bfbe5bdddf9a8c404bf83e4bee3f0b2c683 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Wed, 3 Jun 2020 09:48:54 -0400 Subject: [PATCH 47/50] [Uptime] Create new atomic params type for status alerts (#67720) * Create new atomic params type for status alerts. * Update executor params typing to support both alert params types. * Update snapshot for alert factory function. * Fix broken types and refresh snapshots. * Clean up naming of action/selector. * Fix a bug and add tests. Co-authored-by: Elastic Machine --- .../stringify_kueries.test.ts.snap | 0 .../combine_filters_and_user_search.test.ts | 0 .../lib}/__tests__/stringify_kueries.test.ts | 28 +++ .../lib}/combine_filters_and_user_search.ts | 0 x-pack/plugins/uptime/common/lib/index.ts | 8 + .../lib}/stringify_kueries.ts | 4 + .../runtime_types/alerts/status_check.ts | 27 ++- .../overview/alerts/alert_monitor_status.tsx | 6 +- .../alert_monitor_status.tsx | 10 +- .../filters_expression_select.tsx | 22 +- .../time_expression_select.tsx | 3 +- .../overview/kuery_bar/kuery_bar.tsx | 11 +- x-pack/plugins/uptime/public/hooks/index.ts | 1 + .../public/hooks/update_kuery_string.ts | 2 +- .../uptime/public/hooks/use_search_text.ts | 22 ++ .../__tests__/monitor_status.test.ts | 96 ++------ .../public/lib/alert_types/monitor_status.tsx | 41 +--- .../plugins/uptime/public/lib/helper/index.ts | 2 - .../plugins/uptime/public/state/actions/ui.ts | 2 + .../__tests__/__snapshots__/ui.test.ts.snap | 2 + .../state/reducers/__tests__/ui.test.ts | 4 + .../uptime/public/state/reducers/ui.ts | 8 + .../state/selectors/__tests__/index.test.ts | 1 + .../uptime/public/state/selectors/index.ts | 2 + .../lib/adapters/framework/adapter_types.ts | 2 +- .../lib/alerts/__tests__/status_check.test.ts | 210 +++++++++++++++++- .../uptime/server/lib/alerts/status_check.ts | 110 +++++++-- .../server/lib/requests/get_index_pattern.ts | 2 +- .../server/lib/requests/uptime_requests.ts | 3 +- 29 files changed, 474 insertions(+), 155 deletions(-) rename x-pack/plugins/uptime/{public/lib/helper => common/lib}/__tests__/__snapshots__/stringify_kueries.test.ts.snap (100%) rename x-pack/plugins/uptime/{public/lib/helper => common/lib}/__tests__/combine_filters_and_user_search.test.ts (100%) rename x-pack/plugins/uptime/{public/lib/helper => common/lib}/__tests__/stringify_kueries.test.ts (69%) rename x-pack/plugins/uptime/{public/lib/helper => common/lib}/combine_filters_and_user_search.ts (100%) create mode 100644 x-pack/plugins/uptime/common/lib/index.ts rename x-pack/plugins/uptime/{public/lib/helper => common/lib}/stringify_kueries.ts (93%) create mode 100644 x-pack/plugins/uptime/public/hooks/use_search_text.ts diff --git a/x-pack/plugins/uptime/public/lib/helper/__tests__/__snapshots__/stringify_kueries.test.ts.snap b/x-pack/plugins/uptime/common/lib/__tests__/__snapshots__/stringify_kueries.test.ts.snap similarity index 100% rename from x-pack/plugins/uptime/public/lib/helper/__tests__/__snapshots__/stringify_kueries.test.ts.snap rename to x-pack/plugins/uptime/common/lib/__tests__/__snapshots__/stringify_kueries.test.ts.snap diff --git a/x-pack/plugins/uptime/public/lib/helper/__tests__/combine_filters_and_user_search.test.ts b/x-pack/plugins/uptime/common/lib/__tests__/combine_filters_and_user_search.test.ts similarity index 100% rename from x-pack/plugins/uptime/public/lib/helper/__tests__/combine_filters_and_user_search.test.ts rename to x-pack/plugins/uptime/common/lib/__tests__/combine_filters_and_user_search.test.ts diff --git a/x-pack/plugins/uptime/public/lib/helper/__tests__/stringify_kueries.test.ts b/x-pack/plugins/uptime/common/lib/__tests__/stringify_kueries.test.ts similarity index 69% rename from x-pack/plugins/uptime/public/lib/helper/__tests__/stringify_kueries.test.ts rename to x-pack/plugins/uptime/common/lib/__tests__/stringify_kueries.test.ts index db1a846c295a..4c32b1976542 100644 --- a/x-pack/plugins/uptime/public/lib/helper/__tests__/stringify_kueries.test.ts +++ b/x-pack/plugins/uptime/common/lib/__tests__/stringify_kueries.test.ts @@ -59,4 +59,32 @@ describe('stringifyKueries', () => { kueries.set('monitor.id', ['https://elastic.co', 'https://example.com']); expect(stringifyKueries(kueries)).toMatchSnapshot(); }); + + it('handles precending empty array', () => { + kueries = new Map( + Object.entries({ + 'monitor.type': [], + 'observer.geo.name': ['us-east', 'apj', 'sydney', 'us-west'], + tags: [], + 'url.port': [], + }) + ); + expect(stringifyKueries(kueries)).toMatchInlineSnapshot( + `"(observer.geo.name:us-east or observer.geo.name:apj or observer.geo.name:sydney or observer.geo.name:us-west)"` + ); + }); + + it('handles skipped empty arrays', () => { + kueries = new Map( + Object.entries({ + tags: [], + 'monitor.type': ['http'], + 'url.port': [], + 'observer.geo.name': ['us-east', 'apj', 'sydney', 'us-west'], + }) + ); + expect(stringifyKueries(kueries)).toMatchInlineSnapshot( + `"monitor.type:http and (observer.geo.name:us-east or observer.geo.name:apj or observer.geo.name:sydney or observer.geo.name:us-west)"` + ); + }); }); diff --git a/x-pack/plugins/uptime/public/lib/helper/combine_filters_and_user_search.ts b/x-pack/plugins/uptime/common/lib/combine_filters_and_user_search.ts similarity index 100% rename from x-pack/plugins/uptime/public/lib/helper/combine_filters_and_user_search.ts rename to x-pack/plugins/uptime/common/lib/combine_filters_and_user_search.ts diff --git a/x-pack/plugins/uptime/common/lib/index.ts b/x-pack/plugins/uptime/common/lib/index.ts new file mode 100644 index 000000000000..2daec0adf87e --- /dev/null +++ b/x-pack/plugins/uptime/common/lib/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 * from './combine_filters_and_user_search'; +export * from './stringify_kueries'; diff --git a/x-pack/plugins/uptime/public/lib/helper/stringify_kueries.ts b/x-pack/plugins/uptime/common/lib/stringify_kueries.ts similarity index 93% rename from x-pack/plugins/uptime/public/lib/helper/stringify_kueries.ts rename to x-pack/plugins/uptime/common/lib/stringify_kueries.ts index d34ee9665de3..490fd1661f78 100644 --- a/x-pack/plugins/uptime/public/lib/helper/stringify_kueries.ts +++ b/x-pack/plugins/uptime/common/lib/stringify_kueries.ts @@ -35,6 +35,10 @@ export const stringifyKueries = (kueries: Map>): .reduce((prev, cur, index, array) => { if (array.length === 1 || index === 0) { return cur; + } else if (cur === '') { + return prev; + } else if (prev === '' && !!cur) { + return cur; } return `${prev} and ${cur}`; }, ''); diff --git a/x-pack/plugins/uptime/common/runtime_types/alerts/status_check.ts b/x-pack/plugins/uptime/common/runtime_types/alerts/status_check.ts index 909669bb5d3e..74d533725660 100644 --- a/x-pack/plugins/uptime/common/runtime_types/alerts/status_check.ts +++ b/x-pack/plugins/uptime/common/runtime_types/alerts/status_check.ts @@ -6,7 +6,30 @@ import * as t from 'io-ts'; -export const StatusCheckExecutorParamsType = t.intersection([ +export const StatusCheckFiltersType = t.type({ + 'monitor.type': t.array(t.string), + 'observer.geo.name': t.array(t.string), + tags: t.array(t.string), + 'url.port': t.array(t.string), +}); + +export type StatusCheckFilters = t.TypeOf; + +export const AtomicStatusCheckParamsType = t.intersection([ + t.type({ + numTimes: t.number, + timerangeCount: t.number, + timerangeUnit: t.string, + }), + t.partial({ + search: t.string, + filters: StatusCheckFiltersType, + }), +]); + +export type AtomicStatusCheckParams = t.TypeOf; + +export const StatusCheckParamsType = t.intersection([ t.partial({ filters: t.string, }), @@ -20,4 +43,4 @@ export const StatusCheckExecutorParamsType = t.intersection([ }), ]); -export type StatusCheckExecutorParams = t.TypeOf; +export type StatusCheckParams = t.TypeOf; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/alert_monitor_status.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/alert_monitor_status.tsx index 62f92fe8a514..e2e44124ec65 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/alert_monitor_status.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/alert_monitor_status.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { EuiSpacer } from '@elastic/eui'; import { DataPublicPluginSetup } from 'src/plugins/data/public'; import * as labels from './translations'; @@ -35,10 +35,6 @@ export const AlertMonitorStatusComponent: React.FC = (p const [newFilters, setNewFilters] = useState([]); - useEffect(() => { - setAlertParams('filters', filters); - }, [filters, setAlertParams]); - return ( <> diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx index 77e0c98c0260..973a3e1d477b 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useEffect } from 'react'; import { useSelector } from 'react-redux'; import { DataPublicPluginSetup } from 'src/plugins/data/public'; -import { selectMonitorStatusAlert } from '../../../../state/selectors'; +import { selectMonitorStatusAlert, searchTextSelector } from '../../../../state/selectors'; import { AlertMonitorStatusComponent } from '../index'; interface Props { @@ -29,6 +29,12 @@ export const AlertMonitorStatus: React.FC = ({ timerange, }) => { const { filters, locations } = useSelector(selectMonitorStatusAlert); + const searchText = useSelector(searchTextSelector); + + useEffect(() => { + setAlertParams('search', searchText); + }, [setAlertParams, searchText]); + return ( = ({ updatedFieldValues.values ); - useEffect(() => { - if (updatedFieldValues.fieldName === 'observer.geo.name') { - setAlertParams('locations', updatedFieldValues.values); - } - }, [setAlertParams, updatedFieldValues]); + const [filters, setFilters] = useState({ + 'observer.geo.name': selectedLocations, + 'url.port': selectedPorts, + tags: selectedTags, + 'monitor.type': selectedSchemes, + }); useEffect(() => { - setAlertParams('locations', []); - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + setAlertParams('filters', filters); + }, [filters, setAlertParams]); const onFilterFieldChange = (fieldName: string, values: string[]) => { + setFilters({ + ...filters, + [fieldName]: values, + }); setUpdatedFieldValues({ fieldName, values }); }; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/time_expression_select.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/time_expression_select.tsx index aabc6fd6e662..e3893845862f 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/time_expression_select.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/time_expression_select.tsx @@ -51,7 +51,8 @@ export const TimeExpressionSelect: React.FC = ({ setAlertParams }) => { useEffect(() => { const timerangeUnit = timerangeUnitOptions.find(({ checked }) => checked === 'on')?.key ?? 'm'; - setAlertParams('timerange', { from: `now-${numUnits}${timerangeUnit}`, to: 'now' }); + setAlertParams('timerangeUnit', timerangeUnit); + setAlertParams('timerangeCount', numUnits); }, [numUnits, timerangeUnitOptions, setAlertParams]); return ( 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 a63dbfdecef4..5c0ee632a2bd 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 @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { uniqueId, startsWith } from 'lodash'; import { EuiCallOut } from '@elastic/eui'; import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n/react'; import { Typeahead } from './typeahead'; -import { useUrlParams } from '../../../hooks'; +import { useSearchText, useUrlParams } from '../../../hooks'; import { esKuery, IIndexPattern, @@ -45,6 +45,7 @@ export function KueryBar({ 'data-test-subj': dataTestSubj, }: Props) { const { loading, index_pattern: indexPattern } = useIndexPattern(); + const { updateSearchText } = useSearchText(); const [state, setState] = useState({ suggestions: [], @@ -56,6 +57,10 @@ export function KueryBar({ const [getUrlParams, updateUrlParams] = useUrlParams(); const { search: kuery } = getUrlParams(); + useEffect(() => { + updateSearchText(kuery); + }, [kuery, updateSearchText]); + const indexPatternMissing = loading && !indexPattern; async function onChange(inputValue: string, selectionStart: number) { @@ -63,6 +68,8 @@ export function KueryBar({ return; } + updateSearchText(inputValue); + setIsLoadingSuggestions(true); setState({ ...state, suggestions: [] }); diff --git a/x-pack/plugins/uptime/public/hooks/index.ts b/x-pack/plugins/uptime/public/hooks/index.ts index b92d2d4cf7df..14264710f7a0 100644 --- a/x-pack/plugins/uptime/public/hooks/index.ts +++ b/x-pack/plugins/uptime/public/hooks/index.ts @@ -9,3 +9,4 @@ export * from './use_url_params'; export * from './use_telemetry'; export * from './update_kuery_string'; export * from './use_cert_status'; +export * from './use_search_text'; diff --git a/x-pack/plugins/uptime/public/hooks/update_kuery_string.ts b/x-pack/plugins/uptime/public/hooks/update_kuery_string.ts index 492d2bab5bb8..8a9e134e98bf 100644 --- a/x-pack/plugins/uptime/public/hooks/update_kuery_string.ts +++ b/x-pack/plugins/uptime/public/hooks/update_kuery_string.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { combineFiltersAndUserSearch, stringifyKueries } from '../lib/helper'; import { esKuery, IIndexPattern } from '../../../../../src/plugins/data/public'; +import { combineFiltersAndUserSearch, stringifyKueries } from '../../common/lib'; const getKueryString = (urlFilters: string): string => { let kueryString = ''; diff --git a/x-pack/plugins/uptime/public/hooks/use_search_text.ts b/x-pack/plugins/uptime/public/hooks/use_search_text.ts new file mode 100644 index 000000000000..8226c2365b76 --- /dev/null +++ b/x-pack/plugins/uptime/public/hooks/use_search_text.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useCallback } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { setSearchTextAction } from '../state/actions'; +import { searchTextSelector } from '../state/selectors'; + +export const useSearchText = () => { + const dispatch = useDispatch(); + const searchText = useSelector(searchTextSelector); + + const updateSearchText = useCallback( + (nextSearchText: string) => dispatch(setSearchTextAction(nextSearchText)), + [dispatch] + ); + + return { searchText, updateSearchText }; +}; diff --git a/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts b/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts index b06a7cc93f62..098a999b0d89 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts +++ b/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts @@ -12,12 +12,9 @@ describe('monitor status alert type', () => { beforeEach(() => { params = { - locations: [], numTimes: 5, - timerange: { - from: 'now-15m', - to: 'now', - }, + timerangeCount: 15, + timerangeUnit: 'm', }; }); @@ -27,9 +24,9 @@ describe('monitor status alert type', () => { "errors": Object { "typeCheckFailure": "Provided parameters do not conform to the expected type.", "typeCheckParsingMessage": Array [ - "Invalid value undefined supplied to : (Partial<{ filters: string }> & { locations: Array, numTimes: number, timerange: { from: string, to: string } })/1: { locations: Array, numTimes: number, timerange: { from: string, to: string } }/locations: Array", - "Invalid value undefined supplied to : (Partial<{ filters: string }> & { locations: Array, numTimes: number, timerange: { from: string, to: string } })/1: { locations: Array, numTimes: number, timerange: { from: string, to: string } }/numTimes: number", - "Invalid value undefined supplied to : (Partial<{ filters: string }> & { locations: Array, numTimes: number, timerange: { from: string, to: string } })/1: { locations: Array, numTimes: number, timerange: { from: string, to: string } }/timerange: { from: string, to: string }", + "Invalid value undefined supplied to : ({ numTimes: number, timerangeCount: number, timerangeUnit: string } & Partial<{ search: string, filters: { monitor.type: Array, observer.geo.name: Array, tags: Array, url.port: Array } }>)/0: { numTimes: number, timerangeCount: number, timerangeUnit: string }/numTimes: number", + "Invalid value undefined supplied to : ({ numTimes: number, timerangeCount: number, timerangeUnit: string } & Partial<{ search: string, filters: { monitor.type: Array, observer.geo.name: Array, tags: Array, url.port: Array } }>)/0: { numTimes: number, timerangeCount: number, timerangeUnit: string }/timerangeCount: number", + "Invalid value undefined supplied to : ({ numTimes: number, timerangeCount: number, timerangeUnit: string } & Partial<{ search: string, filters: { monitor.type: Array, observer.geo.name: Array, tags: Array, url.port: Array } }>)/0: { numTimes: number, timerangeCount: number, timerangeUnit: string }/timerangeUnit: string", ], }, } @@ -37,88 +34,21 @@ describe('monitor status alert type', () => { }); describe('timerange', () => { - it('is undefined', () => { - delete params.timerange; - expect(validate(params)).toMatchInlineSnapshot(` - Object { - "errors": Object { - "typeCheckFailure": "Provided parameters do not conform to the expected type.", - "typeCheckParsingMessage": Array [ - "Invalid value undefined supplied to : (Partial<{ filters: string }> & { locations: Array, numTimes: number, timerange: { from: string, to: string } })/1: { locations: Array, numTimes: number, timerange: { from: string, to: string } }/timerange: { from: string, to: string }", - ], - }, - } - `); - }); - - it('is missing `from` or `to` value', () => { - expect( - validate({ - ...params, - timerange: {}, - }) - ).toMatchInlineSnapshot(` - Object { - "errors": Object { - "typeCheckFailure": "Provided parameters do not conform to the expected type.", - "typeCheckParsingMessage": Array [ - "Invalid value undefined supplied to : (Partial<{ filters: string }> & { locations: Array, numTimes: number, timerange: { from: string, to: string } })/1: { locations: Array, numTimes: number, timerange: { from: string, to: string } }/timerange: { from: string, to: string }/from: string", - "Invalid value undefined supplied to : (Partial<{ filters: string }> & { locations: Array, numTimes: number, timerange: { from: string, to: string } })/1: { locations: Array, numTimes: number, timerange: { from: string, to: string } }/timerange: { from: string, to: string }/to: string", - ], - }, - } - `); - }); - - it('is invalid timespan', () => { - expect( - validate({ - ...params, - timerange: { - from: 'now', - to: 'now-15m', - }, - }) - ).toMatchInlineSnapshot(` - Object { - "errors": Object { - "invalidTimeRange": "Time range start cannot exceed time range end", - }, - } - `); - }); - - it('has unparseable `from` value', () => { - expect( - validate({ - ...params, - timerange: { - from: 'cannot parse this to a date', - to: 'now', - }, - }) - ).toMatchInlineSnapshot(` + it('has invalid timerangeCount value', () => { + expect(validate({ ...params, timerangeCount: 0 })).toMatchInlineSnapshot(` Object { "errors": Object { - "timeRangeStartValueNaN": "Specified time range \`from\` is an invalid value", + "invalidTimeRangeValue": "Time range value must be greater than 0", }, } `); }); - it('has unparseable `to` value', () => { - expect( - validate({ - ...params, - timerange: { - from: 'now-15m', - to: 'cannot parse this to a date', - }, - }) - ).toMatchInlineSnapshot(` + it('has NaN timerangeCount value', () => { + expect(validate({ ...params, timerangeCount: NaN })).toMatchInlineSnapshot(` Object { "errors": Object { - "timeRangeEndValueNaN": "Specified time range \`to\` is an invalid value", + "timeRangeStartValueNaN": "Specified time range value must be a number", }, } `); @@ -133,7 +63,7 @@ describe('monitor status alert type', () => { "errors": Object { "typeCheckFailure": "Provided parameters do not conform to the expected type.", "typeCheckParsingMessage": Array [ - "Invalid value undefined supplied to : (Partial<{ filters: string }> & { locations: Array, numTimes: number, timerange: { from: string, to: string } })/1: { locations: Array, numTimes: number, timerange: { from: string, to: string } }/numTimes: number", + "Invalid value undefined supplied to : ({ numTimes: number, timerangeCount: number, timerangeUnit: string } & Partial<{ search: string, filters: { monitor.type: Array, observer.geo.name: Array, tags: Array, url.port: Array } }>)/0: { numTimes: number, timerangeCount: number, timerangeUnit: string }/numTimes: number", ], }, } @@ -146,7 +76,7 @@ describe('monitor status alert type', () => { "errors": Object { "typeCheckFailure": "Provided parameters do not conform to the expected type.", "typeCheckParsingMessage": Array [ - "Invalid value \\"this isn't a number\\" supplied to : (Partial<{ filters: string }> & { locations: Array, numTimes: number, timerange: { from: string, to: string } })/1: { locations: Array, numTimes: number, timerange: { from: string, to: string } }/numTimes: number", + "Invalid value \\"this isn't a number\\" supplied to : ({ numTimes: number, timerangeCount: number, timerangeUnit: string } & Partial<{ search: string, filters: { monitor.type: Array, observer.geo.name: Array, tags: Array, url.port: Array } }>)/0: { numTimes: number, timerangeCount: number, timerangeUnit: string }/numTimes: number", ], }, } diff --git a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx index 08fc044bee20..a39317f8db1e 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx +++ b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx @@ -4,51 +4,34 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PathReporter } from 'io-ts/lib/PathReporter'; import React from 'react'; -import DateMath from '@elastic/datemath'; import { isRight } from 'fp-ts/lib/Either'; +import { PathReporter } from 'io-ts/lib/PathReporter'; import { AlertTypeModel } from '../../../../triggers_actions_ui/public'; import { AlertTypeInitializer } from '.'; -import { StatusCheckExecutorParamsType } from '../../../common/runtime_types'; +import { AtomicStatusCheckParamsType } from '../../../common/runtime_types'; import { MonitorStatusTitle } from './monitor_status_title'; import { CLIENT_ALERT_TYPES } from '../../../common/constants'; import { MonitorStatusTranslations } from './translations'; -export const validate = (alertParams: any) => { +export const validate = (alertParams: unknown) => { const errors: Record = {}; - const decoded = StatusCheckExecutorParamsType.decode(alertParams); + const decoded = AtomicStatusCheckParamsType.decode(alertParams); - /* - * When the UI initially loads, this validate function is called with an - * empty set of params, we don't want to type check against that. - */ if (!isRight(decoded)) { errors.typeCheckFailure = 'Provided parameters do not conform to the expected type.'; errors.typeCheckParsingMessage = PathReporter.report(decoded); - } - - if (isRight(decoded)) { - const { numTimes, timerange } = decoded.right; - const { from, to } = timerange; - const fromAbs = DateMath.parse(from)?.valueOf(); - const toAbs = DateMath.parse(to)?.valueOf(); - if (!fromAbs || isNaN(fromAbs)) { - errors.timeRangeStartValueNaN = 'Specified time range `from` is an invalid value'; - } - if (!toAbs || isNaN(toAbs)) { - errors.timeRangeEndValueNaN = 'Specified time range `to` is an invalid value'; - } - - // the default values for this test will pass, we only want to specify an error - // in the case that `from` is more recent than `to` - if ((fromAbs ?? 0) > (toAbs ?? 1)) { - errors.invalidTimeRange = 'Time range start cannot exceed time range end'; - } - + } else { + const { numTimes, timerangeCount } = decoded.right; if (numTimes < 1) { errors.invalidNumTimes = 'Number of alert check down times must be an integer greater than 0'; } + if (isNaN(timerangeCount)) { + errors.timeRangeStartValueNaN = 'Specified time range value must be a number'; + } + if (timerangeCount <= 0) { + errors.invalidTimeRangeValue = 'Time range value must be greater than 0'; + } } return { errors }; diff --git a/x-pack/plugins/uptime/public/lib/helper/index.ts b/x-pack/plugins/uptime/public/lib/helper/index.ts index e2aa4a2b3d42..cf49328141b8 100644 --- a/x-pack/plugins/uptime/public/lib/helper/index.ts +++ b/x-pack/plugins/uptime/public/lib/helper/index.ts @@ -4,10 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -export { combineFiltersAndUserSearch } from './combine_filters_and_user_search'; export { convertMicrosecondsToMilliseconds } from './convert_measurements'; export * from './observability_integration'; export { getChartDateLabel } from './charts'; export { seriesHasDownValues } from './series_has_down_values'; -export { stringifyKueries } from './stringify_kueries'; export { UptimeUrlParams, getSupportedUrlParams } from './url_params'; diff --git a/x-pack/plugins/uptime/public/state/actions/ui.ts b/x-pack/plugins/uptime/public/state/actions/ui.ts index 0d21e177d3e4..04ad6c2fa0bf 100644 --- a/x-pack/plugins/uptime/public/state/actions/ui.ts +++ b/x-pack/plugins/uptime/public/state/actions/ui.ts @@ -20,6 +20,8 @@ export const setBasePath = createAction('SET BASE PATH'); export const setEsKueryString = createAction('SET ES KUERY STRING'); +export const setSearchTextAction = createAction('SET SEARCH'); + export const toggleIntegrationsPopover = createAction( 'TOGGLE INTEGRATION POPOVER STATE' ); diff --git a/x-pack/plugins/uptime/public/state/reducers/__tests__/__snapshots__/ui.test.ts.snap b/x-pack/plugins/uptime/public/state/reducers/__tests__/__snapshots__/ui.test.ts.snap index f8faf78fbc50..c11b146101d3 100644 --- a/x-pack/plugins/uptime/public/state/reducers/__tests__/__snapshots__/ui.test.ts.snap +++ b/x-pack/plugins/uptime/public/state/reducers/__tests__/__snapshots__/ui.test.ts.snap @@ -9,6 +9,7 @@ Object { "id": "popover-2", "open": true, }, + "searchText": "", } `; @@ -18,5 +19,6 @@ Object { "basePath": "yyz", "esKuery": "", "integrationsPopoverOpen": null, + "searchText": "", } `; diff --git a/x-pack/plugins/uptime/public/state/reducers/__tests__/ui.test.ts b/x-pack/plugins/uptime/public/state/reducers/__tests__/ui.test.ts index 94bc626088c8..3b8447ec2d71 100644 --- a/x-pack/plugins/uptime/public/state/reducers/__tests__/ui.test.ts +++ b/x-pack/plugins/uptime/public/state/reducers/__tests__/ui.test.ts @@ -18,6 +18,7 @@ describe('ui reducer', () => { basePath: 'abc', esKuery: '', integrationsPopoverOpen: null, + searchText: '', }, action ) @@ -36,6 +37,7 @@ describe('ui reducer', () => { basePath: '', esKuery: '', integrationsPopoverOpen: null, + searchText: '', }, action ) @@ -51,6 +53,7 @@ describe('ui reducer', () => { basePath: '', esKuery: '', integrationsPopoverOpen: null, + searchText: '', }, action ) @@ -60,6 +63,7 @@ describe('ui reducer', () => { "basePath": "", "esKuery": "", "integrationsPopoverOpen": null, + "searchText": "", } `); }); diff --git a/x-pack/plugins/uptime/public/state/reducers/ui.ts b/x-pack/plugins/uptime/public/state/reducers/ui.ts index 9e7bc2ad0272..3cf4ae9c0bbf 100644 --- a/x-pack/plugins/uptime/public/state/reducers/ui.ts +++ b/x-pack/plugins/uptime/public/state/reducers/ui.ts @@ -13,6 +13,7 @@ import { UiPayload, setAlertFlyoutType, setAlertFlyoutVisible, + setSearchTextAction, } from '../actions'; export interface UiState { @@ -20,6 +21,7 @@ export interface UiState { alertFlyoutType?: string; basePath: string; esKuery: string; + searchText: string; integrationsPopoverOpen: PopoverState | null; } @@ -27,6 +29,7 @@ const initialState: UiState = { alertFlyoutVisible: false, basePath: '', esKuery: '', + searchText: '', integrationsPopoverOpen: null, }; @@ -56,6 +59,11 @@ export const uiReducer = handleActions( ...state, alertFlyoutType: action.payload, }), + + [String(setSearchTextAction)]: (state, action: Action) => ({ + ...state, + searchText: action.payload, + }), }, initialState ); diff --git a/x-pack/plugins/uptime/public/state/selectors/__tests__/index.test.ts b/x-pack/plugins/uptime/public/state/selectors/__tests__/index.test.ts index d8121e29d0ca..2eb0f1e8cb0e 100644 --- a/x-pack/plugins/uptime/public/state/selectors/__tests__/index.test.ts +++ b/x-pack/plugins/uptime/public/state/selectors/__tests__/index.test.ts @@ -44,6 +44,7 @@ describe('state selectors', () => { basePath: 'yyz', esKuery: '', integrationsPopoverOpen: null, + searchText: '', }, monitorStatus: { status: null, diff --git a/x-pack/plugins/uptime/public/state/selectors/index.ts b/x-pack/plugins/uptime/public/state/selectors/index.ts index ce295faaf576..b088c346ad81 100644 --- a/x-pack/plugins/uptime/public/state/selectors/index.ts +++ b/x-pack/plugins/uptime/public/state/selectors/index.ts @@ -84,3 +84,5 @@ export const monitorListSelector = ({ monitorList }: AppState) => monitorList; export const overviewFiltersSelector = ({ overviewFilters }: AppState) => overviewFilters; export const esKuerySelector = ({ ui: { esKuery } }: AppState) => esKuery; + +export const searchTextSelector = ({ ui: { searchText } }: AppState) => searchText; diff --git a/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts index 5ffc71945cae..8d2683881126 100644 --- a/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts @@ -23,7 +23,7 @@ export type APICaller = ( export type UMElasticsearchQueryFn = ( params: { callES: APICaller; dynamicSettings: DynamicSettings } & P -) => Promise | R; +) => Promise; export type UMSavedObjectsQueryFn = ( client: SavedObjectsClientContract | ISavedObjectsRepository, diff --git a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts index 8c487c85c572..6cd836525c07 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts @@ -6,9 +6,11 @@ import { contextMessage, - uniqueMonitorIds, - statusCheckAlertFactory, fullListByIdAndLocation, + genFilterString, + hasFilters, + statusCheckAlertFactory, + uniqueMonitorIds, } from '../status_check'; import { GetMonitorStatusResult } from '../../requests'; import { AlertType } from '../../../../../alerts/server'; @@ -310,9 +312,12 @@ describe('status check alert', () => { expect(Object.keys(alert.validate?.params?.props ?? {})).toMatchInlineSnapshot(` Array [ "filters", + "locations", "numTimes", + "search", + "timerangeCount", + "timerangeUnit", "timerange", - "locations", ] `); }); @@ -332,6 +337,205 @@ describe('status check alert', () => { }); }); + describe('hasFilters', () => { + it('returns false for undefined filters', () => { + expect(hasFilters()).toBe(false); + }); + + it('returns false for empty filters', () => { + expect( + hasFilters({ + 'monitor.type': [], + 'observer.geo.name': [], + tags: [], + 'url.port': [], + }) + ).toBe(false); + }); + + it('returns true for an object with a filter', () => { + expect( + hasFilters({ + 'monitor.type': [], + 'observer.geo.name': ['us-east', 'us-west'], + tags: [], + 'url.port': [], + }) + ).toBe(true); + }); + }); + + describe('genFilterString', () => { + const mockGetIndexPattern = jest.fn(); + mockGetIndexPattern.mockReturnValue(undefined); + + it('returns `undefined` for no filters or search', async () => { + expect(await genFilterString(mockGetIndexPattern)).toBeUndefined(); + }); + + it('creates a filter string for filters only', async () => { + const res = await genFilterString(mockGetIndexPattern, { + 'monitor.type': [], + 'observer.geo.name': ['us-east', 'us-west'], + tags: [], + 'url.port': [], + }); + expect(res).toMatchInlineSnapshot(` + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "observer.geo.name": "us-east", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "observer.geo.name": "us-west", + }, + }, + ], + }, + }, + ], + }, + } + `); + }); + + it('creates a filter string for search only', async () => { + expect(await genFilterString(mockGetIndexPattern, undefined, 'monitor.id: "kibana-dev"')) + .toMatchInlineSnapshot(` + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "monitor.id": "kibana-dev", + }, + }, + ], + }, + } + `); + }); + + it('creates a filter string for filters and string', async () => { + const res = await genFilterString( + mockGetIndexPattern, + { + 'monitor.type': [], + 'observer.geo.name': ['us-east', 'apj', 'sydney', 'us-west'], + tags: [], + 'url.port': [], + }, + 'monitor.id: "kibana-dev"' + ); + expect(res).toMatchInlineSnapshot(` + Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "observer.geo.name": "us-east", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "observer.geo.name": "apj", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "observer.geo.name": "sydney", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "observer.geo.name": "us-west", + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "monitor.id": "kibana-dev", + }, + }, + ], + }, + }, + ], + }, + } + `); + }); + }); + describe('uniqueMonitorIds', () => { let items: GetMonitorStatusResult[]; beforeEach(() => { diff --git a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts index 3dd1558f5da9..cd42082b42c8 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts @@ -11,11 +11,19 @@ import { i18n } from '@kbn/i18n'; import { AlertExecutorOptions } from '../../../../alerts/server'; import { UptimeAlertTypeFactory } from './types'; import { GetMonitorStatusResult } from '../requests'; -import { StatusCheckExecutorParamsType } from '../../../common/runtime_types'; +import { esKuery, IIndexPattern } from '../../../../../../src/plugins/data/server'; +import { JsonObject } from '../../../../../../src/plugins/kibana_utils/common'; +import { + StatusCheckParamsType, + StatusCheckParams, + StatusCheckFilters, + AtomicStatusCheckParamsType, +} from '../../../common/runtime_types'; import { ACTION_GROUP_DEFINITIONS } from '../../../common/constants'; import { savedObjectsAdapter } from '../saved_objects'; import { updateState } from './common'; import { commonStateTranslations } from './translations'; +import { stringifyKueries, combineFiltersAndUserSearch } from '../../../common/lib'; const { MONITOR_STATUS } = ACTION_GROUP_DEFINITIONS; @@ -124,6 +132,44 @@ export const fullListByIdAndLocation = ( // we might want to make this a parameter in the future const DEFAULT_MAX_MESSAGE_ROWS = 3; +export const hasFilters = (filters?: StatusCheckFilters) => { + if (!filters) return false; + for (const list of Object.values(filters)) { + if (list.length > 0) { + return true; + } + } + return false; +}; + +export const genFilterString = async ( + getIndexPattern: () => Promise, + filters?: StatusCheckFilters, + search?: string +): Promise => { + const filtersExist = hasFilters(filters); + if (!filtersExist && !search) return undefined; + + let filterString: string | undefined; + if (filtersExist) { + filterString = stringifyKueries(new Map(Object.entries(filters ?? {}))); + } + + let combinedString: string | undefined; + if (filterString && search) { + combinedString = combineFiltersAndUserSearch(filterString, search); + } else if (filterString) { + combinedString = filterString; + } else if (search) { + combinedString = search; + } + + return esKuery.toElasticsearchQuery( + esKuery.fromKueryExpression(combinedString ?? ''), + await getIndexPattern() + ); +}; + export const statusCheckAlertFactory: UptimeAlertTypeFactory = (_server, libs) => ({ id: 'xpack.uptime.alerts.monitorStatus', name: i18n.translate('xpack.uptime.alerts.monitorStatus', { @@ -131,13 +177,28 @@ export const statusCheckAlertFactory: UptimeAlertTypeFactory = (_server, libs) = }), validate: { params: schema.object({ - filters: schema.maybe(schema.string()), + filters: schema.maybe( + schema.oneOf([ + schema.object({ + 'monitor.type': schema.maybe(schema.arrayOf(schema.string())), + 'observer.geo.name': schema.maybe(schema.arrayOf(schema.string())), + tags: schema.maybe(schema.arrayOf(schema.string())), + 'url.port': schema.maybe(schema.arrayOf(schema.string())), + }), + schema.string(), + ]) + ), + locations: schema.maybe(schema.arrayOf(schema.string())), numTimes: schema.number(), - timerange: schema.object({ - from: schema.string(), - to: schema.string(), - }), - locations: schema.arrayOf(schema.string()), + search: schema.maybe(schema.string()), + timerangeCount: schema.maybe(schema.number()), + timerangeUnit: schema.maybe(schema.string()), + timerange: schema.maybe( + schema.object({ + from: schema.string(), + to: schema.string(), + }) + ), }), }, defaultActionGroupId: MONITOR_STATUS.id, @@ -174,18 +235,41 @@ export const statusCheckAlertFactory: UptimeAlertTypeFactory = (_server, libs) = producer: 'uptime', async executor(options: AlertExecutorOptions) { const { params: rawParams } = options; - const decoded = StatusCheckExecutorParamsType.decode(rawParams); - if (!isRight(decoded)) { + const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings( + options.services.savedObjectsClient + ); + const atomicDecoded = AtomicStatusCheckParamsType.decode(rawParams); + const decoded = StatusCheckParamsType.decode(rawParams); + let params: StatusCheckParams; + if (isRight(atomicDecoded)) { + const { filters, search, numTimes, timerangeCount, timerangeUnit } = atomicDecoded.right; + const timerange = { from: `now-${String(timerangeCount) + timerangeUnit}`, to: 'now' }; + const filterString = JSON.stringify( + await genFilterString( + () => + libs.requests.getIndexPattern({ + callES: options.services.callCluster, + dynamicSettings, + }), + filters, + search + ) + ); + params = { + timerange, + numTimes, + locations: [], + filters: filterString, + }; + } else if (isRight(decoded)) { + params = decoded.right; + } else { ThrowReporter.report(decoded); return { error: 'Alert param types do not conform to required shape.', }; } - const params = decoded.right; - const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings( - options.services.savedObjectsClient - ); /* This is called `monitorsByLocation` but it's really * monitors by location by status. The query we run to generate this * filters on the status field, so effectively there should be one and only one diff --git a/x-pack/plugins/uptime/server/lib/requests/get_index_pattern.ts b/x-pack/plugins/uptime/server/lib/requests/get_index_pattern.ts index 7902d9a5c853..7b08752f1224 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_index_pattern.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_index_pattern.ts @@ -8,7 +8,7 @@ import { APICaller, CallAPIOptions } from 'src/core/server'; import { UMElasticsearchQueryFn } from '../adapters'; import { IndexPatternsFetcher, IIndexPattern } from '../../../../../../src/plugins/data/server'; -export const getUptimeIndexPattern: UMElasticsearchQueryFn<{}, {}> = async ({ +export const getUptimeIndexPattern: UMElasticsearchQueryFn<{}, IIndexPattern | undefined> = async ({ callES, dynamicSettings, }) => { diff --git a/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts b/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts index 367db924cf1c..85fc2c3ef977 100644 --- a/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts +++ b/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts @@ -33,13 +33,14 @@ import { } from '.'; import { GetMonitorStatesResult } from './get_monitor_states'; import { GetSnapshotCountParams } from './get_snapshot_counts'; +import { IIndexPattern } from '../../../../../../src/plugins/data/server'; type ESQ = UMElasticsearchQueryFn; export interface UptimeRequests { getCerts: ESQ; getFilterBar: ESQ; - getIndexPattern: ESQ<{}, {}>; + getIndexPattern: ESQ<{}, IIndexPattern | undefined>; getLatestMonitor: ESQ; getMonitorDurationChart: ESQ; getMonitorDetails: ESQ; From 08974187957c3cd5640ec20e47dbf20dabd6fe01 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Wed, 3 Jun 2020 15:23:54 +0100 Subject: [PATCH 48/50] [ML] Adding per_partition_categorization to job interface and schema (#67953) * [ML] Adding per_partition_categorization to job interface and schema * adding new fields to Category interface --- .../plugins/ml/common/types/anomaly_detection_jobs/job.ts | 6 ++++++ x-pack/plugins/ml/common/types/categories.ts | 2 ++ .../ml/server/routes/schemas/anomaly_detectors_schema.ts | 6 ++++++ 3 files changed, 14 insertions(+) diff --git a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts index bc55c7549c58..c75387a4b410 100644 --- a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts +++ b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts @@ -50,6 +50,7 @@ export interface AnalysisConfig { latency?: number; multivariate_by_fields?: boolean; summary_count_field_name?: string; + per_partition_categorization?: PerPartitionCategorization; } export interface Detector { @@ -86,3 +87,8 @@ export interface CustomRule { scope?: object; conditions: any[]; } + +export interface PerPartitionCategorization { + enabled: boolean; + stop_on_warn?: boolean; +} diff --git a/x-pack/plugins/ml/common/types/categories.ts b/x-pack/plugins/ml/common/types/categories.ts index 5d4c3eab53ee..b3655f274b36 100644 --- a/x-pack/plugins/ml/common/types/categories.ts +++ b/x-pack/plugins/ml/common/types/categories.ts @@ -16,6 +16,8 @@ export interface Category { max_matching_length: number; examples: string[]; grok_pattern: string; + partition_field_name?: string; // TODO: make non-optional once fields have been added to the results + partition_field_value?: string; // TODO: make non-optional once fields have been added to the results } export interface Token { diff --git a/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts index 88b86de322e3..de393e002c55 100644 --- a/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts @@ -77,6 +77,12 @@ export const analysisConfigSchema = schema.object({ detectors: schema.arrayOf(detectorSchema), influencers: schema.arrayOf(schema.maybe(schema.string())), categorization_field_name: schema.maybe(schema.string()), + per_partition_categorization: schema.maybe( + schema.object({ + enabled: schema.boolean(), + stop_on_warn: schema.maybe(schema.boolean()), + }) + ), }); export const anomalyDetectionJobSchema = { From c718afcbba114fe274d762fd3fef32f5d19f6bba Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Wed, 3 Jun 2020 15:24:13 +0100 Subject: [PATCH 49/50] [ML] Fix expanded row bug when job has no datafeed (#68074) --- .../jobs_list/components/job_details/extract_job_details.js | 2 +- .../jobs/jobs_list/components/job_details/job_details.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js index 50e5aeeb29dd..8f89c4a04918 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js @@ -133,7 +133,7 @@ export function extractJobDetails(job) { defaultMessage: 'Datafeed', }), position: 'left', - items: filterObjects(job.datafeed_config, true, true), + items: filterObjects(job.datafeed_config || {}, true, true), }; if (job.node) { datafeed.items.push(['node', JSON.stringify(job.node)]); diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js index 0375997b86bb..56da4f1e0ff8 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js @@ -125,7 +125,7 @@ export class JobDetails extends Component { }, ]; - if (showFullDetails) { + if (showFullDetails && datafeed.items.length) { // Datafeed should be at index 2 in tabs array for full details tabs.splice(2, 0, { id: 'datafeed', From d7b830a6bfba6d98206779630fd101cf3f1a0ecf Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Wed, 3 Jun 2020 17:42:11 +0300 Subject: [PATCH 50/50] Kibana 7.7.0: Chart split orientation is wrong (#67840) Co-authored-by: Elastic Machine --- .../visualization_migrations.test.ts | 52 ++++++++++++++----- .../saved_objects/visualization_migrations.ts | 46 +++++++++++++++- 2 files changed, 84 insertions(+), 14 deletions(-) diff --git a/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts b/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts index 83d53d27e41f..d27d021465dc 100644 --- a/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts +++ b/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts @@ -623,12 +623,12 @@ describe('migration visualization', () => { { id: '2', schema: 'split', - params: { foo: 'bar', row: true }, + params: { foo: 'bar' }, }, { id: '3', schema: 'split', - params: { hey: 'ya', row: false }, + params: { hey: 'ya' }, }, ]; const tableDoc = generateDoc('table', aggs); @@ -656,7 +656,7 @@ describe('migration visualization', () => { { id: '2', schema: 'split', - params: { foo: 'bar', row: true }, + params: { foo: 'bar' }, }, { id: '3', @@ -681,7 +681,7 @@ describe('migration visualization', () => { { id: '2', schema: 'split', - params: { foo: 'bar', row: true }, + params: { foo: 'bar' }, }, ]; const tableDoc = generateDoc('table', aggs); @@ -701,12 +701,12 @@ describe('migration visualization', () => { { id: '2', schema: 'split', - params: { foo: 'bar', row: true }, + params: { foo: 'bar' }, }, { id: '3', schema: 'split', - params: { hey: 'ya', row: false }, + params: { hey: 'ya' }, }, { id: '4', @@ -731,15 +731,15 @@ describe('migration visualization', () => { { id: '2', schema: 'split', - params: { foo: 'bar', row: true }, + params: { foo: 'bar' }, }, { id: '3', schema: 'split', - params: { hey: 'ya', row: false }, + params: { hey: 'ya' }, }, ]; - const expected = [{}, { foo: 'bar', row: true }, { hey: 'ya' }]; + const expected = [{}, { foo: 'bar' }, { hey: 'ya' }]; const migrated = migrate(generateDoc('table', aggs)); const actual = JSON.parse(migrated.attributes.visState); @@ -1386,11 +1386,11 @@ describe('migration visualization', () => { doc as Parameters[0], savedObjectMigrationContext ); - const generateDoc = (params: any) => ({ + const generateDoc = (visState: any) => ({ attributes: { title: 'My Vis', description: 'This is my super cool vis.', - visState: JSON.stringify({ params }), + visState: JSON.stringify(visState), uiStateJSON: '{}', version: 1, kibanaSavedObjectMeta: { @@ -1416,7 +1416,7 @@ describe('migration visualization', () => { }, ], }; - const timeSeriesDoc = generateDoc(params); + const timeSeriesDoc = generateDoc({ params }); const migratedtimeSeriesDoc = migrate(timeSeriesDoc); const migratedParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; @@ -1453,12 +1453,38 @@ describe('migration visualization', () => { }, ], }; - const timeSeriesDoc = generateDoc(params); + const timeSeriesDoc = generateDoc({ params }); const migratedtimeSeriesDoc = migrate(timeSeriesDoc); const migratedParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; expect(migratedParams.gauge_color_rules[1]).toEqual(params.gauge_color_rules[1]); }); + + it('should move "row" field on split chart by a row or column to vis.params', () => { + const visData = { + type: 'area', + aggs: [ + { + id: '1', + schema: 'metric', + params: {}, + }, + { + id: '2', + type: 'terms', + schema: 'split', + params: { foo: 'bar', row: true }, + }, + ], + params: {}, + }; + + const migrated = migrate(generateDoc(visData)); + const actual = JSON.parse(migrated.attributes.visState); + + expect(actual.aggs.filter((agg: any) => 'row' in agg.params)).toEqual([]); + expect(actual.params.row).toBeTruthy(); + }); }); describe('7.8.0 tsvb split_color_mode', () => { diff --git a/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts index 71b286cd91a5..27fe722019a2 100644 --- a/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts +++ b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts @@ -131,6 +131,50 @@ const migrateOperatorKeyTypo: SavedObjectMigrationFn = (doc) => { return doc; }; +/** + * Moving setting wether to do a row or column split to vis.params + * + * @see https://github.com/elastic/kibana/pull/58462/files#diff-ae69fe15b20a5099d038e9bbe2ed3849 + **/ +const migrateSplitByChartRow: SavedObjectMigrationFn = (doc) => { + const visStateJSON = get(doc, 'attributes.visState'); + let visState: any; + + if (visStateJSON) { + try { + visState = JSON.parse(visStateJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + } + + if (visState && visState.aggs && visState.params) { + let row: boolean | undefined; + + visState.aggs.forEach((agg: any) => { + if (agg.type === 'terms' && agg.schema === 'split' && 'row' in agg.params) { + row = agg.params.row; + + delete agg.params.row; + } + }); + + if (row !== undefined) { + visState.params.row = row; + } + + return { + ...doc, + attributes: { + ...doc.attributes, + visState: JSON.stringify(visState), + }, + }; + } + } + + return doc; +}; + // Migrate date histogram aggregation (remove customInterval) const migrateDateHistogramAggregation: SavedObjectMigrationFn = (doc) => { const visStateJSON = get(doc, 'attributes.visState'); @@ -673,6 +717,6 @@ export const visualizationSavedObjectTypeMigrations = { ), '7.3.1': flow>(migrateFiltersAggQueryStringQueries), '7.4.2': flow>(transformSplitFiltersStringToQueryObject), - '7.7.0': flow>(migrateOperatorKeyTypo), + '7.7.0': flow>(migrateOperatorKeyTypo, migrateSplitByChartRow), '7.8.0': flow>(migrateTsvbDefaultColorPalettes), };