| AuthenticatedUser | null>;
+export type RequestHandlerUser = RequestHandler extends (...a: infer U) => infer R
+ ? (user: ReportingUser, ...a: U) => R
+ : never;
export const authorizedUserPreRoutingFactory = function authorizedUserPreRoutingFn(
- config: ReportingConfig,
- plugins: ReportingSetupDeps,
- logger: Logger
+ reporting: ReportingCore
) {
- const getUser = getUserFactory(plugins.security, logger);
- const { info: xpackInfo } = plugins.__LEGACY.plugins.xpack_main;
-
- return async function authorizedUserPreRouting(request: Legacy.Request) {
- if (!xpackInfo || !xpackInfo.isAvailable()) {
- logger.warn('Unable to authorize user before xpack info is available.', [
- 'authorizedUserPreRouting',
- ]);
- return Boom.notFound();
- }
-
- const security = xpackInfo.feature('security');
- if (!security.isEnabled() || !security.isAvailable()) {
- return null;
- }
-
- const user = await getUser(request);
-
- if (!user) {
- return Boom.unauthorized(`Sorry, you aren't authenticated`);
- }
-
- const authorizedRoles = [superuserRole, ...(config.get('roles', 'allow') as string[])];
- if (!user.roles.find((role) => authorizedRoles.includes(role))) {
- return Boom.forbidden(`Sorry, you don't have access to Reporting`);
- }
-
- return user;
+ const config = reporting.getConfig();
+ const setupDeps = reporting.getPluginSetupDeps();
+ const getUser = getUserFactory(setupDeps.security);
+ return (handler: RequestHandlerUser): RequestHandler
=> {
+ return (context, req, res) => {
+ let user: ReportingUser = null;
+ if (setupDeps.security) {
+ // find the authenticated user, or null if security is not enabled
+ user = getUser(req);
+ if (!user) {
+ // security is enabled but the user is null
+ return res.unauthorized({ body: `Sorry, you aren't authenticated` });
+ }
+ }
+
+ if (user) {
+ // check allowance with the configured set of roleas + "superuser"
+ const allowedRoles = config.get('roles', 'allow') || [];
+ const authorizedRoles = [superuserRole, ...allowedRoles];
+
+ if (!user.roles.find((role) => authorizedRoles.includes(role))) {
+ // user's roles do not allow
+ return res.forbidden({ body: `Sorry, you don't have access to Reporting` });
+ }
+ }
+
+ return handler(user, context, req, res);
+ };
};
};
diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts
index 6a228c1915615..e16f5278c8cc7 100644
--- a/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts
+++ b/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts
@@ -12,15 +12,10 @@ import { statuses } from '../../lib/esqueue/constants/statuses';
import { ExportTypesRegistry } from '../../lib/export_types_registry';
import { ExportTypeDefinition, JobDocOutput, JobSource } from '../../types';
-interface ICustomHeaders {
- [x: string]: any;
-}
-
type ExportTypeType = ExportTypeDefinition;
interface ErrorFromPayload {
message: string;
- reason: string | null;
}
// A camelCase version of JobDocOutput
@@ -37,7 +32,7 @@ const getTitle = (exportType: ExportTypeType, title?: string): string =>
`${title || DEFAULT_TITLE}.${exportType.jobContentExtension}`;
const getReportingHeaders = (output: JobDocOutput, exportType: ExportTypeType) => {
- const metaDataHeaders: ICustomHeaders = {};
+ const metaDataHeaders: Record = {};
if (exportType.jobType === CSV_JOB_TYPE) {
const csvContainsFormulas = _.get(output, 'csv_contains_formulas', false);
@@ -76,12 +71,13 @@ export function getDocumentPayloadFactory(exportTypesRegistry: ExportTypesRegist
};
}
+ // @TODO: These should be semantic HTTP codes as 500/503's indicate
+ // error then these are really operating properly.
function getFailure(output: JobDocOutput): Payload {
return {
statusCode: 500,
content: {
- message: 'Reporting generation failed',
- reason: output.content,
+ message: `Reporting generation failed: ${output.content}`,
},
contentType: 'application/json',
headers: {},
@@ -92,7 +88,7 @@ export function getDocumentPayloadFactory(exportTypesRegistry: ExportTypesRegist
return {
statusCode: 503,
content: status,
- contentType: 'application/json',
+ contentType: 'text/plain',
headers: { 'retry-after': 30 },
};
}
diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.ts
index 174ec15c81d8a..990af2d0aca80 100644
--- a/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.ts
+++ b/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.ts
@@ -4,9 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import Boom from 'boom';
-import { ResponseToolkit } from 'hapi';
-import { ElasticsearchServiceSetup } from 'kibana/server';
+import { ElasticsearchServiceSetup, kibanaResponseFactory } from 'kibana/server';
+import { AuthenticatedUser } from '../../../../../../plugins/security/server';
import { ReportingConfig } from '../../';
import { WHITELISTED_JOB_CONTENT_TYPES } from '../../../common/constants';
import { ExportTypesRegistry } from '../../lib/export_types_registry';
@@ -29,40 +28,43 @@ export function downloadJobResponseHandlerFactory(
const jobsQuery = jobsQueryFactory(config, elasticsearch);
const getDocumentPayload = getDocumentPayloadFactory(exportTypesRegistry);
- return function jobResponseHandler(
+ return async function jobResponseHandler(
+ res: typeof kibanaResponseFactory,
validJobTypes: string[],
- user: any,
- h: ResponseToolkit,
+ user: AuthenticatedUser | null,
params: JobResponseHandlerParams,
opts: JobResponseHandlerOpts = {}
) {
const { docId } = params;
- // TODO: async/await
- return jobsQuery.get(user, docId, { includeContent: !opts.excludeContent }).then((doc) => {
- if (!doc) return Boom.notFound();
- const { jobtype: jobType } = doc._source;
- if (!validJobTypes.includes(jobType)) {
- return Boom.unauthorized(`Sorry, you are not authorized to download ${jobType} reports`);
- }
+ const doc = await jobsQuery.get(user, docId, { includeContent: !opts.excludeContent });
+ if (!doc) {
+ return res.notFound();
+ }
- const output = getDocumentPayload(doc);
+ const { jobtype: jobType } = doc._source;
- if (!WHITELISTED_JOB_CONTENT_TYPES.includes(output.contentType)) {
- return Boom.badImplementation(
- `Unsupported content-type of ${output.contentType} specified by job output`
- );
- }
+ if (!validJobTypes.includes(jobType)) {
+ return res.unauthorized({
+ body: `Sorry, you are not authorized to download ${jobType} reports`,
+ });
+ }
- const response = h.response(output.content).type(output.contentType).code(output.statusCode);
+ const response = getDocumentPayload(doc);
- if (output.headers) {
- Object.keys(output.headers).forEach((key) => {
- response.header(key, output.headers[key]);
- });
- }
+ if (!WHITELISTED_JOB_CONTENT_TYPES.includes(response.contentType)) {
+ return res.badRequest({
+ body: `Unsupported content-type of ${response.contentType} specified by job output`,
+ });
+ }
- return response; // Hapi
+ return res.custom({
+ body: typeof response.content === 'string' ? Buffer.from(response.content) : response.content,
+ statusCode: response.statusCode,
+ headers: {
+ ...response.headers,
+ 'content-type': response.contentType,
+ },
});
};
}
@@ -74,26 +76,37 @@ export function deleteJobResponseHandlerFactory(
const jobsQuery = jobsQueryFactory(config, elasticsearch);
return async function deleteJobResponseHander(
+ res: typeof kibanaResponseFactory,
validJobTypes: string[],
- user: any,
- h: ResponseToolkit,
+ user: AuthenticatedUser | null,
params: JobResponseHandlerParams
) {
const { docId } = params;
const doc = await jobsQuery.get(user, docId, { includeContent: false });
- if (!doc) return Boom.notFound();
+
+ if (!doc) {
+ return res.notFound();
+ }
const { jobtype: jobType } = doc._source;
+
if (!validJobTypes.includes(jobType)) {
- return Boom.unauthorized(`Sorry, you are not authorized to delete ${jobType} reports`);
+ return res.unauthorized({
+ body: `Sorry, you are not authorized to delete ${jobType} reports`,
+ });
}
try {
const docIndex = doc._index;
await jobsQuery.delete(docIndex, docId);
- return h.response({ deleted: true });
+ return res.ok({
+ body: { deleted: true },
+ });
} catch (error) {
- return Boom.boomify(error, { statusCode: error.statusCode });
+ return res.customError({
+ statusCode: error.statusCode,
+ body: error.message,
+ });
}
};
}
diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.test.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.test.ts
deleted file mode 100644
index 8cdb7b4c018d7..0000000000000
--- a/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.test.ts
+++ /dev/null
@@ -1,62 +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 { Legacy } from 'kibana';
-import { makeRequestFacade } from './make_request_facade';
-
-describe('makeRequestFacade', () => {
- test('creates a default object', () => {
- const legacyRequest = ({
- getBasePath: () => 'basebase',
- params: {
- param1: 123,
- },
- payload: {
- payload1: 123,
- },
- headers: {
- user: 123,
- },
- } as unknown) as Legacy.Request;
-
- expect(makeRequestFacade(legacyRequest)).toMatchInlineSnapshot(`
- Object {
- "getBasePath": [Function],
- "getRawRequest": [Function],
- "getSavedObjectsClient": undefined,
- "headers": Object {
- "user": 123,
- },
- "params": Object {
- "param1": 123,
- },
- "payload": Object {
- "payload1": 123,
- },
- "pre": undefined,
- "query": undefined,
- "route": undefined,
- }
- `);
- });
-
- test('getRawRequest', () => {
- const legacyRequest = ({
- getBasePath: () => 'basebase',
- params: {
- param1: 123,
- },
- payload: {
- payload1: 123,
- },
- headers: {
- user: 123,
- },
- } as unknown) as Legacy.Request;
-
- expect(makeRequestFacade(legacyRequest).getRawRequest()).toBe(legacyRequest);
- });
-});
diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.ts
deleted file mode 100644
index 5dd62711f2565..0000000000000
--- a/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.ts
+++ /dev/null
@@ -1,32 +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 { RequestQuery } from 'hapi';
-import { Legacy } from 'kibana';
-import {
- RequestFacade,
- ReportingRequestPayload,
- ReportingRequestPre,
- ReportingRequestQuery,
-} from '../../../server/types';
-
-export function makeRequestFacade(request: Legacy.Request): RequestFacade {
- // This condition is for unit tests
- const getSavedObjectsClient = request.getSavedObjectsClient
- ? request.getSavedObjectsClient.bind(request)
- : request.getSavedObjectsClient;
- return {
- getSavedObjectsClient,
- headers: request.headers,
- params: request.params,
- payload: (request.payload as object) as ReportingRequestPayload,
- query: ((request.query as RequestQuery) as object) as ReportingRequestQuery,
- pre: (request.pre as Record) as ReportingRequestPre,
- getBasePath: request.getBasePath,
- route: request.route,
- getRawRequest: () => request,
- };
-}
diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.ts
deleted file mode 100644
index f9c7571e25bac..0000000000000
--- a/x-pack/legacy/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.ts
+++ /dev/null
@@ -1,36 +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 Boom from 'boom';
-import { Legacy } from 'kibana';
-import { ReportingConfig } from '../../';
-import { LevelLogger as Logger } from '../../lib';
-import { ReportingSetupDeps } from '../../types';
-
-export type GetReportingFeatureIdFn = (request: Legacy.Request) => string;
-
-export const reportingFeaturePreRoutingFactory = function reportingFeaturePreRoutingFn(
- config: ReportingConfig,
- plugins: ReportingSetupDeps,
- logger: Logger
-) {
- const xpackMainPlugin = plugins.__LEGACY.plugins.xpack_main;
- const pluginId = 'reporting';
-
- // License checking and enable/disable logic
- return function reportingFeaturePreRouting(getReportingFeatureId: GetReportingFeatureIdFn) {
- return function licensePreRouting(request: Legacy.Request) {
- const licenseCheckResults = xpackMainPlugin.info.feature(pluginId).getLicenseCheckResults();
- const reportingFeatureId = getReportingFeatureId(request) as string;
- const reportingFeature = licenseCheckResults[reportingFeatureId];
- if (!reportingFeature.showLinks || !reportingFeature.enableLinks) {
- throw Boom.forbidden(reportingFeature.message);
- } else {
- return reportingFeature;
- }
- };
- };
-};
diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/route_config_factories.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/route_config_factories.ts
deleted file mode 100644
index 0ee9db4678684..0000000000000
--- a/x-pack/legacy/plugins/reporting/server/routes/lib/route_config_factories.ts
+++ /dev/null
@@ -1,130 +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 Joi from 'joi';
-import { ReportingConfig } from '../../';
-import { LevelLogger as Logger } from '../../lib';
-import { CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../common/constants';
-import { ReportingSetupDeps } from '../../types';
-import { authorizedUserPreRoutingFactory } from './authorized_user_pre_routing';
-import {
- GetReportingFeatureIdFn,
- reportingFeaturePreRoutingFactory,
-} from './reporting_feature_pre_routing';
-
-const API_TAG = 'api';
-
-export interface RouteConfigFactory {
- tags?: string[];
- pre: any[];
- response?: {
- ranges: boolean;
- };
-}
-
-export type GetRouteConfigFactoryFn = (
- getFeatureId?: GetReportingFeatureIdFn
-) => RouteConfigFactory;
-
-export function getRouteConfigFactoryReportingPre(
- config: ReportingConfig,
- plugins: ReportingSetupDeps,
- logger: Logger
-): GetRouteConfigFactoryFn {
- const authorizedUserPreRouting = authorizedUserPreRoutingFactory(config, plugins, logger);
- const reportingFeaturePreRouting = reportingFeaturePreRoutingFactory(config, plugins, logger);
-
- return (getFeatureId?: GetReportingFeatureIdFn): RouteConfigFactory => {
- const preRouting: any[] = [{ method: authorizedUserPreRouting, assign: 'user' }];
- if (getFeatureId) {
- preRouting.push(reportingFeaturePreRouting(getFeatureId));
- }
-
- return {
- tags: [API_TAG],
- pre: preRouting,
- };
- };
-}
-
-export function getRouteOptionsCsv(
- config: ReportingConfig,
- plugins: ReportingSetupDeps,
- logger: Logger
-) {
- const getRouteConfig = getRouteConfigFactoryReportingPre(config, plugins, logger);
- return {
- ...getRouteConfig(() => CSV_FROM_SAVEDOBJECT_JOB_TYPE),
- validate: {
- params: Joi.object({
- savedObjectType: Joi.string().required(),
- savedObjectId: Joi.string().required(),
- }).required(),
- payload: Joi.object({
- state: Joi.object().default({}),
- timerange: Joi.object({
- timezone: Joi.string().default('UTC'),
- min: Joi.date().required(),
- max: Joi.date().required(),
- }).optional(),
- }),
- },
- };
-}
-
-export function getRouteConfigFactoryManagementPre(
- config: ReportingConfig,
- plugins: ReportingSetupDeps,
- logger: Logger
-): GetRouteConfigFactoryFn {
- const authorizedUserPreRouting = authorizedUserPreRoutingFactory(config, plugins, logger);
- const reportingFeaturePreRouting = reportingFeaturePreRoutingFactory(config, plugins, logger);
- const managementPreRouting = reportingFeaturePreRouting(() => 'management');
-
- return (): RouteConfigFactory => {
- return {
- pre: [
- { method: authorizedUserPreRouting, assign: 'user' },
- { method: managementPreRouting, assign: 'management' },
- ],
- };
- };
-}
-
-// NOTE: We're disabling range request for downloading the PDF. There's a bug in Firefox's PDF.js viewer
-// (https://github.com/mozilla/pdf.js/issues/8958) where they're using a range request to retrieve the
-// TOC at the end of the PDF, but it's sending multiple cookies and causing our auth to fail with a 401.
-// Additionally, the range-request doesn't alleviate any performance issues on the server as the entire
-// download is loaded into memory.
-export function getRouteConfigFactoryDownloadPre(
- config: ReportingConfig,
- plugins: ReportingSetupDeps,
- logger: Logger
-): GetRouteConfigFactoryFn {
- const getManagementRouteConfig = getRouteConfigFactoryManagementPre(config, plugins, logger);
- return (): RouteConfigFactory => ({
- ...getManagementRouteConfig(),
- tags: [API_TAG, 'download'],
- response: {
- ranges: false,
- },
- });
-}
-
-export function getRouteConfigFactoryDeletePre(
- config: ReportingConfig,
- plugins: ReportingSetupDeps,
- logger: Logger
-): GetRouteConfigFactoryFn {
- const getManagementRouteConfig = getRouteConfigFactoryManagementPre(config, plugins, logger);
- return (): RouteConfigFactory => ({
- ...getManagementRouteConfig(),
- tags: [API_TAG, 'delete'],
- response: {
- ranges: false,
- },
- });
-}
diff --git a/x-pack/legacy/plugins/reporting/server/routes/types.d.ts b/x-pack/legacy/plugins/reporting/server/routes/types.d.ts
index 2ebe1ada418dc..afa3fd3358fc1 100644
--- a/x-pack/legacy/plugins/reporting/server/routes/types.d.ts
+++ b/x-pack/legacy/plugins/reporting/server/routes/types.d.ts
@@ -4,17 +4,20 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { Legacy } from 'kibana';
+import { KibanaResponseFactory, KibanaRequest, RequestHandlerContext } from 'src/core/server';
+import { AuthenticatedUser } from '../../../../../plugins/security/common/model/authenticated_user';
import { JobDocPayload } from '../types';
export type HandlerFunction = (
+ user: AuthenticatedUser | null,
exportType: string,
jobParams: object,
- request: Legacy.Request,
- h: ReportingResponseToolkit
+ context: RequestHandlerContext,
+ req: KibanaRequest,
+ res: KibanaResponseFactory
) => any;
-export type HandlerErrorFunction = (exportType: string, err: Error) => any;
+export type HandlerErrorFunction = (res: KibanaResponseFactory, err: Error) => any;
export interface QueuedJobPayload {
error?: boolean;
@@ -24,5 +27,3 @@ export interface QueuedJobPayload {
};
};
}
-
-export type ReportingResponseToolkit = Legacy.ResponseToolkit;
diff --git a/x-pack/legacy/plugins/reporting/server/types.ts b/x-pack/legacy/plugins/reporting/server/types.ts
index bfab568fe9fb3..2ccc209c3ce50 100644
--- a/x-pack/legacy/plugins/reporting/server/types.ts
+++ b/x-pack/legacy/plugins/reporting/server/types.ts
@@ -5,6 +5,7 @@
*/
import { Legacy } from 'kibana';
+import { KibanaRequest, RequestHandlerContext } from 'src/core/server';
import { ElasticsearchServiceSetup } from 'kibana/server';
import * as Rx from 'rxjs';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
@@ -53,8 +54,8 @@ export type ReportingRequestPayload = GenerateExportTypePayload | JobParamPostPa
export interface TimeRangeParams {
timezone: string;
- min: Date | string | number;
- max: Date | string | number;
+ min: Date | string | number | null;
+ max: Date | string | number | null;
}
export interface JobParamPostPayload {
@@ -189,22 +190,10 @@ export interface LegacySetup {
* Internal Types
*/
-export interface RequestFacade {
- getBasePath: Legacy.Request['getBasePath'];
- getSavedObjectsClient: Legacy.Request['getSavedObjectsClient'];
- headers: Legacy.Request['headers'];
- params: Legacy.Request['params'];
- payload: JobParamPostPayload | GenerateExportTypePayload;
- query: ReportingRequestQuery;
- route: Legacy.Request['route'];
- pre: ReportingRequestPre;
- getRawRequest: () => Legacy.Request;
-}
-
export type ESQueueCreateJobFn = (
jobParams: JobParamsType,
- headers: Record,
- request: RequestFacade
+ context: RequestHandlerContext,
+ request: KibanaRequest
) => Promise;
export type ESQueueWorkerExecuteFn = (
diff --git a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts
index 286e072f1f8f6..f6dbccdfe3980 100644
--- a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts
+++ b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_reportingplugin.ts
@@ -12,17 +12,21 @@ jest.mock('../server/lib/create_queue');
jest.mock('../server/lib/enqueue_job');
jest.mock('../server/lib/validate');
+import { of } from 'rxjs';
import { EventEmitter } from 'events';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { coreMock } from 'src/core/server/mocks';
import { ReportingConfig, ReportingCore, ReportingPlugin } from '../server';
import { ReportingSetupDeps, ReportingStartDeps } from '../server/types';
+import { ReportingInternalSetup } from '../server/core';
const createMockSetupDeps = (setupMock?: any): ReportingSetupDeps => {
return {
elasticsearch: setupMock.elasticsearch,
security: setupMock.security,
- licensing: {} as any,
+ licensing: {
+ license$: of({ isAvailable: true, isActive: true, type: 'basic' }),
+ } as any,
usageCollection: {} as any,
__LEGACY: { plugins: { xpack_main: { status: new EventEmitter() } } } as any,
};
@@ -49,8 +53,18 @@ const createMockReportingPlugin = async (config: ReportingConfig): Promise => {
+export const createMockReportingCore = async (
+ config: ReportingConfig,
+ setupDepsMock?: ReportingInternalSetup
+): Promise => {
config = config || {};
const plugin = await createMockReportingPlugin(config);
- return plugin.getReportingCore();
+ const core = plugin.getReportingCore();
+
+ if (setupDepsMock) {
+ // @ts-ignore overwriting private properties
+ core.pluginSetupDeps = setupDepsMock;
+ }
+
+ return core;
};
diff --git a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_server.ts b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_server.ts
index 819636b714631..01b9f6cbd9cd6 100644
--- a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_server.ts
+++ b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_server.ts
@@ -4,9 +4,32 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { ServerFacade } from '../server/types';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { createHttpServer, createCoreContext } from 'src/core/server/http/test_utils';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { coreMock } from 'src/core/server/mocks';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { ContextService } from 'src/core/server/context/context_service';
-export const createMockServer = (): ServerFacade => {
- const mockServer = {};
- return mockServer as any;
+const coreId = Symbol('reporting');
+
+export const createMockServer = async () => {
+ const coreContext = createCoreContext({ coreId });
+ const contextService = new ContextService(coreContext);
+
+ const server = createHttpServer(coreContext);
+ const httpSetup = await server.setup({
+ context: contextService.setup({ pluginDependencies: new Map() }),
+ });
+ const handlerContext = coreMock.createRequestHandlerContext();
+
+ httpSetup.registerRouteHandlerContext(coreId, 'core', async (ctx, req, res) => {
+ return handlerContext;
+ });
+
+ return {
+ server,
+ httpSetup,
+ handlerContext,
+ };
};
diff --git a/x-pack/legacy/plugins/spaces/index.ts b/x-pack/legacy/plugins/spaces/index.ts
index 2f3e5e0a86d21..79c57e564b4e1 100644
--- a/x-pack/legacy/plugins/spaces/index.ts
+++ b/x-pack/legacy/plugins/spaces/index.ts
@@ -17,7 +17,13 @@ export const spaces = (kibana: Record) =>
configPrefix: 'xpack.spaces',
publicDir: resolve(__dirname, 'public'),
require: ['kibana', 'elasticsearch', 'xpack_main'],
-
+ config(Joi: any) {
+ return Joi.object({
+ enabled: Joi.boolean().default(true),
+ })
+ .unknown()
+ .default();
+ },
uiExports: {
managementSections: [],
apps: [],
diff --git a/x-pack/plugins/actions/README.md b/x-pack/plugins/actions/README.md
index 847172ae972fd..96d5f04ac088f 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/actions/server/actions_client.mock.ts b/x-pack/plugins/actions/server/actions_client.mock.ts
index 64b43e1ab6bbc..a2b64e49f76e3 100644
--- a/x-pack/plugins/actions/server/actions_client.mock.ts
+++ b/x-pack/plugins/actions/server/actions_client.mock.ts
@@ -16,6 +16,7 @@ const createActionsClientMock = () => {
delete: jest.fn(),
update: jest.fn(),
getAll: jest.fn(),
+ getBulk: jest.fn(),
};
return mocked;
};
diff --git a/x-pack/plugins/actions/server/actions_client.test.ts b/x-pack/plugins/actions/server/actions_client.test.ts
index 0132cc8bdb01a..bf55a1c18d169 100644
--- a/x-pack/plugins/actions/server/actions_client.test.ts
+++ b/x-pack/plugins/actions/server/actions_client.test.ts
@@ -423,6 +423,74 @@ describe('getAll()', () => {
});
});
+describe('getBulk()', () => {
+ test('calls getBulk savedObjectsClient with parameters', async () => {
+ savedObjectsClient.bulkGet.mockResolvedValueOnce({
+ saved_objects: [
+ {
+ id: '1',
+ type: 'action',
+ attributes: {
+ actionTypeId: 'test',
+ name: 'test',
+ config: {
+ foo: 'bar',
+ },
+ },
+ references: [],
+ },
+ ],
+ });
+ scopedClusterClient.callAsInternalUser.mockResolvedValueOnce({
+ aggregations: {
+ '1': { doc_count: 6 },
+ testPreconfigured: { doc_count: 2 },
+ },
+ });
+
+ actionsClient = new ActionsClient({
+ actionTypeRegistry,
+ savedObjectsClient,
+ scopedClusterClient,
+ defaultKibanaIndex,
+ preconfiguredActions: [
+ {
+ id: 'testPreconfigured',
+ actionTypeId: '.slack',
+ secrets: {},
+ isPreconfigured: true,
+ name: 'test',
+ config: {
+ foo: 'bar',
+ },
+ },
+ ],
+ });
+ const result = await actionsClient.getBulk(['1', 'testPreconfigured']);
+ expect(result).toEqual([
+ {
+ actionTypeId: '.slack',
+ config: {
+ foo: 'bar',
+ },
+ id: 'testPreconfigured',
+ isPreconfigured: true,
+ name: 'test',
+ secrets: {},
+ },
+ {
+ actionTypeId: 'test',
+ config: {
+ foo: 'bar',
+ },
+ id: '1',
+ isPreconfigured: false,
+ name: 'test',
+ },
+ ]);
+ });
+});
+
describe('delete()', () => {
test('calls savedObjectsClient with id', async () => {
const expectedResult = Symbol();
diff --git a/x-pack/plugins/actions/server/actions_client.ts b/x-pack/plugins/actions/server/actions_client.ts
index c9052cf53d948..48703f01f5509 100644
--- a/x-pack/plugins/actions/server/actions_client.ts
+++ b/x-pack/plugins/actions/server/actions_client.ts
@@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-
+import Boom from 'boom';
import {
IScopedClusterClient,
SavedObjectsClientContract,
@@ -193,6 +193,44 @@ export class ActionsClient {
);
}
+ /**
+ * Get bulk actions with preconfigured list
+ */
+ public async getBulk(ids: string[]): Promise {
+ const actionResults = new Array();
+ for (const actionId of ids) {
+ const action = this.preconfiguredActions.find(
+ (preconfiguredAction) => preconfiguredAction.id === actionId
+ );
+ if (action !== undefined) {
+ actionResults.push(action);
+ }
+ }
+
+ // Fetch action objects in bulk
+ // Excluding preconfigured actions to avoid an not found error, which is already added
+ const actionSavedObjectsIds = [
+ ...new Set(
+ ids.filter(
+ (actionId) => !actionResults.find((actionResult) => actionResult.id === actionId)
+ )
+ ),
+ ];
+
+ const bulkGetOpts = actionSavedObjectsIds.map((id) => ({ id, type: 'action' }));
+ const bulkGetResult = await this.savedObjectsClient.bulkGet(bulkGetOpts);
+
+ for (const action of bulkGetResult.saved_objects) {
+ if (action.error) {
+ throw Boom.badRequest(
+ `Failed to load action ${action.id} (${action.error.statusCode}): ${action.error.message}`
+ );
+ }
+ actionResults.push(actionFromSavedObject(action));
+ }
+ return actionResults;
+ }
+
/**
* Delete action
*/
diff --git a/x-pack/plugins/alerting_builtins/README.md b/x-pack/plugins/alerting_builtins/README.md
index 233984a1ff23f..2944247e4714c 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 78de9a1ae0165..cc613d5247ef4 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 475efc87b443a..d9232195b0f52 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 15139ae34c93d..c3a132bc609d6 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 fbe107054ce9d..9787ece323c59 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 0a4accc983d79..fa991786a60b6 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 40e6f187ce18f..a22395cb0961b 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 f93041fa3c142..71a904dcbab3d 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 9a9483f9c9dfa..12d1b080c7c63 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 ff07b85fd3038..95d34371a6d1e 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 dfa2991895429..811478426a8d3 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 2574e73dd4f9a..88a8da5a3e575 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 59c4bb2221b0a..3509f79dbbe4d 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 1149e6fc249a9..45b9f5ba8fe2e 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 ee9432885d671..5b7cd2128f386 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 f30629789b4ed..933ed442523c8 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 e78e5ab7932c2..6d7cf621ab0ca 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 0163cb71166e8..8f36afe062aa5 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 98%
rename from x-pack/plugins/alerting/server/alerts_client.test.ts
rename to x-pack/plugins/alerts/server/alerts_client.test.ts
index fa86c27651136..9685f58b8fb31 100644
--- a/x-pack/plugins/alerting/server/alerts_client.test.ts
+++ b/x-pack/plugins/alerts/server/alerts_client.test.ts
@@ -7,12 +7,13 @@ 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();
const alertTypeRegistry = alertTypeRegistryMock.create();
@@ -30,7 +31,7 @@ const alertsClientParams = {
invalidateAPIKey: jest.fn(),
logger: loggingServiceMock.create().get(),
encryptedSavedObjectsClient: encryptedSavedObjects,
- preconfiguredActions: [],
+ getActionsClient: jest.fn(),
};
beforeEach(() => {
@@ -42,6 +43,34 @@ beforeEach(() => {
});
alertsClientParams.getUserName.mockResolvedValue('elastic');
taskManager.runNow.mockResolvedValue({ id: '' });
+ const actionsClient = actionsClientMock.create();
+ actionsClient.getBulk.mockResolvedValueOnce([
+ {
+ id: '1',
+ isPreconfigured: false,
+ actionTypeId: 'test',
+ name: 'test',
+ config: {
+ foo: 'bar',
+ },
+ },
+ {
+ id: '2',
+ isPreconfigured: false,
+ actionTypeId: 'test2',
+ name: 'test2',
+ config: {
+ foo: 'bar',
+ },
+ },
+ {
+ id: 'testPreconfigured',
+ actionTypeId: '.slack',
+ isPreconfigured: true,
+ name: 'test',
+ },
+ ]);
+ alertsClientParams.getActionsClient.mockResolvedValue(actionsClient);
});
const mockedDate = new Date('2019-02-12T21:01:22.479Z');
@@ -97,18 +126,6 @@ describe('create()', () => {
test('creates an alert', async () => {
const data = getMockData();
- savedObjectsClient.bulkGet.mockResolvedValueOnce({
- saved_objects: [
- {
- id: '1',
- type: 'action',
- attributes: {
- actionTypeId: 'test',
- },
- references: [],
- },
- ],
- });
savedObjectsClient.create.mockResolvedValueOnce({
id: '1',
type: 'alert',
@@ -297,26 +314,6 @@ describe('create()', () => {
},
],
});
- savedObjectsClient.bulkGet.mockResolvedValueOnce({
- saved_objects: [
- {
- id: '1',
- type: 'action',
- attributes: {
- actionTypeId: 'test',
- },
- references: [],
- },
- {
- id: '2',
- type: 'action',
- attributes: {
- actionTypeId: 'test2',
- },
- references: [],
- },
- ],
- });
savedObjectsClient.create.mockResolvedValueOnce({
id: '1',
type: 'alert',
@@ -435,16 +432,6 @@ describe('create()', () => {
"updatedAt": 2019-02-12T21:01:22.479Z,
}
`);
- expect(savedObjectsClient.bulkGet).toHaveBeenCalledWith([
- {
- id: '1',
- type: 'action',
- },
- {
- id: '2',
- type: 'action',
- },
- ]);
});
test('creates a disabled alert', async () => {
@@ -549,7 +536,9 @@ describe('create()', () => {
test('throws error if loading actions fails', async () => {
const data = getMockData();
- savedObjectsClient.bulkGet.mockRejectedValueOnce(new Error('Test Error'));
+ const actionsClient = actionsClientMock.create();
+ actionsClient.getBulk.mockRejectedValueOnce(new Error('Test Error'));
+ alertsClientParams.getActionsClient.mockResolvedValue(actionsClient);
await expect(alertsClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot(
`"Test Error"`
);
@@ -1688,7 +1677,7 @@ describe('find()', () => {
},
],
});
- const result = await alertsClient.find();
+ const result = await alertsClient.find({ options: {} });
expect(result).toMatchInlineSnapshot(`
Object {
"data": Array [
@@ -1903,26 +1892,6 @@ describe('update()', () => {
});
test('updates given parameters', async () => {
- savedObjectsClient.bulkGet.mockResolvedValueOnce({
- saved_objects: [
- {
- id: '1',
- type: 'action',
- attributes: {
- actionTypeId: 'test',
- },
- references: [],
- },
- {
- id: '2',
- type: 'action',
- attributes: {
- actionTypeId: 'test2',
- },
- references: [],
- },
- ],
- });
savedObjectsClient.update.mockResolvedValueOnce({
id: '1',
type: 'alert',
diff --git a/x-pack/plugins/alerting/server/alerts_client.ts b/x-pack/plugins/alerts/server/alerts_client.ts
similarity index 87%
rename from x-pack/plugins/alerting/server/alerts_client.ts
rename to x-pack/plugins/alerts/server/alerts_client.ts
index e43939e2f44c3..6b091a5a4503b 100644
--- a/x-pack/plugins/alerting/server/alerts_client.ts
+++ b/x-pack/plugins/alerts/server/alerts_client.ts
@@ -13,7 +13,7 @@ import {
SavedObjectReference,
SavedObject,
} from 'src/core/server';
-import { PreConfiguredAction } from '../../actions/server';
+import { ActionsClient } from '../../actions/server';
import {
Alert,
PartialAlert,
@@ -24,16 +24,15 @@ import {
IntervalSchedule,
SanitizedAlert,
AlertTaskState,
- RawAlertAction,
} from './types';
import { validateAlertTypeParams } from './lib';
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';
@@ -56,25 +55,32 @@ interface ConstructorOptions {
getUserName: () => Promise;
createAPIKey: () => Promise;
invalidateAPIKey: (params: InvalidateAPIKeyParams) => Promise;
- preconfiguredActions: PreConfiguredAction[];
+ 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 {
@@ -127,7 +133,7 @@ export class AlertsClient {
private readonly invalidateAPIKey: (
params: InvalidateAPIKeyParams
) => Promise;
- private preconfiguredActions: PreConfiguredAction[];
+ private readonly getActionsClient: () => Promise;
encryptedSavedObjectsClient: EncryptedSavedObjectsClient;
constructor({
@@ -141,7 +147,7 @@ export class AlertsClient {
createAPIKey,
invalidateAPIKey,
encryptedSavedObjectsClient,
- preconfiguredActions,
+ getActionsClient,
}: ConstructorOptions) {
this.logger = logger;
this.getUserName = getUserName;
@@ -153,7 +159,7 @@ export class AlertsClient {
this.createAPIKey = createAPIKey;
this.invalidateAPIKey = invalidateAPIKey;
this.encryptedSavedObjectsClient = encryptedSavedObjectsClient;
- this.preconfiguredActions = preconfiguredActions;
+ this.getActionsClient = getActionsClient;
}
public async create({ data, options }: CreateOptions): Promise {
@@ -226,7 +232,7 @@ export class AlertsClient {
}
}
- public async find({ options = {} }: FindOptions = {}): Promise {
+ public async find({ options = {} }: { options: FindOptions }): Promise {
const {
page,
per_page: perPage,
@@ -534,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)) {
@@ -600,7 +600,7 @@ export class AlertsClient {
actions: RawAlert['actions'],
references: SavedObjectReference[]
) {
- return actions.map((action, i) => {
+ return actions.map((action) => {
const reference = references.find((ref) => ref.name === action.actionRef);
if (!reference) {
throw new Error(`Reference ${action.actionRef} not found`);
@@ -653,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(', '),
@@ -666,58 +666,31 @@ export class AlertsClient {
private async denormalizeActions(
alertActions: NormalizedAlertAction[]
): Promise<{ actions: RawAlert['actions']; references: SavedObjectReference[] }> {
- const actionMap = new Map();
- // map preconfigured actions
- for (const alertAction of alertActions) {
- const action = this.preconfiguredActions.find(
- (preconfiguredAction) => preconfiguredAction.id === alertAction.id
- );
- if (action !== undefined) {
- actionMap.set(action.id, action);
- }
- }
- // Fetch action objects in bulk
- // Excluding preconfigured actions to avoid an not found error, which is already mapped
- const actionIds = [
- ...new Set(
- alertActions
- .filter((alertAction) => !actionMap.has(alertAction.id))
- .map((alertAction) => alertAction.id)
- ),
- ];
- if (actionIds.length > 0) {
- const bulkGetOpts = actionIds.map((id) => ({ id, type: 'action' }));
- const bulkGetResult = await this.savedObjectsClient.bulkGet(bulkGetOpts);
-
- for (const action of bulkGetResult.saved_objects) {
- if (action.error) {
- throw Boom.badRequest(
- `Failed to load action ${action.id} (${action.error.statusCode}): ${action.error.message}`
- );
- }
- actionMap.set(action.id, action);
- }
- }
- // Extract references and set actionTypeId
+ const actionsClient = await this.getActionsClient();
+ const actionIds = [...new Set(alertActions.map((alertAction) => alertAction.id))];
+ const actionResults = await actionsClient.getBulk(actionIds);
const references: SavedObjectReference[] = [];
const actions = alertActions.map(({ id, ...alertAction }, i) => {
- const actionRef = `action_${i}`;
- references.push({
- id,
- name: actionRef,
- type: 'action',
- });
- const actionMapValue = actionMap.get(id);
- // if action is a save object, than actionTypeId should be under attributes property
- // if action is a preconfigured, than actionTypeId is the action property
- const actionTypeId = actionIds.find((actionId) => actionId === id)
- ? (actionMapValue as SavedObject>).attributes.actionTypeId
- : (actionMapValue as RawAlertAction).actionTypeId;
- return {
- ...alertAction,
- actionRef,
- actionTypeId,
- };
+ const actionResultValue = actionResults.find((action) => action.id === id);
+ if (actionResultValue) {
+ const actionRef = `action_${i}`;
+ references.push({
+ id,
+ name: actionRef,
+ type: 'action',
+ });
+ return {
+ ...alertAction,
+ actionRef,
+ actionTypeId: actionResultValue.actionTypeId,
+ };
+ } else {
+ return {
+ ...alertAction,
+ actionRef: '',
+ actionTypeId: '',
+ };
+ }
});
return {
actions,
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 87%
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 cc792d11c890d..50dafba00a7e4 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,13 @@
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');
@@ -25,7 +26,7 @@ const alertsClientFactoryParams: jest.Mocked = {
getSpaceId: jest.fn(),
spaceIdToNamespace: jest.fn(),
encryptedSavedObjectsClient: encryptedSavedObjectsMock.createClient(),
- preconfiguredActions: [],
+ actions: actionsMock.createStart(),
};
const fakeRequest = ({
headers: {},
@@ -65,7 +66,7 @@ test('creates an alerts client with proper constructor arguments', async () => {
createAPIKey: expect.any(Function),
invalidateAPIKey: expect.any(Function),
encryptedSavedObjectsClient: alertsClientFactoryParams.encryptedSavedObjectsClient,
- preconfiguredActions: [],
+ getActionsClient: expect.any(Function),
});
});
@@ -95,6 +96,16 @@ test('getUserName() returns a name when security is enabled', async () => {
expect(userNameResult).toEqual('bob');
});
+test('getActionsClient() returns ActionsClient', async () => {
+ const factory = new AlertsClientFactory();
+ factory.initialize(alertsClientFactoryParams);
+ factory.create(KibanaRequest.from(fakeRequest), savedObjectsClient);
+ const constructorCall = jest.requireMock('./alerts_client').AlertsClient.mock.calls[0][0];
+
+ const actionsClient = await constructorCall.getActionsClient();
+ expect(actionsClient).not.toBe(null);
+});
+
test('createAPIKey() returns { apiKeysEnabled: false } when security is disabled', async () => {
const factory = new AlertsClientFactory();
factory.initialize(alertsClientFactoryParams);
diff --git a/x-pack/plugins/alerting/server/alerts_client_factory.ts b/x-pack/plugins/alerts/server/alerts_client_factory.ts
similarity index 87%
rename from x-pack/plugins/alerting/server/alerts_client_factory.ts
rename to x-pack/plugins/alerts/server/alerts_client_factory.ts
index 913b4e2e81fe1..af546f965d7df 100644
--- a/x-pack/plugins/alerting/server/alerts_client_factory.ts
+++ b/x-pack/plugins/alerts/server/alerts_client_factory.ts
@@ -4,13 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { PreConfiguredAction } from '../../actions/server';
+import { PluginStartContract as ActionsPluginStartContract } from '../../actions/server';
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;
@@ -20,7 +20,7 @@ export interface AlertsClientFactoryOpts {
getSpaceId: (request: KibanaRequest) => string | undefined;
spaceIdToNamespace: SpaceIdToNamespaceFunction;
encryptedSavedObjectsClient: EncryptedSavedObjectsClient;
- preconfiguredActions: PreConfiguredAction[];
+ actions: ActionsPluginStartContract;
}
export class AlertsClientFactory {
@@ -32,7 +32,7 @@ export class AlertsClientFactory {
private getSpaceId!: (request: KibanaRequest) => string | undefined;
private spaceIdToNamespace!: SpaceIdToNamespaceFunction;
private encryptedSavedObjectsClient!: EncryptedSavedObjectsClient;
- private preconfiguredActions!: PreConfiguredAction[];
+ private actions!: ActionsPluginStartContract;
public initialize(options: AlertsClientFactoryOpts) {
if (this.isInitialized) {
@@ -46,14 +46,14 @@ export class AlertsClientFactory {
this.securityPluginSetup = options.securityPluginSetup;
this.spaceIdToNamespace = options.spaceIdToNamespace;
this.encryptedSavedObjectsClient = options.encryptedSavedObjectsClient;
- this.preconfiguredActions = options.preconfiguredActions;
+ this.actions = options.actions;
}
public create(
request: KibanaRequest,
savedObjectsClient: SavedObjectsClientContract
): AlertsClient {
- const { securityPluginSetup } = this;
+ const { securityPluginSetup, actions } = this;
const spaceId = this.getSpaceId(request);
return new AlertsClient({
spaceId,
@@ -104,7 +104,9 @@ export class AlertsClientFactory {
result: invalidateAPIKeyResult,
};
},
- preconfiguredActions: this.preconfiguredActions,
+ async getActionsClient() {
+ return actions.getActionsClientWithRequest(request);
+ },
});
}
}
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 9c276ed1d75de..c180b68680841 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 cbab98a6311dd..50b4e6b4439f7 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 211d7a75dc4fa..ea0106f717b02 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 0411899290ab2..008a9bb804c5b 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 98%
rename from x-pack/plugins/alerting/server/plugin.ts
rename to x-pack/plugins/alerts/server/plugin.ts
index 33fb8d9e0d212..324bc9fbfb72b 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';
@@ -216,7 +216,7 @@ export class AlertingPlugin {
getSpaceId(request: KibanaRequest) {
return spaces?.getSpaceId(request);
},
- preconfiguredActions: plugins.actions.preconfiguredActions,
+ actions: plugins.actions,
});
taskRunnerFactory.initialize({
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 a4910495c8a40..9e941903eeaed 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 cc3b7d48162e3..6238fca024e55 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 416628d015b5a..9ba4e20312e17 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 f5a7add632edc..2034bd21fbed6 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 fde095e9145b6..a82d09854a604 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 e1eb089cf4e85..dfc5dfbdd5aa2 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 e4e89e3f06380..4ee3a12a59dc7 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 90e8f552898d9..b6f86b97d6a3a 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 cc601bd42b8ca..f20ee0a54dcd9 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 3de95c9580cd4..80c9c20eec7da 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 7335f13c85a4d..b11224ff4794e 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 cd78e7fbacddb..ae9ebe1299371 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 20a420ca00986..8c9051093f85b 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 a5cb14154db67..b27ae3758e1b9 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 9b1c95c393f56..b3f41e03ebdc9 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 fb4c5e76a02c9..b66e28b24e8a7 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 b3cf48c52fe17..e0c620b0670c9 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 0000000000000..bfe60a0ecc648
--- /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 e940b2d102045..3192154f6664c 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 f5b4e3263f341..51a4558108e29 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 5ef9e3694f8f4..bcdb8cbd022ac 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 b43a1ec30ed1f..5b05d7231c385 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 2e6adedb76df9..c382c12de21cd 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 c0c69fe9653da..00550f4af3418 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 1756dbd3fb41d..e13af38fe4cb1 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 d4b6e8b7d61b1..1efc9ed40054e 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 9b9542c606741..b2e2f24e91de9 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 97ccd8f0adce7..967f9f890c9fb 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 cd96f289b8714..c7d23f2670b45 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 23fea7dc4002f..99b81dfc5b56e 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 0347feb24a235..babae59553b5b 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 9d88201d7cd43..4736351a25cbd 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 28b3576dffc6e..efac4c5dcdc01 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 4be506b78493b..a290f3fa33c70 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 61bbab50b1222..3c58c6d9ba288 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 98824b8cf4e1a..983dff86d5602 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 0831163d1d326..be399893088e3 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 c1318bac48dfb..7d9710d8a3e08 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 c50e288d2e520..ca762cf2b2105 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/dev_docs/vscode_setup.md b/x-pack/plugins/apm/dev_docs/vscode_setup.md
index 1c80d1476520d..c7adad4fd0942 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/e2e/.gitignore b/x-pack/plugins/apm/e2e/.gitignore
index 9eb738ede51e3..5042f0bca0300 100644
--- a/x-pack/plugins/apm/e2e/.gitignore
+++ b/x-pack/plugins/apm/e2e/.gitignore
@@ -1,4 +1,5 @@
cypress/screenshots/*
-cypress/videos/*
cypress/test-results
+cypress/videos/*
+/snapshots.js
tmp
diff --git a/x-pack/plugins/apm/e2e/run-e2e.sh b/x-pack/plugins/apm/e2e/run-e2e.sh
index 157c42cc7e4ee..ae764d171c45c 100755
--- a/x-pack/plugins/apm/e2e/run-e2e.sh
+++ b/x-pack/plugins/apm/e2e/run-e2e.sh
@@ -26,20 +26,23 @@ cd ${E2E_DIR}
#
# Ask user to start Kibana
##################################################
-echo "\n${bold}To start Kibana please run the following command:${normal}
+echo "" # newline
+echo "${bold}To start Kibana please run the following command:${normal}
node ./scripts/kibana --no-base-path --dev --no-dev-config --config x-pack/plugins/apm/e2e/ci/kibana.e2e.yml"
#
# Create tmp folder
##################################################
-echo "\n${bold}Temporary folder${normal}"
-echo "Temporary files will be stored in: ${TMP_DIR}"
+echo "" # newline
+echo "${bold}Temporary folder${normal}"
+echo "Temporary files will be stored in: ${E2E_DIR}${TMP_DIR}"
mkdir -p ${TMP_DIR}
#
# apm-integration-testing
##################################################
-printf "\n${bold}apm-integration-testing (logs: ${TMP_DIR}/apm-it.log)\n${normal}"
+echo "" # newline
+echo "${bold}apm-integration-testing (logs: ${E2E_DIR}${TMP_DIR}/apm-it.log)${normal}"
# pull if folder already exists
if [ -d ${APM_IT_DIR} ]; then
@@ -54,7 +57,7 @@ fi
# Stop if clone/pull failed
if [ $? -ne 0 ]; then
- printf "\n⚠️ Initializing apm-integration-testing failed. \n"
+ echo "⚠️ Initializing apm-integration-testing failed."
exit 1
fi
@@ -71,23 +74,34 @@ ${APM_IT_DIR}/scripts/compose.py start master \
# Stop if apm-integration-testing failed to start correctly
if [ $? -ne 0 ]; then
- printf "⚠️ apm-integration-testing could not be started.\n"
- printf "Please see the logs in ${TMP_DIR}/apm-it.log\n\n"
- printf "As a last resort, reset docker with:\n\n cd ${APM_IT_DIR} && scripts/compose.py stop && docker system prune --all --force --volumes\n"
+ echo "⚠️ apm-integration-testing could not be started"
+ echo "" # newline
+ echo "As a last resort, reset docker with:"
+ echo "" # newline
+ echo "cd ${E2E_DIR}${APM_IT_DIR} && scripts/compose.py stop && docker system prune --all --force --volumes"
+ echo "" # newline
+
+ # output logs for excited docker containers
+ cd ${APM_IT_DIR} && docker-compose ps --filter "status=exited" -q | xargs -L1 docker logs --tail=10 && cd -
+
+ echo "" # newline
+ echo "Find the full logs in ${E2E_DIR}${TMP_DIR}/apm-it.log"
exit 1
fi
#
# Cypress
##################################################
-echo "\n${bold}Cypress (logs: ${TMP_DIR}/e2e-yarn.log)${normal}"
+echo "" # newline
+echo "${bold}Cypress (logs: ${E2E_DIR}${TMP_DIR}/e2e-yarn.log)${normal}"
echo "Installing cypress dependencies "
yarn &> ${TMP_DIR}/e2e-yarn.log
#
# Static mock data
##################################################
-printf "\n${bold}Static mock data (logs: ${TMP_DIR}/ingest-data.log)\n${normal}"
+echo "" # newline
+echo "${bold}Static mock data (logs: ${E2E_DIR}${TMP_DIR}/ingest-data.log)${normal}"
# Download static data if not already done
if [ ! -e "${TMP_DIR}/events.json" ]; then
@@ -102,16 +116,32 @@ curl --silent --user admin:changeme -XDELETE "localhost:${ELASTICSEARCH_PORT}/ap
# Ingest data into APM Server
node ingest-data/replay.js --server-url http://localhost:$APM_SERVER_PORT --events ${TMP_DIR}/events.json 2>> ${TMP_DIR}/ingest-data.log
-# Stop if not all events were ingested correctly
+# Abort if not all events were ingested correctly
if [ $? -ne 0 ]; then
- printf "\n⚠️ Not all events were ingested correctly. This might affect test tests. \n"
+ echo "⚠️ Not all events were ingested correctly. This might affect test tests."
+ echo "Aborting. Please try again."
+ echo "" # newline
+ echo "Full logs in ${E2E_DIR}${TMP_DIR}/ingest-data.log:"
+
+ # output logs for excited docker containers
+ cd ${APM_IT_DIR} && docker-compose ps --filter "status=exited" -q | xargs -L1 docker logs --tail=3 && cd -
+
+ # stop docker containers
+ cd ${APM_IT_DIR} && ./scripts/compose.py stop > /dev/null && cd -
exit 1
fi
+# create empty snapshot file if it doesn't exist
+SNAPSHOTS_FILE=cypress/integration/snapshots.js
+if [ ! -f ${SNAPSHOTS_FILE} ]; then
+ echo "{}" > ${SNAPSHOTS_FILE}
+fi
+
#
# Wait for Kibana to start
##################################################
-echo "\n${bold}Waiting for Kibana to start...${normal}"
+echo "" # newline
+echo "${bold}Waiting for Kibana to start...${normal}"
echo "Note: you need to start Kibana manually. Find the instructions at the top."
yarn wait-on -i 500 -w 500 http-get://admin:changeme@localhost:$KIBANA_PORT/api/status > /dev/null
@@ -119,12 +149,13 @@ yarn wait-on -i 500 -w 500 http-get://admin:changeme@localhost:$KIBANA_PORT/api/
## See: https://github.com/elastic/kibana/issues/66326
if [ -e kibana.log ] ; then
grep -m 1 "http server running" <(tail -f -n +1 kibana.log)
- echo "\n✅ Kibana server running...\n"
+ echo "✅ Kibana server running..."
grep -m 1 "bundles compiled successfully" <(tail -f -n +1 kibana.log)
- echo "\n✅ Kibana bundles have been compiled...\n"
+ echo "✅ Kibana bundles have been compiled..."
fi
-echo "\n✅ Setup completed successfully. Running tests...\n"
+
+echo "✅ Setup completed successfully. Running tests..."
#
# run cypress tests
@@ -134,9 +165,6 @@ yarn cypress run --config pageLoadTimeout=100000,watchForFileChanges=true
#
# Run interactively
##################################################
-echo "
-
-${bold}If you want to run the test interactively, run:${normal}
-
-yarn cypress open --config pageLoadTimeout=100000,watchForFileChanges=true
-"
+echo "${bold}If you want to run the test interactively, run:${normal}"
+echo "" # newline
+echo "cd ${E2E_DIR} && yarn cypress open --config pageLoadTimeout=100000,watchForFileChanges=true"
diff --git a/x-pack/plugins/apm/jest.config.js b/x-pack/plugins/apm/jest.config.js
new file mode 100644
index 0000000000000..c3ae694fe8e14
--- /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/kibana.json b/x-pack/plugins/apm/kibana.json
index dd89fac66f6e8..2de3c9c97065d 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 c3d426a6275a7..0dbde5ea86a18 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 e732e695b36b1..76320efe617ea 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/readme.md b/x-pack/plugins/apm/readme.md
index 62465e920d793..ceed5e6c39716 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
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 8af9f386ecebf..4b8e9cf937a2b 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 ee7bd9eeb4b6f..53843b7f7412b 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 afb402200a07b..1fd1aef4c8b70 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 b9ad14f7ec47d..d32d16d4c3cc8 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/index_lifecycle_management/public/application/services/api.js b/x-pack/plugins/index_lifecycle_management/public/application/services/api.js
index 386df63111a89..6b46d6e6ea735 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/services/api.js
+++ b/x-pack/plugins/index_lifecycle_management/public/application/services/api.js
@@ -28,8 +28,7 @@ export async function loadIndexTemplates() {
}
export async function loadPolicies(withIndices) {
- const query = withIndices ? '?withIndices=true' : '';
- return await sendGet('policies', query);
+ return await sendGet('policies', { withIndices });
}
export async function savePolicy(policy) {
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 05abe284fab32..8f464987418c0 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();
diff --git a/x-pack/plugins/index_management/server/client/elasticsearch.ts b/x-pack/plugins/index_management/server/client/elasticsearch.ts
new file mode 100644
index 0000000000000..65bd5411a249b
--- /dev/null
+++ b/x-pack/plugins/index_management/server/client/elasticsearch.ts
@@ -0,0 +1,63 @@
+/*
+ * 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 elasticsearchJsPlugin = (Client: any, config: any, components: any) => {
+ const ca = components.clientAction.factory;
+
+ Client.prototype.dataManagement = components.clientAction.namespaceFactory();
+ const dataManagement = Client.prototype.dataManagement.prototype;
+
+ dataManagement.getComponentTemplates = ca({
+ urls: [
+ {
+ fmt: '/_component_template',
+ },
+ ],
+ method: 'GET',
+ });
+
+ dataManagement.getComponentTemplate = ca({
+ urls: [
+ {
+ fmt: '/_component_template/<%=name%>',
+ req: {
+ name: {
+ type: 'string',
+ },
+ },
+ },
+ ],
+ method: 'GET',
+ });
+
+ dataManagement.saveComponentTemplate = ca({
+ urls: [
+ {
+ fmt: '/_component_template/<%=name%>',
+ req: {
+ name: {
+ type: 'string',
+ },
+ },
+ },
+ ],
+ method: 'PUT',
+ });
+
+ dataManagement.deleteComponentTemplate = ca({
+ urls: [
+ {
+ fmt: '/_component_template/<%=name%>',
+ req: {
+ name: {
+ type: 'string',
+ },
+ },
+ },
+ ],
+ method: 'DELETE',
+ });
+};
diff --git a/x-pack/plugins/index_management/server/plugin.ts b/x-pack/plugins/index_management/server/plugin.ts
index e5bd7451b028f..f254333007c39 100644
--- a/x-pack/plugins/index_management/server/plugin.ts
+++ b/x-pack/plugins/index_management/server/plugin.ts
@@ -3,14 +3,33 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
+
+declare module 'kibana/server' {
+ interface RequestHandlerContext {
+ dataManagement?: DataManagementContext;
+ }
+}
+
import { i18n } from '@kbn/i18n';
-import { CoreSetup, Plugin, Logger, PluginInitializerContext } from 'src/core/server';
+import {
+ CoreSetup,
+ Plugin,
+ Logger,
+ PluginInitializerContext,
+ IScopedClusterClient,
+ ICustomClusterClient,
+} from 'src/core/server';
import { PLUGIN } from '../common';
import { Dependencies } from './types';
import { ApiRoutes } from './routes';
import { License, IndexDataEnricher } from './services';
import { isEsError } from './lib/is_es_error';
+import { elasticsearchJsPlugin } from './client/elasticsearch';
+
+export interface DataManagementContext {
+ client: IScopedClusterClient;
+}
export interface IndexManagementPluginSetup {
indexDataEnricher: {
@@ -18,11 +37,18 @@ export interface IndexManagementPluginSetup {
};
}
+async function getCustomEsClient(getStartServices: CoreSetup['getStartServices']) {
+ const [core] = await getStartServices();
+ const esClientConfig = { plugins: [elasticsearchJsPlugin] };
+ return core.elasticsearch.legacy.createClient('dataManagement', esClientConfig);
+}
+
export class IndexMgmtServerPlugin implements Plugin {
private readonly apiRoutes: ApiRoutes;
private readonly license: License;
private readonly logger: Logger;
private readonly indexDataEnricher: IndexDataEnricher;
+ private dataManagementESClient?: ICustomClusterClient;
constructor(initContext: PluginInitializerContext) {
this.logger = initContext.logger.get();
@@ -31,7 +57,10 @@ export class IndexMgmtServerPlugin implements Plugin {
+ this.dataManagementESClient =
+ this.dataManagementESClient ?? (await getCustomEsClient(getStartServices));
+
+ return {
+ client: this.dataManagementESClient.asScoped(request),
+ };
+ });
+
this.apiRoutes.setup({
router,
license: this.license,
@@ -65,5 +103,10 @@ export class IndexMgmtServerPlugin implements Plugin {
+ router.post(
+ {
+ path: addBasePath('/component_templates'),
+ validate: {
+ body: bodySchema,
+ },
+ },
+ license.guardApiRoute(async (ctx, req, res) => {
+ const { callAsCurrentUser } = ctx.dataManagement!.client;
+
+ const { name, ...componentTemplateDefinition } = req.body;
+
+ try {
+ // Check that a component template with the same name doesn't already exist
+ const componentTemplateResponse = await callAsCurrentUser(
+ 'dataManagement.getComponentTemplate',
+ { name }
+ );
+
+ const { component_templates: componentTemplates } = componentTemplateResponse;
+
+ if (componentTemplates.length) {
+ return res.conflict({
+ body: new Error(
+ i18n.translate('xpack.idxMgmt.componentTemplates.createRoute.duplicateErrorMessage', {
+ defaultMessage: "There is already a component template with name '{name}'.",
+ values: {
+ name,
+ },
+ })
+ ),
+ });
+ }
+ } catch (e) {
+ // Silently swallow error
+ }
+
+ try {
+ const response = await callAsCurrentUser('dataManagement.saveComponentTemplate', {
+ name,
+ body: componentTemplateDefinition,
+ });
+
+ return res.ok({ body: response });
+ } catch (error) {
+ if (isEsError(error)) {
+ return res.customError({
+ statusCode: error.statusCode,
+ body: error,
+ });
+ }
+
+ return res.internalError({ body: error });
+ }
+ })
+ );
+};
diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/delete.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/delete.ts
new file mode 100644
index 0000000000000..9e11967202b9c
--- /dev/null
+++ b/x-pack/plugins/index_management/server/routes/api/component_templates/delete.ts
@@ -0,0 +1,51 @@
+/*
+ * 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 { schema } from '@kbn/config-schema';
+
+import { RouteDependencies } from '../../../types';
+import { addBasePath } from '../index';
+
+const paramsSchema = schema.object({
+ names: schema.string(),
+});
+
+export const registerDeleteRoute = ({ router, license }: RouteDependencies): void => {
+ router.delete(
+ {
+ path: addBasePath('/component_templates/{names}'),
+ validate: {
+ params: paramsSchema,
+ },
+ },
+ license.guardApiRoute(async (ctx, req, res) => {
+ const { callAsCurrentUser } = ctx.dataManagement!.client;
+ const { names } = req.params;
+ const componentNames = names.split(',');
+
+ const response: { itemsDeleted: string[]; errors: any[] } = {
+ itemsDeleted: [],
+ errors: [],
+ };
+
+ await Promise.all(
+ componentNames.map((componentName) => {
+ return callAsCurrentUser('dataManagement.deleteComponentTemplate', {
+ name: componentName,
+ })
+ .then(() => response.itemsDeleted.push(componentName))
+ .catch((e) =>
+ response.errors.push({
+ name: componentName,
+ error: e,
+ })
+ );
+ })
+ );
+
+ return res.ok({ body: response });
+ })
+ );
+};
diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/get.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/get.ts
new file mode 100644
index 0000000000000..87aa64421624e
--- /dev/null
+++ b/x-pack/plugins/index_management/server/routes/api/component_templates/get.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 { schema } from '@kbn/config-schema';
+
+import { RouteDependencies } from '../../../types';
+import { addBasePath } from '../index';
+
+const paramsSchema = schema.object({
+ name: schema.string(),
+});
+
+export function registerGetAllRoute({ router, license, lib: { isEsError } }: RouteDependencies) {
+ // Get all component templates
+ router.get(
+ { path: addBasePath('/component_templates'), validate: false },
+ license.guardApiRoute(async (ctx, req, res) => {
+ const { callAsCurrentUser } = ctx.dataManagement!.client;
+
+ try {
+ const response = await callAsCurrentUser('dataManagement.getComponentTemplates');
+
+ return res.ok({ body: response.component_templates });
+ } catch (error) {
+ if (isEsError(error)) {
+ return res.customError({
+ statusCode: error.statusCode,
+ body: error,
+ });
+ }
+
+ return res.internalError({ body: error });
+ }
+ })
+ );
+
+ // Get single component template
+ router.get(
+ {
+ path: addBasePath('/component_templates/{name}'),
+ validate: {
+ params: paramsSchema,
+ },
+ },
+ license.guardApiRoute(async (ctx, req, res) => {
+ const { callAsCurrentUser } = ctx.dataManagement!.client;
+ const { name } = req.params;
+
+ try {
+ const { component_templates: componentTemplates } = await callAsCurrentUser(
+ 'dataManagement.getComponentTemplates',
+ {
+ name,
+ }
+ );
+
+ return res.ok({
+ body: {
+ ...componentTemplates[0],
+ name,
+ },
+ });
+ } catch (error) {
+ if (isEsError(error)) {
+ return res.customError({
+ statusCode: error.statusCode,
+ body: error,
+ });
+ }
+
+ return res.internalError({ body: error });
+ }
+ })
+ );
+}
diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/index.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/index.ts
new file mode 100644
index 0000000000000..7ecb71182e87e
--- /dev/null
+++ b/x-pack/plugins/index_management/server/routes/api/component_templates/index.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { RouteDependencies } from '../../../types';
+
+import { registerGetAllRoute } from './get';
+import { registerCreateRoute } from './create';
+import { registerUpdateRoute } from './update';
+import { registerDeleteRoute } from './delete';
+
+export function registerComponentTemplateRoutes(dependencies: RouteDependencies) {
+ registerGetAllRoute(dependencies);
+ registerCreateRoute(dependencies);
+ registerUpdateRoute(dependencies);
+ registerDeleteRoute(dependencies);
+}
diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/schema_validation.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/schema_validation.ts
new file mode 100644
index 0000000000000..7d32637c6b977
--- /dev/null
+++ b/x-pack/plugins/index_management/server/routes/api/component_templates/schema_validation.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { schema } from '@kbn/config-schema';
+
+export const componentTemplateSchema = {
+ template: schema.object({
+ settings: schema.maybe(schema.object({}, { unknowns: 'allow' })),
+ aliases: schema.maybe(schema.object({}, { unknowns: 'allow' })),
+ mappings: schema.maybe(schema.object({}, { unknowns: 'allow' })),
+ }),
+ version: schema.maybe(schema.number()),
+ _meta: schema.maybe(schema.object({}, { unknowns: 'allow' })),
+};
diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/update.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/update.ts
new file mode 100644
index 0000000000000..7e447bb110c67
--- /dev/null
+++ b/x-pack/plugins/index_management/server/routes/api/component_templates/update.ts
@@ -0,0 +1,62 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { schema } from '@kbn/config-schema';
+
+import { RouteDependencies } from '../../../types';
+import { addBasePath } from '../index';
+import { componentTemplateSchema } from './schema_validation';
+
+const bodySchema = schema.object(componentTemplateSchema);
+
+const paramsSchema = schema.object({
+ name: schema.string(),
+});
+
+export const registerUpdateRoute = ({
+ router,
+ license,
+ lib: { isEsError },
+}: RouteDependencies): void => {
+ router.put(
+ {
+ path: addBasePath('/component_templates/{name}'),
+ validate: {
+ body: bodySchema,
+ params: paramsSchema,
+ },
+ },
+ license.guardApiRoute(async (ctx, req, res) => {
+ const { callAsCurrentUser } = ctx.dataManagement!.client;
+ const { name } = req.params;
+ const { template, version, _meta } = req.body;
+
+ try {
+ // Verify component exists; ES will throw 404 if not
+ await callAsCurrentUser('dataManagement.getComponentTemplate', { name });
+
+ const response = await callAsCurrentUser('dataManagement.saveComponentTemplate', {
+ name,
+ body: {
+ template,
+ version,
+ _meta,
+ },
+ });
+
+ return res.ok({ body: response });
+ } catch (error) {
+ if (isEsError(error)) {
+ return res.customError({
+ statusCode: error.statusCode,
+ body: error,
+ });
+ }
+
+ return res.internalError({ body: error });
+ }
+ })
+ );
+};
diff --git a/x-pack/plugins/index_management/server/routes/index.ts b/x-pack/plugins/index_management/server/routes/index.ts
index 870cfa36ecc6a..1e5aaf8087624 100644
--- a/x-pack/plugins/index_management/server/routes/index.ts
+++ b/x-pack/plugins/index_management/server/routes/index.ts
@@ -11,6 +11,7 @@ import { registerTemplateRoutes } from './api/templates';
import { registerMappingRoute } from './api/mapping';
import { registerSettingsRoutes } from './api/settings';
import { registerStatsRoute } from './api/stats';
+import { registerComponentTemplateRoutes } from './api/component_templates';
export class ApiRoutes {
setup(dependencies: RouteDependencies) {
@@ -19,6 +20,7 @@ export class ApiRoutes {
registerSettingsRoutes(dependencies);
registerStatsRoute(dependencies);
registerMappingRoute(dependencies);
+ registerComponentTemplateRoutes(dependencies);
}
start() {}
diff --git a/x-pack/plugins/index_management/server/services/license.ts b/x-pack/plugins/index_management/server/services/license.ts
index 2d863e283d440..9b68acd073c4a 100644
--- a/x-pack/plugins/index_management/server/services/license.ts
+++ b/x-pack/plugins/index_management/server/services/license.ts
@@ -53,12 +53,12 @@ export class License {
});
}
- guardApiRoute(handler: RequestHandler) {
+ guardApiRoute(handler: RequestHandler
) {
const license = this;
return function licenseCheck(
ctx: RequestHandlerContext,
- request: KibanaRequest,
+ request: KibanaRequest
,
response: KibanaResponseFactory
) {
const licenseStatus = license.getStatus();
diff --git a/x-pack/plugins/infra/kibana.json b/x-pack/plugins/infra/kibana.json
index ea66ae7a46d4e..4701182c96813 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 4bbbf8dcdee03..d00afbc7b497a 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 b36de2a3bd091..5a34a6665e781 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 995d415ef3c8f..a3b9e85458416 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 eedaf4202b37d..ee4e1fcb3f6e2 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 cdb4d2d968479..ed7e82fe29e4c 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 19efc88e216ca..8260ebed84622 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 d1cb60112aa42..233a34a67d1ec 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 ae74ed82038fd..989a2917b0520 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 a265d53fc1bf8..2fd614830c05d 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/ingest_manager/server/services/epm/elasticsearch/ilm/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ilm/install.ts
index 1d06bf23a8c0f..9590167657d98 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 bdf6ecfcdb9aa..11543fe73886f 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 c600c8ba3efb8..9d0b6b5d078ad 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 f321e2d614a04..0f7b1d6cab178 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 c6f7a1f6b97aa..37fcf0db67131 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 dddb21bc4e075..736711f9152e9 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: {
@@ -90,7 +92,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 +121,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 +138,8 @@ export async function installPackage(options: {
registryPackageInfo,
callCluster,
pkgName,
- pkgVersion
+ pkgVersion,
+ paths
);
const toSaveESIndexPatterns = generateESIndexPatterns(registryPackageInfo.datasets);
@@ -186,13 +190,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 +242,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 17d52bc745a55..d2a14fcf04dff 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 8e9b920875617..0393cabca8ba2 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();
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 1762965478292..f1a2edd2d554f 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 a77fbbb597564..ffa59a6fb6bc9 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 7c875935f6320..032ce8325dca1 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}
/>
);
};
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 3fbc42f9a25a0..924f44a37c459 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 0d86a051b0faa..e53e465c18950 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 f89b6ef32d3f7..cc8d97a445016 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 0000000000000..1f987f86d3950
--- /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 814b7fc644c9c..bd501db2b752a 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:
diff --git a/x-pack/plugins/lens/server/usage/task.ts b/x-pack/plugins/lens/server/usage/task.ts
index 5a5d26fa2afde..cde6e7eb6c090 100644
--- a/x-pack/plugins/lens/server/usage/task.ts
+++ b/x-pack/plugins/lens/server/usage/task.ts
@@ -6,6 +6,7 @@
import { APICaller, CoreSetup, Logger } from 'kibana/server';
import { Observable } from 'rxjs';
+import { first } from 'rxjs/operators';
import moment from 'moment';
import {
RunContext,
@@ -191,7 +192,7 @@ export function telemetryTaskRunner(
return {
async run() {
- const kibanaIndex = (await config.toPromise()).kibana.index;
+ const kibanaIndex = (await config.pipe(first()).toPromise()).kibana.index;
return Promise.all([
getDailyEvents(kibanaIndex, callCluster),
diff --git a/x-pack/plugins/licensing/server/plugin.ts b/x-pack/plugins/licensing/server/plugin.ts
index 33f70c549914d..e1aa4a1b32517 100644
--- a/x-pack/plugins/licensing/server/plugin.ts
+++ b/x-pack/plugins/licensing/server/plugin.ts
@@ -107,7 +107,7 @@ export class LicensingPlugin implements Plugin
): ReturnType {
const [coreStart] = await core.getStartServices();
- const client = coreStart.elasticsearch.legacy.client;
- return await client.asScoped(request).callAsCurrentUser(...args);
+ const _client = coreStart.elasticsearch.legacy.client;
+ return await _client.asScoped(request).callAsCurrentUser(...args);
},
callAsInternalUser,
};
@@ -124,7 +124,7 @@ export class LicensingPlugin implements Plugin {
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 f0ef0dbec0b22..39f7aa6503b35 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 0c6613d37f63a..9bfcb28f36b2a 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/plugins/lists/README.md b/x-pack/plugins/lists/README.md
new file mode 100644
index 0000000000000..cb343c95b0103
--- /dev/null
+++ b/x-pack/plugins/lists/README.md
@@ -0,0 +1,254 @@
+README.md for developers working on the backend lists on how to get started
+using the CURL scripts in the scripts folder.
+
+The scripts rely on CURL and jq:
+
+- [CURL](https://curl.haxx.se)
+- [jq](https://stedolan.github.io/jq/)
+
+Install curl and jq (mac instructions)
+
+```sh
+brew update
+brew install curl
+brew install jq
+```
+
+Open `$HOME/.zshrc` or `${HOME}.bashrc` depending on your SHELL output from `echo $SHELL`
+and add these environment variables:
+
+```sh
+export ELASTICSEARCH_USERNAME=${user}
+export ELASTICSEARCH_PASSWORD=${password}
+export ELASTICSEARCH_URL=https://${ip}:9200
+export KIBANA_URL=http://localhost:5601
+export TASK_MANAGER_INDEX=.kibana-task-manager-${your user id}
+export KIBANA_INDEX=.kibana-${your user id}
+```
+
+source `$HOME/.zshrc` or `${HOME}.bashrc` to ensure variables are set:
+
+```sh
+source ~/.zshrc
+```
+
+Open your `kibana.dev.yml` file and add these lines:
+
+```sh
+# Enable lists feature
+xpack.lists.enabled: true
+xpack.lists.listIndex: '.lists-frank'
+xpack.lists.listItemIndex: '.items-frank'
+```
+
+Restart Kibana and ensure that you are using `--no-base-path` as changing the base path is a feature but will
+get in the way of the CURL scripts written as is.
+
+Go to the scripts folder `cd kibana/x-pack/plugins/lists/server/scripts` and run:
+
+```sh
+./hard_reset.sh
+./post_list.sh
+```
+
+which will:
+
+- Delete any existing lists you have
+- Delete any existing list items you have
+- Delete any existing exception lists you have
+- Delete any existing exception list items you have
+- Delete any existing mapping, policies, and templates, you might have previously had.
+- Add the latest list and list item index and its mappings using your settings from `kibana.dev.yml` environment variable of `xpack.lists.listIndex` and `xpack.lists.listItemIndex`.
+- Posts the sample list from `./lists/new/list_ip.json`
+
+Now you can run
+
+```sh
+./post_list.sh
+```
+
+You should see the new list created like so:
+
+```sh
+{
+ "id": "list-ip",
+ "created_at": "2020-05-28T19:15:22.344Z",
+ "created_by": "yo",
+ "description": "This list describes bad internet ip",
+ "name": "Simple list with an ip",
+ "tie_breaker_id": "c57efbc4-4977-4a32-995f-cfd296bed521",
+ "type": "ip",
+ "updated_at": "2020-05-28T19:15:22.344Z",
+ "updated_by": "yo"
+}
+```
+
+You can add a list item like so:
+
+```sh
+ ./post_list_item.sh
+```
+
+You should see the new list item created and attached to the above list like so:
+
+```sh
+{
+ "id": "hand_inserted_item_id",
+ "type": "ip",
+ "value": "127.0.0.1",
+ "created_at": "2020-05-28T19:15:49.790Z",
+ "created_by": "yo",
+ "list_id": "list-ip",
+ "tie_breaker_id": "a881bf2e-1e17-4592-bba8-d567cb07d234",
+ "updated_at": "2020-05-28T19:15:49.790Z",
+ "updated_by": "yo"
+}
+```
+
+If you want to post an exception list it would be like so:
+
+```sh
+./post_exception_list.sh
+```
+
+You should see the new exception list created like so:
+
+```sh
+{
+ "_tags": [
+ "endpoint",
+ "process",
+ "malware",
+ "os:linux"
+ ],
+ "created_at": "2020-05-28T19:16:31.052Z",
+ "created_by": "yo",
+ "description": "This is a sample endpoint type exception",
+ "id": "bcb94680-a117-11ea-ad9d-c71f4820e65b",
+ "list_id": "endpoint_list",
+ "name": "Sample Endpoint Exception List",
+ "namespace_type": "single",
+ "tags": [
+ "user added string for a tag",
+ "malware"
+ ],
+ "tie_breaker_id": "86e08c8c-c970-4b08-a6e2-cdba7bb4e023",
+ "type": "endpoint",
+ "updated_at": "2020-05-28T19:16:31.080Z",
+ "updated_by": "yo"
+}
+```
+
+And you can attach exception list items like so:
+
+```ts
+{
+ "_tags": [
+ "endpoint",
+ "process",
+ "malware",
+ "os:linux"
+ ],
+ "comment": [],
+ "created_at": "2020-05-28T19:17:21.099Z",
+ "created_by": "yo",
+ "description": "This is a sample endpoint type exception",
+ "entries": [
+ {
+ "field": "actingProcess.file.signer",
+ "operator": "included",
+ "match": "Elastic, N.V."
+ },
+ {
+ "field": "event.category",
+ "operator": "included",
+ "match_any": [
+ "process",
+ "malware"
+ ]
+ }
+ ],
+ "id": "da8d3b30-a117-11ea-ad9d-c71f4820e65b",
+ "item_id": "endpoint_list_item",
+ "list_id": "endpoint_list",
+ "name": "Sample Endpoint Exception List",
+ "namespace_type": "single",
+ "tags": [
+ "user added string for a tag",
+ "malware"
+ ],
+ "tie_breaker_id": "21f84703-9476-4af8-a212-aad31e18dcb9",
+ "type": "simple",
+ "updated_at": "2020-05-28T19:17:21.123Z",
+ "updated_by": "yo"
+}
+```
+
+You can then do find for each one like so:
+
+```sh
+./find_lists.sh
+```
+
+```sh
+{
+ "cursor": "WzIwLFsiYzU3ZWZiYzQtNDk3Ny00YTMyLTk5NWYtY2ZkMjk2YmVkNTIxIl1d",
+ "data": [
+ {
+ "id": "list-ip",
+ "created_at": "2020-05-28T19:15:22.344Z",
+ "created_by": "yo",
+ "description": "This list describes bad internet ip",
+ "name": "Simple list with an ip",
+ "tie_breaker_id": "c57efbc4-4977-4a32-995f-cfd296bed521",
+ "type": "ip",
+ "updated_at": "2020-05-28T19:15:22.344Z",
+ "updated_by": "yo"
+ }
+ ],
+ "page": 1,
+ "per_page": 20,
+ "total": 1
+}
+```
+
+or for finding exception lists:
+
+```sh
+./find_exception_lists.sh
+```
+
+```sh
+{
+ "data": [
+ {
+ "_tags": [
+ "endpoint",
+ "process",
+ "malware",
+ "os:linux"
+ ],
+ "created_at": "2020-05-28T19:16:31.052Z",
+ "created_by": "yo",
+ "description": "This is a sample endpoint type exception",
+ "id": "bcb94680-a117-11ea-ad9d-c71f4820e65b",
+ "list_id": "endpoint_list",
+ "name": "Sample Endpoint Exception List",
+ "namespace_type": "single",
+ "tags": [
+ "user added string for a tag",
+ "malware"
+ ],
+ "tie_breaker_id": "86e08c8c-c970-4b08-a6e2-cdba7bb4e023",
+ "type": "endpoint",
+ "updated_at": "2020-05-28T19:16:31.080Z",
+ "updated_by": "yo"
+ }
+ ],
+ "page": 1,
+ "per_page": 20,
+ "total": 1
+}
+```
+
+See the full scripts folder for all the capabilities.
diff --git a/x-pack/plugins/lists/common/constants.mock.ts b/x-pack/plugins/lists/common/constants.mock.ts
index 8c5f6b0cbe56c..d8e4dfba1599e 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 96d28bf618ce4..6cb88b19483ce 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 0000000000000..f9af10245b7ee
--- /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/common/schemas/response/exception_list_item_schema.mock.ts b/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.mock.ts
new file mode 100644
index 0000000000000..901715b601b80
--- /dev/null
+++ b/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.mock.ts
@@ -0,0 +1,40 @@
+/*
+ * 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 { ExceptionListItemSchema } from './exception_list_item_schema';
+
+export const getExceptionListItemSchemaMock = (): ExceptionListItemSchema => ({
+ _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',
+});
diff --git a/x-pack/plugins/lists/common/schemas/response/exception_list_schema.mock.ts b/x-pack/plugins/lists/common/schemas/response/exception_list_schema.mock.ts
new file mode 100644
index 0000000000000..017b959a2baf3
--- /dev/null
+++ b/x-pack/plugins/lists/common/schemas/response/exception_list_schema.mock.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { ExceptionListSchema } from './exception_list_schema';
+
+export const getExceptionListSchemaMock = (): ExceptionListSchema => ({
+ _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',
+});
diff --git a/x-pack/plugins/lists/common/schemas/response/found_exception_list_item_schema.mock.ts b/x-pack/plugins/lists/common/schemas/response/found_exception_list_item_schema.mock.ts
new file mode 100644
index 0000000000000..f760e602605ba
--- /dev/null
+++ b/x-pack/plugins/lists/common/schemas/response/found_exception_list_item_schema.mock.ts
@@ -0,0 +1,15 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { getExceptionListItemSchemaMock } from './exception_list_item_schema.mock';
+import { FoundExceptionListItemSchema } from './found_exception_list_item_schema';
+
+export const getFoundExceptionListItemSchemaMock = (): FoundExceptionListItemSchema => ({
+ data: [getExceptionListItemSchemaMock()],
+ page: 1,
+ per_page: 1,
+ total: 1,
+});
diff --git a/x-pack/plugins/lists/common/schemas/response/found_exception_list_schema.mock.ts b/x-pack/plugins/lists/common/schemas/response/found_exception_list_schema.mock.ts
new file mode 100644
index 0000000000000..ce71a27dbc4d4
--- /dev/null
+++ b/x-pack/plugins/lists/common/schemas/response/found_exception_list_schema.mock.ts
@@ -0,0 +1,15 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { getExceptionListSchemaMock } from './exception_list_schema.mock';
+import { FoundExceptionListSchema } from './found_exception_list_schema';
+
+export const getFoundExceptionListSchemaMock = (): FoundExceptionListSchema => ({
+ data: [getExceptionListSchemaMock()],
+ page: 1,
+ per_page: 1,
+ total: 1,
+});
diff --git a/x-pack/plugins/lists/common/schemas/response/found_list_item_schema.mock.ts b/x-pack/plugins/lists/common/schemas/response/found_list_item_schema.mock.ts
new file mode 100644
index 0000000000000..e96188c619d78
--- /dev/null
+++ b/x-pack/plugins/lists/common/schemas/response/found_list_item_schema.mock.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { FoundListItemSchema } from './found_list_item_schema';
+import { getListItemResponseMock } from './list_item_schema.mock';
+
+export const getFoundListItemSchemaMock = (): FoundListItemSchema => ({
+ cursor: '123',
+ data: [getListItemResponseMock()],
+ page: 1,
+ per_page: 1,
+ total: 1,
+});
diff --git a/x-pack/plugins/lists/common/schemas/response/found_list_schema.mock.ts b/x-pack/plugins/lists/common/schemas/response/found_list_schema.mock.ts
new file mode 100644
index 0000000000000..63d6a3b220ac1
--- /dev/null
+++ b/x-pack/plugins/lists/common/schemas/response/found_list_schema.mock.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { FoundListSchema } from './found_list_schema';
+import { getListResponseMock } from './list_schema.mock';
+
+export const getFoundListSchemaMock = (): FoundListSchema => ({
+ cursor: '123',
+ data: [getListResponseMock()],
+ page: 1,
+ per_page: 1,
+ total: 1,
+});
diff --git a/x-pack/plugins/lists/public/exceptions/__mocks__/api.ts b/x-pack/plugins/lists/public/exceptions/__mocks__/api.ts
index f624189915dcf..ecc771279b3ab 100644
--- a/x-pack/plugins/lists/public/exceptions/__mocks__/api.ts
+++ b/x-pack/plugins/lists/public/exceptions/__mocks__/api.ts
@@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { getExceptionListItemSchemaMock } from '../../../common/schemas/response/exception_list_item_schema.mock';
+import { getExceptionListSchemaMock } from '../../../common/schemas/response/exception_list_schema.mock';
import {
ExceptionListItemSchema,
ExceptionListSchema,
@@ -15,37 +17,40 @@ import {
ApiCallByIdProps,
ApiCallByListIdProps,
} from '../types';
-import { mockExceptionItem, mockExceptionList } from '../mock';
/* eslint-disable @typescript-eslint/no-unused-vars */
export const addExceptionList = async ({
http,
list,
signal,
-}: AddExceptionListProps): Promise => Promise.resolve(mockExceptionList);
+}: AddExceptionListProps): Promise =>
+ Promise.resolve(getExceptionListSchemaMock());
export const addExceptionListItem = async ({
http,
listItem,
signal,
}: AddExceptionListItemProps): Promise =>
- Promise.resolve(mockExceptionItem);
+ Promise.resolve(getExceptionListItemSchemaMock());
export const fetchExceptionListById = async ({
http,
id,
signal,
-}: ApiCallByIdProps): Promise => Promise.resolve(mockExceptionList);
+}: ApiCallByIdProps): Promise => Promise.resolve(getExceptionListSchemaMock());
export const fetchExceptionListItemsByListId = async ({
+ filterOptions,
http,
listId,
+ pagination,
signal,
}: ApiCallByListIdProps): Promise =>
- Promise.resolve({ data: [mockExceptionItem], page: 1, per_page: 20, total: 1 });
+ Promise.resolve({ data: [getExceptionListItemSchemaMock()], page: 1, per_page: 20, total: 1 });
export const fetchExceptionListItemById = async ({
http,
id,
signal,
-}: ApiCallByIdProps): Promise => Promise.resolve(mockExceptionItem);
+}: ApiCallByIdProps): Promise =>
+ Promise.resolve(getExceptionListItemSchemaMock());
diff --git a/x-pack/plugins/lists/public/exceptions/api.test.ts b/x-pack/plugins/lists/public/exceptions/api.test.ts
index 3a61140e5621d..b9512bb398745 100644
--- a/x-pack/plugins/lists/public/exceptions/api.test.ts
+++ b/x-pack/plugins/lists/public/exceptions/api.test.ts
@@ -4,13 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
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 {
- mockExceptionItem,
- mockExceptionList,
- mockNewExceptionItem,
- mockNewExceptionList,
-} from './mock';
import {
addExceptionList,
addExceptionListItem,
@@ -40,246 +38,355 @@ const mockKibanaHttpService = ((createKibanaCoreStartMock() as unknown) as jest.
);
describe('Exceptions Lists API', () => {
- describe('addExceptionList', () => {
+ describe('#addExceptionList', () => {
beforeEach(() => {
fetchMock.mockClear();
- fetchMock.mockResolvedValue(mockExceptionList);
+ 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: mockExceptionList,
+ 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(mockExceptionList);
+ expect(exceptionResponse).toEqual(getExceptionListSchemaMock());
});
});
- describe('addExceptionListItem', () => {
+ describe('#addExceptionListItem', () => {
beforeEach(() => {
fetchMock.mockClear();
- fetchMock.mockResolvedValue(mockExceptionItem);
+ 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: mockExceptionItem,
+ 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(mockExceptionItem);
+ expect(exceptionResponse).toEqual(getExceptionListItemSchemaMock());
});
});
- describe('fetchExceptionListById', () => {
+ describe('#fetchExceptionListById', () => {
beforeEach(() => {
fetchMock.mockClear();
- fetchMock.mockResolvedValue(mockExceptionList);
+ 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(mockExceptionList);
+ 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(mockExceptionList);
+ fetchMock.mockResolvedValue(getExceptionListSchemaMock());
});
test('check parameter url, body when deleting exception item', async () => {
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(mockExceptionList);
+ expect(exceptionResponse).toEqual(getExceptionListSchemaMock());
});
});
- describe('deleteExceptionListItemById', () => {
+ describe('#deleteExceptionListItemById', () => {
beforeEach(() => {
fetchMock.mockClear();
- fetchMock.mockResolvedValue(mockExceptionItem);
+ fetchMock.mockResolvedValue(getExceptionListItemSchemaMock());
});
test('check parameter url, body when deleting exception item', async () => {
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(mockExceptionItem);
+ 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 fdd9d62539e06..6968ba5f50e72 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 098ee1f81f492..1db18168b11fe 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 { mockExceptionItem } from '../mock';
+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](mockExceptionItem);
+ 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](mockExceptionItem);
+ 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 0ed007e805013..d9fe3a82ac177 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 5cad95a38dbec..80d6e27043c99 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 { mockExceptionList } from '../mock';
+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](mockExceptionList);
+ 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](mockExceptionList);
+ 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 45330c9725ae7..5848a17145194 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 308d1cf4d1b17..a6a25ab4d4e9d 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 d0ac357e05aa0..116233cd89348 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/mock.ts b/x-pack/plugins/lists/public/exceptions/mock.ts
index 38a0e65992982..fd06dac65c6fb 100644
--- a/x-pack/plugins/lists/public/exceptions/mock.ts
+++ b/x-pack/plugins/lists/public/exceptions/mock.ts
@@ -6,27 +6,8 @@
import {
CreateExceptionListItemSchemaPartial,
CreateExceptionListSchemaPartial,
- ExceptionListItemSchema,
- ExceptionListSchema,
} from '../../common/schemas';
-export const mockExceptionList: ExceptionListSchema = {
- _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',
-};
-
export const mockNewExceptionList: CreateExceptionListSchemaPartial = {
_tags: ['endpoint', 'process', 'malware', 'os:linux'],
description: 'This is a sample endpoint type exception',
@@ -59,36 +40,3 @@ export const mockNewExceptionItem: CreateExceptionListItemSchemaPartial = {
tags: ['user added string for a tag', 'malware'],
type: 'simple',
};
-
-export const mockExceptionItem: ExceptionListItemSchema = {
- _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',
-};
diff --git a/x-pack/plugins/lists/public/exceptions/types.ts b/x-pack/plugins/lists/public/exceptions/types.ts
index fcf2108e7323a..cf6b6c3ec1c59 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;
}
diff --git a/x-pack/plugins/lists/public/index.tsx b/x-pack/plugins/lists/public/index.tsx
index b23f31abd4d87..fb4d5de06ae54 100644
--- a/x-pack/plugins/lists/public/index.tsx
+++ b/x-pack/plugins/lists/public/index.tsx
@@ -7,9 +7,4 @@
export { usePersistExceptionItem } from './exceptions/hooks/persist_exception_item';
export { usePersistExceptionList } from './exceptions/hooks/persist_exception_list';
export { useExceptionList } from './exceptions/hooks/use_exception_list';
-export {
- mockExceptionItem,
- mockExceptionList,
- mockNewExceptionItem,
- mockNewExceptionList,
-} from './exceptions/mock';
+export { mockNewExceptionItem, mockNewExceptionList } from './exceptions/mock';
diff --git a/x-pack/plugins/lists/server/get_user.test.ts b/x-pack/plugins/lists/server/get_user.test.ts
index 0992e3c361fcf..a1c78f5ea4684 100644
--- a/x-pack/plugins/lists/server/get_user.test.ts
+++ b/x-pack/plugins/lists/server/get_user.test.ts
@@ -8,7 +8,6 @@ import { httpServerMock } from 'src/core/server/mocks';
import { KibanaRequest } from 'src/core/server';
import { securityMock } from '../../security/server/mocks';
-import { SecurityPluginSetup } from '../../security/server';
import { getUser } from './get_user';
@@ -24,42 +23,42 @@ describe('get_user', () => {
});
test('it returns "bob" as the user given a security request with "bob"', () => {
- const security: SecurityPluginSetup = securityMock.createSetup();
+ const security = securityMock.createSetup();
security.authc.getCurrentUser = jest.fn().mockReturnValue({ username: 'bob' });
const user = getUser({ request, security });
expect(user).toEqual('bob');
});
test('it returns "alice" as the user given a security request with "alice"', () => {
- const security: SecurityPluginSetup = securityMock.createSetup();
+ const security = securityMock.createSetup();
security.authc.getCurrentUser = jest.fn().mockReturnValue({ username: 'alice' });
const user = getUser({ request, security });
expect(user).toEqual('alice');
});
test('it returns "elastic" as the user given null as the current user', () => {
- const security: SecurityPluginSetup = securityMock.createSetup();
+ const security = securityMock.createSetup();
security.authc.getCurrentUser = jest.fn().mockReturnValue(null);
const user = getUser({ request, security });
expect(user).toEqual('elastic');
});
test('it returns "elastic" as the user given undefined as the current user', () => {
- const security: SecurityPluginSetup = securityMock.createSetup();
+ const security = securityMock.createSetup();
security.authc.getCurrentUser = jest.fn().mockReturnValue(undefined);
const user = getUser({ request, security });
expect(user).toEqual('elastic');
});
test('it returns "elastic" as the user given undefined as the plugin', () => {
- const security: SecurityPluginSetup = securityMock.createSetup();
+ const security = securityMock.createSetup();
security.authc.getCurrentUser = jest.fn().mockReturnValue(undefined);
const user = getUser({ request, security: undefined });
expect(user).toEqual('elastic');
});
test('it returns "elastic" as the user given null as the plugin', () => {
- const security: SecurityPluginSetup = securityMock.createSetup();
+ const security = securityMock.createSetup();
security.authc.getCurrentUser = jest.fn().mockReturnValue(undefined);
const user = getUser({ request, security: null });
expect(user).toEqual('elastic');
diff --git a/x-pack/plugins/lists/server/mocks.ts b/x-pack/plugins/lists/server/mocks.ts
new file mode 100644
index 0000000000000..aad4a25a900a1
--- /dev/null
+++ b/x-pack/plugins/lists/server/mocks.ts
@@ -0,0 +1,23 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { ListPluginSetup } from './types';
+import { getListClientMock } from './services/lists/list_client.mock';
+import { getExceptionListClientMock } from './services/exception_lists/exception_list_client.mock';
+
+const createSetupMock = (): jest.Mocked => {
+ const mock: jest.Mocked = {
+ getExceptionListClient: jest.fn().mockReturnValue(getExceptionListClientMock()),
+ getListClient: jest.fn().mockReturnValue(getListClientMock()),
+ };
+ return mock;
+};
+
+export const listMock = {
+ createSetup: createSetupMock,
+ getExceptionList: getExceptionListClientMock,
+ getListClient: getListClientMock,
+};
diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts
new file mode 100644
index 0000000000000..d0e238f8c5c40
--- /dev/null
+++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { savedObjectsClientMock } from 'src/core/server/mocks';
+
+import { getFoundExceptionListSchemaMock } from '../../../common/schemas/response/found_exception_list_schema.mock';
+import { getFoundExceptionListItemSchemaMock } from '../../../common/schemas/response/found_exception_list_item_schema.mock';
+import { getExceptionListItemSchemaMock } from '../../../common/schemas/response/exception_list_item_schema.mock';
+import { getExceptionListSchemaMock } from '../../../common/schemas/response/exception_list_schema.mock';
+
+import { ExceptionListClient } from './exception_list_client';
+
+export class ExceptionListClientMock extends ExceptionListClient {
+ public getExceptionList = jest.fn().mockResolvedValue(getExceptionListSchemaMock());
+ public getExceptionListItem = jest.fn().mockResolvedValue(getExceptionListItemSchemaMock());
+ public createExceptionList = jest.fn().mockResolvedValue(getExceptionListSchemaMock());
+ public updateExceptionList = jest.fn().mockResolvedValue(getExceptionListSchemaMock());
+ public deleteExceptionList = jest.fn().mockResolvedValue(getExceptionListSchemaMock());
+ public createExceptionListItem = jest.fn().mockResolvedValue(getExceptionListItemSchemaMock());
+ public updateExceptionListItem = jest.fn().mockResolvedValue(getExceptionListItemSchemaMock());
+ public deleteExceptionListItem = jest.fn().mockResolvedValue(getExceptionListItemSchemaMock());
+ public findExceptionListItem = jest.fn().mockResolvedValue(getFoundExceptionListItemSchemaMock());
+ public findExceptionList = jest.fn().mockResolvedValue(getFoundExceptionListSchemaMock());
+}
+
+export const getExceptionListClientMock = (): ExceptionListClient => {
+ const mock = new ExceptionListClientMock({
+ savedObjectsClient: savedObjectsClientMock.create(),
+ user: 'elastic',
+ });
+ return mock;
+};
diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.test.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.test.ts
new file mode 100644
index 0000000000000..f91331f5b4308
--- /dev/null
+++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.test.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 { getExceptionListItemSchemaMock } from '../../../common/schemas/response/exception_list_item_schema.mock';
+import { getExceptionListSchemaMock } from '../../../common/schemas/response/exception_list_schema.mock';
+
+import { getExceptionListClientMock } from './exception_list_client.mock';
+
+describe('exception_list_client', () => {
+ describe('Mock client sanity checks', () => {
+ test('it returns the exception list as expected', async () => {
+ const mock = getExceptionListClientMock();
+ const list = await mock.getExceptionList({
+ id: '123',
+ listId: '123',
+ namespaceType: 'single',
+ });
+ expect(list).toEqual(getExceptionListSchemaMock());
+ });
+
+ test('it returns the the exception list item as expected', async () => {
+ const mock = getExceptionListClientMock();
+ const listItem = await mock.getExceptionListItem({
+ id: '123',
+ itemId: '123',
+ namespaceType: 'single',
+ });
+ expect(listItem).toEqual(getExceptionListItemSchemaMock());
+ });
+ });
+});
diff --git a/x-pack/plugins/lists/server/services/lists/list_client.mock.ts b/x-pack/plugins/lists/server/services/lists/list_client.mock.ts
new file mode 100644
index 0000000000000..43a01a3ca62dc
--- /dev/null
+++ b/x-pack/plugins/lists/server/services/lists/list_client.mock.ts
@@ -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 { getFoundListItemSchemaMock } from '../../../common/schemas/response/found_list_item_schema.mock';
+import { getFoundListSchemaMock } from '../../../common/schemas/response/found_list_schema.mock';
+import { getListItemResponseMock } from '../../../common/schemas/response/list_item_schema.mock';
+import { getListResponseMock } from '../../../common/schemas/response/list_schema.mock';
+import { getCallClusterMock } from '../../../common/get_call_cluster.mock';
+import { LIST_INDEX, LIST_ITEM_INDEX } from '../../../common/constants.mock';
+
+import { ListClient } from './list_client';
+
+export class ListClientMock extends ListClient {
+ public getListIndex = jest.fn().mockReturnValue(LIST_INDEX);
+ public getListItemIndex = jest.fn().mockReturnValue(LIST_ITEM_INDEX);
+ public getList = jest.fn().mockResolvedValue(getListResponseMock());
+ public createList = jest.fn().mockResolvedValue(getListResponseMock());
+ public createListIfItDoesNotExist = jest.fn().mockResolvedValue(getListResponseMock());
+ public getListIndexExists = jest.fn().mockResolvedValue(true);
+ public getListItemIndexExists = jest.fn().mockResolvedValue(true);
+ public createListBootStrapIndex = jest.fn().mockResolvedValue({});
+ public createListItemBootStrapIndex = jest.fn().mockResolvedValue({});
+ public getListPolicyExists = jest.fn().mockResolvedValue(true);
+ public getListItemPolicyExists = jest.fn().mockResolvedValue(true);
+ public getListTemplateExists = jest.fn().mockResolvedValue(true);
+ public getListItemTemplateExists = jest.fn().mockResolvedValue(true);
+ public getListTemplate = jest.fn().mockResolvedValue({});
+ public getListItemTemplate = jest.fn().mockResolvedValue({});
+ public setListTemplate = jest.fn().mockResolvedValue({});
+ public setListItemTemplate = jest.fn().mockResolvedValue({});
+ public setListPolicy = jest.fn().mockResolvedValue({});
+ public setListItemPolicy = jest.fn().mockResolvedValue({});
+ public deleteListIndex = jest.fn().mockResolvedValue(true);
+ public deleteListItemIndex = jest.fn().mockResolvedValue(true);
+ public deleteListPolicy = jest.fn().mockResolvedValue({});
+ public deleteListItemPolicy = jest.fn().mockResolvedValue({});
+ public deleteListTemplate = jest.fn().mockResolvedValue({});
+ public deleteListItemTemplate = jest.fn().mockResolvedValue({});
+ public deleteListItem = jest.fn().mockResolvedValue(getListItemResponseMock());
+ public deleteListItemByValue = jest.fn().mockResolvedValue(getListItemResponseMock());
+ public deleteList = jest.fn().mockResolvedValue(getListResponseMock());
+ public exportListItemsToStream = jest.fn().mockResolvedValue(undefined);
+ public importListItemsToStream = jest.fn().mockResolvedValue(undefined);
+ public getListItemByValue = jest.fn().mockResolvedValue([getListItemResponseMock()]);
+ public createListItem = jest.fn().mockResolvedValue(getListItemResponseMock());
+ public updateListItem = jest.fn().mockResolvedValue(getListItemResponseMock());
+ public updateList = jest.fn().mockResolvedValue(getListResponseMock());
+ public getListItem = jest.fn().mockResolvedValue(getListItemResponseMock());
+ public getListItemByValues = jest.fn().mockResolvedValue([getListItemResponseMock()]);
+ public findList = jest.fn().mockResolvedValue(getFoundListSchemaMock());
+ public findListItem = jest.fn().mockResolvedValue(getFoundListItemSchemaMock());
+}
+
+export const getListClientMock = (): ListClient => {
+ const mock = new ListClientMock({
+ callCluster: getCallClusterMock(),
+ config: {
+ enabled: true,
+ listIndex: LIST_INDEX,
+ listItemIndex: LIST_ITEM_INDEX,
+ },
+ spaceId: 'default',
+ user: 'elastic',
+ });
+ return mock;
+};
diff --git a/x-pack/plugins/lists/server/services/lists/list_client.test.ts b/x-pack/plugins/lists/server/services/lists/list_client.test.ts
new file mode 100644
index 0000000000000..0c3a58283ffe2
--- /dev/null
+++ b/x-pack/plugins/lists/server/services/lists/list_client.test.ts
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { getListItemResponseMock } from '../../../common/schemas/response/list_item_schema.mock';
+import { LIST_INDEX, LIST_ITEM_INDEX } from '../../../common/constants.mock';
+
+import { getListClientMock } from './list_client.mock';
+
+describe('list_client', () => {
+ describe('Mock client sanity checks', () => {
+ test('it returns the get list index as expected', () => {
+ const mock = getListClientMock();
+ expect(mock.getListIndex()).toEqual(LIST_INDEX);
+ });
+
+ test('it returns the get list item index as expected', () => {
+ const mock = getListClientMock();
+ expect(mock.getListItemIndex()).toEqual(LIST_ITEM_INDEX);
+ });
+
+ test('it returns a mock list item', async () => {
+ const mock = getListClientMock();
+ const listItem = await mock.getListItem({ id: '123' });
+ expect(listItem).toEqual(getListItemResponseMock());
+ });
+ });
+});
diff --git a/x-pack/plugins/lists/server/services/lists/types.ts b/x-pack/plugins/lists/server/services/lists/types.ts
index 2e0e4b7d038e7..47419929c43a6 100644
--- a/x-pack/plugins/lists/server/services/lists/types.ts
+++ b/x-pack/plugins/lists/server/services/lists/types.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-interface Scroll {
+export interface Scroll {
searchAfter: string[] | undefined;
validSearchAfterFound: boolean;
}
diff --git a/x-pack/plugins/lists/server/services/utils/get_search_after_scroll.ts b/x-pack/plugins/lists/server/services/utils/get_search_after_scroll.ts
index 9721baefbe5ee..2af501106d659 100644
--- a/x-pack/plugins/lists/server/services/utils/get_search_after_scroll.ts
+++ b/x-pack/plugins/lists/server/services/utils/get_search_after_scroll.ts
@@ -7,6 +7,7 @@
import { APICaller } from 'kibana/server';
import { Filter, SortFieldOrUndefined, SortOrderOrUndefined } from '../../../common/schemas';
+import { Scroll } from '../lists/types';
import { getQueryFilter } from './get_query_filter';
import { getSortWithTieBreaker } from './get_sort_with_tie_breaker';
diff --git a/x-pack/plugins/lists/server/services/utils/scroll_to_start_page.ts b/x-pack/plugins/lists/server/services/utils/scroll_to_start_page.ts
index 16e07044dc0d4..6b898a54bb9fe 100644
--- a/x-pack/plugins/lists/server/services/utils/scroll_to_start_page.ts
+++ b/x-pack/plugins/lists/server/services/utils/scroll_to_start_page.ts
@@ -7,6 +7,7 @@
import { APICaller } from 'kibana/server';
import { Filter, SortFieldOrUndefined, SortOrderOrUndefined } from '../../../common/schemas';
+import { Scroll } from '../lists/types';
import { calculateScrollMath } from './calculate_scroll_math';
import { getSearchAfterScroll } from './get_search_after_scroll';
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 0000000000000..5ba7f9c191a7f
--- /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/components/job_messages/job_messages.tsx b/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx
index 9cea47ded09b4..fd2b7902833a6 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/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 2ef1515726d1b..33217f127f998 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 2923938ae68ac..2d433f6b18484 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/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 fc860251bf83d..0dd9eba172e1c 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/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 7383f565bd673..26cefff0a3f59 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 0d1a87e7c4c1f..68aa58e7e1f19 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/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 fbb64db94cd56..486de90d2299c 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 (
+
+ );
};
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 89950a659f609..7cdd5478e3983 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 16e25067fd91e..e2569f6217b34 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