From e6e7354a9e44416b1242bee9ffed3105579f97bc Mon Sep 17 00:00:00 2001
From: Poff Poffenberger
Date: Tue, 18 Aug 2020 12:39:46 -0500
Subject: [PATCH 01/85] [Canvas] Remove dependency on legacy expressions APIs
(#74885)
* Remove legacy types and function registration
* Pull server interpreter functions routes into Canvas and update them to use new expressions API
* Clean up comment
* Removing boom and doing more cleanup
* Add functions test and refactor other router tests
* Adding a type and refactoring a forgotten test
* more tests
Co-authored-by: Elastic Machine
---
x-pack/plugins/canvas/common/lib/constants.ts | 1 +
x-pack/plugins/canvas/kibana.json | 2 +-
x-pack/plugins/canvas/public/application.tsx | 2 +-
.../canvas/public/functions/filters.ts | 2 +-
.../plugins/canvas/public/functions/index.ts | 2 +-
x-pack/plugins/canvas/public/functions/to.ts | 2 +-
x-pack/plugins/canvas/public/plugin.tsx | 2 +
.../canvas/public/services/expressions.ts | 39 +++++++++-
x-pack/plugins/canvas/public/store.ts | 3 +-
x-pack/plugins/canvas/server/plugin.ts | 10 ++-
.../routes/custom_elements/create.test.ts | 19 ++---
.../routes/custom_elements/delete.test.ts | 18 ++---
.../routes/custom_elements/find.test.ts | 18 ++---
.../server/routes/custom_elements/get.test.ts | 18 ++---
.../routes/custom_elements/update.test.ts | 18 ++---
.../server/routes/es_fields/es_fields.test.ts | 18 ++---
.../server/routes/functions/functions.test.ts | 51 +++++++++++++
.../server/routes/functions/functions.ts | 73 +++++++++++++++++++
.../canvas/server/routes/functions/index.ts | 13 ++++
x-pack/plugins/canvas/server/routes/index.ts | 9 ++-
.../server/routes/shareables/download.test.ts | 13 ++--
.../server/routes/shareables/zip.test.ts | 14 ++--
.../server/routes/templates/list.test.ts | 25 ++-----
.../canvas/server/routes/test_helpers.ts | 29 ++++++++
.../server/routes/workpad/create.test.ts | 19 ++---
.../server/routes/workpad/delete.test.ts | 18 ++---
.../canvas/server/routes/workpad/find.test.ts | 18 ++---
.../canvas/server/routes/workpad/get.test.ts | 18 ++---
.../server/routes/workpad/update.test.ts | 29 +++-----
.../canvas/server/setup_interpreter.ts | 2 +-
30 files changed, 306 insertions(+), 199 deletions(-)
create mode 100644 x-pack/plugins/canvas/server/routes/functions/functions.test.ts
create mode 100644 x-pack/plugins/canvas/server/routes/functions/functions.ts
create mode 100644 x-pack/plugins/canvas/server/routes/functions/index.ts
create mode 100644 x-pack/plugins/canvas/server/routes/test_helpers.ts
diff --git a/x-pack/plugins/canvas/common/lib/constants.ts b/x-pack/plugins/canvas/common/lib/constants.ts
index e960a86bd76dc..d514fb53b4a4b 100644
--- a/x-pack/plugins/canvas/common/lib/constants.ts
+++ b/x-pack/plugins/canvas/common/lib/constants.ts
@@ -42,3 +42,4 @@ export const API_ROUTE_SHAREABLE_RUNTIME = '/public/canvas/runtime';
export const API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD = `/public/canvas/${SHAREABLE_RUNTIME_NAME}.js`;
export const CANVAS_EMBEDDABLE_CLASSNAME = `canvasEmbeddable`;
export const CONTEXT_MENU_TOP_BORDER_CLASSNAME = 'canvasContextMenu--topBorder';
+export const API_ROUTE_FUNCTIONS = `${API_ROUTE}/fns`;
diff --git a/x-pack/plugins/canvas/kibana.json b/x-pack/plugins/canvas/kibana.json
index 5f4ea5802cb13..68fbec3e8429d 100644
--- a/x-pack/plugins/canvas/kibana.json
+++ b/x-pack/plugins/canvas/kibana.json
@@ -5,7 +5,7 @@
"configPath": ["xpack", "canvas"],
"server": true,
"ui": true,
- "requiredPlugins": ["data", "embeddable", "expressions", "features", "home", "inspector", "uiActions"],
+ "requiredPlugins": ["bfetch", "data", "embeddable", "expressions", "features", "home", "inspector", "uiActions"],
"optionalPlugins": ["usageCollection"],
"requiredBundles": ["kibanaReact", "maps", "lens", "visualizations", "kibanaUtils", "kibanaLegacy", "discover", "savedObjects", "reporting"]
}
diff --git a/x-pack/plugins/canvas/public/application.tsx b/x-pack/plugins/canvas/public/application.tsx
index 90173a20500e5..482cd04373105 100644
--- a/x-pack/plugins/canvas/public/application.tsx
+++ b/x-pack/plugins/canvas/public/application.tsx
@@ -86,7 +86,7 @@ export const initializeCanvas = async (
const canvasFunctions = initFunctions({
timefilter: setupPlugins.data.query.timefilter.timefilter,
prependBasePath: coreSetup.http.basePath.prepend,
- typesRegistry: setupPlugins.expressions.__LEGACY.types,
+ types: setupPlugins.expressions.getTypes(),
});
for (const fn of canvasFunctions) {
diff --git a/x-pack/plugins/canvas/public/functions/filters.ts b/x-pack/plugins/canvas/public/functions/filters.ts
index 61fa67dc63316..fdb5d69d35515 100644
--- a/x-pack/plugins/canvas/public/functions/filters.ts
+++ b/x-pack/plugins/canvas/public/functions/filters.ts
@@ -81,7 +81,7 @@ export function filtersFunctionFactory(initialize: InitializeArguments): () => F
const filterAST = fromExpression(filterExpression);
return interpretAst(filterAST, getWorkpadVariablesAsObject(getState()));
} else {
- const filterType = initialize.typesRegistry.get('filter');
+ const filterType = initialize.types.filter;
return filterType?.from(null, {});
}
},
diff --git a/x-pack/plugins/canvas/public/functions/index.ts b/x-pack/plugins/canvas/public/functions/index.ts
index 5e098d8f175c5..a7893162be8f8 100644
--- a/x-pack/plugins/canvas/public/functions/index.ts
+++ b/x-pack/plugins/canvas/public/functions/index.ts
@@ -12,7 +12,7 @@ import { CanvasSetupDeps, CoreSetup } from '../plugin';
export interface InitializeArguments {
prependBasePath: CoreSetup['http']['basePath']['prepend'];
- typesRegistry: CanvasSetupDeps['expressions']['__LEGACY']['types'];
+ types: ReturnType;
timefilter: CanvasSetupDeps['data']['query']['timefilter']['timefilter'];
}
diff --git a/x-pack/plugins/canvas/public/functions/to.ts b/x-pack/plugins/canvas/public/functions/to.ts
index 032873dfa6cf2..36b2d3f9f04c6 100644
--- a/x-pack/plugins/canvas/public/functions/to.ts
+++ b/x-pack/plugins/canvas/public/functions/to.ts
@@ -38,7 +38,7 @@ export function toFunctionFactory(initialize: InitializeArguments): () => ToFunc
throw errors.missingType();
}
- return castProvider(initialize.typesRegistry.toJS())(input, args.type);
+ return castProvider(initialize.types)(input, args.type);
},
};
};
diff --git a/x-pack/plugins/canvas/public/plugin.tsx b/x-pack/plugins/canvas/public/plugin.tsx
index 4829a94bb0db8..0269774a446c1 100644
--- a/x-pack/plugins/canvas/public/plugin.tsx
+++ b/x-pack/plugins/canvas/public/plugin.tsx
@@ -24,6 +24,7 @@ import { UiActionsStart } from '../../../../src/plugins/ui_actions/public';
import { EmbeddableStart } from '../../../../src/plugins/embeddable/public';
import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public';
import { Start as InspectorStart } from '../../../../src/plugins/inspector/public';
+import { BfetchPublicSetup } from '../../../../src/plugins/bfetch/public';
// @ts-expect-error untyped local
import { argTypeSpecs } from './expression_types/arg_types';
import { transitions } from './transitions';
@@ -41,6 +42,7 @@ export interface CanvasSetupDeps {
expressions: ExpressionsSetup;
home: HomePublicPluginSetup;
usageCollection?: UsageCollectionSetup;
+ bfetch: BfetchPublicSetup;
}
export interface CanvasStartDeps {
diff --git a/x-pack/plugins/canvas/public/services/expressions.ts b/x-pack/plugins/canvas/public/services/expressions.ts
index 1376aab0ca8b9..87a657641acfd 100644
--- a/x-pack/plugins/canvas/public/services/expressions.ts
+++ b/x-pack/plugins/canvas/public/services/expressions.ts
@@ -5,7 +5,11 @@
*/
import { CanvasServiceFactory } from '.';
-import { ExpressionsService } from '../../../../../src/plugins/expressions/common';
+import {
+ ExpressionsService,
+ serializeProvider,
+} from '../../../../../src/plugins/expressions/common';
+import { API_ROUTE_FUNCTIONS } from '../../common/lib/constants';
export const expressionsServiceFactory: CanvasServiceFactory = async (
coreSetup,
@@ -13,6 +17,37 @@ export const expressionsServiceFactory: CanvasServiceFactory
setupPlugins,
startPlugins
) => {
- await setupPlugins.expressions.__LEGACY.loadLegacyServerFunctionWrappers();
+ const { expressions, bfetch } = setupPlugins;
+
+ let cached: Promise | null = null;
+ const loadServerFunctionWrappers = async () => {
+ if (!cached) {
+ cached = (async () => {
+ const serverFunctionList = await coreSetup.http.get(API_ROUTE_FUNCTIONS);
+ const batchedFunction = bfetch.batchedFunction({ url: API_ROUTE_FUNCTIONS });
+ const { serialize } = serializeProvider(expressions.getTypes());
+
+ // For every sever-side function, register a client-side
+ // function that matches its definition, but which simply
+ // calls the server-side function endpoint.
+ Object.keys(serverFunctionList).forEach((functionName) => {
+ if (expressions.getFunction(functionName)) {
+ return;
+ }
+ const fn = () => ({
+ ...serverFunctionList[functionName],
+ fn: (input: any, args: any) => {
+ return batchedFunction({ functionName, args, context: serialize(input) });
+ },
+ });
+ expressions.registerFunction(fn);
+ });
+ })();
+ }
+ return cached;
+ };
+
+ await loadServerFunctionWrappers();
+
return setupPlugins.expressions.fork();
};
diff --git a/x-pack/plugins/canvas/public/store.ts b/x-pack/plugins/canvas/public/store.ts
index ef93a34296da2..b1c10f8d46d27 100644
--- a/x-pack/plugins/canvas/public/store.ts
+++ b/x-pack/plugins/canvas/public/store.ts
@@ -15,6 +15,7 @@ import {
import { getInitialState } from './state/initial_state';
import { CoreSetup } from '../../../../src/core/public';
+import { API_ROUTE_FUNCTIONS } from '../common/lib/constants';
import { CanvasSetupDeps } from './plugin';
export async function createStore(core: CoreSetup, plugins: CanvasSetupDeps) {
@@ -31,7 +32,7 @@ export async function createFreshStore(core: CoreSetup, plugins: CanvasSetupDeps
const basePath = core.http.basePath.get();
// Retrieve server functions
- const serverFunctionsResponse = await core.http.get(`/api/interpreter/fns`);
+ const serverFunctionsResponse = await core.http.get(API_ROUTE_FUNCTIONS);
const serverFunctions = Object.values(serverFunctionsResponse);
initialState.app = {
diff --git a/x-pack/plugins/canvas/server/plugin.ts b/x-pack/plugins/canvas/server/plugin.ts
index 4fa7e2d934647..c822ed86cb01c 100644
--- a/x-pack/plugins/canvas/server/plugin.ts
+++ b/x-pack/plugins/canvas/server/plugin.ts
@@ -7,6 +7,7 @@
import { first } from 'rxjs/operators';
import { CoreSetup, PluginInitializerContext, Plugin, Logger, CoreStart } from 'src/core/server';
import { ExpressionsServerSetup } from 'src/plugins/expressions/server';
+import { BfetchServerSetup } from 'src/plugins/bfetch/server';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { HomeServerPluginSetup } from 'src/plugins/home/server';
import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server';
@@ -21,6 +22,7 @@ interface PluginsSetup {
expressions: ExpressionsServerSetup;
features: FeaturesPluginSetup;
home: HomeServerPluginSetup;
+ bfetch: BfetchServerSetup;
usageCollection?: UsageCollectionSetup;
}
@@ -67,7 +69,13 @@ export class CanvasPlugin implements Plugin {
const canvasRouter = coreSetup.http.createRouter();
- initRoutes({ router: canvasRouter, logger: this.logger });
+ initRoutes({
+ router: canvasRouter,
+ expressions: plugins.expressions,
+ bfetch: plugins.bfetch,
+ elasticsearch: coreSetup.elasticsearch,
+ logger: this.logger,
+ });
loadSampleData(
plugins.home.sampleData.addSavedObjectsToSampleDataset,
diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/create.test.ts b/x-pack/plugins/canvas/server/routes/custom_elements/create.test.ts
index 290175d9062ea..4f6215b811505 100644
--- a/x-pack/plugins/canvas/server/routes/custom_elements/create.test.ts
+++ b/x-pack/plugins/canvas/server/routes/custom_elements/create.test.ts
@@ -5,15 +5,11 @@
*/
import sinon from 'sinon';
-import {
- savedObjectsClientMock,
- httpServiceMock,
- httpServerMock,
- loggingSystemMock,
-} from 'src/core/server/mocks';
+import { savedObjectsClientMock, httpServerMock } from 'src/core/server/mocks';
import { CUSTOM_ELEMENT_TYPE } from '../../../common/lib/constants';
import { initializeCreateCustomElementRoute } from './create';
import { kibanaResponseFactory, RequestHandlerContext, RequestHandler } from 'src/core/server';
+import { getMockedRouterDeps } from '../test_helpers';
const mockRouteContext = ({
core: {
@@ -36,15 +32,10 @@ describe('POST custom element', () => {
beforeEach(() => {
clock = sinon.useFakeTimers(now);
- const httpService = httpServiceMock.createSetupContract();
+ const routerDeps = getMockedRouterDeps();
+ initializeCreateCustomElementRoute(routerDeps);
- const router = httpService.createRouter();
- initializeCreateCustomElementRoute({
- router,
- logger: loggingSystemMock.create().get(),
- });
-
- routeHandler = router.post.mock.calls[0][1];
+ routeHandler = routerDeps.router.post.mock.calls[0][1];
});
afterEach(() => {
diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/delete.test.ts b/x-pack/plugins/canvas/server/routes/custom_elements/delete.test.ts
index 62ce4b9c3593c..1a72917a6bce9 100644
--- a/x-pack/plugins/canvas/server/routes/custom_elements/delete.test.ts
+++ b/x-pack/plugins/canvas/server/routes/custom_elements/delete.test.ts
@@ -7,12 +7,8 @@
import { CUSTOM_ELEMENT_TYPE } from '../../../common/lib/constants';
import { initializeDeleteCustomElementRoute } from './delete';
import { kibanaResponseFactory, RequestHandlerContext, RequestHandler } from 'src/core/server';
-import {
- savedObjectsClientMock,
- httpServiceMock,
- httpServerMock,
- loggingSystemMock,
-} from 'src/core/server/mocks';
+import { savedObjectsClientMock, httpServerMock } from 'src/core/server/mocks';
+import { getMockedRouterDeps } from '../test_helpers';
const mockRouteContext = ({
core: {
@@ -26,14 +22,10 @@ describe('DELETE custom element', () => {
let routeHandler: RequestHandler;
beforeEach(() => {
- const httpService = httpServiceMock.createSetupContract();
- const router = httpService.createRouter();
- initializeDeleteCustomElementRoute({
- router,
- logger: loggingSystemMock.create().get(),
- });
+ const routerDeps = getMockedRouterDeps();
+ initializeDeleteCustomElementRoute(routerDeps);
- routeHandler = router.delete.mock.calls[0][1];
+ routeHandler = routerDeps.router.delete.mock.calls[0][1];
});
it(`returns 200 ok when the custom element is deleted`, async () => {
diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/find.test.ts b/x-pack/plugins/canvas/server/routes/custom_elements/find.test.ts
index d42c97b62e0f3..10aaa633be53c 100644
--- a/x-pack/plugins/canvas/server/routes/custom_elements/find.test.ts
+++ b/x-pack/plugins/canvas/server/routes/custom_elements/find.test.ts
@@ -6,12 +6,8 @@
import { initializeFindCustomElementsRoute } from './find';
import { kibanaResponseFactory, RequestHandlerContext, RequestHandler } from 'src/core/server';
-import {
- savedObjectsClientMock,
- httpServiceMock,
- httpServerMock,
- loggingSystemMock,
-} from 'src/core/server/mocks';
+import { savedObjectsClientMock, httpServerMock } from 'src/core/server/mocks';
+import { getMockedRouterDeps } from '../test_helpers';
const mockRouteContext = ({
core: {
@@ -25,14 +21,10 @@ describe('Find custom element', () => {
let routeHandler: RequestHandler;
beforeEach(() => {
- const httpService = httpServiceMock.createSetupContract();
- const router = httpService.createRouter();
- initializeFindCustomElementsRoute({
- router,
- logger: loggingSystemMock.create().get(),
- });
+ const routerDeps = getMockedRouterDeps();
+ initializeFindCustomElementsRoute(routerDeps);
- routeHandler = router.get.mock.calls[0][1];
+ routeHandler = routerDeps.router.get.mock.calls[0][1];
});
it(`returns 200 with the found custom elements`, async () => {
diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/get.test.ts b/x-pack/plugins/canvas/server/routes/custom_elements/get.test.ts
index 7b4d0eba37419..a6c3d60ee9096 100644
--- a/x-pack/plugins/canvas/server/routes/custom_elements/get.test.ts
+++ b/x-pack/plugins/canvas/server/routes/custom_elements/get.test.ts
@@ -7,12 +7,8 @@
import { CUSTOM_ELEMENT_TYPE } from '../../../common/lib/constants';
import { initializeGetCustomElementRoute } from './get';
import { kibanaResponseFactory, RequestHandlerContext, RequestHandler } from 'src/core/server';
-import {
- savedObjectsClientMock,
- httpServiceMock,
- httpServerMock,
- loggingSystemMock,
-} from 'src/core/server/mocks';
+import { savedObjectsClientMock, httpServerMock } from 'src/core/server/mocks';
+import { getMockedRouterDeps } from '../test_helpers';
const mockRouteContext = ({
core: {
@@ -26,14 +22,10 @@ describe('GET custom element', () => {
let routeHandler: RequestHandler;
beforeEach(() => {
- const httpService = httpServiceMock.createSetupContract();
- const router = httpService.createRouter();
- initializeGetCustomElementRoute({
- router,
- logger: loggingSystemMock.create().get(),
- });
+ const routerDeps = getMockedRouterDeps();
+ initializeGetCustomElementRoute(routerDeps);
- routeHandler = router.get.mock.calls[0][1];
+ routeHandler = routerDeps.router.get.mock.calls[0][1];
});
it(`returns 200 when the custom element is found`, async () => {
diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/update.test.ts b/x-pack/plugins/canvas/server/routes/custom_elements/update.test.ts
index 0f954904355ae..6b81cf9e3faa1 100644
--- a/x-pack/plugins/canvas/server/routes/custom_elements/update.test.ts
+++ b/x-pack/plugins/canvas/server/routes/custom_elements/update.test.ts
@@ -9,13 +9,9 @@ import { CustomElement } from '../../../types';
import { CUSTOM_ELEMENT_TYPE } from '../../../common/lib/constants';
import { initializeUpdateCustomElementRoute } from './update';
import { kibanaResponseFactory, RequestHandlerContext, RequestHandler } from 'src/core/server';
-import {
- savedObjectsClientMock,
- httpServiceMock,
- httpServerMock,
- loggingSystemMock,
-} from 'src/core/server/mocks';
+import { savedObjectsClientMock, httpServerMock } from 'src/core/server/mocks';
import { okResponse } from '../ok_response';
+import { getMockedRouterDeps } from '../test_helpers';
const mockRouteContext = ({
core: {
@@ -51,14 +47,10 @@ describe('PUT custom element', () => {
beforeEach(() => {
clock = sinon.useFakeTimers(now);
- const httpService = httpServiceMock.createSetupContract();
- const router = httpService.createRouter();
- initializeUpdateCustomElementRoute({
- router,
- logger: loggingSystemMock.create().get(),
- });
+ const routerDeps = getMockedRouterDeps();
+ initializeUpdateCustomElementRoute(routerDeps);
- routeHandler = router.put.mock.calls[0][1];
+ routeHandler = routerDeps.router.put.mock.calls[0][1];
});
afterEach(() => {
diff --git a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts
index c2cff83f85f0d..a3aff029868d7 100644
--- a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts
+++ b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts
@@ -6,12 +6,8 @@
import { initializeESFieldsRoute } from './es_fields';
import { kibanaResponseFactory, RequestHandlerContext, RequestHandler } from 'src/core/server';
-import {
- httpServiceMock,
- httpServerMock,
- loggingSystemMock,
- elasticsearchServiceMock,
-} from 'src/core/server/mocks';
+import { httpServerMock, elasticsearchServiceMock } from 'src/core/server/mocks';
+import { getMockedRouterDeps } from '../test_helpers';
const mockRouteContext = ({
core: {
@@ -27,14 +23,10 @@ describe('Retrieve ES Fields', () => {
let routeHandler: RequestHandler;
beforeEach(() => {
- const httpService = httpServiceMock.createSetupContract();
- const router = httpService.createRouter();
- initializeESFieldsRoute({
- router,
- logger: loggingSystemMock.create().get(),
- });
+ const routerDeps = getMockedRouterDeps();
+ initializeESFieldsRoute(routerDeps);
- routeHandler = router.get.mock.calls[0][1];
+ routeHandler = routerDeps.router.get.mock.calls[0][1];
});
it(`returns 200 with fields from existing index/index pattern`, async () => {
diff --git a/x-pack/plugins/canvas/server/routes/functions/functions.test.ts b/x-pack/plugins/canvas/server/routes/functions/functions.test.ts
new file mode 100644
index 0000000000000..755af3eb4ef7e
--- /dev/null
+++ b/x-pack/plugins/canvas/server/routes/functions/functions.test.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 { kibanaResponseFactory, RequestHandlerContext, RequestHandler } from 'src/core/server';
+import { httpServerMock } from 'src/core/server/mocks';
+import { ExpressionFunction } from 'src/plugins/expressions/common/expression_functions';
+import { initializeGetFunctionsRoute } from './functions';
+import { getMockedRouterDeps } from '../test_helpers';
+import { API_ROUTE_FUNCTIONS } from '../../../common/lib';
+import { functions } from '../../../canvas_plugin_src/functions/server';
+
+const mockRouteContext = {} as RequestHandlerContext;
+const routePath = API_ROUTE_FUNCTIONS;
+
+describe('Get list of serverside expression functions', () => {
+ let routeHandler: RequestHandler;
+ let mockFuncs: Record;
+
+ beforeEach(() => {
+ mockFuncs = {
+ demodata: new ExpressionFunction(functions[0]()),
+ };
+
+ const routerDeps = getMockedRouterDeps();
+
+ routerDeps.expressions.getFunctions.mockReturnValueOnce(mockFuncs);
+
+ initializeGetFunctionsRoute(routerDeps);
+
+ routeHandler = routerDeps.router.get.mock.calls[0][1];
+ });
+
+ afterAll(() => {
+ jest.restoreAllMocks();
+ });
+
+ it(`returns 200 with list of functions`, async () => {
+ const request = httpServerMock.createKibanaRequest({
+ method: 'get',
+ path: routePath,
+ });
+
+ const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory);
+
+ expect(response.status).toBe(200);
+ expect(response.payload).toBe(JSON.stringify(mockFuncs));
+ });
+});
diff --git a/x-pack/plugins/canvas/server/routes/functions/functions.ts b/x-pack/plugins/canvas/server/routes/functions/functions.ts
new file mode 100644
index 0000000000000..dba3d315c5f8b
--- /dev/null
+++ b/x-pack/plugins/canvas/server/routes/functions/functions.ts
@@ -0,0 +1,73 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { LegacyAPICaller } from 'src/core/server';
+import { serializeProvider } from '../../../../../../src/plugins/expressions/common';
+import { RouteInitializerDeps } from '../';
+import { API_ROUTE_FUNCTIONS } from '../../../common/lib/constants';
+
+interface FunctionCall {
+ functionName: string;
+ args: Record;
+ context: Record;
+}
+
+export function initializeGetFunctionsRoute(deps: RouteInitializerDeps) {
+ const { router, expressions } = deps;
+ router.get(
+ {
+ path: API_ROUTE_FUNCTIONS,
+ validate: false,
+ },
+ async (context, request, response) => {
+ const functions = expressions.getFunctions();
+ const body = JSON.stringify(functions);
+ return response.ok({
+ body,
+ });
+ }
+ );
+}
+
+export function initializeBatchFunctionsRoute(deps: RouteInitializerDeps) {
+ const { bfetch, elasticsearch, expressions } = deps;
+
+ async function runFunction(
+ handlers: { environment: string; elasticsearchClient: LegacyAPICaller },
+ fnCall: FunctionCall
+ ) {
+ const { functionName, args, context } = fnCall;
+ const { deserialize } = serializeProvider(expressions.getTypes());
+
+ const fnDef = expressions.getFunctions()[functionName];
+ if (!fnDef) throw new Error(`Function "${functionName}" could not be found.`);
+
+ const deserialized = deserialize(context);
+ const result = fnDef.fn(deserialized, args, handlers);
+
+ return result;
+ }
+
+ /**
+ * Register an endpoint that executes a batch of functions, and streams the
+ * results back using ND-JSON.
+ */
+ bfetch.addBatchProcessingRoute(API_ROUTE_FUNCTIONS, (request) => {
+ return {
+ onBatchItem: async (fnCall: FunctionCall) => {
+ const handlers = {
+ environment: 'server',
+ elasticsearchClient: elasticsearch.legacy.client.asScoped(request).callAsCurrentUser,
+ };
+ const result = await runFunction(handlers, fnCall);
+ if (typeof result === 'undefined') {
+ throw new Error(`Function ${fnCall.functionName} did not return anything.`);
+ }
+ return result;
+ },
+ };
+ });
+}
diff --git a/x-pack/plugins/canvas/server/routes/functions/index.ts b/x-pack/plugins/canvas/server/routes/functions/index.ts
new file mode 100644
index 0000000000000..768320fdf499a
--- /dev/null
+++ b/x-pack/plugins/canvas/server/routes/functions/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { initializeGetFunctionsRoute, initializeBatchFunctionsRoute } from './functions';
+import { RouteInitializerDeps } from '..';
+
+export function initFunctionsRoutes(deps: RouteInitializerDeps) {
+ initializeGetFunctionsRoute(deps);
+ initializeBatchFunctionsRoute(deps);
+}
diff --git a/x-pack/plugins/canvas/server/routes/index.ts b/x-pack/plugins/canvas/server/routes/index.ts
index 56874151530aa..2999f4dc5cfe6 100644
--- a/x-pack/plugins/canvas/server/routes/index.ts
+++ b/x-pack/plugins/canvas/server/routes/index.ts
@@ -4,16 +4,22 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { IRouter, Logger } from 'src/core/server';
+import { IRouter, Logger, ElasticsearchServiceSetup } from 'src/core/server';
+import { ExpressionsServerSetup } from 'src/plugins/expressions/server';
+import { BfetchServerSetup } from 'src/plugins/bfetch/server';
import { initCustomElementsRoutes } from './custom_elements';
import { initESFieldsRoutes } from './es_fields';
import { initShareablesRoutes } from './shareables';
import { initWorkpadRoutes } from './workpad';
import { initTemplateRoutes } from './templates';
+import { initFunctionsRoutes } from './functions';
export interface RouteInitializerDeps {
router: IRouter;
logger: Logger;
+ expressions: ExpressionsServerSetup;
+ bfetch: BfetchServerSetup;
+ elasticsearch: ElasticsearchServiceSetup;
}
export function initRoutes(deps: RouteInitializerDeps) {
@@ -22,4 +28,5 @@ export function initRoutes(deps: RouteInitializerDeps) {
initShareablesRoutes(deps);
initWorkpadRoutes(deps);
initTemplateRoutes(deps);
+ initFunctionsRoutes(deps);
}
diff --git a/x-pack/plugins/canvas/server/routes/shareables/download.test.ts b/x-pack/plugins/canvas/server/routes/shareables/download.test.ts
index 0267a695ae9fe..1edf9f52e164a 100644
--- a/x-pack/plugins/canvas/server/routes/shareables/download.test.ts
+++ b/x-pack/plugins/canvas/server/routes/shareables/download.test.ts
@@ -8,8 +8,9 @@ jest.mock('fs');
import fs from 'fs';
import { kibanaResponseFactory, RequestHandlerContext, RequestHandler } from 'src/core/server';
-import { httpServiceMock, httpServerMock, loggingSystemMock } from 'src/core/server/mocks';
+import { httpServerMock } from 'src/core/server/mocks';
import { initializeDownloadShareableWorkpadRoute } from './download';
+import { getMockedRouterDeps } from '../test_helpers';
const mockRouteContext = {} as RequestHandlerContext;
const path = `api/canvas/workpad/find`;
@@ -19,14 +20,10 @@ describe('Download Canvas shareables runtime', () => {
let routeHandler: RequestHandler;
beforeEach(() => {
- const httpService = httpServiceMock.createSetupContract();
- const router = httpService.createRouter();
- initializeDownloadShareableWorkpadRoute({
- router,
- logger: loggingSystemMock.create().get(),
- });
+ const routerDeps = getMockedRouterDeps();
+ initializeDownloadShareableWorkpadRoute(routerDeps);
- routeHandler = router.get.mock.calls[0][1];
+ routeHandler = routerDeps.router.get.mock.calls[0][1];
});
afterAll(() => {
diff --git a/x-pack/plugins/canvas/server/routes/shareables/zip.test.ts b/x-pack/plugins/canvas/server/routes/shareables/zip.test.ts
index 0c19886f07e5c..b1649c7524c7e 100644
--- a/x-pack/plugins/canvas/server/routes/shareables/zip.test.ts
+++ b/x-pack/plugins/canvas/server/routes/shareables/zip.test.ts
@@ -9,8 +9,9 @@ jest.mock('archiver');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const archiver = require('archiver') as jest.Mock;
import { kibanaResponseFactory, RequestHandlerContext, RequestHandler } from 'src/core/server';
-import { httpServiceMock, httpServerMock, loggingSystemMock } from 'src/core/server/mocks';
+import { httpServerMock } from 'src/core/server/mocks';
import { initializeZipShareableWorkpadRoute } from './zip';
+import { getMockedRouterDeps } from '../test_helpers';
import { API_ROUTE_SHAREABLE_ZIP } from '../../../common/lib';
import {
SHAREABLE_RUNTIME_FILE,
@@ -26,14 +27,9 @@ describe('Zips Canvas shareables runtime together with workpad', () => {
let routeHandler: RequestHandler;
beforeEach(() => {
- const httpService = httpServiceMock.createSetupContract();
- const router = httpService.createRouter();
- initializeZipShareableWorkpadRoute({
- router,
- logger: loggingSystemMock.create().get(),
- });
-
- routeHandler = router.post.mock.calls[0][1];
+ const routerDeps = getMockedRouterDeps();
+ initializeZipShareableWorkpadRoute(routerDeps);
+ routeHandler = routerDeps.router.post.mock.calls[0][1];
});
afterAll(() => {
diff --git a/x-pack/plugins/canvas/server/routes/templates/list.test.ts b/x-pack/plugins/canvas/server/routes/templates/list.test.ts
index 95658e6a7b511..9ceab3b5c9705 100644
--- a/x-pack/plugins/canvas/server/routes/templates/list.test.ts
+++ b/x-pack/plugins/canvas/server/routes/templates/list.test.ts
@@ -6,18 +6,9 @@
import { badRequest } from 'boom';
import { initializeListTemplates } from './list';
-import {
- IRouter,
- kibanaResponseFactory,
- RequestHandlerContext,
- RequestHandler,
-} from 'src/core/server';
-import {
- savedObjectsClientMock,
- httpServiceMock,
- httpServerMock,
- loggingSystemMock,
-} from 'src/core/server/mocks';
+import { kibanaResponseFactory, RequestHandlerContext, RequestHandler } from 'src/core/server';
+import { savedObjectsClientMock, httpServerMock } from 'src/core/server/mocks';
+import { getMockedRouterDeps } from '../test_helpers';
const mockRouteContext = ({
core: {
@@ -31,14 +22,10 @@ describe('Find workpad', () => {
let routeHandler: RequestHandler;
beforeEach(() => {
- const httpService = httpServiceMock.createSetupContract();
- const router = httpService.createRouter() as jest.Mocked;
- initializeListTemplates({
- router,
- logger: loggingSystemMock.create().get(),
- });
+ const routerDeps = getMockedRouterDeps();
+ initializeListTemplates(routerDeps);
- routeHandler = router.get.mock.calls[0][1];
+ routeHandler = routerDeps.router.get.mock.calls[0][1];
});
it(`returns 200 with the found templates`, async () => {
diff --git a/x-pack/plugins/canvas/server/routes/test_helpers.ts b/x-pack/plugins/canvas/server/routes/test_helpers.ts
new file mode 100644
index 0000000000000..695cc79e513dc
--- /dev/null
+++ b/x-pack/plugins/canvas/server/routes/test_helpers.ts
@@ -0,0 +1,29 @@
+/*
+ * 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 {
+ httpServiceMock,
+ loggingSystemMock,
+ elasticsearchServiceMock,
+} from 'src/core/server/mocks';
+import { bfetchPluginMock } from '../../../../../src/plugins/bfetch/server/mocks';
+import { expressionsPluginMock } from '../../../../../src/plugins/expressions/server/mocks';
+
+export function getMockedRouterDeps() {
+ const httpService = httpServiceMock.createSetupContract();
+ const elasticsearch = elasticsearchServiceMock.createSetup();
+ const bfetch = bfetchPluginMock.createSetupContract();
+ const expressions = expressionsPluginMock.createSetupContract();
+ const router = httpService.createRouter();
+
+ return {
+ router,
+ expressions,
+ elasticsearch,
+ bfetch,
+ logger: loggingSystemMock.create().get(),
+ };
+}
diff --git a/x-pack/plugins/canvas/server/routes/workpad/create.test.ts b/x-pack/plugins/canvas/server/routes/workpad/create.test.ts
index 4756349a8a5ff..4e927751a026f 100644
--- a/x-pack/plugins/canvas/server/routes/workpad/create.test.ts
+++ b/x-pack/plugins/canvas/server/routes/workpad/create.test.ts
@@ -5,15 +5,11 @@
*/
import sinon from 'sinon';
-import {
- savedObjectsClientMock,
- httpServiceMock,
- httpServerMock,
- loggingSystemMock,
-} from 'src/core/server/mocks';
+import { savedObjectsClientMock, httpServerMock } from 'src/core/server/mocks';
import { CANVAS_TYPE } from '../../../common/lib/constants';
import { initializeCreateWorkpadRoute } from './create';
import { kibanaResponseFactory, RequestHandlerContext, RequestHandler } from 'src/core/server';
+import { getMockedRouterDeps } from '../test_helpers';
let mockRouteContext = ({
core: {
@@ -44,15 +40,10 @@ describe('POST workpad', () => {
clock = sinon.useFakeTimers(now);
- const httpService = httpServiceMock.createSetupContract();
+ const routerDeps = getMockedRouterDeps();
+ initializeCreateWorkpadRoute(routerDeps);
- const router = httpService.createRouter();
- initializeCreateWorkpadRoute({
- router,
- logger: loggingSystemMock.create().get(),
- });
-
- routeHandler = router.post.mock.calls[0][1];
+ routeHandler = routerDeps.router.post.mock.calls[0][1];
});
afterEach(() => {
diff --git a/x-pack/plugins/canvas/server/routes/workpad/delete.test.ts b/x-pack/plugins/canvas/server/routes/workpad/delete.test.ts
index 32ce30325b60a..e66628ffb4d4e 100644
--- a/x-pack/plugins/canvas/server/routes/workpad/delete.test.ts
+++ b/x-pack/plugins/canvas/server/routes/workpad/delete.test.ts
@@ -7,12 +7,8 @@
import { CANVAS_TYPE } from '../../../common/lib/constants';
import { initializeDeleteWorkpadRoute } from './delete';
import { kibanaResponseFactory, RequestHandlerContext, RequestHandler } from 'src/core/server';
-import {
- savedObjectsClientMock,
- httpServiceMock,
- httpServerMock,
- loggingSystemMock,
-} from 'src/core/server/mocks';
+import { savedObjectsClientMock, httpServerMock } from 'src/core/server/mocks';
+import { getMockedRouterDeps } from '../test_helpers';
const mockRouteContext = ({
core: {
@@ -26,14 +22,10 @@ describe('DELETE workpad', () => {
let routeHandler: RequestHandler;
beforeEach(() => {
- const httpService = httpServiceMock.createSetupContract();
- const router = httpService.createRouter();
- initializeDeleteWorkpadRoute({
- router,
- logger: loggingSystemMock.create().get(),
- });
+ const routerDeps = getMockedRouterDeps();
+ initializeDeleteWorkpadRoute(routerDeps);
- routeHandler = router.delete.mock.calls[0][1];
+ routeHandler = routerDeps.router.delete.mock.calls[0][1];
});
it(`returns 200 ok when the workpad is deleted`, async () => {
diff --git a/x-pack/plugins/canvas/server/routes/workpad/find.test.ts b/x-pack/plugins/canvas/server/routes/workpad/find.test.ts
index a87cf7be57d81..593e4062686f9 100644
--- a/x-pack/plugins/canvas/server/routes/workpad/find.test.ts
+++ b/x-pack/plugins/canvas/server/routes/workpad/find.test.ts
@@ -6,12 +6,8 @@
import { initializeFindWorkpadsRoute } from './find';
import { kibanaResponseFactory, RequestHandlerContext, RequestHandler } from 'src/core/server';
-import {
- savedObjectsClientMock,
- httpServiceMock,
- httpServerMock,
- loggingSystemMock,
-} from 'src/core/server/mocks';
+import { savedObjectsClientMock, httpServerMock } from 'src/core/server/mocks';
+import { getMockedRouterDeps } from '../test_helpers';
const mockRouteContext = ({
core: {
@@ -25,14 +21,10 @@ describe('Find workpad', () => {
let routeHandler: RequestHandler;
beforeEach(() => {
- const httpService = httpServiceMock.createSetupContract();
- const router = httpService.createRouter();
- initializeFindWorkpadsRoute({
- router,
- logger: loggingSystemMock.create().get(),
- });
+ const routerDeps = getMockedRouterDeps();
+ initializeFindWorkpadsRoute(routerDeps);
- routeHandler = router.get.mock.calls[0][1];
+ routeHandler = routerDeps.router.get.mock.calls[0][1];
});
it(`returns 200 with the found workpads`, async () => {
diff --git a/x-pack/plugins/canvas/server/routes/workpad/get.test.ts b/x-pack/plugins/canvas/server/routes/workpad/get.test.ts
index 8cc190dc6231c..a51cbefd4031e 100644
--- a/x-pack/plugins/canvas/server/routes/workpad/get.test.ts
+++ b/x-pack/plugins/canvas/server/routes/workpad/get.test.ts
@@ -7,14 +7,10 @@
import { CANVAS_TYPE } from '../../../common/lib/constants';
import { initializeGetWorkpadRoute } from './get';
import { kibanaResponseFactory, RequestHandlerContext, RequestHandler } from 'src/core/server';
-import {
- savedObjectsClientMock,
- httpServiceMock,
- httpServerMock,
- loggingSystemMock,
-} from 'src/core/server/mocks';
+import { savedObjectsClientMock, httpServerMock } from 'src/core/server/mocks';
import { workpadWithGroupAsElement } from '../../../__tests__/fixtures/workpads';
import { CanvasWorkpad } from '../../../types';
+import { getMockedRouterDeps } from '../test_helpers';
const mockRouteContext = ({
core: {
@@ -28,14 +24,10 @@ describe('GET workpad', () => {
let routeHandler: RequestHandler;
beforeEach(() => {
- const httpService = httpServiceMock.createSetupContract();
- const router = httpService.createRouter();
- initializeGetWorkpadRoute({
- router,
- logger: loggingSystemMock.create().get(),
- });
+ const routerDeps = getMockedRouterDeps();
+ initializeGetWorkpadRoute(routerDeps);
- routeHandler = router.get.mock.calls[0][1];
+ routeHandler = routerDeps.router.get.mock.calls[0][1];
});
it(`returns 200 when the workpad is found`, async () => {
diff --git a/x-pack/plugins/canvas/server/routes/workpad/update.test.ts b/x-pack/plugins/canvas/server/routes/workpad/update.test.ts
index 6d7ea06852a5e..0d97145c90298 100644
--- a/x-pack/plugins/canvas/server/routes/workpad/update.test.ts
+++ b/x-pack/plugins/canvas/server/routes/workpad/update.test.ts
@@ -8,14 +8,10 @@ import sinon from 'sinon';
import { CANVAS_TYPE } from '../../../common/lib/constants';
import { initializeUpdateWorkpadRoute, initializeUpdateWorkpadAssetsRoute } from './update';
import { kibanaResponseFactory, RequestHandlerContext, RequestHandler } from 'src/core/server';
-import {
- savedObjectsClientMock,
- httpServiceMock,
- httpServerMock,
- loggingSystemMock,
-} from 'src/core/server/mocks';
+import { savedObjectsClientMock, httpServerMock } from 'src/core/server/mocks';
import { workpads } from '../../../__tests__/fixtures/workpads';
import { okResponse } from '../ok_response';
+import { getMockedRouterDeps } from '../test_helpers';
const mockRouteContext = ({
core: {
@@ -38,14 +34,10 @@ describe('PUT workpad', () => {
beforeEach(() => {
clock = sinon.useFakeTimers(now);
- const httpService = httpServiceMock.createSetupContract();
- const router = httpService.createRouter();
- initializeUpdateWorkpadRoute({
- router,
- logger: loggingSystemMock.create().get(),
- });
+ const routerDeps = getMockedRouterDeps();
+ initializeUpdateWorkpadRoute(routerDeps);
- routeHandler = router.put.mock.calls[0][1];
+ routeHandler = routerDeps.router.put.mock.calls[0][1];
});
afterEach(() => {
@@ -152,14 +144,11 @@ describe('update assets', () => {
beforeEach(() => {
clock = sinon.useFakeTimers(now);
- const httpService = httpServiceMock.createSetupContract();
- const router = httpService.createRouter();
- initializeUpdateWorkpadAssetsRoute({
- router,
- logger: loggingSystemMock.create().get(),
- });
- routeHandler = router.put.mock.calls[0][1];
+ const routerDeps = getMockedRouterDeps();
+ initializeUpdateWorkpadAssetsRoute(routerDeps);
+
+ routeHandler = routerDeps.router.put.mock.calls[0][1];
});
afterEach(() => {
diff --git a/x-pack/plugins/canvas/server/setup_interpreter.ts b/x-pack/plugins/canvas/server/setup_interpreter.ts
index 79bc2f3c1996b..8dc431345e5f5 100644
--- a/x-pack/plugins/canvas/server/setup_interpreter.ts
+++ b/x-pack/plugins/canvas/server/setup_interpreter.ts
@@ -8,5 +8,5 @@ import { ExpressionsServerSetup } from 'src/plugins/expressions/server';
import { functions } from '../canvas_plugin_src/functions/server';
export function setupInterpreter(expressions: ExpressionsServerSetup) {
- expressions.__LEGACY.register({ types: [], serverFunctions: functions });
+ functions.forEach((f) => expressions.registerFunction(f));
}
From 4a274e0325c2af8d673c3ea1a623f1128db238d9 Mon Sep 17 00:00:00 2001
From: Bohdan Tsymbala
Date: Tue, 18 Aug 2020 19:42:47 +0200
Subject: [PATCH 02/85] [ENDPOINT] Reintroduced tabs to endpoint management and
migrated pages to use common security components (#74886)
* Reintroduced tabs to endpoint management and migrated pages to use common security components.
* Empty trusted apps tab.
* Changed casing in the translations.
* Switched to using route path generation functions.
* Added propagation of data-test-subj attribute to Wrapper component.
* Fixed CommonProps import.
* Moved out shared component for administration list page.
* Removed unused file.
* Removed unused translation keys.
* Removed redundant snapshot.
* Added some minimal tests.
* Attempt to fix functional tests.
* Attempt to fix functional tests again.
* Reverted function declarations back to const.
* Wrapped component in memo.
---
.../__snapshots__/page_view.test.tsx.snap | 802 ------------------
.../components/endpoint/page_view.test.tsx | 88 --
.../common/components/endpoint/page_view.tsx | 184 ----
.../common/components/wrapper_page/index.tsx | 18 +-
.../public/management/common/constants.ts | 1 +
.../public/management/common/routing.ts | 17 +-
.../public/management/common/translations.ts | 8 +
.../components/administration_list_page.tsx | 65 ++
.../components/management_page_view.tsx | 19 -
.../pages/endpoint_hosts/view/index.test.tsx | 1 +
.../pages/endpoint_hosts/view/index.tsx | 57 +-
.../public/management/pages/index.test.tsx | 11 +-
.../public/management/pages/index.tsx | 30 +-
.../pages/policy/view/policy_details.test.tsx | 27 +-
.../pages/policy/view/policy_details.tsx | 56 +-
.../pages/policy/view/policy_list.test.tsx | 1 +
.../pages/policy/view/policy_list.tsx | 56 +-
.../management/pages/trusted_apps/index.tsx | 20 +
.../trusted_apps_page.test.tsx.snap | 21 +
.../pages/trusted_apps/view/index.ts | 6 +
.../view/trusted_apps_page.test.tsx} | 17 +-
.../trusted_apps/view/trusted_apps_page.tsx | 28 +
.../public/management/types.ts | 1 +
.../translations/translations/ja-JP.json | 2 -
.../translations/translations/zh-CN.json | 2 -
.../apps/endpoint/endpoint_list.ts | 4 +-
.../apps/endpoint/policy_details.ts | 2 +-
.../apps/endpoint/policy_list.ts | 4 +-
.../apps/endpoint/trusted_apps_list.ts | 29 +
.../page_objects/index.ts | 2 +
.../page_objects/trusted_apps_page.ts | 20 +
31 files changed, 325 insertions(+), 1274 deletions(-)
delete mode 100644 x-pack/plugins/security_solution/public/common/components/endpoint/__snapshots__/page_view.test.tsx.snap
delete mode 100644 x-pack/plugins/security_solution/public/common/components/endpoint/page_view.test.tsx
delete mode 100644 x-pack/plugins/security_solution/public/common/components/endpoint/page_view.tsx
create mode 100644 x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/components/management_page_view.tsx
create mode 100644 x-pack/plugins/security_solution/public/management/pages/trusted_apps/index.tsx
create mode 100644 x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_page.test.tsx.snap
create mode 100644 x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/index.ts
rename x-pack/plugins/security_solution/public/management/pages/{endpoint_hosts/routes.tsx => trusted_apps/view/trusted_apps_page.test.tsx} (51%)
create mode 100644 x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx
create mode 100644 x-pack/test/security_solution_endpoint/apps/endpoint/trusted_apps_list.ts
create mode 100644 x-pack/test/security_solution_endpoint/page_objects/trusted_apps_page.ts
diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/__snapshots__/page_view.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/endpoint/__snapshots__/page_view.test.tsx.snap
deleted file mode 100644
index bed5ac6950a2b..0000000000000
--- a/x-pack/plugins/security_solution/public/common/components/endpoint/__snapshots__/page_view.test.tsx.snap
+++ /dev/null
@@ -1,802 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`PageView component should display body header custom element 1`] = `
-.c0.endpoint--isListView {
- padding: 0 24px;
-}
-
-.c0.endpoint--isListView .endpoint-header {
- padding: 24px;
- margin-bottom: 0;
-}
-
-.c0.endpoint--isListView .endpoint-page-content {
- border-left: none;
- border-right: none;
-}
-
-.c0.endpoint--isDetailsView .endpoint-page-content {
- padding: 0;
- border: none;
- background: none;
-}
-
-.c0 .endpoint-navTabs {
- margin-left: 12px;
-}
-
-.c0 .endpoint-header-leftSection {
- overflow: hidden;
-}
-
-
- body header
-
- }
- viewType="list"
->
-
-
-
-
-
-
-
-
-
-
-
-
-
- body content
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`PageView component should display body header wrapped in EuiTitle 1`] = `
-.c0.endpoint--isListView {
- padding: 0 24px;
-}
-
-.c0.endpoint--isListView .endpoint-header {
- padding: 24px;
- margin-bottom: 0;
-}
-
-.c0.endpoint--isListView .endpoint-page-content {
- border-left: none;
- border-right: none;
-}
-
-.c0.endpoint--isDetailsView .endpoint-page-content {
- padding: 0;
- border: none;
- background: none;
-}
-
-.c0 .endpoint-navTabs {
- margin-left: 12px;
-}
-
-.c0 .endpoint-header-leftSection {
- overflow: hidden;
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- body header
-
-
-
-
-
-
-
-
-
- body content
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`PageView component should display header left and right 1`] = `
-.c0.endpoint--isListView {
- padding: 0 24px;
-}
-
-.c0.endpoint--isListView .endpoint-header {
- padding: 24px;
- margin-bottom: 0;
-}
-
-.c0.endpoint--isListView .endpoint-page-content {
- border-left: none;
- border-right: none;
-}
-
-.c0.endpoint--isDetailsView .endpoint-page-content {
- padding: 0;
- border: none;
- background: none;
-}
-
-.c0 .endpoint-navTabs {
- margin-left: 12px;
-}
-
-.c0 .endpoint-header-leftSection {
- overflow: hidden;
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- page title
-
-
-
-
-
-
-
- right side actions
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`PageView component should display only body if not header props used 1`] = `
-.c0.endpoint--isListView {
- padding: 0 24px;
-}
-
-.c0.endpoint--isListView .endpoint-header {
- padding: 24px;
- margin-bottom: 0;
-}
-
-.c0.endpoint--isListView .endpoint-page-content {
- border-left: none;
- border-right: none;
-}
-
-.c0.endpoint--isDetailsView .endpoint-page-content {
- padding: 0;
- border: none;
- background: none;
-}
-
-.c0 .endpoint-navTabs {
- margin-left: 12px;
-}
-
-.c0 .endpoint-header-leftSection {
- overflow: hidden;
-}
-
-
-
-
-
-
-
-
-`;
-
-exports[`PageView component should display only header left 1`] = `
-.c0.endpoint--isListView {
- padding: 0 24px;
-}
-
-.c0.endpoint--isListView .endpoint-header {
- padding: 24px;
- margin-bottom: 0;
-}
-
-.c0.endpoint--isListView .endpoint-page-content {
- border-left: none;
- border-right: none;
-}
-
-.c0.endpoint--isDetailsView .endpoint-page-content {
- padding: 0;
- border: none;
- background: none;
-}
-
-.c0 .endpoint-navTabs {
- margin-left: 12px;
-}
-
-.c0 .endpoint-header-leftSection {
- overflow: hidden;
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- page title
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`PageView component should display only header right but include an empty left side 1`] = `
-.c0.endpoint--isListView {
- padding: 0 24px;
-}
-
-.c0.endpoint--isListView .endpoint-header {
- padding: 24px;
- margin-bottom: 0;
-}
-
-.c0.endpoint--isListView .endpoint-page-content {
- border-left: none;
- border-right: none;
-}
-
-.c0.endpoint--isDetailsView .endpoint-page-content {
- padding: 0;
- border: none;
- background: none;
-}
-
-.c0 .endpoint-navTabs {
- margin-left: 12px;
-}
-
-.c0 .endpoint-header-leftSection {
- overflow: hidden;
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- right side actions
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`PageView component should pass through EuiPage props 1`] = `
-.c0.endpoint--isListView {
- padding: 0 24px;
-}
-
-.c0.endpoint--isListView .endpoint-header {
- padding: 24px;
- margin-bottom: 0;
-}
-
-.c0.endpoint--isListView .endpoint-page-content {
- border-left: none;
- border-right: none;
-}
-
-.c0.endpoint--isDetailsView .endpoint-page-content {
- padding: 0;
- border: none;
- background: none;
-}
-
-.c0 .endpoint-navTabs {
- margin-left: 12px;
-}
-
-.c0 .endpoint-header-leftSection {
- overflow: hidden;
-}
-
-
-
-
-
-
-
-
-`;
-
-exports[`PageView component should use custom element for header left and not wrap in EuiTitle 1`] = `
-.c0.endpoint--isListView {
- padding: 0 24px;
-}
-
-.c0.endpoint--isListView .endpoint-header {
- padding: 24px;
- margin-bottom: 0;
-}
-
-.c0.endpoint--isListView .endpoint-page-content {
- border-left: none;
- border-right: none;
-}
-
-.c0.endpoint--isDetailsView .endpoint-page-content {
- padding: 0;
- border: none;
- background: none;
-}
-
-.c0 .endpoint-navTabs {
- margin-left: 12px;
-}
-
-.c0 .endpoint-header-leftSection {
- overflow: hidden;
-}
-
-
- title here
-
- }
- viewType="list"
->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/page_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/page_view.test.tsx
deleted file mode 100644
index 2c14f66b64865..0000000000000
--- a/x-pack/plugins/security_solution/public/common/components/endpoint/page_view.test.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import React from 'react';
-import { mount } from 'enzyme';
-import { PageView } from './page_view';
-import { EuiThemeProvider } from '../../../../../../legacy/common/eui_styled_components';
-
-describe('PageView component', () => {
- const render = (ui: Parameters[0]) =>
- mount(ui, { wrappingComponent: EuiThemeProvider });
-
- it('should display only body if not header props used', () => {
- expect(render({'body content'} )).toMatchSnapshot();
- });
- it('should display header left and right', () => {
- expect(
- render(
-
- {'body content'}
-
- )
- ).toMatchSnapshot();
- });
- it('should display only header left', () => {
- expect(
- render(
-
- {'body content'}
-
- )
- ).toMatchSnapshot();
- });
- it('should display only header right but include an empty left side', () => {
- expect(
- render(
-
- {'body content'}
-
- )
- ).toMatchSnapshot();
- });
- it(`should use custom element for header left and not wrap in EuiTitle`, () => {
- expect(
- render(
- {'title here'}}>
- {'body content'}
-
- )
- ).toMatchSnapshot();
- });
- it('should display body header wrapped in EuiTitle', () => {
- expect(
- render(
-
- {'body content'}
-
- )
- ).toMatchSnapshot();
- });
- it('should display body header custom element', () => {
- expect(
- render(
- {'body header'}}>
- {'body content'}
-
- )
- ).toMatchSnapshot();
- });
- it('should pass through EuiPage props', () => {
- expect(
- render(
-
- {'body content'}
-
- )
- ).toMatchSnapshot();
- });
-});
diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/page_view.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/page_view.tsx
deleted file mode 100644
index d4753b3a64e24..0000000000000
--- a/x-pack/plugins/security_solution/public/common/components/endpoint/page_view.tsx
+++ /dev/null
@@ -1,184 +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 {
- EuiPage,
- EuiPageBody,
- EuiPageContent,
- EuiPageContentBody,
- EuiPageContentHeader,
- EuiPageContentHeaderSection,
- EuiPageHeader,
- EuiPageHeaderSection,
- EuiPageProps,
- EuiTab,
- EuiTabs,
- EuiTitle,
- EuiTitleProps,
-} from '@elastic/eui';
-import React, { memo, MouseEventHandler, ReactNode, useMemo } from 'react';
-import styled from 'styled-components';
-import { EuiTabProps } from '@elastic/eui/src/components/tabs/tab';
-
-const StyledEuiPage = styled(EuiPage)`
- &.endpoint--isListView {
- padding: 0 ${(props) => props.theme.eui.euiSizeL};
-
- .endpoint-header {
- padding: ${(props) => props.theme.eui.euiSizeL};
- margin-bottom: 0;
- }
- .endpoint-page-content {
- border-left: none;
- border-right: none;
- }
- }
- &.endpoint--isDetailsView {
- .endpoint-page-content {
- padding: 0;
- border: none;
- background: none;
- }
- }
- .endpoint-navTabs {
- margin-left: ${(props) => props.theme.eui.euiSizeM};
- }
- .endpoint-header-leftSection {
- overflow: hidden;
- }
-`;
-
-const isStringOrNumber = /(string|number)/;
-
-/**
- * The `PageView` component used to render `headerLeft` when it is set as a `string`
- * Can be used when wanting to customize the `headerLeft` value but still use the standard
- * title component
- */
-export const PageViewHeaderTitle = memo & { children: ReactNode }>(
- ({ children, size = 'l', ...otherProps }) => {
- return (
-
- {children}
-
- );
- }
-);
-
-PageViewHeaderTitle.displayName = 'PageViewHeaderTitle';
-
-/**
- * The `PageView` component used to render `bodyHeader` when it is set as a `string`
- * Can be used when wanting to customize the `bodyHeader` value but still use the standard
- * title component
- */
-export const PageViewBodyHeaderTitle = memo<{ children: ReactNode }>(
- ({ children, ...otherProps }) => {
- return (
-
- {children}
-
- );
- }
-);
-PageViewBodyHeaderTitle.displayName = 'PageViewBodyHeaderTitle';
-
-export type PageViewProps = EuiPageProps & {
- /**
- * The type of view
- */
- viewType: 'list' | 'details';
- /**
- * content to be placed on the left side of the header. If a `string` is used, then it will
- * be wrapped with ` `, else it will just be used as is.
- */
- headerLeft?: ReactNode;
- /** Content for the right side of the header */
- headerRight?: ReactNode;
- /**
- * body (sub-)header section. If a `string` is used, then it will be wrapped with
- * ` `
- */
- bodyHeader?: ReactNode;
- /**
- * The list of tab navigation items
- */
- tabs?: Array<
- EuiTabProps & {
- name: ReactNode;
- id: string;
- href?: string;
- onClick?: MouseEventHandler;
- }
- >;
- children?: ReactNode;
-};
-
-/**
- * Page View layout for use in Endpoint
- */
-export const PageView = memo(
- ({ viewType, children, headerLeft, headerRight, bodyHeader, tabs, ...otherProps }) => {
- const tabComponents = useMemo(() => {
- if (!tabs) {
- return [];
- }
- return tabs.map(({ name, id, ...otherEuiTabProps }) => (
-
- {name}
-
- ));
- }, [tabs]);
-
- return (
-
-
- {(headerLeft || headerRight) && (
-
-
- {isStringOrNumber.test(typeof headerLeft) ? (
- {headerLeft}
- ) : (
- headerLeft
- )}
-
- {headerRight && (
-
- {headerRight}
-
- )}
-
- )}
- {tabComponents.length > 0 && (
- {tabComponents}
- )}
-
- {bodyHeader && (
-
-
- {isStringOrNumber.test(typeof bodyHeader) ? (
- {bodyHeader}
- ) : (
- bodyHeader
- )}
-
-
- )}
- {children}
-
-
-
- );
- }
-);
-
-PageView.displayName = 'PageView';
diff --git a/x-pack/plugins/security_solution/public/common/components/wrapper_page/index.tsx b/x-pack/plugins/security_solution/public/common/components/wrapper_page/index.tsx
index 373c1f7aaec75..f3136b0a40b3e 100644
--- a/x-pack/plugins/security_solution/public/common/components/wrapper_page/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/wrapper_page/index.tsx
@@ -7,14 +7,15 @@
import classNames from 'classnames';
import React, { useEffect } from 'react';
import styled from 'styled-components';
+import { CommonProps } from '@elastic/eui';
import { useFullScreen } from '../../containers/use_full_screen';
import { gutterTimeline } from '../../lib/helpers';
import { AppGlobalStyle } from '../page/index';
const Wrapper = styled.div`
- padding: ${({ theme }) =>
- `${theme.eui.paddingSizes.l} ${gutterTimeline} ${theme.eui.paddingSizes.l} ${theme.eui.paddingSizes.l}`};
+ padding: ${(props) => `${props.theme.eui.paddingSizes.l}`};
+
&.siemWrapperPage--restrictWidthDefault,
&.siemWrapperPage--restrictWidthCustom {
box-sizing: content-box;
@@ -29,6 +30,10 @@ const Wrapper = styled.div`
height: 100%;
}
+ &.siemWrapperPage--withTimeline {
+ padding-right: ${gutterTimeline};
+ }
+
&.siemWrapperPage--noPadding {
padding: 0;
}
@@ -38,18 +43,20 @@ Wrapper.displayName = 'Wrapper';
interface WrapperPageProps {
children: React.ReactNode;
- className?: string;
restrictWidth?: boolean | number | string;
style?: Record;
noPadding?: boolean;
+ noTimeline?: boolean;
}
-const WrapperPageComponent: React.FC = ({
+const WrapperPageComponent: React.FC = ({
children,
className,
restrictWidth,
style,
noPadding,
+ noTimeline,
+ ...otherProps
}) => {
const { globalFullScreen, setGlobalFullScreen } = useFullScreen();
useEffect(() => {
@@ -59,6 +66,7 @@ const WrapperPageComponent: React.FC = ({
const classes = classNames(className, {
siemWrapperPage: true,
'siemWrapperPage--noPadding': noPadding,
+ 'siemWrapperPage--withTimeline': !noTimeline,
'siemWrapperPage--fullHeight': globalFullScreen,
'siemWrapperPage--restrictWidthDefault':
restrictWidth && typeof restrictWidth === 'boolean' && restrictWidth === true,
@@ -73,7 +81,7 @@ const WrapperPageComponent: React.FC = ({
}
return (
-
+
{children}
diff --git a/x-pack/plugins/security_solution/public/management/common/constants.ts b/x-pack/plugins/security_solution/public/management/common/constants.ts
index 88396cc24a5e2..39d42114f9939 100644
--- a/x-pack/plugins/security_solution/public/management/common/constants.ts
+++ b/x-pack/plugins/security_solution/public/management/common/constants.ts
@@ -13,6 +13,7 @@ export const MANAGEMENT_ROUTING_ROOT_PATH = '';
export const MANAGEMENT_ROUTING_ENDPOINTS_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${AdministrationSubTab.endpoints})`;
export const MANAGEMENT_ROUTING_POLICIES_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${AdministrationSubTab.policies})`;
export const MANAGEMENT_ROUTING_POLICY_DETAILS_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId`;
+export const MANAGEMENT_ROUTING_TRUSTED_APPS_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${AdministrationSubTab.trustedApps})`;
// --[ STORE ]---------------------------------------------------------------------------
/** The SIEM global store namespace where the management state will be mounted */
diff --git a/x-pack/plugins/security_solution/public/management/common/routing.ts b/x-pack/plugins/security_solution/public/management/common/routing.ts
index ea162422abb6f..c5ced6f3bcf55 100644
--- a/x-pack/plugins/security_solution/public/management/common/routing.ts
+++ b/x-pack/plugins/security_solution/public/management/common/routing.ts
@@ -13,6 +13,7 @@ import {
MANAGEMENT_ROUTING_ENDPOINTS_PATH,
MANAGEMENT_ROUTING_POLICIES_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_PATH,
+ MANAGEMENT_ROUTING_TRUSTED_APPS_PATH,
} from './constants';
import { AdministrationSubTab } from '../types';
import { appendSearch } from '../../common/components/link_to/helpers';
@@ -72,13 +73,21 @@ export const getEndpointDetailsPath = (
})}${appendSearch(`${urlQueryParams ? `${urlQueryParams}${urlSearch}` : urlSearch}`)}`;
};
-export const getPoliciesPath = (search?: string) =>
- `${generatePath(MANAGEMENT_ROUTING_POLICIES_PATH, {
+export const getPoliciesPath = (search?: string) => {
+ return `${generatePath(MANAGEMENT_ROUTING_POLICIES_PATH, {
tabName: AdministrationSubTab.policies,
})}${appendSearch(search)}`;
+};
-export const getPolicyDetailPath = (policyId: string, search?: string) =>
- `${generatePath(MANAGEMENT_ROUTING_POLICY_DETAILS_PATH, {
+export const getPolicyDetailPath = (policyId: string, search?: string) => {
+ return `${generatePath(MANAGEMENT_ROUTING_POLICY_DETAILS_PATH, {
tabName: AdministrationSubTab.policies,
policyId,
})}${appendSearch(search)}`;
+};
+
+export const getTrustedAppsListPath = (search?: string) => {
+ return `${generatePath(MANAGEMENT_ROUTING_TRUSTED_APPS_PATH, {
+ tabName: AdministrationSubTab.trustedApps,
+ })}${appendSearch(search)}`;
+};
diff --git a/x-pack/plugins/security_solution/public/management/common/translations.ts b/x-pack/plugins/security_solution/public/management/common/translations.ts
index 03f6a80ef99a4..d24eb1bd315fa 100644
--- a/x-pack/plugins/security_solution/public/management/common/translations.ts
+++ b/x-pack/plugins/security_solution/public/management/common/translations.ts
@@ -13,3 +13,11 @@ export const ENDPOINTS_TAB = i18n.translate('xpack.securitySolution.endpointsTab
export const POLICIES_TAB = i18n.translate('xpack.securitySolution.policiesTab', {
defaultMessage: 'Policies',
});
+
+export const TRUSTED_APPS_TAB = i18n.translate('xpack.securitySolution.trustedAppsTab', {
+ defaultMessage: 'Trusted applications',
+});
+
+export const BETA_BADGE_LABEL = i18n.translate('xpack.securitySolution.administration.list.beta', {
+ defaultMessage: 'Beta',
+});
diff --git a/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx b/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx
new file mode 100644
index 0000000000000..3df525b4d59d6
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx
@@ -0,0 +1,65 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React, { FC, memo } from 'react';
+import { EuiPanel, EuiSpacer, CommonProps } from '@elastic/eui';
+import { SecurityPageName } from '../../../common/constants';
+import { WrapperPage } from '../../common/components/wrapper_page';
+import { HeaderPage } from '../../common/components/header_page';
+import { SiemNavigation } from '../../common/components/navigation';
+import { SpyRoute } from '../../common/utils/route/spy_routes';
+import { AdministrationSubTab } from '../types';
+import { ENDPOINTS_TAB, TRUSTED_APPS_TAB, BETA_BADGE_LABEL } from '../common/translations';
+import { getEndpointListPath, getTrustedAppsListPath } from '../common/routing';
+
+interface AdministrationListPageProps {
+ beta: boolean;
+ title: React.ReactNode;
+ subtitle: React.ReactNode;
+ actions?: React.ReactNode;
+}
+
+export const AdministrationListPage: FC = memo(
+ ({ beta, title, subtitle, actions, children, ...otherProps }) => {
+ const badgeOptions = !beta ? undefined : { beta: true, text: BETA_BADGE_LABEL };
+
+ return (
+
+
+ {actions}
+
+
+
+
+
+
+ {children}
+
+
+
+ );
+ }
+);
+
+AdministrationListPage.displayName = 'AdministrationListPage';
diff --git a/x-pack/plugins/security_solution/public/management/components/management_page_view.tsx b/x-pack/plugins/security_solution/public/management/components/management_page_view.tsx
deleted file mode 100644
index 54d9131209d0d..0000000000000
--- a/x-pack/plugins/security_solution/public/management/components/management_page_view.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import React, { memo } from 'react';
-import { EuiErrorBoundary } from '@elastic/eui';
-import { PageView, PageViewProps } from '../../common/components/endpoint/page_view';
-
-export const ManagementPageView = memo>((options) => {
- return (
-
-
-
- );
-});
-
-ManagementPageView.displayName = 'ManagementPageView';
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
index 09df6d6ece042..fe06bcc8131f8 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
@@ -8,6 +8,7 @@ import React from 'react';
import * as reactTestingLibrary from '@testing-library/react';
import { EndpointList } from './index';
+import '../../../../common/mock/match_media.ts';
import {
mockEndpointDetailsApiResult,
mockEndpointResultList,
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx
index a923d49012d70..611e69391ab06 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx
@@ -10,15 +10,10 @@ import {
EuiBasicTable,
EuiBasicTableColumn,
EuiText,
- EuiTitle,
- EuiSpacer,
EuiLink,
EuiHealth,
EuiToolTip,
EuiSelectableProps,
- EuiBetaBadge,
- EuiFlexGroup,
- EuiFlexItem,
} from '@elastic/eui';
import { useHistory } from 'react-router-dom';
import { i18n } from '@kbn/i18n';
@@ -36,8 +31,6 @@ import {
import { useNavigateByRouterEventHandler } from '../../../../common/hooks/endpoint/use_navigate_by_router_event_handler';
import { CreateStructuredSelector } from '../../../../common/store';
import { Immutable, HostInfo } from '../../../../../common/endpoint/types';
-import { SpyRoute } from '../../../../common/utils/route/spy_routes';
-import { ManagementPageView } from '../../../components/management_page_view';
import { PolicyEmptyState, HostsEmptyState } from '../../../components/management_empty_state';
import { FormattedDate } from '../../../../common/components/formatted_date';
import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
@@ -50,6 +43,7 @@ import { getEndpointListPath, getEndpointDetailsPath } from '../../../common/rou
import { useFormatUrl } from '../../../../common/components/link_to';
import { EndpointAction } from '../store/action';
import { EndpointPolicyLink } from './components/endpoint_policy_link';
+import { AdministrationListPage } from '../../../components/administration_list_page';
const EndpointListNavLink = memo<{
name: string;
@@ -375,40 +369,20 @@ export const EndpointList = () => {
]);
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
+ beta={true}
+ title={
+
+ }
+ subtitle={
+
}
>
{hasSelectedEndpoint && }
@@ -425,7 +399,6 @@ export const EndpointList = () => {
>
)}
{renderTableOrEmptyState}
-
-
+
);
};
diff --git a/x-pack/plugins/security_solution/public/management/pages/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/index.test.tsx
index 9e496ce6c0b50..c04d3b1ec1a90 100644
--- a/x-pack/plugins/security_solution/public/management/pages/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/index.test.tsx
@@ -7,6 +7,7 @@
import React from 'react';
import { ManagementContainer } from './index';
+import '../../common/mock/match_media.ts';
import { AppContextTestRender, createAppRootMockRenderer } from '../../common/mock/endpoint';
import { useIngestEnabledCheck } from '../../common/hooks/endpoint/ingest_enabled';
@@ -22,15 +23,13 @@ describe('when in the Admistration tab', () => {
it('should display the No Permissions view when Ingest is OFF', async () => {
(useIngestEnabledCheck as jest.Mock).mockReturnValue({ allEnabled: false });
- const renderResult = render();
- const noIngestPermissions = await renderResult.findByTestId('noIngestPermissions');
- expect(noIngestPermissions).not.toBeNull();
+
+ expect(await render().findByTestId('noIngestPermissions')).not.toBeNull();
});
it('should display the Management view when Ingest is ON', async () => {
(useIngestEnabledCheck as jest.Mock).mockReturnValue({ allEnabled: true });
- const renderResult = render();
- const endpointPage = await renderResult.findByTestId('endpointPage');
- expect(endpointPage).not.toBeNull();
+
+ expect(await render().findByTestId('endpointPage')).not.toBeNull();
});
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/index.tsx b/x-pack/plugins/security_solution/public/management/pages/index.tsx
index c20a3dd31d6a4..959753cba7bd7 100644
--- a/x-pack/plugins/security_solution/public/management/pages/index.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/index.tsx
@@ -11,55 +11,50 @@ import { useHistory, Route, Switch } from 'react-router-dom';
import { ChromeBreadcrumb } from 'kibana/public';
import { EuiText, EuiEmptyPrompt } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
-import { PolicyContainer } from './policy';
import {
MANAGEMENT_ROUTING_ENDPOINTS_PATH,
MANAGEMENT_ROUTING_POLICIES_PATH,
MANAGEMENT_ROUTING_ROOT_PATH,
+ MANAGEMENT_ROUTING_TRUSTED_APPS_PATH,
} from '../common/constants';
import { NotFoundPage } from '../../app/404';
import { EndpointsContainer } from './endpoint_hosts';
+import { PolicyContainer } from './policy';
+import { TrustedAppsContainer } from './trusted_apps';
import { getEndpointListPath } from '../common/routing';
import { APP_ID, SecurityPageName } from '../../../common/constants';
import { GetUrlForApp } from '../../common/components/navigation/types';
import { AdministrationRouteSpyState } from '../../common/utils/route/types';
import { ADMINISTRATION } from '../../app/home/translations';
import { AdministrationSubTab } from '../types';
-import { ENDPOINTS_TAB, POLICIES_TAB } from '../common/translations';
+import { ENDPOINTS_TAB, POLICIES_TAB, TRUSTED_APPS_TAB } from '../common/translations';
import { SpyRoute } from '../../common/utils/route/spy_routes';
import { useIngestEnabledCheck } from '../../common/hooks/endpoint/ingest_enabled';
-const TabNameMappedToI18nKey: Record = {
+const TabNameMappedToI18nKey: Record = {
[AdministrationSubTab.endpoints]: ENDPOINTS_TAB,
[AdministrationSubTab.policies]: POLICIES_TAB,
+ [AdministrationSubTab.trustedApps]: TRUSTED_APPS_TAB,
};
-export const getBreadcrumbs = (
+export function getBreadcrumbs(
params: AdministrationRouteSpyState,
search: string[],
getUrlForApp: GetUrlForApp
-): ChromeBreadcrumb[] => {
- let breadcrumb = [
+): ChromeBreadcrumb[] {
+ return [
{
text: ADMINISTRATION,
href: getUrlForApp(`${APP_ID}:${SecurityPageName.administration}`, {
path: !isEmpty(search[0]) ? search[0] : '',
}),
},
- ];
-
- const tabName = params?.tabName;
- if (!tabName) return breadcrumb;
-
- breadcrumb = [
- ...breadcrumb,
- {
+ ...(params?.tabName ? [params?.tabName] : []).map((tabName) => ({
text: TabNameMappedToI18nKey[tabName],
href: '',
- },
+ })),
];
- return breadcrumb;
-};
+}
const NoPermissions = memo(() => {
return (
@@ -104,6 +99,7 @@ export const ManagementContainer = memo(() => {
+
{
it('should display back to list button and policy title', () => {
policyView.update();
- const pageHeaderLeft = policyView.find(
- 'EuiPageHeaderSection[data-test-subj="pageViewHeaderLeft"]'
- );
- const backToListButton = pageHeaderLeft.find('EuiButtonEmpty');
- expect(backToListButton.prop('iconType')).toBe('arrowLeft');
- expect(backToListButton.prop('href')).toBe(endpointListPath);
- expect(backToListButton.text()).toBe('Back to endpoint hosts');
+ const backToListLink = policyView.find('LinkIcon[dataTestSubj="policyDetailsBackLink"]');
+ expect(backToListLink.prop('iconType')).toBe('arrowLeft');
+ expect(backToListLink.prop('href')).toBe(endpointListPath);
+ expect(backToListLink.text()).toBe('Back to endpoint hosts');
- const pageTitle = pageHeaderLeft.find('[data-test-subj="pageViewHeaderLeftTitle"]');
+ const pageTitle = policyView.find('h1[data-test-subj="header-page-title"]');
expect(pageTitle).toHaveLength(1);
expect(pageTitle.text()).toEqual(policyPackageConfig.name);
});
it('should navigate to list if back to link is clicked', async () => {
policyView.update();
- const backToListButton = policyView.find(
- 'EuiPageHeaderSection[data-test-subj="pageViewHeaderLeft"] EuiButtonEmpty'
- );
+
+ const backToListLink = policyView.find('a[data-test-subj="policyDetailsBackLink"]');
expect(history.location.pathname).toEqual(policyDetailsPathUrl);
- backToListButton.simulate('click', { button: 0 });
+ backToListLink.simulate('click', { button: 0 });
expect(history.location.pathname).toEqual(endpointListPath);
});
it('should display agent stats', async () => {
await asyncActions;
policyView.update();
- const headerRight = policyView.find(
- 'EuiPageHeaderSection[data-test-subj="pageViewHeaderRight"]'
- );
- const agentsSummary = headerRight.find('EuiFlexGroup[data-test-subj="policyAgentsSummary"]');
+
+ const agentsSummary = policyView.find('EuiFlexGroup[data-test-subj="policyAgentsSummary"]');
expect(agentsSummary).toHaveLength(1);
expect(agentsSummary.text()).toBe('Endpoints5Online3Offline1Error1');
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx
index e54e684d788c0..636c0e5e6a0c3 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx
@@ -38,9 +38,6 @@ import { WindowsEvents, MacEvents, LinuxEvents } from './policy_forms/events';
import { MalwareProtections } from './policy_forms/protections/malware';
import { useToasts } from '../../../../common/lib/kibana';
import { AppAction } from '../../../../common/store/actions';
-import { useNavigateByRouterEventHandler } from '../../../../common/hooks/endpoint/use_navigate_by_router_event_handler';
-import { PageViewHeaderTitle } from '../../../../common/components/endpoint/page_view';
-import { ManagementPageView } from '../../../components/management_page_view';
import { SpyRoute } from '../../../../common/utils/route/spy_routes';
import { SecurityPageName } from '../../../../app/types';
import { getEndpointListPath } from '../../../common/routing';
@@ -48,6 +45,8 @@ import { useFormatUrl } from '../../../../common/components/link_to';
import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
import { MANAGEMENT_APP_ID } from '../../../common/constants';
import { PolicyDetailsRouteState } from '../../../../../common/endpoint/types';
+import { WrapperPage } from '../../../../common/components/wrapper_page';
+import { HeaderPage } from '../../../../common/components/header_page';
export const PolicyDetails = React.memo(() => {
const dispatch = useDispatch<(action: AppAction) => void>();
@@ -109,8 +108,6 @@ export const PolicyDetails = React.memo(() => {
}
}, [navigateToApp, toasts, policyName, policyUpdateStatus, routeState]);
- const handleBackToListOnClick = useNavigateByRouterEventHandler(hostListRouterPath);
-
const navigateToAppArguments = useMemo((): Parameters => {
return routeState?.onCancelNavigateTo ?? [MANAGEMENT_APP_ID, { path: hostListRouterPath }];
}, [hostListRouterPath, routeState?.onCancelNavigateTo]);
@@ -142,7 +139,7 @@ export const PolicyDetails = React.memo(() => {
// Else, if we have an error, then show error on the page.
if (!policyItem) {
return (
-
+
{isPolicyLoading ? (
) : policyApiError ? (
@@ -151,28 +148,10 @@ export const PolicyDetails = React.memo(() => {
) : null}
-
+
);
}
- const headerLeftContent = (
-
- {/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
-
-
-
-
{policyItem.name}
-
- );
-
const headerRightContent = (
@@ -222,12 +201,21 @@ export const PolicyDetails = React.memo(() => {
onConfirm={handleSaveConfirmation}
/>
)}
-
+
+
+ {headerRightContent}
+
+
{
/>
+
+
{
/>
+
-
+
+
>
);
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx
index 97eaceff91e9c..b0139bf3ecb21 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx
@@ -6,6 +6,7 @@
import React from 'react';
import { PolicyList } from './index';
+import '../../../../common/mock/match_media.ts';
import { AppContextTestRender, createAppRootMockRenderer } from '../../../../common/mock/endpoint';
import { setPolicyListApiMockImplementation } from '../store/policy_list/test_mock_utils';
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx
index 39b77d259add1..b97ea958fd1c2 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx
@@ -8,7 +8,6 @@ import React, { useCallback, useEffect, useMemo, CSSProperties, useState } from
import {
EuiBasicTable,
EuiText,
- EuiTitle,
EuiSpacer,
EuiFlexGroup,
EuiFlexItem,
@@ -23,7 +22,6 @@ import {
EuiConfirmModal,
EuiCallOut,
EuiButton,
- EuiBetaBadge,
EuiHorizontalRule,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@@ -41,9 +39,7 @@ import { useKibana } from '../../../../../../../../src/plugins/kibana_react/publ
import { Immutable, PolicyData } from '../../../../../common/endpoint/types';
import { useNavigateByRouterEventHandler } from '../../../../common/hooks/endpoint/use_navigate_by_router_event_handler';
import { LinkToApp } from '../../../../common/components/endpoint/link_to_app';
-import { ManagementPageView } from '../../../components/management_page_view';
import { PolicyEmptyState } from '../../../components/management_empty_state';
-import { SpyRoute } from '../../../../common/utils/route/spy_routes';
import { FormattedDateAndTime } from '../../../../common/components/endpoint/formatted_date_time';
import { SecurityPageName } from '../../../../app/types';
import { useFormatUrl } from '../../../../common/components/link_to';
@@ -51,6 +47,7 @@ import { getPolicyDetailPath, getPoliciesPath } from '../../../common/routing';
import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
import { CreatePackageConfigRouteState } from '../../../../../../ingest_manager/public';
import { MANAGEMENT_APP_ID } from '../../../common/constants';
+import { AdministrationListPage } from '../../../components/administration_list_page';
interface TableChangeCallbackArguments {
page: { index: number; size: number };
@@ -405,42 +402,22 @@ export const PolicyList = React.memo(() => {
}}
/>
)}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
+ beta={true}
+ title={
+
+ }
+ subtitle={
+
}
- headerRight={
+ actions={
{
>
)}
{bodyContent}
-
-
+
>
);
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/index.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/index.tsx
new file mode 100644
index 0000000000000..ddc16cc448c8a
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/index.tsx
@@ -0,0 +1,20 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { Switch, Route } from 'react-router-dom';
+import React from 'react';
+import { TrustedAppsPage } from './view';
+import { MANAGEMENT_ROUTING_TRUSTED_APPS_PATH } from '../../common/constants';
+import { NotFoundPage } from '../../../app/404';
+
+export function TrustedAppsContainer() {
+ return (
+
+
+
+
+ );
+}
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_page.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_page.test.tsx.snap
new file mode 100644
index 0000000000000..6f074f3809036
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_page.test.tsx.snap
@@ -0,0 +1,21 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`TrustedAppsPage rendering 1`] = `
+
+ }
+ title={
+
+ }
+/>
+`;
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/index.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/index.ts
new file mode 100644
index 0000000000000..af6f7e2863dfb
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/index.ts
@@ -0,0 +1,6 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+export * from './trusted_apps_page';
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/routes.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx
similarity index 51%
rename from x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/routes.tsx
rename to x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx
index 727d2715f0974..cc7dbd42d7a7d 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/routes.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx
@@ -3,16 +3,13 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-
+import { shallow } from 'enzyme';
import React from 'react';
-import { Route, Switch } from 'react-router-dom';
-import { EndpointList } from './view';
+import { TrustedAppsPage } from './trusted_apps_page';
-export const EndpointHostsRoutes: React.FC = () => (
-
-
-
-
-
-);
+describe('TrustedAppsPage', () => {
+ test('rendering', () => {
+ expect(shallow( )).toMatchSnapshot();
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx
new file mode 100644
index 0000000000000..7045fa49ffad3
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx
@@ -0,0 +1,28 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { AdministrationListPage } from '../../../components/administration_list_page';
+
+export function TrustedAppsPage() {
+ return (
+
+ }
+ subtitle={
+
+ }
+ />
+ );
+}
diff --git a/x-pack/plugins/security_solution/public/management/types.ts b/x-pack/plugins/security_solution/public/management/types.ts
index c35c9277b4488..21214241d1981 100644
--- a/x-pack/plugins/security_solution/public/management/types.ts
+++ b/x-pack/plugins/security_solution/public/management/types.ts
@@ -27,6 +27,7 @@ export type ManagementState = CombinedState<{
export enum AdministrationSubTab {
endpoints = 'endpoints',
policies = 'policy',
+ trustedApps = 'trusted_apps',
}
/**
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 3daa936f5be58..e617c7ff30885 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -16251,7 +16251,6 @@
"xpack.securitySolution.endpoint.details.policyResponse.success": "成功",
"xpack.securitySolution.endpoint.details.policyResponse.warning": "警告",
"xpack.securitySolution.endpoint.details.policyResponse.workflow": "ワークフロー",
- "xpack.securitySolution.endpoint.list.beta": "ベータ",
"xpack.securitySolution.endpoint.list.loadingPolicies": "ポリシー構成を読み込んでいます…",
"xpack.securitySolution.endpoint.list.noEndpointsInstructions": "セキュリティポリシーを作成しました。以下のステップに従い、エージェントでElastic Endpoint Security機能を有効にする必要があります。",
"xpack.securitySolution.endpoint.list.noEndpointsPrompt": "エージェントでElastic Endpoint Securityを有効にする",
@@ -16310,7 +16309,6 @@
"xpack.securitySolution.endpoint.policyList.actionButtonText": "Endpoint Securityを追加",
"xpack.securitySolution.endpoint.policyList.actionMenu": "開く",
"xpack.securitySolution.endpoint.policyList.agentConfigAction": "エージェント構成を表示",
- "xpack.securitySolution.endpoint.policyList.beta": "ベータ",
"xpack.securitySolution.endpoint.policyList.createdAt": "作成日時",
"xpack.securitySolution.endpoint.policyList.createdBy": "作成者",
"xpack.securitySolution.endpoint.policyList.createNewButton": "新しいポリシーを作成",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index f396d950f7526..4c2691035ee54 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -16257,7 +16257,6 @@
"xpack.securitySolution.endpoint.details.policyResponse.success": "成功",
"xpack.securitySolution.endpoint.details.policyResponse.warning": "警告",
"xpack.securitySolution.endpoint.details.policyResponse.workflow": "工作流",
- "xpack.securitySolution.endpoint.list.beta": "公测版",
"xpack.securitySolution.endpoint.list.loadingPolicies": "正在加载政策配置",
"xpack.securitySolution.endpoint.list.noEndpointsInstructions": "您已创建安全策略。现在您需要按照下面的步骤在代理上启用 Elastic Endpoint Security 功能。",
"xpack.securitySolution.endpoint.list.noEndpointsPrompt": "在您的代理上启用 Elastic Endpoint Security",
@@ -16317,7 +16316,6 @@
"xpack.securitySolution.endpoint.policyList.actionButtonText": "添加 Endpoint Security",
"xpack.securitySolution.endpoint.policyList.actionMenu": "打开",
"xpack.securitySolution.endpoint.policyList.agentConfigAction": "查看代理配置",
- "xpack.securitySolution.endpoint.policyList.beta": "公测版",
"xpack.securitySolution.endpoint.policyList.createdAt": "创建日期",
"xpack.securitySolution.endpoint.policyList.createdBy": "创建者",
"xpack.securitySolution.endpoint.policyList.createNewButton": "创建新策略",
diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts
index 0037c39b8fed2..1843fca29758a 100644
--- a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts
+++ b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts
@@ -27,8 +27,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
it('finds page title', async () => {
- const title = await testSubjects.getVisibleText('pageViewHeaderLeftTitle');
- expect(title).to.equal('Endpoints');
+ const title = await testSubjects.getVisibleText('header-page-title');
+ expect(title).to.equal('Endpoints BETA');
});
it('displays table data', async () => {
diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts
index 1119906ba5cfa..9efba2b595bde 100644
--- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts
+++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts
@@ -47,7 +47,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
});
it('should display policy view', async () => {
- expect(await testSubjects.getVisibleText('pageViewHeaderLeftTitle')).to.equal(
+ expect(await testSubjects.getVisibleText('header-page-title')).to.equal(
policyInfo.packageConfig.name
);
});
diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_list.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_list.ts
index 0c5e15ed4104c..cf93ca1b68991 100644
--- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_list.ts
+++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_list.ts
@@ -29,8 +29,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await testSubjects.existOrFail('policyListPage');
});
it('displays page title', async () => {
- const policyTitle = await testSubjects.getVisibleText('pageViewHeaderLeftTitle');
- expect(policyTitle).to.equal('Policies');
+ const policyTitle = await testSubjects.getVisibleText('header-page-title');
+ expect(policyTitle).to.equal('Policies BETA');
});
it('shows header create policy button', async () => {
const createButtonTitle = await testSubjects.getVisibleText('headerCreateNewPolicyButton');
diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/trusted_apps_list.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/trusted_apps_list.ts
new file mode 100644
index 0000000000000..5f749ac272474
--- /dev/null
+++ b/x-pack/test/security_solution_endpoint/apps/endpoint/trusted_apps_list.ts
@@ -0,0 +1,29 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import expect from '@kbn/expect';
+import { FtrProviderContext } from '../../ftr_provider_context';
+
+export default ({ getPageObjects, getService }: FtrProviderContext) => {
+ const pageObjects = getPageObjects(['trustedApps']);
+ const testSubjects = getService('testSubjects');
+
+ describe('endpoint list', function () {
+ this.tags('ciGroup7');
+
+ describe('when there is data', () => {
+ before(async () => {
+ await pageObjects.trustedApps.navigateToTrustedAppsList();
+ });
+
+ it('finds page title', async () => {
+ expect(await testSubjects.getVisibleText('header-page-title')).to.equal(
+ 'Trusted applications BETA'
+ );
+ });
+ });
+ });
+};
diff --git a/x-pack/test/security_solution_endpoint/page_objects/index.ts b/x-pack/test/security_solution_endpoint/page_objects/index.ts
index 68e1ad00619c7..05762ba887c6f 100644
--- a/x-pack/test/security_solution_endpoint/page_objects/index.ts
+++ b/x-pack/test/security_solution_endpoint/page_objects/index.ts
@@ -7,6 +7,7 @@
import { pageObjects as xpackFunctionalPageObjects } from '../../functional/page_objects';
import { EndpointPageProvider } from './endpoint_page';
import { EndpointPolicyPageProvider } from './policy_page';
+import { TrustedAppsPageProvider } from './trusted_apps_page';
import { EndpointPageUtils } from './page_utils';
import { IngestManagerCreatePackageConfig } from './ingest_manager_create_package_config_page';
@@ -14,6 +15,7 @@ export const pageObjects = {
...xpackFunctionalPageObjects,
endpoint: EndpointPageProvider,
policy: EndpointPolicyPageProvider,
+ trustedApps: TrustedAppsPageProvider,
endpointPageUtils: EndpointPageUtils,
ingestManagerCreatePackageConfig: IngestManagerCreatePackageConfig,
};
diff --git a/x-pack/test/security_solution_endpoint/page_objects/trusted_apps_page.ts b/x-pack/test/security_solution_endpoint/page_objects/trusted_apps_page.ts
new file mode 100644
index 0000000000000..c02ac0ca9ffe0
--- /dev/null
+++ b/x-pack/test/security_solution_endpoint/page_objects/trusted_apps_page.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { FtrProviderContext } from '../ftr_provider_context';
+
+export function TrustedAppsPageProvider({ getPageObjects }: FtrProviderContext) {
+ const pageObjects = getPageObjects(['common', 'header', 'endpointPageUtils']);
+
+ return {
+ async navigateToTrustedAppsList(searchParams?: string) {
+ await pageObjects.common.navigateToUrlWithBrowserHistory(
+ 'securitySolutionManagement',
+ `/trusted_apps${searchParams ? `?${searchParams}` : ''}`
+ );
+ await pageObjects.header.waitUntilLoadingHasFinished();
+ },
+ };
+}
From f0430f298fad4afd527ad7f113b0290241cab20e Mon Sep 17 00:00:00 2001
From: Spencer
Date: Tue, 18 Aug 2020 10:45:26 -0700
Subject: [PATCH 03/85] [src/dev/build] remove node-version from snapshots
(#75303)
Co-authored-by: spalger
---
.../nodejs/extract_node_builds_task.test.ts | 29 ++++++++++++++-----
.../verify_existing_node_builds_task.test.ts | 15 +++++++++-
2 files changed, 35 insertions(+), 9 deletions(-)
diff --git a/src/dev/build/tasks/nodejs/extract_node_builds_task.test.ts b/src/dev/build/tasks/nodejs/extract_node_builds_task.test.ts
index f1700ef7b578c..a5b9e01714f38 100644
--- a/src/dev/build/tasks/nodejs/extract_node_builds_task.test.ts
+++ b/src/dev/build/tasks/nodejs/extract_node_builds_task.test.ts
@@ -17,10 +17,15 @@
* under the License.
*/
+import { readFileSync } from 'fs';
+import Path from 'path';
+
import {
ToolingLog,
ToolingLogCollectingWriter,
createAbsolutePathSerializer,
+ createRecursiveSerializer,
+ REPO_ROOT,
} from '@kbn/dev-utils';
import { Config } from '../../lib';
@@ -37,6 +42,14 @@ log.setWriters([testWriter]);
expect.addSnapshotSerializer(createAbsolutePathSerializer());
+const nodeVersion = readFileSync(Path.resolve(REPO_ROOT, '.node-version'), 'utf8').trim();
+expect.addSnapshotSerializer(
+ createRecursiveSerializer(
+ (s) => typeof s === 'string' && s.includes(nodeVersion),
+ (s) => s.split(nodeVersion).join('')
+ )
+);
+
async function setup() {
const config = await Config.create({
isRelease: true,
@@ -74,8 +87,8 @@ it('runs expected fs operations', async () => {
Object {
"copy": Array [
Array [
- /.node_binaries/10.21.0/node.exe,
- /.node_binaries/10.21.0/win32-x64/node.exe,
+ /.node_binaries//node.exe,
+ /.node_binaries//win32-x64/node.exe,
Object {
"clone": true,
},
@@ -83,22 +96,22 @@ it('runs expected fs operations', async () => {
],
"untar": Array [
Array [
- /.node_binaries/10.21.0/node-v10.21.0-linux-x64.tar.gz,
- /.node_binaries/10.21.0/linux-x64,
+ /.node_binaries//node-v-linux-x64.tar.gz,
+ /.node_binaries//linux-x64,
Object {
"strip": 1,
},
],
Array [
- /.node_binaries/10.21.0/node-v10.21.0-linux-arm64.tar.gz,
- /.node_binaries/10.21.0/linux-arm64,
+ /.node_binaries//node-v-linux-arm64.tar.gz,
+ /.node_binaries//linux-arm64,
Object {
"strip": 1,
},
],
Array [
- /.node_binaries/10.21.0/node-v10.21.0-darwin-x64.tar.gz,
- /.node_binaries/10.21.0/darwin-x64,
+ /.node_binaries//node-v-darwin-x64.tar.gz,
+ /.node_binaries//darwin-x64,
Object {
"strip": 1,
},
diff --git a/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.test.ts b/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.test.ts
index 19416963d5edd..1a850890a33fe 100644
--- a/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.test.ts
+++ b/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.test.ts
@@ -17,10 +17,15 @@
* under the License.
*/
+import Path from 'path';
+import Fs from 'fs';
+
import {
ToolingLog,
ToolingLogCollectingWriter,
createAnyInstanceSerializer,
+ createRecursiveSerializer,
+ REPO_ROOT,
} from '@kbn/dev-utils';
import { Config, Platform } from '../../lib';
@@ -41,6 +46,14 @@ log.setWriters([testWriter]);
expect.addSnapshotSerializer(createAnyInstanceSerializer(Config));
+const nodeVersion = Fs.readFileSync(Path.resolve(REPO_ROOT, '.node-version'), 'utf8').trim();
+expect.addSnapshotSerializer(
+ createRecursiveSerializer(
+ (s) => typeof s === 'string' && s.includes(nodeVersion),
+ (s) => s.split(nodeVersion).join('')
+ )
+);
+
async function setup(actualShaSums?: Record) {
const config = await Config.create({
isRelease: true,
@@ -87,7 +100,7 @@ it('checks shasums for each downloaded node build', async () => {
[MockFunction] {
"calls": Array [
Array [
- "10.21.0",
+ "",
],
],
"results": Array [
From 83bc5004e3ea33b4d671b0204f5f72e1274a1388 Mon Sep 17 00:00:00 2001
From: Brandon Kobel
Date: Tue, 18 Aug 2020 10:46:08 -0700
Subject: [PATCH 04/85] Allow routes to specify the idle socket timeout in
addition to the payload timeout (#73730)
* Route options timeout -> timeout.payload
* timeout.idleSocket can now be specified per route
* Removing nested ternary
* Fixing integration tests
* Trying to actually fix the integration tests. Existing tests are hitting
idle socket timeout, not the payload timeout
* Fixing payload post timeout integration test
* Fixing PUT and DELETE payload sending too long tests
* Fixing type-script errors
* GET routes can't specify the payload timeout, they can't accept payloads
* Removing some redundancy in the tests
* Adding 'Transfer-Encoding: chunked' to the POST test
* Fixing POST/GET/PUT quick tests
* Adding idleSocket timeout test
* Removing unnecessary `isSafeMethod` call
* Updating documentation
* Removing PUT/DELETE integration tests
* Working around the HapiJS bug
* Deleting unused type import
* The socket can be undefined...
This occurs when using @hapi/shot directly or indirectly via
Server.inject. In these scenarios, there isn't a socket. This can also
occur when a "fake request" is used by the hacky background jobs:
Reporting and Alerting...
* Update src/core/server/http/http_server.ts
Co-authored-by: Josh Dover
* Adding payload timeout functional tests
* Adding idle socket timeout functional tests
* Adding better comments, using ?? instead of ||
* Fixing the plugin fixture TS
* Fixing some typescript errors
* Fixing plugin fixture tsconfig.json
Co-authored-by: Elastic Machine
Co-authored-by: Josh Dover
---
...a-plugin-core-server.routeconfigoptions.md | 2 +-
...-core-server.routeconfigoptions.timeout.md | 7 +-
src/core/server/http/http_server.test.ts | 314 ++++++++++++------
src/core/server/http/http_server.ts | 37 ++-
.../http/integration_tests/router.test.ts | 280 ++++++++++------
src/core/server/http/router/request.ts | 12 +-
src/core/server/http/router/route.ts | 15 +-
src/core/server/server.api.md | 5 +-
test/plugin_functional/config.ts | 1 +
.../core_plugin_route_timeouts/kibana.json | 8 +
.../core_plugin_route_timeouts/package.json | 17 +
.../server/index.ts | 23 ++
.../server/plugin.ts | 124 +++++++
.../core_plugin_route_timeouts/tsconfig.json | 12 +
.../test_suites/core/index.ts | 25 ++
.../test_suites/core/route.ts | 174 ++++++++++
.../server/routes/import_list_item_route.ts | 4 +-
17 files changed, 843 insertions(+), 217 deletions(-)
create mode 100644 test/plugin_functional/plugins/core_plugin_route_timeouts/kibana.json
create mode 100644 test/plugin_functional/plugins/core_plugin_route_timeouts/package.json
create mode 100644 test/plugin_functional/plugins/core_plugin_route_timeouts/server/index.ts
create mode 100644 test/plugin_functional/plugins/core_plugin_route_timeouts/server/plugin.ts
create mode 100644 test/plugin_functional/plugins/core_plugin_route_timeouts/tsconfig.json
create mode 100644 test/plugin_functional/test_suites/core/index.ts
create mode 100644 test/plugin_functional/test_suites/core/route.ts
diff --git a/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.md b/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.md
index fee6124f8d866..17fafe2af0de1 100644
--- a/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.md
+++ b/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.md
@@ -19,6 +19,6 @@ export interface RouteConfigOptions
| [authRequired](./kibana-plugin-core-server.routeconfigoptions.authrequired.md) | boolean | 'optional'
| Defines authentication mode for a route: - true. A user has to have valid credentials to access a resource - false. A user can access a resource without any credentials. - 'optional'. A user can access a resource if has valid credentials or no credentials at all. Can be useful when we grant access to a resource but want to identify a user if possible.Defaults to true
if an auth mechanism is registered. |
| [body](./kibana-plugin-core-server.routeconfigoptions.body.md) | Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody
| Additional body options [RouteConfigOptionsBody](./kibana-plugin-core-server.routeconfigoptionsbody.md). |
| [tags](./kibana-plugin-core-server.routeconfigoptions.tags.md) | readonly string[]
| Additional metadata tag strings to attach to the route. |
-| [timeout](./kibana-plugin-core-server.routeconfigoptions.timeout.md) | number
| Timeouts for processing durations. Response timeout is in milliseconds. Default value: 2 minutes |
+| [timeout](./kibana-plugin-core-server.routeconfigoptions.timeout.md) | {
payload?: Method extends 'get' | 'options' ? undefined : number;
idleSocket?: number;
}
| Defines per-route timeouts. |
| [xsrfRequired](./kibana-plugin-core-server.routeconfigoptions.xsrfrequired.md) | Method extends 'get' ? never : boolean
| Defines xsrf protection requirements for a route: - true. Requires an incoming POST/PUT/DELETE request to contain kbn-xsrf
header. - false. Disables xsrf protection.Set to true by default |
diff --git a/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.timeout.md b/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.timeout.md
index 479fcf883ec4d..f602a8913964f 100644
--- a/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.timeout.md
+++ b/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.timeout.md
@@ -4,10 +4,13 @@
## RouteConfigOptions.timeout property
-Timeouts for processing durations. Response timeout is in milliseconds. Default value: 2 minutes
+Defines per-route timeouts.
Signature:
```typescript
-timeout?: number;
+timeout?: {
+ payload?: Method extends 'get' | 'options' ? undefined : number;
+ idleSocket?: number;
+ };
```
diff --git a/src/core/server/http/http_server.test.ts b/src/core/server/http/http_server.test.ts
index 007d75a69b955..abe70e003732b 100644
--- a/src/core/server/http/http_server.test.ts
+++ b/src/core/server/http/http_server.test.ts
@@ -799,6 +799,7 @@ test('exposes route details of incoming request to a route handler', async () =>
authRequired: true,
xsrfRequired: false,
tags: [],
+ timeout: {},
},
});
});
@@ -906,6 +907,9 @@ test('exposes route details of incoming request to a route handler (POST + paylo
authRequired: true,
xsrfRequired: true,
tags: [],
+ timeout: {
+ payload: 10000,
+ },
body: {
parse: true, // hapi populates the default
maxBytes: 1024, // hapi populates the default
@@ -993,129 +997,249 @@ describe('body options', () => {
});
describe('timeout options', () => {
- test('should accept a socket "timeout" which is 3 minutes in milliseconds, "300000" for a POST', async () => {
- const { registerRouter, server: innerServer } = await server.setup(config);
+ describe('payload timeout', () => {
+ test('POST routes set the payload timeout', async () => {
+ const { registerRouter, server: innerServer } = await server.setup(config);
+
+ const router = new Router('', logger, enhanceWithContext);
+ router.post(
+ {
+ path: '/',
+ validate: false,
+ options: {
+ timeout: {
+ payload: 300000,
+ },
+ },
+ },
+ (context, req, res) => {
+ try {
+ return res.ok({
+ body: {
+ timeout: req.route.options.timeout,
+ },
+ });
+ } catch (err) {
+ return res.internalError({ body: err.message });
+ }
+ }
+ );
+ registerRouter(router);
+ await server.start();
+ await supertest(innerServer.listener)
+ .post('/')
+ .send({ test: 1 })
+ .expect(200, {
+ timeout: {
+ payload: 300000,
+ },
+ });
+ });
- const router = new Router('', logger, enhanceWithContext);
- router.post(
- {
- path: '/',
- validate: false,
- options: { timeout: 300000 },
- },
- (context, req, res) => {
- try {
- return res.ok({ body: { timeout: req.route.options.timeout } });
- } catch (err) {
- return res.internalError({ body: err.message });
+ test('DELETE routes set the payload timeout', async () => {
+ const { registerRouter, server: innerServer } = await server.setup(config);
+
+ const router = new Router('', logger, enhanceWithContext);
+ router.delete(
+ {
+ path: '/',
+ validate: false,
+ options: {
+ timeout: {
+ payload: 300000,
+ },
+ },
+ },
+ (context, req, res) => {
+ try {
+ return res.ok({
+ body: {
+ timeout: req.route.options.timeout,
+ },
+ });
+ } catch (err) {
+ return res.internalError({ body: err.message });
+ }
}
- }
- );
- registerRouter(router);
- await server.start();
- await supertest(innerServer.listener).post('/').send({ test: 1 }).expect(200, {
- timeout: 300000,
+ );
+ registerRouter(router);
+ await server.start();
+ await supertest(innerServer.listener)
+ .delete('/')
+ .expect(200, {
+ timeout: {
+ payload: 300000,
+ },
+ });
});
- });
- test('should accept a socket "timeout" which is 3 minutes in milliseconds, "300000" for a GET', async () => {
- const { registerRouter, server: innerServer } = await server.setup(config);
+ test('PUT routes set the payload timeout and automatically adjusts the idle socket timeout', async () => {
+ const { registerRouter, server: innerServer } = await server.setup(config);
+
+ const router = new Router('', logger, enhanceWithContext);
+ router.put(
+ {
+ path: '/',
+ validate: false,
+ options: {
+ timeout: {
+ payload: 300000,
+ },
+ },
+ },
+ (context, req, res) => {
+ try {
+ return res.ok({
+ body: {
+ timeout: req.route.options.timeout,
+ },
+ });
+ } catch (err) {
+ return res.internalError({ body: err.message });
+ }
+ }
+ );
+ registerRouter(router);
+ await server.start();
+ await supertest(innerServer.listener)
+ .put('/')
+ .expect(200, {
+ timeout: {
+ payload: 300000,
+ },
+ });
+ });
- const router = new Router('', logger, enhanceWithContext);
- router.get(
- {
- path: '/',
- validate: false,
- options: { timeout: 300000 },
- },
- (context, req, res) => {
- try {
- return res.ok({ body: { timeout: req.route.options.timeout } });
- } catch (err) {
- return res.internalError({ body: err.message });
+ test('PATCH routes set the payload timeout and automatically adjusts the idle socket timeout', async () => {
+ const { registerRouter, server: innerServer } = await server.setup(config);
+
+ const router = new Router('', logger, enhanceWithContext);
+ router.patch(
+ {
+ path: '/',
+ validate: false,
+ options: {
+ timeout: {
+ payload: 300000,
+ },
+ },
+ },
+ (context, req, res) => {
+ try {
+ return res.ok({
+ body: {
+ timeout: req.route.options.timeout,
+ },
+ });
+ } catch (err) {
+ return res.internalError({ body: err.message });
+ }
}
- }
- );
- registerRouter(router);
- await server.start();
- await supertest(innerServer.listener).get('/').expect(200, {
- timeout: 300000,
+ );
+ registerRouter(router);
+ await server.start();
+ await supertest(innerServer.listener)
+ .patch('/')
+ .expect(200, {
+ timeout: {
+ payload: 300000,
+ },
+ });
});
});
- test('should accept a socket "timeout" which is 3 minutes in milliseconds, "300000" for a DELETE', async () => {
- const { registerRouter, server: innerServer } = await server.setup(config);
+ describe('idleSocket timeout', () => {
+ test('uses server socket timeout when not specified in the route', async () => {
+ const { registerRouter, server: innerServer } = await server.setup({
+ ...config,
+ socketTimeout: 11000,
+ });
- const router = new Router('', logger, enhanceWithContext);
- router.delete(
- {
- path: '/',
- validate: false,
- options: { timeout: 300000 },
- },
- (context, req, res) => {
- try {
- return res.ok({ body: { timeout: req.route.options.timeout } });
- } catch (err) {
- return res.internalError({ body: err.message });
+ const router = new Router('', logger, enhanceWithContext);
+ router.get(
+ {
+ path: '/',
+ validate: { body: schema.any() },
+ },
+ (context, req, res) => {
+ return res.ok({
+ body: {
+ timeout: req.route.options.timeout,
+ },
+ });
}
- }
- );
- registerRouter(router);
- await server.start();
- await supertest(innerServer.listener).delete('/').expect(200, {
- timeout: 300000,
+ );
+ registerRouter(router);
+
+ await server.start();
+ await supertest(innerServer.listener)
+ .get('/')
+ .send()
+ .expect(200, {
+ timeout: {
+ idleSocket: 11000,
+ },
+ });
});
- });
- test('should accept a socket "timeout" which is 3 minutes in milliseconds, "300000" for a PUT', async () => {
- const { registerRouter, server: innerServer } = await server.setup(config);
+ test('sets the socket timeout when specified in the route', async () => {
+ const { registerRouter, server: innerServer } = await server.setup({
+ ...config,
+ socketTimeout: 11000,
+ });
- const router = new Router('', logger, enhanceWithContext);
- router.put(
- {
- path: '/',
- validate: false,
- options: { timeout: 300000 },
- },
- (context, req, res) => {
- try {
- return res.ok({ body: { timeout: req.route.options.timeout } });
- } catch (err) {
- return res.internalError({ body: err.message });
+ const router = new Router('', logger, enhanceWithContext);
+ router.get(
+ {
+ path: '/',
+ validate: { body: schema.any() },
+ options: { timeout: { idleSocket: 12000 } },
+ },
+ (context, req, res) => {
+ return res.ok({
+ body: {
+ timeout: req.route.options.timeout,
+ },
+ });
}
- }
- );
- registerRouter(router);
- await server.start();
- await supertest(innerServer.listener).put('/').expect(200, {
- timeout: 300000,
+ );
+ registerRouter(router);
+
+ await server.start();
+ await supertest(innerServer.listener)
+ .get('/')
+ .send()
+ .expect(200, {
+ timeout: {
+ idleSocket: 12000,
+ },
+ });
});
});
- test('should accept a socket "timeout" which is 3 minutes in milliseconds, "300000" for a PATCH', async () => {
- const { registerRouter, server: innerServer } = await server.setup(config);
+ test(`idleSocket timeout can be smaller than the payload timeout`, async () => {
+ const { registerRouter } = await server.setup(config);
const router = new Router('', logger, enhanceWithContext);
- router.patch(
+ router.post(
{
path: '/',
- validate: false,
- options: { timeout: 300000 },
+ validate: { body: schema.any() },
+ options: {
+ timeout: {
+ payload: 1000,
+ idleSocket: 10,
+ },
+ },
},
(context, req, res) => {
- try {
- return res.ok({ body: { timeout: req.route.options.timeout } });
- } catch (err) {
- return res.internalError({ body: err.message });
- }
+ return res.ok({ body: { timeout: req.route.options.timeout } });
}
);
+
registerRouter(router);
+
await server.start();
- await supertest(innerServer.listener).patch('/').expect(200, {
- timeout: 300000,
- });
});
});
diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts
index 4b70f58deba99..99ab0ef16c2f9 100644
--- a/src/core/server/http/http_server.ts
+++ b/src/core/server/http/http_server.ts
@@ -163,13 +163,17 @@ export class HttpServer {
const validate = isSafeMethod(route.method) ? undefined : { payload: true };
const { authRequired, tags, body = {}, timeout } = route.options;
const { accepts: allow, maxBytes, output, parse } = body;
- // Hapi does not allow timeouts on payloads to be specified for 'head' or 'get' requests
- const payloadTimeout = isSafeMethod(route.method) || timeout == null ? undefined : timeout;
const kibanaRouteState: KibanaRouteState = {
xsrfRequired: route.options.xsrfRequired ?? !isSafeMethod(route.method),
};
+ // To work around https://github.com/hapijs/hapi/issues/4122 until v20, set the socket
+ // timeout on the route to a fake timeout only when the payload timeout is specified.
+ // Within the onPreAuth lifecycle of the route itself, we'll override the timeout with the
+ // real socket timeout.
+ const fakeSocketTimeout = timeout?.payload ? timeout.payload + 1 : undefined;
+
this.server.route({
handler: route.handler,
method: route.method,
@@ -177,13 +181,29 @@ export class HttpServer {
options: {
auth: this.getAuthOption(authRequired),
app: kibanaRouteState,
+ ext: {
+ onPreAuth: {
+ method: (request, h) => {
+ // At this point, the socket timeout has only been set to work-around the HapiJS bug.
+ // We need to either set the real per-route timeout or use the default idle socket timeout
+ if (timeout?.idleSocket) {
+ request.raw.req.socket.setTimeout(timeout.idleSocket);
+ } else if (fakeSocketTimeout) {
+ // NodeJS uses a socket timeout of `0` to denote "no timeout"
+ request.raw.req.socket.setTimeout(this.config!.socketTimeout ?? 0);
+ }
+
+ return h.continue;
+ },
+ },
+ },
tags: tags ? Array.from(tags) : undefined,
// TODO: This 'validate' section can be removed once the legacy platform is completely removed.
// We are telling Hapi that NP routes can accept any payload, so that it can bypass the default
// validation applied in ./http_tools#getServerOptions
// (All NP routes are already required to specify their own validation in order to access the payload)
validate,
- payload: [allow, maxBytes, output, parse, payloadTimeout].some(
+ payload: [allow, maxBytes, output, parse, timeout?.payload].some(
(v) => typeof v !== 'undefined'
)
? {
@@ -191,15 +211,12 @@ export class HttpServer {
maxBytes,
output,
parse,
- timeout: payloadTimeout,
+ timeout: timeout?.payload,
}
: undefined,
- timeout:
- timeout != null
- ? {
- socket: timeout + 1, // Hapi server requires the socket to be greater than payload settings so we add 1 millisecond
- }
- : undefined,
+ timeout: {
+ socket: fakeSocketTimeout,
+ },
},
});
}
diff --git a/src/core/server/http/integration_tests/router.test.ts b/src/core/server/http/integration_tests/router.test.ts
index 434e22e3cf6f5..e19c348511f1a 100644
--- a/src/core/server/http/integration_tests/router.test.ts
+++ b/src/core/server/http/integration_tests/router.test.ts
@@ -304,126 +304,204 @@ describe('Options', () => {
});
describe('timeout', () => {
- it('should timeout if configured with a small timeout value for a POST', async () => {
- const { server: innerServer, createRouter } = await server.setup(setupDeps);
- const router = createRouter('/');
+ const writeBodyCharAtATime = (request: supertest.Test, body: string, interval: number) => {
+ return new Promise((resolve, reject) => {
+ let i = 0;
+ const intervalId = setInterval(() => {
+ if (i < body.length) {
+ request.write(body[i++]);
+ } else {
+ clearInterval(intervalId);
+ request.end((err, res) => {
+ resolve(res);
+ });
+ }
+ }, interval);
+ request.on('error', (err) => {
+ clearInterval(intervalId);
+ reject(err);
+ });
+ });
+ };
- router.post(
- { path: '/a', validate: false, options: { timeout: 1000 } },
- async (context, req, res) => {
- await new Promise((resolve) => setTimeout(resolve, 2000));
- return res.ok({});
- }
- );
- router.post({ path: '/b', validate: false }, (context, req, res) => res.ok({}));
- await server.start();
- expect(supertest(innerServer.listener).post('/a')).rejects.toThrow('socket hang up');
- await supertest(innerServer.listener).post('/b').expect(200, {});
- });
+ describe('payload', () => {
+ it('should timeout if POST payload sending is too slow', async () => {
+ const { server: innerServer, createRouter } = await server.setup(setupDeps);
+ const router = createRouter('/');
- it('should timeout if configured with a small timeout value for a PUT', async () => {
- const { server: innerServer, createRouter } = await server.setup(setupDeps);
- const router = createRouter('/');
+ router.post(
+ {
+ options: {
+ body: {
+ accepts: ['application/json'],
+ },
+ timeout: { payload: 100 },
+ },
+ path: '/a',
+ validate: false,
+ },
+ async (context, req, res) => {
+ return res.ok({});
+ }
+ );
+ await server.start();
- router.put(
- { path: '/a', validate: false, options: { timeout: 1000 } },
- async (context, req, res) => {
- await new Promise((resolve) => setTimeout(resolve, 2000));
- return res.ok({});
- }
- );
- router.put({ path: '/b', validate: false }, (context, req, res) => res.ok({}));
- await server.start();
+ // start the request
+ const request = supertest(innerServer.listener)
+ .post('/a')
+ .set('Content-Type', 'application/json')
+ .set('Transfer-Encoding', 'chunked');
- expect(supertest(innerServer.listener).put('/a')).rejects.toThrow('socket hang up');
- await supertest(innerServer.listener).put('/b').expect(200, {});
- });
+ const result = writeBodyCharAtATime(request, '{"foo":"bar"}', 10);
- it('should timeout if configured with a small timeout value for a DELETE', async () => {
- const { server: innerServer, createRouter } = await server.setup(setupDeps);
- const router = createRouter('/');
+ await expect(result).rejects.toMatchInlineSnapshot(`[Error: Request Timeout]`);
+ });
- router.delete(
- { path: '/a', validate: false, options: { timeout: 1000 } },
- async (context, req, res) => {
- await new Promise((resolve) => setTimeout(resolve, 2000));
- return res.ok({});
- }
- );
- router.delete({ path: '/b', validate: false }, (context, req, res) => res.ok({}));
- await server.start();
- expect(supertest(innerServer.listener).delete('/a')).rejects.toThrow('socket hang up');
- await supertest(innerServer.listener).delete('/b').expect(200, {});
- });
+ it('should not timeout if POST payload sending is quick', async () => {
+ const { server: innerServer, createRouter } = await server.setup(setupDeps);
+ const router = createRouter('/');
- it('should timeout if configured with a small timeout value for a GET', async () => {
- const { server: innerServer, createRouter } = await server.setup(setupDeps);
- const router = createRouter('/');
+ router.post(
+ {
+ path: '/a',
+ validate: false,
+ options: { body: { accepts: 'application/json' }, timeout: { payload: 10000 } },
+ },
+ async (context, req, res) => res.ok({})
+ );
+ await server.start();
- router.get(
- // Note: There is a bug within Hapi Server where it cannot set the payload timeout for a GET call but it also cannot configure a timeout less than the payload body
- // so the least amount of possible time to configure the timeout is 10 seconds.
- { path: '/a', validate: false, options: { timeout: 100000 } },
- async (context, req, res) => {
- // Cause a wait of 20 seconds to cause the socket hangup
- await new Promise((resolve) => setTimeout(resolve, 200000));
- return res.ok({});
- }
- );
- router.get({ path: '/b', validate: false }, (context, req, res) => res.ok({}));
- await server.start();
+ // start the request
+ const request = supertest(innerServer.listener)
+ .post('/a')
+ .set('Content-Type', 'application/json')
+ .set('Transfer-Encoding', 'chunked');
+
+ const result = writeBodyCharAtATime(request, '{}', 10);
- expect(supertest(innerServer.listener).get('/a')).rejects.toThrow('socket hang up');
- await supertest(innerServer.listener).get('/b').expect(200, {});
+ await expect(result).resolves.toHaveProperty('status', 200);
+ });
});
- it('should not timeout if configured with a 5 minute timeout value for a POST', async () => {
- const { server: innerServer, createRouter } = await server.setup(setupDeps);
- const router = createRouter('/');
+ describe('idleSocket', () => {
+ it('should timeout if payload sending has too long of an idle period', async () => {
+ const { server: innerServer, createRouter } = await server.setup(setupDeps);
+ const router = createRouter('/');
- router.post(
- { path: '/a', validate: false, options: { timeout: 300000 } },
- async (context, req, res) => res.ok({})
- );
- await server.start();
- await supertest(innerServer.listener).post('/a').expect(200, {});
- });
+ router.post(
+ {
+ path: '/a',
+ validate: false,
+ options: {
+ body: {
+ accepts: ['application/json'],
+ },
+ timeout: { idleSocket: 10 },
+ },
+ },
+ async (context, req, res) => {
+ return res.ok({});
+ }
+ );
- it('should not timeout if configured with a 5 minute timeout value for a PUT', async () => {
- const { server: innerServer, createRouter } = await server.setup(setupDeps);
- const router = createRouter('/');
+ await server.start();
- router.put(
- { path: '/a', validate: false, options: { timeout: 300000 } },
- async (context, req, res) => res.ok({})
- );
- await server.start();
+ // start the request
+ const request = supertest(innerServer.listener)
+ .post('/a')
+ .set('Content-Type', 'application/json')
+ .set('Transfer-Encoding', 'chunked');
- await supertest(innerServer.listener).put('/a').expect(200, {});
- });
+ const result = writeBodyCharAtATime(request, '{}', 20);
- it('should not timeout if configured with a 5 minute timeout value for a DELETE', async () => {
- const { server: innerServer, createRouter } = await server.setup(setupDeps);
- const router = createRouter('/');
+ await expect(result).rejects.toThrow('socket hang up');
+ });
- router.delete(
- { path: '/a', validate: false, options: { timeout: 300000 } },
- async (context, req, res) => res.ok({})
- );
- await server.start();
- await supertest(innerServer.listener).delete('/a').expect(200, {});
- });
+ it(`should not timeout if payload sending doesn't have too long of an idle period`, async () => {
+ const { server: innerServer, createRouter } = await server.setup(setupDeps);
+ const router = createRouter('/');
- it('should not timeout if configured with a 5 minute timeout value for a GET', async () => {
- const { server: innerServer, createRouter } = await server.setup(setupDeps);
- const router = createRouter('/');
+ router.post(
+ {
+ path: '/a',
+ validate: false,
+ options: {
+ body: {
+ accepts: ['application/json'],
+ },
+ timeout: { idleSocket: 1000 },
+ },
+ },
+ async (context, req, res) => {
+ return res.ok({});
+ }
+ );
- router.get(
- { path: '/a', validate: false, options: { timeout: 300000 } },
- async (context, req, res) => res.ok({})
- );
- await server.start();
- await supertest(innerServer.listener).get('/a').expect(200, {});
+ await server.start();
+
+ // start the request
+ const request = supertest(innerServer.listener)
+ .post('/a')
+ .set('Content-Type', 'application/json')
+ .set('Transfer-Encoding', 'chunked');
+
+ const result = writeBodyCharAtATime(request, '{}', 10);
+
+ await expect(result).resolves.toHaveProperty('status', 200);
+ });
+
+ it('should timeout if servers response is too slow', async () => {
+ const { server: innerServer, createRouter } = await server.setup(setupDeps);
+ const router = createRouter('/');
+
+ router.post(
+ {
+ path: '/a',
+ validate: false,
+ options: {
+ body: {
+ accepts: ['application/json'],
+ },
+ timeout: { idleSocket: 1000, payload: 100 },
+ },
+ },
+ async (context, req, res) => {
+ await new Promise((resolve) => setTimeout(resolve, 2000));
+ return res.ok({});
+ }
+ );
+
+ await server.start();
+ await expect(supertest(innerServer.listener).post('/a')).rejects.toThrow('socket hang up');
+ });
+
+ it('should not timeout if servers response is quick', async () => {
+ const { server: innerServer, createRouter } = await server.setup(setupDeps);
+ const router = createRouter('/');
+
+ router.post(
+ {
+ path: '/a',
+ validate: false,
+ options: {
+ body: {
+ accepts: ['application/json'],
+ },
+ timeout: { idleSocket: 2000, payload: 100 },
+ },
+ },
+ async (context, req, res) => {
+ await new Promise((resolve) => setTimeout(resolve, 10));
+ return res.ok({});
+ }
+ );
+
+ await server.start();
+ await expect(supertest(innerServer.listener).post('/a')).resolves.toHaveProperty(
+ 'status',
+ 200
+ );
+ });
});
});
});
diff --git a/src/core/server/http/router/request.ts b/src/core/server/http/router/request.ts
index 93ffb5aa48259..278bc222b754b 100644
--- a/src/core/server/http/router/request.ts
+++ b/src/core/server/http/router/request.ts
@@ -211,15 +211,21 @@ export class KibanaRequest<
private getRouteInfo(request: Request): KibanaRequestRoute {
const method = request.method as Method;
- const { parse, maxBytes, allow, output } = request.route.settings.payload || {};
- const timeout = request.route.settings.timeout?.socket;
+ const { parse, maxBytes, allow, output, timeout: payloadTimeout } =
+ request.route.settings.payload || {};
+ // net.Socket#timeout isn't documented, yet, and isn't part of the types... https://github.com/nodejs/node/pull/34543
+ // the socket is also undefined when using @hapi/shot, or when a "fake request" is used
+ const socketTimeout = (request.raw.req.socket as any)?.timeout;
const options = ({
authRequired: this.getAuthRequired(request),
// some places in LP call KibanaRequest.from(request) manually. remove fallback to true before v8
xsrfRequired: (request.route.settings.app as KibanaRouteState)?.xsrfRequired ?? true,
tags: request.route.settings.tags || [],
- timeout: typeof timeout === 'number' ? timeout - 1 : undefined, // We are forced to have the timeout be 1 millisecond greater than the server and payload so we subtract one here to give the user consist settings
+ timeout: {
+ payload: payloadTimeout,
+ idleSocket: socketTimeout === 0 ? undefined : socketTimeout,
+ },
body: isSafeMethod(method)
? undefined
: {
diff --git a/src/core/server/http/router/route.ts b/src/core/server/http/router/route.ts
index 676c494bec522..ce898a34e6b2c 100644
--- a/src/core/server/http/router/route.ts
+++ b/src/core/server/http/router/route.ts
@@ -146,10 +146,19 @@ export interface RouteConfigOptions {
body?: Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody;
/**
- * Timeouts for processing durations. Response timeout is in milliseconds.
- * Default value: 2 minutes
+ * Defines per-route timeouts.
*/
- timeout?: number;
+ timeout?: {
+ /**
+ * Milliseconds to receive the payload
+ */
+ payload?: Method extends 'get' | 'options' ? undefined : number;
+
+ /**
+ * Milliseconds the socket can be idle before it's closed
+ */
+ idleSocket?: number;
+ };
}
/**
diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md
index 60b01aa06d07f..03545284e14fb 100644
--- a/src/core/server/server.api.md
+++ b/src/core/server/server.api.md
@@ -1886,7 +1886,10 @@ export interface RouteConfigOptions {
authRequired?: boolean | 'optional';
body?: Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody;
tags?: readonly string[];
- timeout?: number;
+ timeout?: {
+ payload?: Method extends 'get' | 'options' ? undefined : number;
+ idleSocket?: number;
+ };
xsrfRequired?: Method extends 'get' ? never : boolean;
}
diff --git a/test/plugin_functional/config.ts b/test/plugin_functional/config.ts
index c611300eade10..9ec82e291e537 100644
--- a/test/plugin_functional/config.ts
+++ b/test/plugin_functional/config.ts
@@ -31,6 +31,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
return {
testFiles: [
+ require.resolve('./test_suites/core'),
require.resolve('./test_suites/custom_visualizations'),
require.resolve('./test_suites/panel_actions'),
require.resolve('./test_suites/core_plugins'),
diff --git a/test/plugin_functional/plugins/core_plugin_route_timeouts/kibana.json b/test/plugin_functional/plugins/core_plugin_route_timeouts/kibana.json
new file mode 100644
index 0000000000000..6fbddad22b764
--- /dev/null
+++ b/test/plugin_functional/plugins/core_plugin_route_timeouts/kibana.json
@@ -0,0 +1,8 @@
+{
+ "id": "core_plugin_route_timeouts",
+ "version": "0.0.1",
+ "kibanaVersion": "kibana",
+ "configPath": ["core_plugin_route_timeouts"],
+ "server": true,
+ "ui": false
+}
diff --git a/test/plugin_functional/plugins/core_plugin_route_timeouts/package.json b/test/plugin_functional/plugins/core_plugin_route_timeouts/package.json
new file mode 100644
index 0000000000000..a9c520338457b
--- /dev/null
+++ b/test/plugin_functional/plugins/core_plugin_route_timeouts/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "core_plugin_route_timeouts",
+ "version": "1.0.0",
+ "main": "target/test/plugin_functional/plugins/core_plugin_route_timeouts",
+ "kibana": {
+ "version": "kibana",
+ "templateVersion": "1.0.0"
+ },
+ "license": "Apache-2.0",
+ "scripts": {
+ "kbn": "node ../../../../scripts/kbn.js",
+ "build": "rm -rf './target' && tsc"
+ },
+ "devDependencies": {
+ "typescript": "3.9.5"
+ }
+}
diff --git a/test/plugin_functional/plugins/core_plugin_route_timeouts/server/index.ts b/test/plugin_functional/plugins/core_plugin_route_timeouts/server/index.ts
new file mode 100644
index 0000000000000..4fdd6ef0ab04a
--- /dev/null
+++ b/test/plugin_functional/plugins/core_plugin_route_timeouts/server/index.ts
@@ -0,0 +1,23 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { CorePluginRouteTimeoutsPlugin } from './plugin';
+export { PluginARequestContext } from './plugin';
+
+export const plugin = () => new CorePluginRouteTimeoutsPlugin();
diff --git a/test/plugin_functional/plugins/core_plugin_route_timeouts/server/plugin.ts b/test/plugin_functional/plugins/core_plugin_route_timeouts/server/plugin.ts
new file mode 100644
index 0000000000000..c5bdf6cce084c
--- /dev/null
+++ b/test/plugin_functional/plugins/core_plugin_route_timeouts/server/plugin.ts
@@ -0,0 +1,124 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { Plugin, CoreSetup } from 'kibana/server';
+import { schema } from '@kbn/config-schema';
+
+export interface PluginARequestContext {
+ ping: () => Promise;
+}
+
+declare module 'kibana/server' {
+ interface RequestHandlerContext {
+ pluginA?: PluginARequestContext;
+ }
+}
+
+export class CorePluginRouteTimeoutsPlugin implements Plugin {
+ public setup(core: CoreSetup, deps: {}) {
+ const { http } = core;
+
+ const router = http.createRouter();
+
+ router.post(
+ {
+ options: {
+ body: {
+ accepts: ['application/json'],
+ },
+ timeout: { payload: 100 },
+ },
+ path: '/short_payload_timeout',
+ validate: false,
+ },
+ async (context, req, res) => {
+ return res.ok({});
+ }
+ );
+
+ router.post(
+ {
+ options: {
+ body: {
+ accepts: ['application/json'],
+ },
+ timeout: { payload: 10000 },
+ },
+ path: '/longer_payload_timeout',
+ validate: false,
+ },
+ async (context, req, res) => {
+ return res.ok({});
+ }
+ );
+
+ router.post(
+ {
+ options: {
+ body: {
+ accepts: ['application/json'],
+ },
+ timeout: { idleSocket: 10 },
+ },
+ path: '/short_idle_socket_timeout',
+ validate: {
+ body: schema.maybe(
+ schema.object({
+ responseDelay: schema.maybe(schema.number()),
+ })
+ ),
+ },
+ },
+ async (context, req, res) => {
+ if (req.body?.responseDelay) {
+ await new Promise((resolve) => setTimeout(resolve, req.body!.responseDelay));
+ }
+ return res.ok({});
+ }
+ );
+
+ router.post(
+ {
+ options: {
+ body: {
+ accepts: ['application/json'],
+ },
+ timeout: { idleSocket: 5000 },
+ },
+ path: '/longer_idle_socket_timeout',
+ validate: {
+ body: schema.maybe(
+ schema.object({
+ responseDelay: schema.maybe(schema.number()),
+ })
+ ),
+ },
+ },
+ async (context, req, res) => {
+ if (req.body?.responseDelay) {
+ await new Promise((resolve) => setTimeout(resolve, req.body!.responseDelay));
+ }
+ return res.ok({});
+ }
+ );
+ }
+
+ public start() {}
+ public stop() {}
+}
diff --git a/test/plugin_functional/plugins/core_plugin_route_timeouts/tsconfig.json b/test/plugin_functional/plugins/core_plugin_route_timeouts/tsconfig.json
new file mode 100644
index 0000000000000..d0751f31ecc5e
--- /dev/null
+++ b/test/plugin_functional/plugins/core_plugin_route_timeouts/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "extends": "../../../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./target",
+ "skipLibCheck": true
+ },
+ "include": [
+ "server/**/*.ts",
+ "../../../../typings/**/*"
+ ],
+ "exclude": []
+}
diff --git a/test/plugin_functional/test_suites/core/index.ts b/test/plugin_functional/test_suites/core/index.ts
new file mode 100644
index 0000000000000..5852e21fc3b39
--- /dev/null
+++ b/test/plugin_functional/test_suites/core/index.ts
@@ -0,0 +1,25 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { PluginFunctionalProviderContext } from '../../services';
+
+export default function ({ loadTestFile }: PluginFunctionalProviderContext) {
+ describe('core', function () {
+ loadTestFile(require.resolve('./route'));
+ });
+}
diff --git a/test/plugin_functional/test_suites/core/route.ts b/test/plugin_functional/test_suites/core/route.ts
new file mode 100644
index 0000000000000..becde49a69714
--- /dev/null
+++ b/test/plugin_functional/test_suites/core/route.ts
@@ -0,0 +1,174 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import expect from '@kbn/expect';
+import { Test } from 'supertest';
+import { PluginFunctionalProviderContext } from '../../services';
+
+export default function ({ getService }: PluginFunctionalProviderContext) {
+ const supertest = getService('supertest');
+
+ describe('route', function () {
+ describe('timeouts', function () {
+ const writeBodyCharAtATime = (request: Test, body: string, interval: number) => {
+ return new Promise((resolve, reject) => {
+ let i = 0;
+ const intervalId = setInterval(() => {
+ if (i < body.length) {
+ request.write(body[i++]);
+ } else {
+ clearInterval(intervalId);
+ request.end((err, res) => {
+ resolve(res);
+ });
+ }
+ }, interval);
+ request.on('error', (err) => {
+ clearInterval(intervalId);
+ reject(err);
+ });
+ });
+ };
+
+ describe('payload', function () {
+ it(`should timeout if POST payload sending is too slow`, async () => {
+ // start the request
+ const request = supertest
+ .post('/short_payload_timeout')
+ .set('Content-Type', 'application/json')
+ .set('Transfer-Encoding', 'chunked')
+ .set('kbn-xsrf', 'true');
+
+ const result = writeBodyCharAtATime(request, '{"foo":"bar"}', 10);
+
+ await result.then(
+ (res) => {
+ expect(res).to.be(undefined);
+ },
+ (err) => {
+ expect(err.message).to.be('Request Timeout');
+ }
+ );
+ });
+
+ it(`should not timeout if POST payload sending is quick`, async () => {
+ // start the request
+ const request = supertest
+ .post('/longer_payload_timeout')
+ .set('Content-Type', 'application/json')
+ .set('Transfer-Encoding', 'chunked')
+ .set('kbn-xsrf', 'true');
+
+ const result = writeBodyCharAtATime(request, '{"foo":"bar"}', 10);
+
+ await result.then(
+ (res) => {
+ expect(res).to.have.property('statusCode', 200);
+ },
+ (err) => {
+ expect(err).to.be(undefined);
+ }
+ );
+ });
+ });
+
+ describe('idle socket', function () {
+ it('should timeout if payload sending has too long of an idle period', async function () {
+ // start the request
+ const request = supertest
+ .post('/short_idle_socket_timeout')
+ .set('Content-Type', 'application/json')
+ .set('Transfer-Encoding', 'chunked')
+ .set('kbn-xsrf', 'true');
+
+ const result = writeBodyCharAtATime(request, '{"responseDelay":100}', 20);
+
+ await result.then(
+ (res) => {
+ expect(res).to.be(undefined);
+ },
+ (err) => {
+ expect(err.message).to.be('socket hang up');
+ }
+ );
+ });
+
+ it('should not timeout if payload sending does not have too long of an idle period', async function () {
+ // start the request
+ const request = supertest
+ .post('/longer_idle_socket_timeout')
+ .set('Content-Type', 'application/json')
+ .set('Transfer-Encoding', 'chunked')
+ .set('kbn-xsrf', 'true');
+
+ const result = writeBodyCharAtATime(request, '{"responseDelay":0}', 10);
+
+ await result.then(
+ (res) => {
+ expect(res).to.have.property('statusCode', 200);
+ },
+ (err) => {
+ expect(err).to.be(undefined);
+ }
+ );
+ });
+
+ it('should timeout if servers response is too slow', async function () {
+ // start the request
+ const request = supertest
+ .post('/short_idle_socket_timeout')
+ .set('Content-Type', 'application/json')
+ .set('Transfer-Encoding', 'chunked')
+ .set('kbn-xsrf', 'true');
+
+ const result = writeBodyCharAtATime(request, '{"responseDelay":100}', 0);
+
+ await result.then(
+ (res) => {
+ expect(res).to.be(undefined);
+ },
+ (err) => {
+ expect(err.message).to.be('socket hang up');
+ }
+ );
+ });
+
+ it('should not timeout if servers response is fast enough', async function () {
+ // start the request
+ const request = supertest
+ .post('/longer_idle_socket_timeout')
+ .set('Content-Type', 'application/json')
+ .set('Transfer-Encoding', 'chunked')
+ .set('kbn-xsrf', 'true');
+
+ const result = writeBodyCharAtATime(request, '{"responseDelay":100}', 0);
+
+ await result.then(
+ (res) => {
+ expect(res).to.have.property('statusCode', 200);
+ },
+ (err) => {
+ expect(err).to.be(undefined);
+ }
+ );
+ });
+ });
+ });
+ });
+}
diff --git a/x-pack/plugins/lists/server/routes/import_list_item_route.ts b/x-pack/plugins/lists/server/routes/import_list_item_route.ts
index d46c943d95fe9..f7ecc7ac1ac83 100644
--- a/x-pack/plugins/lists/server/routes/import_list_item_route.ts
+++ b/x-pack/plugins/lists/server/routes/import_list_item_route.ts
@@ -27,7 +27,9 @@ export const importListItemRoute = (router: IRouter, config: ConfigType): void =
parse: false,
},
tags: ['access:lists-all'],
- timeout: config.importTimeout.asMilliseconds(),
+ timeout: {
+ payload: config.importTimeout.asMilliseconds(),
+ },
},
path: `${LIST_ITEM_URL}/_import`,
validate: {
From 47d41437d9dd0ff35874c92042ba737fc709805d Mon Sep 17 00:00:00 2001
From: Tim Sullivan
Date: Tue, 18 Aug 2020 13:26:31 -0700
Subject: [PATCH 05/85] [Reporting] Increase capture.timeouts.openUrl to 1
minute (#75207)
* [Reporting] Increase capture.timeouts.openUrl to 1 minute
* update the docs
---
docs/settings/reporting-settings.asciidoc | 2 +-
x-pack/plugins/reporting/server/config/schema.ts | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/settings/reporting-settings.asciidoc b/docs/settings/reporting-settings.asciidoc
index b31ae76d28052..0b6f94e86a39f 100644
--- a/docs/settings/reporting-settings.asciidoc
+++ b/docs/settings/reporting-settings.asciidoc
@@ -129,7 +129,7 @@ control the capturing process.
| Specify how long to allow the Reporting browser to wait for the "Loading..." screen
to dismiss and find the initial data for the Kibana page. If the time is
exceeded, a page screenshot is captured showing the current state, and the download link shows a warning message.
- Defaults to `30000` (30 seconds).
+ Defaults to `60000` (1 minute).
| `xpack.reporting.capture.timeouts.waitForElements`
| Specify how long to allow the Reporting browser to wait for all visualization
diff --git a/x-pack/plugins/reporting/server/config/schema.ts b/x-pack/plugins/reporting/server/config/schema.ts
index 33249f20757e2..a81ffd754946b 100644
--- a/x-pack/plugins/reporting/server/config/schema.ts
+++ b/x-pack/plugins/reporting/server/config/schema.ts
@@ -46,7 +46,7 @@ const RulesSchema = schema.object({
const CaptureSchema = schema.object({
timeouts: schema.object({
- openUrl: schema.number({ defaultValue: 30000 }),
+ openUrl: schema.number({ defaultValue: 60000 }),
waitForElements: schema.number({ defaultValue: 30000 }),
renderComplete: schema.number({ defaultValue: 30000 }),
}),
From 982402145c959c131b6f48c724674ee9832b414d Mon Sep 17 00:00:00 2001
From: Tim Sullivan
Date: Tue, 18 Aug 2020 13:26:52 -0700
Subject: [PATCH 06/85] [Reporting] Network Policy: Do not throw from the
intercept handler (#75105)
Co-authored-by: Elastic Machine
---
.../server/browsers/chromium/driver/chromium_driver.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts
index 494f7ab0a28db..5f23bbc390bb8 100644
--- a/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts
+++ b/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts
@@ -303,7 +303,7 @@ export class HeadlessChromiumDriver {
if (!allowed || !this.allowRequest(interceptedUrl)) {
this.page.browser().close();
logger.error(getDisallowedOutgoingUrlError(interceptedUrl));
- throw getDisallowedOutgoingUrlError(interceptedUrl);
+ return;
}
});
From 245c0a3708c28ee5893ef859733dbf5ec526fd61 Mon Sep 17 00:00:00 2001
From: Andrea Del Rio
Date: Tue, 18 Aug 2020 13:51:47 -0700
Subject: [PATCH 07/85] [Discover] Create field_button and add popovers to
sidebar (#73226)
Co-authored-by: Michail Yasonik
Co-authored-by: cchaos
Co-authored-by: Joe Reuter
---
.../components/sidebar/discover_field.scss | 4 +
.../sidebar/discover_field.test.tsx | 5 -
.../components/sidebar/discover_field.tsx | 177 ++++++++++--------
.../sidebar/discover_field_details.scss | 5 +
.../sidebar/discover_field_details.tsx | 113 +++++------
.../components/sidebar/discover_sidebar.scss | 47 +----
.../components/sidebar/discover_sidebar.tsx | 20 --
.../__snapshots__/field_button.test.tsx.snap | 134 +++++++++++++
.../public/field_button/field_button.scss | 75 ++++++++
.../public/field_button/field_button.test.tsx | 68 +++++++
.../public/field_button/field_button.tsx | 111 +++++++++++
.../kibana_react/public/field_button/index.ts | 19 ++
src/plugins/kibana_react/public/index.ts | 1 +
.../apps/context/_context_navigation.js | 2 +-
.../indexpattern_datasource/_field_item.scss | 48 +----
.../field_item.test.tsx | 13 +-
.../indexpattern_datasource/field_item.tsx | 83 ++++----
.../translations/translations/ja-JP.json | 3 -
.../translations/translations/zh-CN.json | 3 -
19 files changed, 635 insertions(+), 296 deletions(-)
create mode 100644 src/plugins/discover/public/application/components/sidebar/discover_field.scss
create mode 100644 src/plugins/discover/public/application/components/sidebar/discover_field_details.scss
create mode 100644 src/plugins/kibana_react/public/field_button/__snapshots__/field_button.test.tsx.snap
create mode 100644 src/plugins/kibana_react/public/field_button/field_button.scss
create mode 100644 src/plugins/kibana_react/public/field_button/field_button.test.tsx
create mode 100644 src/plugins/kibana_react/public/field_button/field_button.tsx
create mode 100644 src/plugins/kibana_react/public/field_button/index.ts
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field.scss b/src/plugins/discover/public/application/components/sidebar/discover_field.scss
new file mode 100644
index 0000000000000..8e1dd41f66ab1
--- /dev/null
+++ b/src/plugins/discover/public/application/components/sidebar/discover_field.scss
@@ -0,0 +1,4 @@
+.dscSidebarItem__fieldPopoverPanel {
+ min-width: 260px;
+ max-width: 300px;
+}
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx
index e1abbfd7657d0..a0d9e3c541e47 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx
@@ -104,9 +104,4 @@ describe('discover sidebar field', function () {
findTestSubject(comp, 'fieldToggle-bytes').simulate('click');
expect(props.onRemoveField).toHaveBeenCalledWith('bytes');
});
- it('should trigger onShowDetails', function () {
- const { comp, props } = getComponent();
- findTestSubject(comp, 'field-bytes-showDetails').simulate('click');
- expect(props.onShowDetails).toHaveBeenCalledWith(true, props.field);
- });
});
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field.tsx
index 724908281146d..639dbfe09277c 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_field.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_field.tsx
@@ -16,15 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
-import React from 'react';
-import { EuiButton } from '@elastic/eui';
+import React, { useState } from 'react';
+import { EuiPopover, EuiPopoverTitle, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { DiscoverFieldDetails } from './discover_field_details';
-import { FieldIcon } from '../../../../../kibana_react/public';
+import { FieldIcon, FieldButton } from '../../../../../kibana_react/public';
import { FieldDetails } from './types';
import { IndexPatternField, IndexPattern } from '../../../../../data/public';
import { shortenDottedString } from '../../helpers';
import { getFieldTypeName } from './lib/get_field_type_name';
+import './discover_field.scss';
export interface DiscoverFieldProps {
/**
@@ -48,14 +49,6 @@ export interface DiscoverFieldProps {
* @param fieldName
*/
onRemoveField: (fieldName: string) => void;
- /**
- * Callback to hide/show details, buckets of the field
- */
- onShowDetails: (show: boolean, field: IndexPatternField) => void;
- /**
- * Determines, whether details of the field are displayed
- */
- showDetails: boolean;
/**
* Retrieve details data for the field
*/
@@ -76,22 +69,14 @@ export function DiscoverField({
onAddField,
onRemoveField,
onAddFilter,
- onShowDetails,
- showDetails,
getDetails,
selected,
useShortDots,
}: DiscoverFieldProps) {
- const addLabel = i18n.translate('discover.fieldChooser.discoverField.addButtonLabel', {
- defaultMessage: 'Add',
- });
const addLabelAria = i18n.translate('discover.fieldChooser.discoverField.addButtonAriaLabel', {
defaultMessage: 'Add {field} to table',
values: { field: field.name },
});
- const removeLabel = i18n.translate('discover.fieldChooser.discoverField.removeButtonLabel', {
- defaultMessage: 'Remove',
- });
const removeLabelAria = i18n.translate(
'discover.fieldChooser.discoverField.removeButtonAriaLabel',
{
@@ -100,6 +85,8 @@ export function DiscoverField({
}
);
+ const [infoIsOpen, setOpen] = useState(false);
+
const toggleDisplay = (f: IndexPatternField) => {
if (selected) {
onRemoveField(f.name);
@@ -108,6 +95,10 @@ export function DiscoverField({
}
};
+ function togglePopover() {
+ setOpen(!infoIsOpen);
+ }
+
function wrapOnDot(str?: string) {
// u200B is a non-width white-space character, which allows
// the browser to efficiently word-wrap right after the dot
@@ -115,64 +106,96 @@ export function DiscoverField({
return str ? str.replace(/\./g, '.\u200B') : '';
}
- return (
- <>
- onShowDetails(!showDetails, field)}
- onKeyPress={() => onShowDetails(!showDetails, field)}
- data-test-subj={`field-${field.name}-showDetails`}
+ const dscFieldIcon = (
+
+ );
+
+ const fieldName = (
+
+ {useShortDots ? wrapOnDot(shortenDottedString(field.name)) : wrapOnDot(field.displayName)}
+
+ );
+
+ let actionButton;
+ if (field.name !== '_source' && !selected) {
+ actionButton = (
+
+ ) => {
+ ev.preventDefault();
+ ev.stopPropagation();
+ toggleDisplay(field);
+ }}
+ data-test-subj={`fieldToggle-${field.name}`}
+ aria-label={addLabelAria}
+ />
+
+ );
+ } else if (field.name !== '_source' && selected) {
+ actionButton = (
+
-
-
-
-
- {useShortDots ? wrapOnDot(shortenDottedString(field.name)) : wrapOnDot(field.displayName)}
-
-
- {field.name !== '_source' && !selected && (
- ) => {
- ev.preventDefault();
- ev.stopPropagation();
- toggleDisplay(field);
- }}
- data-test-subj={`fieldToggle-${field.name}`}
- arial-label={addLabelAria}
- >
- {addLabel}
-
- )}
- {field.name !== '_source' && selected && (
- ) => {
- ev.preventDefault();
- ev.stopPropagation();
- toggleDisplay(field);
- }}
- data-test-subj={`fieldToggle-${field.name}`}
- arial-label={removeLabelAria}
- >
- {removeLabel}
-
- )}
-
-
- {showDetails && (
+ ) => {
+ ev.preventDefault();
+ ev.stopPropagation();
+ toggleDisplay(field);
+ }}
+ data-test-subj={`fieldToggle-${field.name}`}
+ aria-label={removeLabelAria}
+ />
+
+ );
+ }
+
+ return (
+ {
+ togglePopover();
+ }}
+ buttonProps={{ 'data-test-subj': `field-${field.name}-showDetails` }}
+ fieldIcon={dscFieldIcon}
+ fieldAction={actionButton}
+ fieldName={fieldName}
+ />
+ }
+ isOpen={infoIsOpen}
+ closePopover={() => setOpen(false)}
+ anchorPosition="rightUp"
+ panelClassName="dscSidebarItem__fieldPopoverPanel"
+ >
+
+ {' '}
+ {i18n.translate('discover.fieldChooser.discoverField.fieldTopValuesLabel', {
+ defaultMessage: 'Top 5 values',
+ })}
+
+ {infoIsOpen && (
)}
- >
+
);
}
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_details.scss b/src/plugins/discover/public/application/components/sidebar/discover_field_details.scss
new file mode 100644
index 0000000000000..f4b3eed741f9f
--- /dev/null
+++ b/src/plugins/discover/public/application/components/sidebar/discover_field_details.scss
@@ -0,0 +1,5 @@
+.dscFieldDetails__visualizeBtn {
+ @include euiFontSizeXS;
+ height: $euiSizeL !important;
+ min-width: $euiSize * 4;
+}
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_details.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field_details.tsx
index dd95a45f71626..875b5a0aa446f 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_field_details.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_field_details.tsx
@@ -17,13 +17,14 @@
* under the License.
*/
import React from 'react';
-import { EuiLink, EuiIconTip, EuiText } from '@elastic/eui';
+import { EuiLink, EuiIconTip, EuiText, EuiPopoverFooter, EuiButton, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { DiscoverFieldBucket } from './discover_field_bucket';
import { getWarnings } from './lib/get_warnings';
import { Bucket, FieldDetails } from './types';
import { getServices } from '../../../kibana_services';
import { IndexPatternField, IndexPattern } from '../../../../../data/public';
+import './discover_field_details.scss';
interface DiscoverFieldDetailsProps {
field: IndexPatternField;
@@ -41,62 +42,68 @@ export function DiscoverFieldDetails({
const warnings = getWarnings(field);
return (
-
- {!details.error && (
-
- {' '}
- {!indexPattern.metaFields.includes(field.name) && !field.scripted ? (
- onAddFilter('_exists_', field.name, '+')}>
- {details.exists}
-
- ) : (
- {details.exists}
- )}{' '}
- / {details.total}{' '}
-
-
- )}
- {details.error &&
{details.error} }
- {!details.error && (
-
- {details.buckets.map((bucket: Bucket, idx: number) => (
-
- ))}
-
- )}
+ <>
+
+ {details.error &&
{details.error} }
+ {!details.error && (
+
+ {details.buckets.map((bucket: Bucket, idx: number) => (
+
+ ))}
+
+ )}
- {details.visualizeUrl && (
- <>
-
{
- getServices().core.application.navigateToApp(details.visualizeUrl.app, {
- path: details.visualizeUrl.path,
- });
- }}
- className="kuiButton kuiButton--secondary kuiButton--small kuiVerticalRhythmSmall"
- data-test-subj={`fieldVisualize-${field.name}`}
- >
-
+ {details.visualizeUrl && (
+ <>
+
+ {
+ getServices().core.application.navigateToApp(details.visualizeUrl.app, {
+ path: details.visualizeUrl.path,
+ });
+ }}
+ size="s"
+ className="dscFieldDetails__visualizeBtn"
+ data-test-subj={`fieldVisualize-${field.name}`}
+ >
+
+
{warnings.length > 0 && (
)}
-
- >
+ >
+ )}
+
+ {!details.error && (
+
+
+ {!indexPattern.metaFields.includes(field.name) && !field.scripted ? (
+ onAddFilter('_exists_', field.name, '+')}>
+ {' '}
+ {details.exists}
+
+ ) : (
+ {details.exists}
+ )}{' '}
+ / {details.total}{' '}
+
+
+
)}
-
+ >
);
}
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss
index 07efd64752c84..f130b0399f467 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss
+++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss
@@ -42,54 +42,15 @@
}
.dscSidebarItem {
- border-top: 1px solid transparent;
- position: relative;
- display: flex;
- align-items: center;
- justify-content: space-between;
- cursor: pointer;
- font-size: $euiFontSizeXS;
- border-top: solid 1px transparent;
- border-bottom: solid 1px transparent;
- line-height: normal;
- margin-bottom: $euiSizeXS * 0.5;
-
&:hover,
- &:focus {
+ &:focus-within,
+ &[class*='-isActive'] {
.dscSidebarItem__action {
opacity: 1;
}
}
}
-.dscSidebarItem--active {
- border-top: 1px solid $euiColorLightShade;
- color: $euiColorFullShade;
-}
-
-.dscSidebarField {
- padding: $euiSizeXS;
- display: flex;
- align-items: center;
- max-width: 100%;
- width: 100%;
- border: none;
- border-radius: $euiBorderRadius - 1px;
- text-align: left;
-}
-
-.dscSidebarField__name {
- margin-left: $euiSizeS;
- flex-grow: 1;
- word-break: break-word;
- padding-right: 1px;
-}
-
-.dscSidebarField__fieldIcon {
- margin-top: $euiSizeXS / 2;
- margin-right: $euiSizeXS / 2;
-}
-
/**
* 1. Only visually hide the action, so that it's still accessible to screen readers.
* 2. When tabbed to, this element needs to be visible for keyboard accessibility.
@@ -101,7 +62,7 @@
&:focus {
opacity: 1; /* 2 */
}
- font-size: 12px;
+ font-size: $euiFontSizeXS;
padding: 2px 6px !important;
height: 22px !important;
min-width: auto !important;
@@ -130,8 +91,6 @@
}
.dscFieldDetails {
- padding: $euiSizeS;
- background-color: $euiColorLightestShade;
color: $euiTextColor;
margin-bottom: $euiSizeS;
}
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx
index 58b468762c501..450bb93f60bf3 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx
@@ -92,7 +92,6 @@ export function DiscoverSidebar({
setIndexPattern,
state,
}: DiscoverSidebarProps) {
- const [openFieldMap, setOpenFieldMap] = useState(new Map());
const [showFields, setShowFields] = useState(false);
const [fields, setFields] = useState(null);
const [fieldFilterState, setFieldFilterState] = useState(getDefaultFieldFilter());
@@ -103,19 +102,6 @@ export function DiscoverSidebar({
setFields(newFields);
}, [selectedIndexPattern, fieldCounts, hits, services]);
- const onShowDetails = useCallback(
- (show: boolean, field: IndexPatternField) => {
- if (!show) {
- setOpenFieldMap(new Map(openFieldMap.set(field.name, false)));
- } else {
- setOpenFieldMap(new Map(openFieldMap.set(field.name, true)));
- if (services.capabilities.discover.save) {
- selectedIndexPattern.popularizeField(field.name, 1);
- }
- }
- },
- [openFieldMap, selectedIndexPattern, services.capabilities.discover.save]
- );
const onChangeFieldSearch = useCallback(
(field: string, value: string | boolean | undefined) => {
const newState = setFieldFilterProp(fieldFilterState, field, value);
@@ -213,9 +199,7 @@ export function DiscoverSidebar({
onAddField={onAddField}
onRemoveField={onRemoveField}
onAddFilter={onAddFilter}
- onShowDetails={onShowDetails}
getDetails={getDetailsByField}
- showDetails={openFieldMap.get(field.name) || false}
selected={true}
useShortDots={useShortDots}
/>
@@ -290,9 +274,7 @@ export function DiscoverSidebar({
onAddField={onAddField}
onRemoveField={onRemoveField}
onAddFilter={onAddFilter}
- onShowDetails={onShowDetails}
getDetails={getDetailsByField}
- showDetails={openFieldMap.get(field.name) || false}
useShortDots={useShortDots}
/>
@@ -318,9 +300,7 @@ export function DiscoverSidebar({
onAddField={onAddField}
onRemoveField={onRemoveField}
onAddFilter={onAddFilter}
- onShowDetails={onShowDetails}
getDetails={getDetailsByField}
- showDetails={openFieldMap.get(field.name) || false}
useShortDots={useShortDots}
/>
diff --git a/src/plugins/kibana_react/public/field_button/__snapshots__/field_button.test.tsx.snap b/src/plugins/kibana_react/public/field_button/__snapshots__/field_button.test.tsx.snap
new file mode 100644
index 0000000000000..e65b5fcb8fbbd
--- /dev/null
+++ b/src/plugins/kibana_react/public/field_button/__snapshots__/field_button.test.tsx.snap
@@ -0,0 +1,134 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`fieldAction is rendered 1`] = `
+
+
+
+ name
+
+
+
+
+ fieldAction
+
+
+
+`;
+
+exports[`fieldIcon is rendered 1`] = `
+
+
+
+
+ fieldIcon
+
+
+
+ name
+
+
+
+`;
+
+exports[`isActive defaults to false 1`] = `
+
+
+
+ name
+
+
+
+`;
+
+exports[`isActive renders true 1`] = `
+
+
+
+ name
+
+
+
+`;
+
+exports[`isDraggable is rendered 1`] = `
+
+
+
+ name
+
+
+
+`;
+
+exports[`sizes m is applied 1`] = `
+
+
+
+ name
+
+
+
+`;
+
+exports[`sizes s is applied 1`] = `
+
+
+
+ name
+
+
+
+`;
diff --git a/src/plugins/kibana_react/public/field_button/field_button.scss b/src/plugins/kibana_react/public/field_button/field_button.scss
new file mode 100644
index 0000000000000..43f60e4503576
--- /dev/null
+++ b/src/plugins/kibana_react/public/field_button/field_button.scss
@@ -0,0 +1,75 @@
+.kbnFieldButton {
+ @include euiFontSizeS;
+ border-radius: $euiBorderRadius;
+ margin-bottom: $euiSizeXS;
+ display: flex;
+ align-items: center;
+ transition: box-shadow $euiAnimSpeedFast $euiAnimSlightResistance,
+ background-color $euiAnimSpeedFast $euiAnimSlightResistance; // sass-lint:disable-line indentation
+
+ &:focus-within,
+ &-isActive {
+ @include euiFocusRing;
+ }
+}
+
+.kbnFieldButton--isDraggable {
+ background: lightOrDarkTheme($euiColorEmptyShade, $euiColorLightestShade);
+
+ &:hover,
+ &:focus,
+ &:focus-within {
+ @include euiBottomShadowMedium;
+ border-radius: $euiBorderRadius;
+ z-index: 2;
+ }
+
+ .kbnFieldButton__button {
+ &:hover,
+ &:focus {
+ cursor: grab;
+ }
+ }
+}
+
+.kbnFieldButton__button {
+ flex-grow: 1;
+ text-align: left;
+ padding: $euiSizeS;
+ display: flex;
+ align-items: flex-start;
+}
+
+.kbnFieldButton__fieldIcon {
+ flex-shrink: 0;
+ line-height: 0;
+ margin-right: $euiSizeS;
+}
+
+.kbnFieldButton__name {
+ flex-grow: 1;
+ word-break: break-word;
+}
+
+.kbnFieldButton__infoIcon {
+ flex-shrink: 0;
+ margin-left: $euiSizeXS;
+}
+
+.kbnFieldButton__fieldAction {
+ margin-right: $euiSizeS;
+}
+
+// Reduce text size and spacing for the small size
+.kbnFieldButton--small {
+ font-size: $euiFontSizeXS;
+
+ .kbnFieldButton__button {
+ padding: $euiSizeXS;
+ }
+
+ .kbnFieldButton__fieldIcon,
+ .kbnFieldButton__fieldAction {
+ margin-right: $euiSizeXS;
+ }
+}
diff --git a/src/plugins/kibana_react/public/field_button/field_button.test.tsx b/src/plugins/kibana_react/public/field_button/field_button.test.tsx
new file mode 100644
index 0000000000000..32e1203b89718
--- /dev/null
+++ b/src/plugins/kibana_react/public/field_button/field_button.test.tsx
@@ -0,0 +1,68 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React from 'react';
+import { shallow } from 'enzyme';
+import { FieldButton, SIZES } from './field_button';
+
+const noop = () => {};
+
+describe('sizes', () => {
+ SIZES.forEach((size) => {
+ test(`${size} is applied`, () => {
+ const component = shallow( );
+ expect(component).toMatchSnapshot();
+ });
+ });
+});
+
+describe('isDraggable', () => {
+ it('is rendered', () => {
+ const component = shallow( );
+ expect(component).toMatchSnapshot();
+ });
+});
+
+describe('fieldIcon', () => {
+ it('is rendered', () => {
+ const component = shallow(
+ fieldIcon} />
+ );
+ expect(component).toMatchSnapshot();
+ });
+});
+
+describe('fieldAction', () => {
+ it('is rendered', () => {
+ const component = shallow(
+ fieldAction} />
+ );
+ expect(component).toMatchSnapshot();
+ });
+});
+
+describe('isActive', () => {
+ it('defaults to false', () => {
+ const component = shallow( );
+ expect(component).toMatchSnapshot();
+ });
+ it('renders true', () => {
+ const component = shallow( );
+ expect(component).toMatchSnapshot();
+ });
+});
diff --git a/src/plugins/kibana_react/public/field_button/field_button.tsx b/src/plugins/kibana_react/public/field_button/field_button.tsx
new file mode 100644
index 0000000000000..e5833b261946a
--- /dev/null
+++ b/src/plugins/kibana_react/public/field_button/field_button.tsx
@@ -0,0 +1,111 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import './field_button.scss';
+import classNames from 'classnames';
+import React, { ReactNode, HTMLAttributes, ButtonHTMLAttributes } from 'react';
+import { CommonProps } from '@elastic/eui';
+
+export interface FieldButtonProps extends HTMLAttributes {
+ /**
+ * Label for the button
+ */
+ fieldName: ReactNode;
+ /**
+ * Icon representing the field type.
+ * Recommend using FieldIcon
+ */
+ fieldIcon?: ReactNode;
+ /**
+ * An optional node to place inside and at the end of the
+ */
+ fieldInfoIcon?: ReactNode;
+ /**
+ * An optional node to place outside of and to the right of the
+ */
+ fieldAction?: ReactNode;
+ /**
+ * Adds a forced focus ring to the whole component
+ */
+ isActive?: boolean;
+ /**
+ * Styles the component differently to indicate it is draggable
+ */
+ isDraggable?: boolean;
+ /**
+ * Use the small size in condensed areas
+ */
+ size?: ButtonSize;
+ className?: string;
+ /**
+ * The component always renders a `` and therefore will always need an `onClick`
+ */
+ onClick: () => void;
+ /**
+ * Pass more button props to the actual `` element
+ */
+ buttonProps?: ButtonHTMLAttributes & CommonProps;
+}
+
+const sizeToClassNameMap = {
+ s: 'kbnFieldButton--small',
+ m: null,
+} as const;
+
+export type ButtonSize = keyof typeof sizeToClassNameMap;
+
+export const SIZES = Object.keys(sizeToClassNameMap) as ButtonSize[];
+
+export function FieldButton({
+ size = 'm',
+ isActive = false,
+ fieldIcon,
+ fieldName,
+ fieldInfoIcon,
+ fieldAction,
+ className,
+ isDraggable = false,
+ onClick,
+ buttonProps,
+ ...rest
+}: FieldButtonProps) {
+ const classes = classNames(
+ 'kbnFieldButton',
+ size ? sizeToClassNameMap[size] : null,
+ { 'kbnFieldButton-isActive': isActive },
+ { 'kbnFieldButton--isDraggable': isDraggable },
+ className
+ );
+
+ const buttonClasses = classNames(
+ 'kbn-resetFocusState kbnFieldButton__button',
+ buttonProps && buttonProps.className
+ );
+
+ return (
+
+
+ {fieldIcon && {fieldIcon} }
+ {fieldName && {fieldName} }
+ {fieldInfoIcon && {fieldInfoIcon}
}
+
+ {fieldAction &&
{fieldAction}
}
+
+ );
+}
diff --git a/src/plugins/kibana_react/public/field_button/index.ts b/src/plugins/kibana_react/public/field_button/index.ts
new file mode 100644
index 0000000000000..4819a7623a163
--- /dev/null
+++ b/src/plugins/kibana_react/public/field_button/index.ts
@@ -0,0 +1,19 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+export * from './field_button';
diff --git a/src/plugins/kibana_react/public/index.ts b/src/plugins/kibana_react/public/index.ts
index 7f8bf6c04cecc..34140703fd8ae 100644
--- a/src/plugins/kibana_react/public/index.ts
+++ b/src/plugins/kibana_react/public/index.ts
@@ -23,6 +23,7 @@ export * from './context';
export * from './overlays';
export * from './ui_settings';
export * from './field_icon';
+export * from './field_button';
export * from './table_list_view';
export * from './split_panel';
export * from './react_router_navigate';
diff --git a/test/functional/apps/context/_context_navigation.js b/test/functional/apps/context/_context_navigation.js
index babefe488d7bc..c5af2fcb79296 100644
--- a/test/functional/apps/context/_context_navigation.js
+++ b/test/functional/apps/context/_context_navigation.js
@@ -18,11 +18,11 @@
*/
const TEST_FILTER_COLUMN_NAMES = [
- ['extension', 'jpg'],
[
'agent',
'Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24',
],
+ ['extension', 'jpg'],
];
export default function ({ getService, getPageObjects }) {
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/_field_item.scss b/x-pack/plugins/lens/public/indexpattern_datasource/_field_item.scss
index 6e51c45ad02c1..d194c694abdf8 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/_field_item.scss
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/_field_item.scss
@@ -1,47 +1,11 @@
-.lnsFieldItem {
- @include euiFontSizeS;
- background: lightOrDarkTheme($euiColorEmptyShade, $euiColorLightestShade);
- border-radius: $euiBorderRadius;
- margin-bottom: $euiSizeXS;
-}
-
-.lnsFieldItem__popoverAnchor:hover,
-.lnsFieldItem__popoverAnchor:focus,
-.lnsFieldItem__popoverAnchor:focus-within {
- @include euiBottomShadowMedium;
- border-radius: $euiBorderRadius;
- z-index: 2;
-}
-
.lnsFieldItem--missing {
- background: lightOrDarkTheme(transparentize($euiColorMediumShade, .9), $euiColorEmptyShade);
- color: $euiColorDarkShade;
+ .lnsFieldItem__info {
+ background: lightOrDarkTheme(transparentize($euiColorMediumShade, .9), $euiColorEmptyShade);
+ color: $euiColorDarkShade;
+ }
}
.lnsFieldItem__info {
- border-radius: $euiBorderRadius - 1px;
- padding: $euiSizeS;
- display: flex;
- align-items: flex-start;
- transition: box-shadow $euiAnimSpeedFast $euiAnimSlightResistance,
- background-color $euiAnimSpeedFast $euiAnimSlightResistance; // sass-lint:disable-line indentation
-
- .lnsFieldItem__name {
- margin-left: $euiSizeS;
- flex-grow: 1;
- word-break: break-word;
- }
-
- .lnsFieldListPanel__fieldIcon,
- .lnsFieldItem__infoIcon {
- flex-shrink: 0;
- }
-
- .lnsFieldListPanel__fieldIcon {
- margin-top: $euiSizeXS / 2;
- margin-right: $euiSizeXS / 2;
- }
-
.lnsFieldItem__infoIcon {
visibility: hidden;
}
@@ -56,10 +20,6 @@
}
}
-.lnsFieldItem__info-isOpen {
- @include euiFocusRing;
-}
-
.lnsFieldItem__topValue {
margin-bottom: $euiSizeS;
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx
index 0a3af97f8ad75..08a2f85ec7053 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx
@@ -5,6 +5,7 @@
*/
import React from 'react';
+import { ReactWrapper } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { EuiLoadingSpinner, EuiPopover } from '@elastic/eui';
import { InnerFieldItem, FieldItemProps } from './field_item';
@@ -17,6 +18,10 @@ import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks'
const chartsThemeService = chartPluginMock.createSetupContract().theme;
+function clickField(wrapper: ReactWrapper, field: string) {
+ wrapper.find(`[data-test-subj="lnsFieldListPanelField-${field}"] button`).simulate('click');
+}
+
describe('IndexPattern Field Item', () => {
let defaultProps: FieldItemProps;
let indexPattern: IndexPattern;
@@ -101,7 +106,7 @@ describe('IndexPattern Field Item', () => {
const wrapper = mountWithIntl( );
await act(async () => {
- wrapper.find('[data-test-subj="lnsFieldListPanelField-bytes"]').simulate('click');
+ clickField(wrapper, 'bytes');
});
expect(core.http.post).toHaveBeenCalledWith(
@@ -125,7 +130,7 @@ describe('IndexPattern Field Item', () => {
const wrapper = mountWithIntl( );
- wrapper.find('[data-test-subj="lnsFieldListPanelField-bytes"]').simulate('click');
+ clickField(wrapper, 'bytes');
expect(core.http.post).toHaveBeenCalledWith(
`/api/lens/index_stats/my-fake-index-pattern/field`,
@@ -174,7 +179,7 @@ describe('IndexPattern Field Item', () => {
expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(0);
- wrapper.find('[data-test-subj="lnsFieldListPanelField-bytes"]').simulate('click');
+ clickField(wrapper, 'bytes');
expect(core.http.post).toHaveBeenCalledTimes(1);
act(() => {
@@ -200,7 +205,7 @@ describe('IndexPattern Field Item', () => {
});
});
- wrapper.find('[data-test-subj="lnsFieldListPanelField-bytes"]').simulate('click');
+ clickField(wrapper, 'bytes');
expect(core.http.post).toHaveBeenCalledTimes(2);
expect(core.http.post).toHaveBeenLastCalledWith(
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx
index fabf9e9e9bfff..5dc6673bc29ec 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx
@@ -11,7 +11,6 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiIconTip,
- EuiKeyboardAccessible,
EuiLoadingSpinner,
EuiPopover,
EuiPopoverFooter,
@@ -40,6 +39,7 @@ import {
esQuery,
IIndexPattern,
} from '../../../../../src/plugins/data/public';
+import { FieldButton } from '../../../../../src/plugins/kibana_react/public';
import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public';
import { DraggedField } from './indexpattern';
import { DragDrop } from '../drag_drop';
@@ -177,8 +177,27 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) {
field,
indexPattern.id,
]);
+ const lensFieldIcon = ;
+ const lensInfoIcon = (
+
+ );
return (
-
- {
- if (exists) {
- togglePopover();
- }
- }}
- onKeyPress={(event) => {
- if (exists && event.key === 'ENTER') {
- togglePopover();
- }
- }}
- aria-label={i18n.translate('xpack.lens.indexPattern.fieldStatsButtonLabel', {
- defaultMessage: 'Click for a field preview, or drag and drop to visualize.',
- })}
- >
-
-
-
- {wrappableHighlightableFieldName}
-
-
-
-
-
+ {
+ if (exists) {
+ togglePopover();
+ }
+ }}
+ aria-label={i18n.translate('xpack.lens.indexPattern.fieldStatsButtonAriaLabel', {
+ defaultMessage: '{fieldName}: {fieldType}. Hit enter for a field preview.',
+ values: {
+ fieldName: field.name,
+ fieldType: field.type,
+ },
+ })}
+ fieldIcon={lensFieldIcon}
+ fieldName={wrappableHighlightableFieldName}
+ fieldInfoIcon={lensInfoIcon}
+ />
}
isOpen={infoIsOpen}
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index e617c7ff30885..c958215a2677e 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -1407,12 +1407,9 @@
"discover.fieldChooser.detailViews.filterOutValueButtonAriaLabel": "{field}を除外:\"{value}\"",
"discover.fieldChooser.detailViews.filterValueButtonAriaLabel": "{field}を除外:\"{value}\"",
"discover.fieldChooser.detailViews.recordsText": "記録",
- "discover.fieldChooser.detailViews.topValuesInRecordsDescription": "次の記録のトップ5の値",
"discover.fieldChooser.detailViews.visualizeLinkText": "可視化",
"discover.fieldChooser.discoverField.addButtonAriaLabel": "{field}を表に追加",
- "discover.fieldChooser.discoverField.addButtonLabel": "追加",
"discover.fieldChooser.discoverField.removeButtonAriaLabel": "{field}を表から削除",
- "discover.fieldChooser.discoverField.removeButtonLabel": "削除",
"discover.fieldChooser.discoverField.scriptedFieldsTakeLongExecuteDescription": "スクリプトフィールドは実行に時間がかかる場合があります。",
"discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForGeoFieldsErrorMessage": "ジオフィールドは分析できません。",
"discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForObjectFieldsErrorMessage": "オブジェクトフィールドは分析できません。",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 4c2691035ee54..a06e5813796f4 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -1408,12 +1408,9 @@
"discover.fieldChooser.detailViews.filterOutValueButtonAriaLabel": "筛除 {field}:“{value}”",
"discover.fieldChooser.detailViews.filterValueButtonAriaLabel": "筛留 {field}:“{value}”",
"discover.fieldChooser.detailViews.recordsText": "个记录",
- "discover.fieldChooser.detailViews.topValuesInRecordsDescription": "排名前 5 值 - 范围",
"discover.fieldChooser.detailViews.visualizeLinkText": "可视化",
"discover.fieldChooser.discoverField.addButtonAriaLabel": "将 {field} 添加到表中",
- "discover.fieldChooser.discoverField.addButtonLabel": "添加",
"discover.fieldChooser.discoverField.removeButtonAriaLabel": "从表中移除 {field}",
- "discover.fieldChooser.discoverField.removeButtonLabel": "移除",
"discover.fieldChooser.discoverField.scriptedFieldsTakeLongExecuteDescription": "脚本字段执行时间会很长。",
"discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForGeoFieldsErrorMessage": "分析不适用于地理字段。",
"discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForObjectFieldsErrorMessage": "分析不适用于对象字段。",
From 0707e29de38b7ba05d8e1903ad0ac94a98169b8b Mon Sep 17 00:00:00 2001
From: Joel Griffith
Date: Tue, 18 Aug 2020 15:46:50 -0700
Subject: [PATCH 08/85] Add libnss3.so to Dockerfile template (reporting)
(#75370)
---
.../tasks/os_packages/docker_generator/templates/Dockerfile | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile b/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile
index c6f333beb060e..d235bfe9d6fbc 100644
--- a/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile
+++ b/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile
@@ -47,7 +47,7 @@ EXPOSE 5601
RUN for iter in {1..10}; do \
{{packageManager}} update --setopt=tsflags=nodocs -y && \
{{packageManager}} install --setopt=tsflags=nodocs -y \
- fontconfig freetype shadow-utils {{#ubi}}findutils{{/ubi}} && \
+ fontconfig freetype shadow-utils libnss3.so {{#ubi}}findutils{{/ubi}} && \
{{packageManager}} clean all && exit_code=0 && break || exit_code=$? && echo "{{packageManager}} error: retry $iter in 10s" && \
sleep 10; \
done; \
@@ -119,4 +119,4 @@ LABEL name="Kibana" \
ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
-CMD ["/usr/local/bin/kibana-docker"]
\ No newline at end of file
+CMD ["/usr/local/bin/kibana-docker"]
From 6c00cb2ec28acee7774851cb111e6b9aa10e1ea7 Mon Sep 17 00:00:00 2001
From: MadameSheema
Date: Wed, 19 Aug 2020 08:39:13 +0200
Subject: [PATCH 09/85] [SIEM] Fixes search bar Cypress test (#74833)
* fixes search bar test
* fixes typecheck issue
* fixes test
* uses data-test-subj locator instead of class
* fixes failing kibana tests
Co-authored-by: Elastic Machine
---
.../cypress/integration/search_bar.spec.ts | 8 ++++++--
.../security_solution/cypress/screens/search_bar.ts | 5 +----
.../security_solution/cypress/tasks/hosts/all_hosts.ts | 2 +-
.../plugins/security_solution/cypress/tasks/search_bar.ts | 4 +++-
4 files changed, 11 insertions(+), 8 deletions(-)
diff --git a/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts b/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts
index 9104f494e3e6b..15ca92714d4a3 100644
--- a/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts
@@ -12,7 +12,7 @@ import { hostIpFilter } from '../objects/filter';
import { HOSTS_URL } from '../urls/navigation';
import { waitForAllHostsToBeLoaded } from '../tasks/hosts/all_hosts';
-describe.skip('SearchBar', () => {
+describe('SearchBar', () => {
before(() => {
loginAndWaitForPage(HOSTS_URL);
waitForAllHostsToBeLoaded();
@@ -21,6 +21,10 @@ describe.skip('SearchBar', () => {
it('adds correctly a filter to the global search bar', () => {
openAddFilterPopover();
fillAddFilterForm(hostIpFilter);
- cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM(hostIpFilter)).should('be.visible');
+
+ cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should(
+ 'have.text',
+ `${hostIpFilter.key}: ${hostIpFilter.value}`
+ );
});
});
diff --git a/x-pack/plugins/security_solution/cypress/screens/search_bar.ts b/x-pack/plugins/security_solution/cypress/screens/search_bar.ts
index 35864749a4065..07e9de137826c 100644
--- a/x-pack/plugins/security_solution/cypress/screens/search_bar.ts
+++ b/x-pack/plugins/security_solution/cypress/screens/search_bar.ts
@@ -4,8 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { SearchBarFilter } from '../objects/filter';
-
export const GLOBAL_SEARCH_BAR_ADD_FILTER =
'[data-test-subj="globalDatePicker"] [data-test-subj="addFilter"]';
@@ -28,5 +26,4 @@ export const ADD_FILTER_FORM_FILTER_VALUE_INPUT = '[data-test-subj="filterParams
export const ADD_FILTER_FORM_SAVE_BUTTON = '[data-test-subj="saveFilter"]';
-export const GLOBAL_SEARCH_BAR_FILTER_ITEM = ({ key, value }: SearchBarFilter) =>
- `[data-test-subj="filter filter-enabled filter-key-${key} filter-value-${value} filter-unpinned"]`;
+export const GLOBAL_SEARCH_BAR_FILTER_ITEM = '#popoverFor_filter0';
diff --git a/x-pack/plugins/security_solution/cypress/tasks/hosts/all_hosts.ts b/x-pack/plugins/security_solution/cypress/tasks/hosts/all_hosts.ts
index f9f902c3de8c7..27d17f966d8fc 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/hosts/all_hosts.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/hosts/all_hosts.ts
@@ -36,5 +36,5 @@ export const openFirstHostDetails = () => {
};
export const waitForAllHostsToBeLoaded = () => {
- cy.get(ALL_HOSTS_TABLE).should('exist');
+ cy.get(ALL_HOSTS_TABLE).should('be.visible');
};
diff --git a/x-pack/plugins/security_solution/cypress/tasks/search_bar.ts b/x-pack/plugins/security_solution/cypress/tasks/search_bar.ts
index a32c38a97fce5..4b194d1233f25 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/search_bar.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/search_bar.ts
@@ -19,7 +19,8 @@ import {
export const openAddFilterPopover = () => {
cy.get(GLOBAL_SEARCH_BAR_SUBMIT_BUTTON).should('be.enabled');
- cy.get(GLOBAL_SEARCH_BAR_ADD_FILTER).click({ force: true });
+ cy.get(GLOBAL_SEARCH_BAR_ADD_FILTER).should('be.visible');
+ cy.get(GLOBAL_SEARCH_BAR_ADD_FILTER).click();
};
export const fillAddFilterForm = ({ key, value }: SearchBarFilter) => {
@@ -32,4 +33,5 @@ export const fillAddFilterForm = ({ key, value }: SearchBarFilter) => {
cy.get(ADD_FILTER_FORM_OPERATOR_OPTION_IS).click();
cy.get(ADD_FILTER_FORM_FILTER_VALUE_INPUT).type(value);
cy.get(ADD_FILTER_FORM_SAVE_BUTTON).click();
+ cy.get(ADD_FILTER_FORM_SAVE_BUTTON).should('not.exist');
};
From de4b502cb31855b2c9c062ae97f99228138af27e Mon Sep 17 00:00:00 2001
From: Shahzad
Date: Wed, 19 Aug 2020 09:13:40 +0200
Subject: [PATCH 10/85] Fix docs in trigger alerting UI (#75363)
---
x-pack/plugins/triggers_actions_ui/README.md | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/x-pack/plugins/triggers_actions_ui/README.md b/x-pack/plugins/triggers_actions_ui/README.md
index b8e765c9ea635..5a0a0c3219d7e 100644
--- a/x-pack/plugins/triggers_actions_ui/README.md
+++ b/x-pack/plugins/triggers_actions_ui/README.md
@@ -1265,7 +1265,7 @@ Then this dependencies will be used to embed Actions form or register your own a
];
export const ComponentWithActionsForm: () => {
- const { http, triggers_actions_ui, toastNotifications } = useKibana().services;
+ const { http, triggers_actions_ui, notifications } = useKibana().services;
const actionTypeRegistry = triggers_actions_ui.actionTypeRegistry;
const initialAlert = ({
name: 'test',
@@ -1307,7 +1307,7 @@ Then this dependencies will be used to embed Actions form or register your own a
actionTypeRegistry={actionTypeRegistry}
defaultActionMessage={'Alert [{{ctx.metadata.name}}] has exceeded the threshold'}
actionTypes={ALOWED_BY_PLUGIN_ACTION_TYPES}
- toastNotifications={toastNotifications}
+ toastNotifications={notifications.toasts}
consumer={initialAlert.consumer}
/>
);
@@ -1409,7 +1409,7 @@ import { ActionsConnectorsContextProvider, ConnectorAddFlyout } from '../../../.
const [addFlyoutVisible, setAddFlyoutVisibility] = useState(false);
// load required dependancied
-const { http, triggers_actions_ui, toastNotifications, capabilities, docLinks } = useKibana().services;
+const { http, triggers_actions_ui, notifications, application, docLinks } = useKibana().services;
const connector = {
secrets: {},
@@ -1438,9 +1438,9 @@ const connector = {
@@ -1526,7 +1526,7 @@ import { ActionsConnectorsContextProvider, ConnectorEditFlyout } from '../../../
const [editFlyoutVisible, setEditFlyoutVisibility] = useState(false);
// load required dependancied
-const { http, triggers_actions_ui, toastNotifications, capabilities } = useKibana().services;
+const { http, triggers_actions_ui, notifications, application } = useKibana().services;
// UI control item for open flyout
Date: Wed, 19 Aug 2020 10:29:23 +0200
Subject: [PATCH 11/85] Use prefix search invis editor field/agg combo box
(#75290)
---
src/plugins/vis_default_editor/public/components/agg_select.tsx | 1 +
.../vis_default_editor/public/components/controls/field.tsx | 1 +
2 files changed, 2 insertions(+)
diff --git a/src/plugins/vis_default_editor/public/components/agg_select.tsx b/src/plugins/vis_default_editor/public/components/agg_select.tsx
index 8d03940a92d34..04b5e48c65e8e 100644
--- a/src/plugins/vis_default_editor/public/components/agg_select.tsx
+++ b/src/plugins/vis_default_editor/public/components/agg_select.tsx
@@ -157,6 +157,7 @@ function DefaultEditorAggSelect({
isClearable={false}
isInvalid={showValidation ? !isValid : false}
fullWidth={true}
+ sortMatchesBy="startsWith"
compressed
/>
diff --git a/src/plugins/vis_default_editor/public/components/controls/field.tsx b/src/plugins/vis_default_editor/public/components/controls/field.tsx
index 24d94c2b18feb..bfc4f881f8458 100644
--- a/src/plugins/vis_default_editor/public/components/controls/field.tsx
+++ b/src/plugins/vis_default_editor/public/components/controls/field.tsx
@@ -124,6 +124,7 @@ function FieldParamEditor({
onChange={onChange}
onBlur={setTouched}
onSearchChange={onSearchChange}
+ sortMatchesBy="startsWith"
data-test-subj="visDefaultEditorField"
fullWidth={true}
/>
From b6c475707c2aea5388b3042eba3981be7434ea3a Mon Sep 17 00:00:00 2001
From: Stratoula Kalafateli
Date: Wed, 19 Aug 2020 11:37:45 +0300
Subject: [PATCH 12/85] Uiactions to navigate to visualize or maps (#74121)
* Navigate from discover to visualize with registering into a trigger
* Implement the VISUALIZE_FIELD action
* Implementation of the maps app trigger actions with the isCompatible functionality
* clean up discover code and tile map action implementation
* Add typeIsHidden on mocks
* Retrieve filters and query from url state
* functional test for oss and tile map
* include geoshape
* fix functional tests
* fix types
* remove unecessary dependencies
* minor fixes
* Remove tilemaps actios as it is going tobe deprecated
* Add useEffect on discover details and move the map action to a separate folder
* Retrieve map tooltips info from context
* Retrieve query and filters from QueryService
* Building urls with urlGenerators
* replace with constants, fetch initialLayers as array
* remove irrelevant comments
* nice improvements
* Return contextualFields for both triggers
* Add getHref on actions, move capabilities to isCompatible method per action and other fixes
* fix type
* Fix type incompatibility after merging with master
* fixes on maps plugin file after merge
* remove unecessary declarations
* nice improvements
* Refactor maps services code to be inline with master
Co-authored-by: Elastic Machine
---
.../sidebar/discover_field_details.tsx | 46 ++++-
.../components/sidebar/discover_sidebar.tsx | 5 +-
.../components/sidebar/lib/get_details.ts | 16 +-
.../sidebar/lib/visualize_trigger_utils.ts | 110 +++++++++++
.../sidebar/lib/visualize_url_utils.ts | 182 ------------------
.../application/components/sidebar/types.ts | 5 +-
.../discover/public/kibana_services.ts | 5 +
src/plugins/discover/public/plugin.ts | 3 +
src/plugins/tile_map/public/plugin.ts | 3 +-
src/plugins/ui_actions/public/index.ts | 14 +-
src/plugins/ui_actions/public/plugin.ts | 10 +-
.../ui_actions/public/triggers/index.ts | 2 +
.../triggers/visualize_field_trigger.ts | 27 +++
.../triggers/visualize_geo_field_trigger.ts | 27 +++
src/plugins/ui_actions/public/types.ts | 14 ++
src/plugins/visualize/common/constants.ts | 20 ++
src/plugins/visualize/kibana.json | 3 +-
.../public/actions/visualize_field_action.ts | 97 ++++++++++
src/plugins/visualize/public/plugin.ts | 39 +++-
src/plugins/visualize/public/services.ts | 37 ++++
.../visualize/public/url_generator.test.ts | 100 ++++++++++
src/plugins/visualize/public/url_generator.ts | 137 +++++++++++++
.../apps/discover/_field_visualize.ts | 6 +
x-pack/plugins/maps/common/constants.ts | 1 +
x-pack/plugins/maps/kibana.json | 3 +-
x-pack/plugins/maps/public/kibana_services.ts | 3 +
x-pack/plugins/maps/public/plugin.ts | 17 +-
.../routing/bootstrap/get_initial_layers.js | 5 +-
.../visualize_geo_field_action.ts | 77 ++++++++
.../plugins/maps/public/url_generator.test.ts | 113 +++++++++++
x-pack/plugins/maps/public/url_generator.ts | 109 +++++++++++
x-pack/typings/rison_node.d.ts | 3 +
32 files changed, 1017 insertions(+), 222 deletions(-)
create mode 100644 src/plugins/discover/public/application/components/sidebar/lib/visualize_trigger_utils.ts
delete mode 100644 src/plugins/discover/public/application/components/sidebar/lib/visualize_url_utils.ts
create mode 100644 src/plugins/ui_actions/public/triggers/visualize_field_trigger.ts
create mode 100644 src/plugins/ui_actions/public/triggers/visualize_geo_field_trigger.ts
create mode 100644 src/plugins/visualize/common/constants.ts
create mode 100644 src/plugins/visualize/public/actions/visualize_field_action.ts
create mode 100644 src/plugins/visualize/public/services.ts
create mode 100644 src/plugins/visualize/public/url_generator.test.ts
create mode 100644 src/plugins/visualize/public/url_generator.ts
create mode 100644 x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts
create mode 100644 x-pack/plugins/maps/public/url_generator.test.ts
create mode 100644 x-pack/plugins/maps/public/url_generator.ts
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_details.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field_details.tsx
index 875b5a0aa446f..3061839bf3ef0 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_field_details.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_field_details.tsx
@@ -16,13 +16,17 @@
* specific language governing permissions and limitations
* under the License.
*/
-import React from 'react';
+import React, { useState, useEffect } from 'react';
import { EuiLink, EuiIconTip, EuiText, EuiPopoverFooter, EuiButton, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { DiscoverFieldBucket } from './discover_field_bucket';
import { getWarnings } from './lib/get_warnings';
+import {
+ triggerVisualizeActions,
+ isFieldVisualizable,
+ getVisualizeHref,
+} from './lib/visualize_trigger_utils';
import { Bucket, FieldDetails } from './types';
-import { getServices } from '../../../kibana_services';
import { IndexPatternField, IndexPattern } from '../../../../../data/public';
import './discover_field_details.scss';
@@ -40,6 +44,34 @@ export function DiscoverFieldDetails({
onAddFilter,
}: DiscoverFieldDetailsProps) {
const warnings = getWarnings(field);
+ const [showVisualizeLink, setShowVisualizeLink] = useState(false);
+ const [visualizeLink, setVisualizeLink] = useState('');
+
+ useEffect(() => {
+ isFieldVisualizable(field, indexPattern.id, details.columns).then(
+ (flag) => {
+ setShowVisualizeLink(flag);
+ // get href only if Visualize button is enabled
+ getVisualizeHref(field, indexPattern.id, details.columns).then(
+ (uri) => {
+ if (uri) setVisualizeLink(uri);
+ },
+ () => {
+ setVisualizeLink('');
+ }
+ );
+ },
+ () => {
+ setShowVisualizeLink(false);
+ }
+ );
+ }, [field, indexPattern.id, details.columns]);
+
+ const handleVisualizeLinkClick = (event: React.MouseEvent) => {
+ // regular link click. let the uiActions code handle the navigation and show popup if needed
+ event.preventDefault();
+ triggerVisualizeActions(field, indexPattern.id, details.columns);
+ };
return (
<>
@@ -58,15 +90,13 @@ export function DiscoverFieldDetails({
)}
- {details.visualizeUrl && (
+ {showVisualizeLink && (
<>
+ {/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
{
- getServices().core.application.navigateToApp(details.visualizeUrl.app, {
- path: details.visualizeUrl.path,
- });
- }}
+ onClick={(e) => handleVisualizeLinkClick(e)}
+ href={visualizeLink}
size="s"
className="dscFieldDetails__visualizeBtn"
data-test-subj={`fieldVisualize-${field.name}`}
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx
index 450bb93f60bf3..1f27766a1756d 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx
@@ -111,9 +111,8 @@ export function DiscoverSidebar({
);
const getDetailsByField = useCallback(
- (ipField: IndexPatternField) =>
- getDetails(ipField, selectedIndexPattern, state, columns, hits, services),
- [selectedIndexPattern, state, columns, hits, services]
+ (ipField: IndexPatternField) => getDetails(ipField, hits, columns),
+ [hits, columns]
);
const popularLimit = services.uiSettings.get(FIELDS_LIMIT_SETTING);
diff --git a/src/plugins/discover/public/application/components/sidebar/lib/get_details.ts b/src/plugins/discover/public/application/components/sidebar/lib/get_details.ts
index 7ac9f009d73d5..41d3393672474 100644
--- a/src/plugins/discover/public/application/components/sidebar/lib/get_details.ts
+++ b/src/plugins/discover/public/application/components/sidebar/lib/get_details.ts
@@ -16,32 +16,24 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { getVisualizeUrl, isFieldVisualizable } from './visualize_url_utils';
-import { AppState } from '../../../angular/discover_state';
+
// @ts-ignore
import { fieldCalculator } from './field_calculator';
-import { IndexPatternField, IndexPattern } from '../../../../../../data/public';
-import { DiscoverServices } from '../../../../build_services';
+import { IndexPatternField } from '../../../../../../data/public';
export function getDetails(
field: IndexPatternField,
- indexPattern: IndexPattern,
- state: AppState,
- columns: string[],
hits: Array>,
- services: DiscoverServices
+ columns: string[]
) {
const details = {
- visualizeUrl:
- services.capabilities.visualize.show && isFieldVisualizable(field, services.visualizations)
- ? getVisualizeUrl(field, indexPattern, state, columns, services)
- : null,
...fieldCalculator.getFieldValueCounts({
hits,
field,
count: 5,
grouped: false,
}),
+ columns,
};
if (details.buckets) {
for (const bucket of details.buckets) {
diff --git a/src/plugins/discover/public/application/components/sidebar/lib/visualize_trigger_utils.ts b/src/plugins/discover/public/application/components/sidebar/lib/visualize_trigger_utils.ts
new file mode 100644
index 0000000000000..f058c198cae7f
--- /dev/null
+++ b/src/plugins/discover/public/application/components/sidebar/lib/visualize_trigger_utils.ts
@@ -0,0 +1,110 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import {
+ VISUALIZE_FIELD_TRIGGER,
+ VISUALIZE_GEO_FIELD_TRIGGER,
+ visualizeFieldTrigger,
+ visualizeGeoFieldTrigger,
+} from '../../../../../../ui_actions/public';
+import { getUiActions } from '../../../../kibana_services';
+import { IndexPatternField, KBN_FIELD_TYPES } from '../../../../../../data/public';
+
+function getTriggerConstant(type: string) {
+ return type === KBN_FIELD_TYPES.GEO_POINT || type === KBN_FIELD_TYPES.GEO_SHAPE
+ ? VISUALIZE_GEO_FIELD_TRIGGER
+ : VISUALIZE_FIELD_TRIGGER;
+}
+
+function getTrigger(type: string) {
+ return type === KBN_FIELD_TYPES.GEO_POINT || type === KBN_FIELD_TYPES.GEO_SHAPE
+ ? visualizeGeoFieldTrigger
+ : visualizeFieldTrigger;
+}
+
+async function getCompatibleActions(
+ fieldName: string,
+ indexPatternId: string,
+ contextualFields: string[],
+ trigger: typeof VISUALIZE_FIELD_TRIGGER | typeof VISUALIZE_GEO_FIELD_TRIGGER
+) {
+ const compatibleActions = await getUiActions().getTriggerCompatibleActions(trigger, {
+ indexPatternId,
+ fieldName,
+ contextualFields,
+ });
+ return compatibleActions;
+}
+
+export async function getVisualizeHref(
+ field: IndexPatternField,
+ indexPatternId: string | undefined,
+ contextualFields: string[]
+) {
+ if (!indexPatternId) return undefined;
+ const triggerOptions = {
+ indexPatternId,
+ fieldName: field.name,
+ contextualFields,
+ trigger: getTrigger(field.type),
+ };
+ const compatibleActions = await getCompatibleActions(
+ field.name,
+ indexPatternId,
+ contextualFields,
+ getTriggerConstant(field.type)
+ );
+ // enable the link only if only one action is registered
+ return compatibleActions.length === 1
+ ? compatibleActions[0].getHref?.(triggerOptions)
+ : undefined;
+}
+
+export function triggerVisualizeActions(
+ field: IndexPatternField,
+ indexPatternId: string | undefined,
+ contextualFields: string[]
+) {
+ if (!indexPatternId) return;
+ const trigger = getTriggerConstant(field.type);
+ const triggerOptions = {
+ indexPatternId,
+ fieldName: field.name,
+ contextualFields,
+ };
+ getUiActions().getTrigger(trigger).exec(triggerOptions);
+}
+
+export async function isFieldVisualizable(
+ field: IndexPatternField,
+ indexPatternId: string | undefined,
+ contextualFields: string[]
+) {
+ if (field.name === '_id' || !indexPatternId) {
+ // for first condition you'd get a 'Fielddata access on the _id field is disallowed' error on ES side.
+ return false;
+ }
+ const trigger = getTriggerConstant(field.type);
+ const compatibleActions = await getCompatibleActions(
+ field.name,
+ indexPatternId,
+ contextualFields,
+ trigger
+ );
+ return compatibleActions.length > 0 && field.visualizable;
+}
diff --git a/src/plugins/discover/public/application/components/sidebar/lib/visualize_url_utils.ts b/src/plugins/discover/public/application/components/sidebar/lib/visualize_url_utils.ts
deleted file mode 100644
index 0c1a44d7845cf..0000000000000
--- a/src/plugins/discover/public/application/components/sidebar/lib/visualize_url_utils.ts
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import uuid from 'uuid/v4';
-import rison from 'rison-node';
-import { parse, stringify } from 'query-string';
-import {
- IFieldType,
- IIndexPattern,
- IndexPatternField,
- KBN_FIELD_TYPES,
-} from '../../../../../../data/public';
-import { AppState } from '../../../angular/discover_state';
-import { DiscoverServices } from '../../../../build_services';
-import { VisualizationsStart, VisTypeAlias } from '../../../../../../visualizations/public';
-import { AGGS_TERMS_SIZE_SETTING } from '../../../../../common';
-
-export function isMapsAppRegistered(visualizations: VisualizationsStart) {
- return visualizations.getAliases().some(({ name }: VisTypeAlias) => {
- return name === 'maps';
- });
-}
-
-export function isFieldVisualizable(field: IFieldType, visualizations: VisualizationsStart) {
- if (field.name === '_id') {
- // Else you'd get a 'Fielddata access on the _id field is disallowed' error on ES side.
- return false;
- }
- if (
- (field.type === KBN_FIELD_TYPES.GEO_POINT || field.type === KBN_FIELD_TYPES.GEO_SHAPE) &&
- isMapsAppRegistered(visualizations)
- ) {
- return true;
- }
- return field.visualizable;
-}
-
-export function getMapsAppUrl(
- field: IFieldType,
- indexPattern: IIndexPattern,
- appState: AppState,
- columns: string[]
-) {
- const mapAppParams = new URLSearchParams();
-
- // Copy global state
- const locationSplit = window.location.hash.split('?');
- if (locationSplit.length > 1) {
- const discoverParams = new URLSearchParams(locationSplit[1]);
- const globalStateUrlValue = discoverParams.get('_g');
- if (globalStateUrlValue) {
- mapAppParams.set('_g', globalStateUrlValue);
- }
- }
-
- // Copy filters and query in app state
- const mapsAppState: any = {
- filters: appState.filters || [],
- };
- if (appState.query) {
- mapsAppState.query = appState.query;
- }
- // @ts-ignore
- mapAppParams.set('_a', rison.encode(mapsAppState));
-
- // create initial layer descriptor
- const hasColumns = columns && columns.length && columns[0] !== '_source';
- const supportsClustering = field.aggregatable;
- mapAppParams.set(
- 'initialLayers',
- // @ts-ignore
- rison.encode_array([
- {
- id: uuid(),
- label: indexPattern.title,
- sourceDescriptor: {
- id: uuid(),
- type: 'ES_SEARCH',
- geoField: field.name,
- tooltipProperties: hasColumns ? columns : [],
- indexPatternId: indexPattern.id,
- scalingType: supportsClustering ? 'CLUSTERS' : 'LIMIT',
- },
- visible: true,
- type: supportsClustering ? 'BLENDED_VECTOR' : 'VECTOR',
- },
- ])
- );
-
- return {
- app: 'maps',
- path: `/map#?${mapAppParams.toString()}`,
- };
-}
-
-export function getVisualizeUrl(
- field: IndexPatternField,
- indexPattern: IIndexPattern,
- state: AppState,
- columns: string[],
- services: DiscoverServices
-) {
- const aggsTermSize = services.uiSettings.get(AGGS_TERMS_SIZE_SETTING);
- const urlParams = parse(services.history().location.search) as Record;
-
- if (
- (field.type === KBN_FIELD_TYPES.GEO_POINT || field.type === KBN_FIELD_TYPES.GEO_SHAPE) &&
- isMapsAppRegistered(services.visualizations)
- ) {
- return getMapsAppUrl(field, indexPattern, state, columns);
- }
-
- let agg;
- const isGeoPoint = field.type === KBN_FIELD_TYPES.GEO_POINT;
- const type = isGeoPoint ? 'tile_map' : 'histogram';
- // If we're visualizing a date field, and our index is time based (and thus has a time filter),
- // then run a date histogram
- if (field.type === 'date' && indexPattern.timeFieldName === field.name) {
- agg = {
- type: 'date_histogram',
- schema: 'segment',
- params: {
- field: field.name,
- interval: 'auto',
- },
- };
- } else if (isGeoPoint) {
- agg = {
- type: 'geohash_grid',
- schema: 'segment',
- params: {
- field: field.name,
- precision: 3,
- },
- };
- } else {
- agg = {
- type: 'terms',
- schema: 'segment',
- params: {
- field: field.name,
- size: parseInt(aggsTermSize, 10),
- orderBy: '1',
- },
- };
- }
- const linkUrlParams = {
- ...urlParams,
- ...{
- indexPattern: state.index!,
- type,
- _a: rison.encode({
- filters: state.filters || [],
- query: state.query,
- vis: {
- type,
- aggs: [{ schema: 'metric', type: 'count', id: '1' }, agg],
- },
- } as any),
- },
- };
-
- return {
- app: 'visualize',
- path: `#/create?${stringify(linkUrlParams)}`,
- };
-}
diff --git a/src/plugins/discover/public/application/components/sidebar/types.ts b/src/plugins/discover/public/application/components/sidebar/types.ts
index e86138761c747..d80662b65cc7b 100644
--- a/src/plugins/discover/public/application/components/sidebar/types.ts
+++ b/src/plugins/discover/public/application/components/sidebar/types.ts
@@ -27,10 +27,7 @@ export interface FieldDetails {
exists: number;
total: boolean;
buckets: Bucket[];
- visualizeUrl: {
- app: string;
- path: string;
- };
+ columns: string[];
}
export interface Bucket {
diff --git a/src/plugins/discover/public/kibana_services.ts b/src/plugins/discover/public/kibana_services.ts
index ecb5d7fd90283..bc25fa71dcf41 100644
--- a/src/plugins/discover/public/kibana_services.ts
+++ b/src/plugins/discover/public/kibana_services.ts
@@ -20,6 +20,7 @@
import _ from 'lodash';
import { createHashHistory } from 'history';
import { ScopedHistory } from 'kibana/public';
+import { UiActionsStart } from 'src/plugins/ui_actions/public';
import { DiscoverServices } from './build_services';
import { createGetterSetter } from '../../kibana_utils/public';
import { search } from '../../data/public';
@@ -27,6 +28,7 @@ import { DocViewsRegistry } from './application/doc_views/doc_views_registry';
let angularModule: any = null;
let services: DiscoverServices | null = null;
+let uiActions: UiActionsStart;
/**
* set bootstrapped inner angular module
@@ -53,6 +55,9 @@ export function setServices(newServices: any) {
services = newServices;
}
+export const setUiActions = (pluginUiActions: UiActionsStart) => (uiActions = pluginUiActions);
+export const getUiActions = () => uiActions;
+
export const [getUrlTracker, setUrlTracker] = createGetterSetter<{
setTrackedUrl: (url: string) => void;
restorePreviousUrl: () => void;
diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts
index 20e13d204e0e9..015f4267646c1 100644
--- a/src/plugins/discover/public/plugin.ts
+++ b/src/plugins/discover/public/plugin.ts
@@ -53,6 +53,7 @@ import {
setUrlTracker,
setAngularModule,
setServices,
+ setUiActions,
setScopedHistory,
getScopedHistory,
syncHistoryLocations,
@@ -314,6 +315,8 @@ export class DiscoverPlugin
this.innerAngularInitialized = true;
};
+ setUiActions(plugins.uiActions);
+
this.initializeServices = async () => {
if (this.servicesInitialized) {
return { core, plugins };
diff --git a/src/plugins/tile_map/public/plugin.ts b/src/plugins/tile_map/public/plugin.ts
index 4582cd2283dc1..9a164f8a303f8 100644
--- a/src/plugins/tile_map/public/plugin.ts
+++ b/src/plugins/tile_map/public/plugin.ts
@@ -34,8 +34,7 @@ import { createTileMapFn } from './tile_map_fn';
import { createTileMapTypeDefinition } from './tile_map_type';
import { IServiceSettings, MapsLegacyPluginSetup } from '../../maps_legacy/public';
import { DataPublicPluginStart } from '../../data/public';
-import { setFormatService, setQueryService } from './services';
-import { setKibanaLegacy } from './services';
+import { setFormatService, setQueryService, setKibanaLegacy } from './services';
import { KibanaLegacyStart } from '../../kibana_legacy/public';
export interface TileMapConfigType {
diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts
index d76ca124ead2c..476ca0ec17066 100644
--- a/src/plugins/ui_actions/public/index.ts
+++ b/src/plugins/ui_actions/public/index.ts
@@ -43,8 +43,20 @@ export {
valueClickTrigger,
APPLY_FILTER_TRIGGER,
applyFilterTrigger,
+ VISUALIZE_FIELD_TRIGGER,
+ visualizeFieldTrigger,
+ VISUALIZE_GEO_FIELD_TRIGGER,
+ visualizeGeoFieldTrigger,
} from './triggers';
-export { TriggerContextMapping, TriggerId, ActionContextMapping, ActionType } from './types';
+export {
+ TriggerContextMapping,
+ TriggerId,
+ ActionContextMapping,
+ ActionType,
+ VisualizeFieldContext,
+ ACTION_VISUALIZE_FIELD,
+ ACTION_VISUALIZE_GEO_FIELD,
+} from './types';
export {
ActionByType,
ActionDefinitionByType,
diff --git a/src/plugins/ui_actions/public/plugin.ts b/src/plugins/ui_actions/public/plugin.ts
index 71148656cbb16..f83cc97c2a8ef 100644
--- a/src/plugins/ui_actions/public/plugin.ts
+++ b/src/plugins/ui_actions/public/plugin.ts
@@ -19,7 +19,13 @@
import { CoreStart, CoreSetup, Plugin, PluginInitializerContext } from 'src/core/public';
import { UiActionsService } from './service';
-import { selectRangeTrigger, valueClickTrigger, applyFilterTrigger } from './triggers';
+import {
+ selectRangeTrigger,
+ valueClickTrigger,
+ applyFilterTrigger,
+ visualizeFieldTrigger,
+ visualizeGeoFieldTrigger,
+} from './triggers';
export type UiActionsSetup = Pick<
UiActionsService,
@@ -42,6 +48,8 @@ export class UiActionsPlugin implements Plugin {
this.service.registerTrigger(selectRangeTrigger);
this.service.registerTrigger(valueClickTrigger);
this.service.registerTrigger(applyFilterTrigger);
+ this.service.registerTrigger(visualizeFieldTrigger);
+ this.service.registerTrigger(visualizeGeoFieldTrigger);
return this.service;
}
diff --git a/src/plugins/ui_actions/public/triggers/index.ts b/src/plugins/ui_actions/public/triggers/index.ts
index dbc54163c5af5..b7039d287c6e2 100644
--- a/src/plugins/ui_actions/public/triggers/index.ts
+++ b/src/plugins/ui_actions/public/triggers/index.ts
@@ -23,4 +23,6 @@ export * from './trigger_internal';
export * from './select_range_trigger';
export * from './value_click_trigger';
export * from './apply_filter_trigger';
+export * from './visualize_field_trigger';
+export * from './visualize_geo_field_trigger';
export * from './default_trigger';
diff --git a/src/plugins/ui_actions/public/triggers/visualize_field_trigger.ts b/src/plugins/ui_actions/public/triggers/visualize_field_trigger.ts
new file mode 100644
index 0000000000000..4f3c5f613eddf
--- /dev/null
+++ b/src/plugins/ui_actions/public/triggers/visualize_field_trigger.ts
@@ -0,0 +1,27 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { Trigger } from '.';
+
+export const VISUALIZE_FIELD_TRIGGER = 'VISUALIZE_FIELD_TRIGGER';
+export const visualizeFieldTrigger: Trigger<'VISUALIZE_FIELD_TRIGGER'> = {
+ id: VISUALIZE_FIELD_TRIGGER,
+ title: 'Visualize field',
+ description: 'Triggered when user wants to visualize a field.',
+};
diff --git a/src/plugins/ui_actions/public/triggers/visualize_geo_field_trigger.ts b/src/plugins/ui_actions/public/triggers/visualize_geo_field_trigger.ts
new file mode 100644
index 0000000000000..5582b3b42660c
--- /dev/null
+++ b/src/plugins/ui_actions/public/triggers/visualize_geo_field_trigger.ts
@@ -0,0 +1,27 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { Trigger } from '.';
+
+export const VISUALIZE_GEO_FIELD_TRIGGER = 'VISUALIZE_GEO_FIELD_TRIGGER';
+export const visualizeGeoFieldTrigger: Trigger<'VISUALIZE_GEO_FIELD_TRIGGER'> = {
+ id: VISUALIZE_GEO_FIELD_TRIGGER,
+ title: 'Visualize Geo field',
+ description: 'Triggered when user wants to visualize a geo field.',
+};
diff --git a/src/plugins/ui_actions/public/types.ts b/src/plugins/ui_actions/public/types.ts
index dcf0bfb14d538..b00f4628ffb96 100644
--- a/src/plugins/ui_actions/public/types.ts
+++ b/src/plugins/ui_actions/public/types.ts
@@ -23,6 +23,8 @@ import {
SELECT_RANGE_TRIGGER,
VALUE_CLICK_TRIGGER,
APPLY_FILTER_TRIGGER,
+ VISUALIZE_FIELD_TRIGGER,
+ VISUALIZE_GEO_FIELD_TRIGGER,
DEFAULT_TRIGGER,
} from './triggers';
import type { RangeSelectContext, ValueClickContext } from '../../embeddable/public';
@@ -32,6 +34,12 @@ export type TriggerRegistry = Map>;
export type ActionRegistry = Map;
export type TriggerToActionsRegistry = Map;
+export interface VisualizeFieldContext {
+ fieldName: string;
+ indexPatternId: string;
+ contextualFields?: string[];
+}
+
export type TriggerId = keyof TriggerContextMapping;
export type BaseContext = object;
@@ -42,11 +50,17 @@ export interface TriggerContextMapping {
[SELECT_RANGE_TRIGGER]: RangeSelectContext;
[VALUE_CLICK_TRIGGER]: ValueClickContext;
[APPLY_FILTER_TRIGGER]: ApplyGlobalFilterActionContext;
+ [VISUALIZE_FIELD_TRIGGER]: VisualizeFieldContext;
+ [VISUALIZE_GEO_FIELD_TRIGGER]: VisualizeFieldContext;
}
const DEFAULT_ACTION = '';
+export const ACTION_VISUALIZE_FIELD = 'ACTION_VISUALIZE_FIELD';
+export const ACTION_VISUALIZE_GEO_FIELD = 'ACTION_VISUALIZE_GEO_FIELD';
export type ActionType = keyof ActionContextMapping;
export interface ActionContextMapping {
[DEFAULT_ACTION]: BaseContext;
+ [ACTION_VISUALIZE_FIELD]: VisualizeFieldContext;
+ [ACTION_VISUALIZE_GEO_FIELD]: VisualizeFieldContext;
}
diff --git a/src/plugins/visualize/common/constants.ts b/src/plugins/visualize/common/constants.ts
new file mode 100644
index 0000000000000..4e33638286a19
--- /dev/null
+++ b/src/plugins/visualize/common/constants.ts
@@ -0,0 +1,20 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export const AGGS_TERMS_SIZE_SETTING = 'discover:aggs:terms:size';
diff --git a/src/plugins/visualize/kibana.json b/src/plugins/visualize/kibana.json
index 520d1e1daa6fe..a6cc8d8f8af60 100644
--- a/src/plugins/visualize/kibana.json
+++ b/src/plugins/visualize/kibana.json
@@ -9,7 +9,8 @@
"navigation",
"savedObjects",
"visualizations",
- "embeddable"
+ "embeddable",
+ "uiActions"
],
"optionalPlugins": ["home", "share"],
"requiredBundles": [
diff --git a/src/plugins/visualize/public/actions/visualize_field_action.ts b/src/plugins/visualize/public/actions/visualize_field_action.ts
new file mode 100644
index 0000000000000..6671d2c981910
--- /dev/null
+++ b/src/plugins/visualize/public/actions/visualize_field_action.ts
@@ -0,0 +1,97 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { i18n } from '@kbn/i18n';
+import {
+ createAction,
+ ACTION_VISUALIZE_FIELD,
+ VisualizeFieldContext,
+} from '../../../ui_actions/public';
+import {
+ getApplication,
+ getUISettings,
+ getIndexPatterns,
+ getQueryService,
+ getShareService,
+} from '../services';
+import { VISUALIZE_APP_URL_GENERATOR, VisualizeUrlGeneratorState } from '../url_generator';
+import { AGGS_TERMS_SIZE_SETTING } from '../../common/constants';
+
+export const visualizeFieldAction = createAction({
+ type: ACTION_VISUALIZE_FIELD,
+ getDisplayName: () =>
+ i18n.translate('visualize.discover.visualizeFieldLabel', {
+ defaultMessage: 'Visualize field',
+ }),
+ isCompatible: async () => !!getApplication().capabilities.visualize.show,
+ getHref: async (context) => {
+ const url = await getVisualizeUrl(context);
+ return url;
+ },
+ execute: async (context) => {
+ const url = await getVisualizeUrl(context);
+ const hash = url.split('#')[1];
+
+ getApplication().navigateToApp('visualize', {
+ path: `/#${hash}`,
+ });
+ },
+});
+
+const getVisualizeUrl = async (context: VisualizeFieldContext) => {
+ const indexPattern = await getIndexPatterns().get(context.indexPatternId);
+ const field = indexPattern.fields.find((fld) => fld.name === context.fieldName);
+ const aggsTermSize = getUISettings().get(AGGS_TERMS_SIZE_SETTING);
+ let agg;
+
+ // If we're visualizing a date field, and our index is time based (and thus has a time filter),
+ // then run a date histogram
+ if (field?.type === 'date' && indexPattern.timeFieldName === context.fieldName) {
+ agg = {
+ type: 'date_histogram',
+ schema: 'segment',
+ params: {
+ field: context.fieldName,
+ interval: 'auto',
+ },
+ };
+ } else {
+ agg = {
+ type: 'terms',
+ schema: 'segment',
+ params: {
+ field: context.fieldName,
+ size: parseInt(aggsTermSize, 10),
+ orderBy: '1',
+ },
+ };
+ }
+ const generator = getShareService().urlGenerators.getUrlGenerator(VISUALIZE_APP_URL_GENERATOR);
+ const urlState: VisualizeUrlGeneratorState = {
+ filters: getQueryService().filterManager.getFilters(),
+ query: getQueryService().queryString.getQuery(),
+ timeRange: getQueryService().timefilter.timefilter.getTime(),
+ indexPatternId: context.indexPatternId,
+ type: 'histogram',
+ vis: {
+ type: 'histogram',
+ aggs: [{ schema: 'metric', type: 'count', id: '1' }, agg],
+ },
+ };
+ return generator.createUrl(urlState);
+};
diff --git a/src/plugins/visualize/public/plugin.ts b/src/plugins/visualize/public/plugin.ts
index 3299319e613a0..8794593d6c958 100644
--- a/src/plugins/visualize/public/plugin.ts
+++ b/src/plugins/visualize/public/plugin.ts
@@ -39,7 +39,7 @@ import {
} from '../../kibana_utils/public';
import { DataPublicPluginStart, DataPublicPluginSetup, esFilters } from '../../data/public';
import { NavigationPublicPluginStart as NavigationStart } from '../../navigation/public';
-import { SharePluginStart } from '../../share/public';
+import { SharePluginStart, SharePluginSetup } from '../../share/public';
import { KibanaLegacySetup, KibanaLegacyStart } from '../../kibana_legacy/public';
import { VisualizationsStart } from '../../visualizations/public';
import { VisualizeConstants } from './application/visualize_constants';
@@ -48,6 +48,16 @@ import { VisualizeServices } from './application/types';
import { DEFAULT_APP_CATEGORIES } from '../../../core/public';
import { SavedObjectsStart } from '../../saved_objects/public';
import { EmbeddableStart } from '../../embeddable/public';
+import { UiActionsStart, VISUALIZE_FIELD_TRIGGER } from '../../ui_actions/public';
+import {
+ setUISettings,
+ setApplication,
+ setIndexPatterns,
+ setQueryService,
+ setShareService,
+} from './services';
+import { visualizeFieldAction } from './actions/visualize_field_action';
+import { createVisualizeUrlGenerator } from './url_generator';
export interface VisualizePluginStartDependencies {
data: DataPublicPluginStart;
@@ -57,12 +67,14 @@ export interface VisualizePluginStartDependencies {
embeddable: EmbeddableStart;
kibanaLegacy: KibanaLegacyStart;
savedObjects: SavedObjectsStart;
+ uiActions: UiActionsStart;
}
export interface VisualizePluginSetupDependencies {
home?: HomePublicPluginSetup;
kibanaLegacy: KibanaLegacySetup;
data: DataPublicPluginSetup;
+ share?: SharePluginSetup;
}
export interface FeatureFlagConfig {
@@ -80,7 +92,7 @@ export class VisualizePlugin
public async setup(
core: CoreSetup,
- { home, kibanaLegacy, data }: VisualizePluginSetupDependencies
+ { home, kibanaLegacy, data, share }: VisualizePluginSetupDependencies
) {
const {
appMounted,
@@ -113,6 +125,18 @@ export class VisualizePlugin
this.stopUrlTracking = () => {
stopUrlTracker();
};
+ if (share) {
+ share.urlGenerators.registerUrlGenerator(
+ createVisualizeUrlGenerator(async () => {
+ const [coreStart] = await core.getStartServices();
+ return {
+ appBasePath: coreStart.application.getUrlForApp('visualize'),
+ useHashedUrl: coreStart.uiSettings.get('state:storeInSessionStorage'),
+ };
+ })
+ );
+ }
+ setUISettings(core.uiSettings);
core.application.register({
id: 'visualize',
@@ -140,7 +164,6 @@ export class VisualizePlugin
const unlistenParentHistory = params.history.listen(() => {
window.dispatchEvent(new HashChangeEvent('hashchange'));
});
-
/**
* current implementation uses 2 history objects:
* 1. the hash history (used for the react hash router)
@@ -207,7 +230,15 @@ export class VisualizePlugin
}
}
- public start(core: CoreStart, plugins: VisualizePluginStartDependencies) {}
+ public start(core: CoreStart, plugins: VisualizePluginStartDependencies) {
+ setApplication(core.application);
+ setIndexPatterns(plugins.data.indexPatterns);
+ setQueryService(plugins.data.query);
+ if (plugins.share) {
+ setShareService(plugins.share);
+ }
+ plugins.uiActions.addTriggerAction(VISUALIZE_FIELD_TRIGGER, visualizeFieldAction);
+ }
stop() {
if (this.stopUrlTracking) {
diff --git a/src/plugins/visualize/public/services.ts b/src/plugins/visualize/public/services.ts
new file mode 100644
index 0000000000000..8190872ec6508
--- /dev/null
+++ b/src/plugins/visualize/public/services.ts
@@ -0,0 +1,37 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { ApplicationStart, IUiSettingsClient } from '../../../core/public';
+import { createGetterSetter } from '../../../plugins/kibana_utils/public';
+import { IndexPatternsContract, DataPublicPluginStart } from '../../../plugins/data/public';
+import { SharePluginStart } from '../../../plugins/share/public';
+
+export const [getUISettings, setUISettings] = createGetterSetter('UISettings');
+
+export const [getApplication, setApplication] = createGetterSetter('Application');
+
+export const [getShareService, setShareService] = createGetterSetter('Share');
+
+export const [getIndexPatterns, setIndexPatterns] = createGetterSetter(
+ 'IndexPatterns'
+);
+
+export const [getQueryService, setQueryService] = createGetterSetter<
+ DataPublicPluginStart['query']
+>('Query');
diff --git a/src/plugins/visualize/public/url_generator.test.ts b/src/plugins/visualize/public/url_generator.test.ts
new file mode 100644
index 0000000000000..8c8a0f70c15e3
--- /dev/null
+++ b/src/plugins/visualize/public/url_generator.test.ts
@@ -0,0 +1,100 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { createVisualizeUrlGenerator } from './url_generator';
+import { esFilters } from '../../data/public';
+
+const APP_BASE_PATH: string = 'test/app/visualize';
+const VISUALIZE_ID: string = '13823000-99b9-11ea-9eb6-d9e8adceb647';
+const INDEXPATTERN_ID: string = '13823000-99b9-11ea-9eb6-d9e8adceb647';
+
+describe('visualize url generator', () => {
+ test('creates a link to a new visualization', async () => {
+ const generator = createVisualizeUrlGenerator(() =>
+ Promise.resolve({
+ appBasePath: APP_BASE_PATH,
+ useHashedUrl: false,
+ })
+ );
+ const url = await generator.createUrl!({ indexPatternId: INDEXPATTERN_ID, type: 'table' });
+ expect(url).toMatchInlineSnapshot(
+ `"test/app/visualize#/create?_g=()&_a=()&indexPattern=${INDEXPATTERN_ID}&type=table"`
+ );
+ });
+
+ test('creates a link with global time range set up', async () => {
+ const generator = createVisualizeUrlGenerator(() =>
+ Promise.resolve({
+ appBasePath: APP_BASE_PATH,
+ useHashedUrl: false,
+ })
+ );
+ const url = await generator.createUrl!({
+ timeRange: { to: 'now', from: 'now-15m', mode: 'relative' },
+ indexPatternId: INDEXPATTERN_ID,
+ type: 'table',
+ });
+ expect(url).toMatchInlineSnapshot(
+ `"test/app/visualize#/create?_g=(time:(from:now-15m,mode:relative,to:now))&_a=()&indexPattern=${INDEXPATTERN_ID}&type=table"`
+ );
+ });
+
+ test('creates a link with filters, time range, refresh interval and query to a saved visualization', async () => {
+ const generator = createVisualizeUrlGenerator(() =>
+ Promise.resolve({
+ appBasePath: APP_BASE_PATH,
+ useHashedUrl: false,
+ indexPatternId: INDEXPATTERN_ID,
+ type: 'table',
+ })
+ );
+ const url = await generator.createUrl!({
+ timeRange: { to: 'now', from: 'now-15m', mode: 'relative' },
+ refreshInterval: { pause: false, value: 300 },
+ visualizationId: VISUALIZE_ID,
+ filters: [
+ {
+ meta: {
+ alias: null,
+ disabled: false,
+ negate: false,
+ },
+ query: { query: 'q1' },
+ },
+ {
+ meta: {
+ alias: null,
+ disabled: false,
+ negate: false,
+ },
+ query: { query: 'q1' },
+ $state: {
+ store: esFilters.FilterStateStore.GLOBAL_STATE,
+ },
+ },
+ ],
+ query: { query: 'q2', language: 'kuery' },
+ indexPatternId: INDEXPATTERN_ID,
+ type: 'table',
+ });
+ expect(url).toMatchInlineSnapshot(
+ `"test/app/visualize#/edit/${VISUALIZE_ID}?_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1))),refreshInterval:(pause:!f,value:300),time:(from:now-15m,mode:relative,to:now))&_a=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1))),query:(language:kuery,query:q2))&indexPattern=${INDEXPATTERN_ID}&type=table"`
+ );
+ });
+});
diff --git a/src/plugins/visualize/public/url_generator.ts b/src/plugins/visualize/public/url_generator.ts
new file mode 100644
index 0000000000000..38b7633c6fde1
--- /dev/null
+++ b/src/plugins/visualize/public/url_generator.ts
@@ -0,0 +1,137 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {
+ TimeRange,
+ Filter,
+ Query,
+ esFilters,
+ QueryState,
+ RefreshInterval,
+} from '../../data/public';
+import { setStateToKbnUrl } from '../../kibana_utils/public';
+import { UrlGeneratorsDefinition } from '../../share/public';
+
+const STATE_STORAGE_KEY = '_a';
+const GLOBAL_STATE_STORAGE_KEY = '_g';
+
+export const VISUALIZE_APP_URL_GENERATOR = 'VISUALIZE_APP_URL_GENERATOR';
+
+export interface VisualizeUrlGeneratorState {
+ /**
+ * If given, it will load the given visualization else will load the create a new visualization page.
+ */
+ visualizationId?: string;
+ /**
+ * Optionally set the time range in the time picker.
+ */
+ timeRange?: TimeRange;
+
+ /**
+ * Optional set indexPatternId.
+ */
+ indexPatternId?: string;
+
+ /**
+ * Optional set visualization type.
+ */
+ type?: string;
+
+ /**
+ * Optionally set the visualization.
+ */
+ vis?: unknown;
+
+ /**
+ * Optionally set the refresh interval.
+ */
+ refreshInterval?: RefreshInterval;
+
+ /**
+ * Optionally apply filers. NOTE: if given and used in conjunction with `dashboardId`, and the
+ * saved dashboard has filters saved with it, this will _replace_ those filters.
+ */
+ filters?: Filter[];
+ /**
+ * Optionally set a query. NOTE: if given and used in conjunction with `dashboardId`, and the
+ * saved dashboard has a query saved with it, this will _replace_ that query.
+ */
+ query?: Query;
+ /**
+ * If not given, will use the uiSettings configuration for `storeInSessionStorage`. useHash determines
+ * whether to hash the data in the url to avoid url length issues.
+ */
+ hash?: boolean;
+}
+
+export const createVisualizeUrlGenerator = (
+ getStartServices: () => Promise<{
+ appBasePath: string;
+ useHashedUrl: boolean;
+ }>
+): UrlGeneratorsDefinition => ({
+ id: VISUALIZE_APP_URL_GENERATOR,
+ createUrl: async ({
+ visualizationId,
+ filters,
+ indexPatternId,
+ query,
+ refreshInterval,
+ vis,
+ type,
+ timeRange,
+ hash,
+ }: VisualizeUrlGeneratorState): Promise => {
+ const startServices = await getStartServices();
+ const useHash = hash ?? startServices.useHashedUrl;
+ const appBasePath = startServices.appBasePath;
+ const mode = visualizationId ? `edit/${visualizationId}` : `create`;
+
+ const appState: {
+ query?: Query;
+ filters?: Filter[];
+ vis?: unknown;
+ } = {};
+ const queryState: QueryState = {};
+
+ if (query) appState.query = query;
+ if (filters && filters.length)
+ appState.filters = filters?.filter((f) => !esFilters.isFilterPinned(f));
+ if (vis) appState.vis = vis;
+
+ if (timeRange) queryState.time = timeRange;
+ if (filters && filters.length)
+ queryState.filters = filters?.filter((f) => esFilters.isFilterPinned(f));
+ if (refreshInterval) queryState.refreshInterval = refreshInterval;
+
+ let url = `${appBasePath}#/${mode}`;
+ url = setStateToKbnUrl(GLOBAL_STATE_STORAGE_KEY, queryState, { useHash }, url);
+ url = setStateToKbnUrl(STATE_STORAGE_KEY, appState, { useHash }, url);
+
+ if (indexPatternId) {
+ url = `${url}&indexPattern=${indexPatternId}`;
+ }
+
+ if (type) {
+ url = `${url}&type=${type}`;
+ }
+
+ return url;
+ },
+});
diff --git a/test/functional/apps/discover/_field_visualize.ts b/test/functional/apps/discover/_field_visualize.ts
index b0db6c149e41a..c95211e98cdba 100644
--- a/test/functional/apps/discover/_field_visualize.ts
+++ b/test/functional/apps/discover/_field_visualize.ts
@@ -87,6 +87,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await inspector.close();
});
+ it('should not show the "Visualize" button for geo field', async () => {
+ await PageObjects.discover.findFieldByName('geo.coordinates');
+ log.debug('visualize a geo field');
+ await PageObjects.discover.expectMissingFieldListItemVisualize('geo.coordinates');
+ });
+
it('should preserve app filters in visualize', async () => {
await filterBar.addFilter('bytes', 'is between', '3500', '4000');
await PageObjects.discover.findFieldByName('geo.src');
diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts
index eec23f95bb17b..363122ac62212 100644
--- a/x-pack/plugins/maps/common/constants.ts
+++ b/x-pack/plugins/maps/common/constants.ts
@@ -26,6 +26,7 @@ export const EMS_TILES_VECTOR_TILE_PATH = 'vector/tile';
export const MAP_SAVED_OBJECT_TYPE = 'map';
export const APP_ID = 'maps';
export const APP_ICON = 'gisApp';
+export const INITIAL_LAYERS_KEY = 'initialLayers';
export const MAPS_APP_PATH = `app/${APP_ID}`;
export const MAP_PATH = 'map';
diff --git a/x-pack/plugins/maps/kibana.json b/x-pack/plugins/maps/kibana.json
index fbf45aee02125..d554d159a196f 100644
--- a/x-pack/plugins/maps/kibana.json
+++ b/x-pack/plugins/maps/kibana.json
@@ -15,7 +15,8 @@
"visualizations",
"embeddable",
"mapsLegacy",
- "usageCollection"
+ "usageCollection",
+ "share"
],
"ui": true,
"server": true,
diff --git a/x-pack/plugins/maps/public/kibana_services.ts b/x-pack/plugins/maps/public/kibana_services.ts
index 3b004e2cda67b..f8f89ebaed102 100644
--- a/x-pack/plugins/maps/public/kibana_services.ts
+++ b/x-pack/plugins/maps/public/kibana_services.ts
@@ -45,6 +45,7 @@ export const getToasts = () => coreStart.notifications.toasts;
export const getSavedObjectsClient = () => coreStart.savedObjects.client;
export const getCoreChrome = () => coreStart.chrome;
export const getMapsCapabilities = () => coreStart.application.capabilities.maps;
+export const getVisualizeCapabilities = () => coreStart.application.capabilities.visualize;
export const getDocLinks = () => coreStart.docLinks;
export const getCoreOverlays = () => coreStart.overlays;
export const getData = () => pluginsStart.data;
@@ -81,3 +82,5 @@ export const getProxyElasticMapsServiceInMaps = () =>
getKibanaCommonConfig().proxyElasticMapsServiceInMaps;
export const getRegionmapLayers = () => _.get(getKibanaCommonConfig(), 'regionmap.layers', []);
export const getTilemap = () => _.get(getKibanaCommonConfig(), 'tilemap', []);
+
+export const getShareService = () => pluginsStart.share;
diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts
index e2b40e22bfe7d..9bb79f2937c68 100644
--- a/x-pack/plugins/maps/public/plugin.ts
+++ b/x-pack/plugins/maps/public/plugin.ts
@@ -31,6 +31,9 @@ import { getMapsVisTypeAlias } from './maps_vis_type_alias';
import { HomePublicPluginSetup } from '../../../../src/plugins/home/public';
import { VisualizationsSetup } from '../../../../src/plugins/visualizations/public';
import { APP_ICON, APP_ID, MAP_SAVED_OBJECT_TYPE } from '../common/constants';
+import { VISUALIZE_GEO_FIELD_TRIGGER } from '../../../../src/plugins/ui_actions/public';
+import { createMapsUrlGenerator } from './url_generator';
+import { visualizeGeoFieldAction } from './trigger_actions/visualize_geo_field_action';
import { MapEmbeddableFactory } from './embeddable/map_embeddable_factory';
import { EmbeddableSetup } from '../../../../src/plugins/embeddable/public';
import { MapsXPackConfig, MapsConfigType } from '../config';
@@ -39,6 +42,7 @@ import { ILicense } from '../../licensing/common/types';
import { lazyLoadMapModules } from './lazy_load_bundle';
import { MapsStartApi } from './api';
import { createSecurityLayerDescriptors, registerLayerWizard, registerSource } from './api';
+import { SharePluginSetup, SharePluginStart } from '../../../../src/plugins/share/public';
import { EmbeddableStart } from '../../../../src/plugins/embeddable/public';
import { MapsLegacyConfigType } from '../../../../src/plugins/maps_legacy/public';
import { DataPublicPluginStart } from '../../../../src/plugins/data/public';
@@ -51,6 +55,7 @@ export interface MapsPluginSetupDependencies {
visualizations: VisualizationsSetup;
embeddable: EmbeddableSetup;
mapsLegacy: { config: MapsLegacyConfigType };
+ share: SharePluginSetup;
}
export interface MapsPluginStartDependencies {
@@ -61,6 +66,7 @@ export interface MapsPluginStartDependencies {
licensing: LicensingPluginStart;
navigation: NavigationPublicPluginStart;
uiActions: UiActionsStart;
+ share: SharePluginStart;
}
/**
@@ -91,6 +97,15 @@ export class MapsPlugin
setKibanaCommonConfig(plugins.mapsLegacy.config);
setMapAppConfig(config);
setKibanaVersion(this._initializerContext.env.packageInfo.version);
+ plugins.share.urlGenerators.registerUrlGenerator(
+ createMapsUrlGenerator(async () => {
+ const [coreStart] = await core.getStartServices();
+ return {
+ appBasePath: coreStart.application.getUrlForApp('maps'),
+ useHashedUrl: coreStart.uiSettings.get('state:storeInSessionStorage'),
+ };
+ })
+ );
plugins.inspector.registerView(MapView);
plugins.home.featureCatalogue.register(featureCatalogueEntry);
@@ -122,7 +137,7 @@ export class MapsPlugin
setLicenseId(license.uid);
});
}
-
+ plugins.uiActions.addTriggerAction(VISUALIZE_GEO_FIELD_TRIGGER, visualizeGeoFieldAction);
setStartServices(core, plugins);
return {
diff --git a/x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.js b/x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.js
index 5d02160fc3eb5..b47f83d5a6664 100644
--- a/x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.js
+++ b/x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.js
@@ -20,6 +20,7 @@ import { TileLayer } from '../../classes/layers/tile_layer/tile_layer';
import { EMSTMSSource } from '../../classes/sources/ems_tms_source';
import { VectorTileLayer } from '../../classes/layers/vector_tile_layer/vector_tile_layer';
import { getIsEmsEnabled, getToasts } from '../../kibana_services';
+import { INITIAL_LAYERS_KEY } from '../../../common/constants';
import { getKibanaTileMap } from '../../meta';
export function getInitialLayers(layerListJSON, initialLayers = []) {
@@ -51,12 +52,12 @@ export function getInitialLayersFromUrlParam() {
return [];
}
const mapAppParams = new URLSearchParams(locationSplit[1]);
- if (!mapAppParams.has('initialLayers')) {
+ if (!mapAppParams.has(INITIAL_LAYERS_KEY)) {
return [];
}
try {
- let mapInitLayers = mapAppParams.get('initialLayers');
+ let mapInitLayers = mapAppParams.get(INITIAL_LAYERS_KEY);
if (mapInitLayers[mapInitLayers.length - 1] === '#') {
mapInitLayers = mapInitLayers.substr(0, mapInitLayers.length - 1);
}
diff --git a/x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts b/x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts
new file mode 100644
index 0000000000000..bdeab292b214c
--- /dev/null
+++ b/x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.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 uuid from 'uuid/v4';
+import { i18n } from '@kbn/i18n';
+import {
+ createAction,
+ ACTION_VISUALIZE_GEO_FIELD,
+ VisualizeFieldContext,
+} from '../../../../../src/plugins/ui_actions/public';
+import {
+ getVisualizeCapabilities,
+ getIndexPatternService,
+ getData,
+ getShareService,
+ getNavigateToApp,
+} from '../kibana_services';
+import { MAPS_APP_URL_GENERATOR, MapsUrlGeneratorState } from '../url_generator';
+import { LAYER_TYPE, SOURCE_TYPES, SCALING_TYPES, APP_ID, MAP_PATH } from '../../common/constants';
+
+export const visualizeGeoFieldAction = createAction({
+ type: ACTION_VISUALIZE_GEO_FIELD,
+ getDisplayName: () =>
+ i18n.translate('xpack.maps.discover.visualizeFieldLabel', {
+ defaultMessage: 'Visualize in Maps',
+ }),
+ isCompatible: async () => !!getVisualizeCapabilities().show,
+ getHref: async (context) => {
+ const url = await getMapsLink(context);
+ return url;
+ },
+ execute: async (context) => {
+ const url = await getMapsLink(context);
+ const hash = url.split('#')[1];
+
+ getNavigateToApp()(APP_ID, {
+ path: `${MAP_PATH}/#${hash}`,
+ });
+ },
+});
+
+const getMapsLink = async (context: VisualizeFieldContext) => {
+ const indexPattern = await getIndexPatternService().get(context.indexPatternId);
+ const field = indexPattern.fields.find((fld) => fld.name === context.fieldName);
+ const supportsClustering = field?.aggregatable;
+ // create initial layer descriptor
+ const hasTooltips =
+ context?.contextualFields?.length && context?.contextualFields[0] !== '_source';
+ const initialLayers = [
+ {
+ id: uuid(),
+ visible: true,
+ type: supportsClustering ? LAYER_TYPE.BLENDED_VECTOR : LAYER_TYPE.VECTOR,
+ sourceDescriptor: {
+ id: uuid(),
+ type: SOURCE_TYPES.ES_SEARCH,
+ tooltipProperties: hasTooltips ? context.contextualFields : [],
+ label: indexPattern.title,
+ indexPatternId: context.indexPatternId,
+ geoField: context.fieldName,
+ scalingType: supportsClustering ? SCALING_TYPES.CLUSTERS : SCALING_TYPES.LIMIT,
+ },
+ },
+ ];
+
+ const generator = getShareService().urlGenerators.getUrlGenerator(MAPS_APP_URL_GENERATOR);
+ const urlState: MapsUrlGeneratorState = {
+ filters: getData().query.filterManager.getFilters(),
+ query: getData().query.queryString.getQuery(),
+ initialLayers,
+ timeRange: getData().query.timefilter.timefilter.getTime(),
+ };
+ return generator.createUrl(urlState);
+};
diff --git a/x-pack/plugins/maps/public/url_generator.test.ts b/x-pack/plugins/maps/public/url_generator.test.ts
new file mode 100644
index 0000000000000..a44f8d952fde1
--- /dev/null
+++ b/x-pack/plugins/maps/public/url_generator.test.ts
@@ -0,0 +1,113 @@
+/*
+ * 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 rison from 'rison-node';
+import { createMapsUrlGenerator } from './url_generator';
+import { LAYER_TYPE, SOURCE_TYPES, SCALING_TYPES } from '../common/constants';
+import { esFilters } from '../../../../src/plugins/data/public';
+
+const APP_BASE_PATH: string = 'test/app/maps';
+const MAP_ID: string = '2c9c1f60-1909-11e9-919b-ffe5949a18d2';
+const LAYER_ID: string = '13823000-99b9-11ea-9eb6-d9e8adceb647';
+const INDEX_PATTERN_ID: string = '90943e30-9a47-11e8-b64d-95841ca0b247';
+
+describe('visualize url generator', () => {
+ test('creates a link to a new visualization', async () => {
+ const generator = createMapsUrlGenerator(() =>
+ Promise.resolve({
+ appBasePath: APP_BASE_PATH,
+ useHashedUrl: false,
+ })
+ );
+ const url = await generator.createUrl!({});
+ expect(url).toMatchInlineSnapshot(`"test/app/maps/map#/?_g=()&_a=()"`);
+ });
+
+ test('creates a link with global time range set up', async () => {
+ const generator = createMapsUrlGenerator(() =>
+ Promise.resolve({
+ appBasePath: APP_BASE_PATH,
+ useHashedUrl: false,
+ })
+ );
+ const url = await generator.createUrl!({
+ timeRange: { to: 'now', from: 'now-15m', mode: 'relative' },
+ });
+ expect(url).toMatchInlineSnapshot(
+ `"test/app/maps/map#/?_g=(time:(from:now-15m,mode:relative,to:now))&_a=()"`
+ );
+ });
+
+ test('creates a link with initialLayers set up', async () => {
+ const generator = createMapsUrlGenerator(() =>
+ Promise.resolve({
+ appBasePath: APP_BASE_PATH,
+ useHashedUrl: false,
+ })
+ );
+ const initialLayers = [
+ {
+ id: LAYER_ID,
+ visible: true,
+ type: LAYER_TYPE.VECTOR,
+ sourceDescriptor: {
+ id: LAYER_ID,
+ type: SOURCE_TYPES.ES_SEARCH,
+ tooltipProperties: [],
+ label: 'Sample Data',
+ indexPatternId: INDEX_PATTERN_ID,
+ geoField: 'test',
+ scalingType: SCALING_TYPES.LIMIT,
+ },
+ },
+ ];
+ const encodedLayers = rison.encode_array(initialLayers);
+ const url = await generator.createUrl!({
+ initialLayers,
+ });
+ expect(url).toMatchInlineSnapshot(
+ `"test/app/maps/map#/?_g=()&_a=()&initialLayers=${encodedLayers}"`
+ );
+ });
+
+ test('creates a link with filters, time range, refresh interval and query to a saved visualization', async () => {
+ const generator = createMapsUrlGenerator(() =>
+ Promise.resolve({
+ appBasePath: APP_BASE_PATH,
+ useHashedUrl: false,
+ })
+ );
+ const url = await generator.createUrl!({
+ timeRange: { to: 'now', from: 'now-15m', mode: 'relative' },
+ refreshInterval: { pause: false, value: 300 },
+ mapId: MAP_ID,
+ filters: [
+ {
+ meta: {
+ alias: null,
+ disabled: false,
+ negate: false,
+ },
+ query: { query: 'q1' },
+ },
+ {
+ meta: {
+ alias: null,
+ disabled: false,
+ negate: false,
+ },
+ query: { query: 'q1' },
+ $state: {
+ store: esFilters.FilterStateStore.GLOBAL_STATE,
+ },
+ },
+ ],
+ query: { query: 'q2', language: 'kuery' },
+ });
+ expect(url).toMatchInlineSnapshot(
+ `"test/app/maps/map#/${MAP_ID}?_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1))),refreshInterval:(pause:!f,value:300),time:(from:now-15m,mode:relative,to:now))&_a=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1))),query:(language:kuery,query:q2))"`
+ );
+ });
+});
diff --git a/x-pack/plugins/maps/public/url_generator.ts b/x-pack/plugins/maps/public/url_generator.ts
new file mode 100644
index 0000000000000..3fbb361342c7a
--- /dev/null
+++ b/x-pack/plugins/maps/public/url_generator.ts
@@ -0,0 +1,109 @@
+/*
+ * 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 rison from 'rison-node';
+import {
+ TimeRange,
+ Filter,
+ Query,
+ esFilters,
+ QueryState,
+ RefreshInterval,
+} from '../../../../src/plugins/data/public';
+import { setStateToKbnUrl } from '../../../../src/plugins/kibana_utils/public';
+import { UrlGeneratorsDefinition } from '../../../../src/plugins/share/public';
+import { LayerDescriptor } from '../common/descriptor_types';
+import { INITIAL_LAYERS_KEY } from '../common/constants';
+
+const STATE_STORAGE_KEY = '_a';
+const GLOBAL_STATE_STORAGE_KEY = '_g';
+
+export const MAPS_APP_URL_GENERATOR = 'MAPS_APP_URL_GENERATOR';
+
+export interface MapsUrlGeneratorState {
+ /**
+ * If given, it will load the given map else will load the create a new map page.
+ */
+ mapId?: string;
+ /**
+ * Optionally set the time range in the time picker.
+ */
+ timeRange?: TimeRange;
+
+ /**
+ * Optionally set the initial Layers.
+ */
+ initialLayers?: LayerDescriptor[];
+
+ /**
+ * Optionally set the refresh interval.
+ */
+ refreshInterval?: RefreshInterval;
+
+ /**
+ * Optionally apply filers. NOTE: if given and used in conjunction with `mapId`, and the
+ * saved map has filters saved with it, this will _replace_ those filters.
+ */
+ filters?: Filter[];
+ /**
+ * Optionally set a query. NOTE: if given and used in conjunction with `mapId`, and the
+ * saved map has a query saved with it, this will _replace_ that query.
+ */
+ query?: Query;
+ /**
+ * If not given, will use the uiSettings configuration for `storeInSessionStorage`. useHash determines
+ * whether to hash the data in the url to avoid url length issues.
+ */
+ hash?: boolean;
+}
+
+export const createMapsUrlGenerator = (
+ getStartServices: () => Promise<{
+ appBasePath: string;
+ useHashedUrl: boolean;
+ }>
+): UrlGeneratorsDefinition => ({
+ id: MAPS_APP_URL_GENERATOR,
+ createUrl: async ({
+ mapId,
+ filters,
+ query,
+ refreshInterval,
+ timeRange,
+ initialLayers,
+ hash,
+ }: MapsUrlGeneratorState): Promise => {
+ const startServices = await getStartServices();
+ const useHash = hash ?? startServices.useHashedUrl;
+ const appBasePath = startServices.appBasePath;
+
+ const appState: {
+ query?: Query;
+ filters?: Filter[];
+ vis?: unknown;
+ } = {};
+ const queryState: QueryState = {};
+
+ if (query) appState.query = query;
+ if (filters && filters.length)
+ appState.filters = filters?.filter((f) => !esFilters.isFilterPinned(f));
+
+ if (timeRange) queryState.time = timeRange;
+ if (filters && filters.length)
+ queryState.filters = filters?.filter((f) => esFilters.isFilterPinned(f));
+ if (refreshInterval) queryState.refreshInterval = refreshInterval;
+
+ let url = `${appBasePath}/map#/${mapId || ''}`;
+ url = setStateToKbnUrl(GLOBAL_STATE_STORAGE_KEY, queryState, { useHash }, url);
+ url = setStateToKbnUrl(STATE_STORAGE_KEY, appState, { useHash }, url);
+
+ if (initialLayers && initialLayers.length) {
+ // @ts-ignore
+ url = `${url}&${INITIAL_LAYERS_KEY}=${rison.encode_array(initialLayers)}`;
+ }
+
+ return url;
+ },
+});
diff --git a/x-pack/typings/rison_node.d.ts b/x-pack/typings/rison_node.d.ts
index 295392af2e05b..0e6069147e66f 100644
--- a/x-pack/typings/rison_node.d.ts
+++ b/x-pack/typings/rison_node.d.ts
@@ -23,4 +23,7 @@ declare module 'rison-node' {
// eslint-disable-next-line @typescript-eslint/naming-convention
export const encode_object: (input: Input) => string;
+
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ export const encode_array: (input: Input) => string;
}
From 0efe286fad9f1b008478b43549c186ad281f3251 Mon Sep 17 00:00:00 2001
From: Matthias Wilhelm
Date: Wed, 19 Aug 2020 10:48:10 +0200
Subject: [PATCH 13/85] [Discover] Fix histogram cloud tests (#75268)
---
test/functional/apps/discover/_discover_histogram.ts | 3 +++
1 file changed, 3 insertions(+)
diff --git a/test/functional/apps/discover/_discover_histogram.ts b/test/functional/apps/discover/_discover_histogram.ts
index 5c78bfccbb966..e06783174e83b 100644
--- a/test/functional/apps/discover/_discover_histogram.ts
+++ b/test/functional/apps/discover/_discover_histogram.ts
@@ -24,6 +24,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const elasticChart = getService('elasticChart');
const kibanaServer = getService('kibanaServer');
+ const security = getService('security');
const PageObjects = getPageObjects(['settings', 'common', 'discover', 'header', 'timePicker']);
const defaultSettings = {
defaultIndex: 'long-window-logstash-*',
@@ -35,12 +36,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await esArchiver.loadIfNeeded('logstash_functional');
await esArchiver.load('long_window_logstash');
await esArchiver.load('long_window_logstash_index_pattern');
+ await security.testUser.setRoles(['kibana_admin', 'long_window_logstash']);
await kibanaServer.uiSettings.replace(defaultSettings);
await PageObjects.common.navigateToApp('discover');
});
after(async () => {
await esArchiver.unload('long_window_logstash');
await esArchiver.unload('long_window_logstash_index_pattern');
+ await security.testUser.restoreDefaults();
});
async function prepareTest(fromTime: string, toTime: string, interval: string) {
From 2559d905518bb8db9b90e9d50932e9dad8c5336b Mon Sep 17 00:00:00 2001
From: Walter Rafelsberger
Date: Wed, 19 Aug 2020 12:07:15 +0200
Subject: [PATCH 14/85] [ML] Anomaly Explorer / Single Metric Viewer: Fix error
reporting for annotations. (#74953)
Fixes error reporting when annotations fail to load for Anomaly Explorer and Single Metric Viewer.
Previously, Anomaly Explorer ended up with a completely empty page when annotations failed to load. Single Metric Viewer would not fail to load, but it would make no difference for the user if existing annotations failed to load of if there were simply no existing annotations. Only in dev console an error message would be visible.
Now a callout is shown when annotations fail to load.
---
x-pack/plugins/ml/common/types/annotations.ts | 10 +-
.../public/application/explorer/explorer.js | 32 ++++-
.../application/explorer/explorer_utils.d.ts | 3 +-
.../application/explorer/explorer_utils.js | 17 ++-
.../reducers/explorer_reducer/state.ts | 8 +-
.../timeseries_chart/timeseries_chart.js | 6 +-
.../timeseriesexplorer/timeseriesexplorer.js | 33 ++++++
.../get_focus_data.ts | 42 ++++---
.../apps/ml/anomaly_detection/annotations.ts | 109 ++++++++++++++++++
.../ml/anomaly_detection/anomaly_explorer.ts | 4 +
.../apps/ml/anomaly_detection/index.ts | 1 +
.../anomaly_detection/single_metric_viewer.ts | 4 +
.../services/ml/anomaly_explorer.ts | 10 ++
.../test/functional/services/ml/navigation.ts | 18 +++
.../services/ml/single_metric_viewer.ts | 6 +
.../functional/services/ml/test_resources.ts | 86 ++++++++++++++
16 files changed, 357 insertions(+), 32 deletions(-)
create mode 100644 x-pack/test/functional/apps/ml/anomaly_detection/annotations.ts
diff --git a/x-pack/plugins/ml/common/types/annotations.ts b/x-pack/plugins/ml/common/types/annotations.ts
index 159a598f16bf5..eba40c2502f57 100644
--- a/x-pack/plugins/ml/common/types/annotations.ts
+++ b/x-pack/plugins/ml/common/types/annotations.ts
@@ -103,8 +103,7 @@ export function isAnnotation(arg: any): arg is Annotation {
);
}
-// eslint-disable-next-line @typescript-eslint/no-empty-interface
-export interface Annotations extends Array {}
+export type Annotations = Annotation[];
export function isAnnotations(arg: any): arg is Annotations {
if (Array.isArray(arg) === false) {
@@ -134,5 +133,12 @@ export type EsAggregationResult = Record;
export interface GetAnnotationsResponse {
aggregations?: EsAggregationResult;
annotations: Record;
+ error?: string;
success: boolean;
}
+
+export interface AnnotationsTable {
+ annotationsData: Annotations;
+ aggregations: EsAggregationResult;
+ error?: string;
+}
diff --git a/x-pack/plugins/ml/public/application/explorer/explorer.js b/x-pack/plugins/ml/public/application/explorer/explorer.js
index 4e27c17631506..06cec14578f2a 100644
--- a/x-pack/plugins/ml/public/application/explorer/explorer.js
+++ b/x-pack/plugins/ml/public/application/explorer/explorer.js
@@ -15,6 +15,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
import {
htmlIdGenerator,
+ EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
@@ -221,7 +222,7 @@ export class Explorer extends React.Component {
selectedJobs,
tableData,
} = this.props.explorerState;
- const { annotationsData, aggregations } = annotations;
+ const { annotationsData, aggregations, error: annotationsError } = annotations;
const jobSelectorProps = {
dateFormatTz: getDateFormatTz(),
@@ -302,9 +303,36 @@ export class Explorer extends React.Component {
setSelectedCells={this.props.setSelectedCells}
/>
- {annotationsData.length > 0 && (
+ {annotationsError !== undefined && (
<>
+
+
+
+
+
+
+ {annotationsError}
+
+
+
+ >
+ )}
+ {annotationsData.length > 0 && (
+ <>
+
Promise;
+) => Promise;
export declare interface AnomaliesTableData {
anomalies: any[];
diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_utils.js b/x-pack/plugins/ml/public/application/explorer/explorer_utils.js
index 03a251d3e2f79..08830decc9449 100644
--- a/x-pack/plugins/ml/public/application/explorer/explorer_utils.js
+++ b/x-pack/plugins/ml/public/application/explorer/explorer_utils.js
@@ -16,6 +16,7 @@ import {
ANOMALIES_TABLE_DEFAULT_QUERY_SIZE,
} from '../../../common/constants/search';
import { getEntityFieldList } from '../../../common/util/anomaly_utils';
+import { extractErrorMessage } from '../../../common/util/errors';
import {
isSourceDataChartableForDetector,
isModelPlotChartableForDetector,
@@ -406,7 +407,12 @@ export function loadAnnotationsTableData(selectedCells, selectedJobs, interval,
.toPromise()
.then((resp) => {
if (resp.error !== undefined || resp.annotations === undefined) {
- return resolve([]);
+ const errorMessage = extractErrorMessage(resp.error);
+ return resolve({
+ annotationsData: [],
+ aggregations: {},
+ error: errorMessage !== '' ? errorMessage : undefined,
+ });
}
const annotationsData = [];
@@ -430,9 +436,12 @@ export function loadAnnotationsTableData(selectedCells, selectedJobs, interval,
});
})
.catch((resp) => {
- console.log('Error loading list of annotations for jobs list:', resp);
- // Silently fail and just return an empty array for annotations to not break the UI.
- return resolve([]);
+ const errorMessage = extractErrorMessage(resp);
+ return resolve({
+ annotationsData: [],
+ aggregations: {},
+ error: errorMessage !== '' ? errorMessage : undefined,
+ });
});
});
}
diff --git a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts
index 889d572f4fabc..ea9a8b5c18054 100644
--- a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts
+++ b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts
@@ -21,14 +21,11 @@ import {
SwimlaneData,
ViewBySwimLaneData,
} from '../../explorer_utils';
-import { Annotations, EsAggregationResult } from '../../../../../common/types/annotations';
+import { AnnotationsTable } from '../../../../../common/types/annotations';
import { SWIM_LANE_DEFAULT_PAGE_SIZE } from '../../explorer_constants';
export interface ExplorerState {
- annotations: {
- annotationsData: Annotations;
- aggregations: EsAggregationResult;
- };
+ annotations: AnnotationsTable;
bounds: TimeRangeBounds | undefined;
chartsData: ExplorerChartsData;
fieldFormatsLoading: boolean;
@@ -67,6 +64,7 @@ function getDefaultIndexPattern() {
export function getExplorerDefaultState(): ExplorerState {
return {
annotations: {
+ error: undefined,
annotationsData: [],
aggregations: {},
},
diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js
index 7ec59f4acbc51..c1afb2994c92f 100644
--- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js
+++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js
@@ -552,7 +552,7 @@ class TimeseriesChartIntl extends Component {
renderFocusChart() {
const {
focusAggregationInterval,
- focusAnnotationData,
+ focusAnnotationData: focusAnnotationDataOriginalPropValue,
focusChartData,
focusForecastData,
modelPlotEnabled,
@@ -565,6 +565,10 @@ class TimeseriesChartIntl extends Component {
zoomToFocusLoaded,
} = this.props;
+ const focusAnnotationData = Array.isArray(focusAnnotationDataOriginalPropValue)
+ ? focusAnnotationDataOriginalPropValue
+ : [];
+
if (focusChartData === undefined) {
return;
}
diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js
index 95dc1ed6988f6..83a789074d353 100644
--- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js
+++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js
@@ -27,6 +27,7 @@ import {
EuiFormRow,
EuiSelect,
EuiSpacer,
+ EuiPanel,
EuiTitle,
EuiAccordion,
EuiBadge,
@@ -1028,6 +1029,7 @@ export class TimeSeriesExplorer extends React.Component {
dataNotChartable,
entityValues,
focusAggregationInterval,
+ focusAnnotationError,
focusAnnotationData,
focusAggregations,
focusChartData,
@@ -1316,6 +1318,36 @@ export class TimeSeriesExplorer extends React.Component {
)}
+ {focusAnnotationError !== undefined && (
+ <>
+
+
+
+
+
+
+
+ {focusAnnotationError}
+
+
+
+ >
+ )}
{focusAnnotationData && focusAnnotationData.length > 0 && (
}
+ data-test-subj="mlAnomalyExplorerAnnotations loaded"
>
{
- // silent fail
- return of({
- annotations: {} as Record,
+ catchError((resp) =>
+ of({
+ annotations: {},
aggregations: {},
+ error: extractErrorMessage(resp),
success: false,
- });
- })
+ } as GetAnnotationsResponse)
+ )
),
// Plus query for forecast data if there is a forecastId stored in the appState.
forecastId !== undefined
@@ -152,16 +154,22 @@ export function getFocusData(
};
if (annotations) {
- refreshFocusData.focusAnnotationData = (annotations.annotations[selectedJob.job_id] ?? [])
- .sort((a, b) => {
- return a.timestamp - b.timestamp;
- })
- .map((d, i: number) => {
- d.key = (i + 1).toString();
- return d;
- });
+ if (annotations.error !== undefined) {
+ refreshFocusData.focusAnnotationError = annotations.error;
+ refreshFocusData.focusAnnotationData = [];
+ refreshFocusData.focusAggregations = {};
+ } else {
+ refreshFocusData.focusAnnotationData = (annotations.annotations[selectedJob.job_id] ?? [])
+ .sort((a, b) => {
+ return a.timestamp - b.timestamp;
+ })
+ .map((d, i: number) => {
+ d.key = (i + 1).toString();
+ return d;
+ });
- refreshFocusData.focusAggregations = annotations.aggregations;
+ refreshFocusData.focusAggregations = annotations.aggregations;
+ }
}
if (forecastData) {
diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/annotations.ts b/x-pack/test/functional/apps/ml/anomaly_detection/annotations.ts
new file mode 100644
index 0000000000000..202910622fb64
--- /dev/null
+++ b/x-pack/test/functional/apps/ml/anomaly_detection/annotations.ts
@@ -0,0 +1,109 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import expect from '@kbn/expect';
+
+import { FtrProviderContext } from '../../../ftr_provider_context';
+import { Job, Datafeed } from '../../../../../plugins/ml/common/types/anomaly_detection_jobs';
+
+const JOB_CONFIG: Job = {
+ job_id: `fq_single_1_smv`,
+ description: 'mean(responsetime) on farequote dataset with 15m bucket span',
+ groups: ['farequote', 'automated', 'single-metric'],
+ analysis_config: {
+ bucket_span: '15m',
+ influencers: [],
+ detectors: [
+ {
+ function: 'mean',
+ field_name: 'responsetime',
+ },
+ ],
+ },
+ data_description: { time_field: '@timestamp' },
+ analysis_limits: { model_memory_limit: '10mb' },
+ model_plot_config: { enabled: true },
+};
+
+const DATAFEED_CONFIG: Datafeed = {
+ datafeed_id: 'datafeed-fq_single_1_smv',
+ indices: ['ft_farequote'],
+ job_id: 'fq_single_1_smv',
+ query: { bool: { must: [{ match_all: {} }] } },
+};
+
+export default function ({ getService }: FtrProviderContext) {
+ const esArchiver = getService('esArchiver');
+ const ml = getService('ml');
+
+ describe('annotations', function () {
+ this.tags(['mlqa']);
+ before(async () => {
+ await esArchiver.loadIfNeeded('ml/farequote');
+ await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp');
+ await ml.testResources.setKibanaTimeZoneToUTC();
+
+ await ml.api.createAndRunAnomalyDetectionLookbackJob(JOB_CONFIG, DATAFEED_CONFIG);
+ // Points the read/write aliases of annotations to an index with wrong mappings
+ // so we can simulate errors when requesting annotations.
+ await ml.testResources.setupBrokenAnnotationsIndexState(JOB_CONFIG.job_id);
+ await ml.securityUI.loginAsMlPowerUser();
+ });
+
+ after(async () => {
+ await ml.api.cleanMlIndices();
+ });
+
+ it('loads from job list row link', async () => {
+ await ml.navigation.navigateToMl();
+ await ml.navigation.navigateToJobManagement();
+
+ await ml.jobTable.waitForJobsToLoad();
+ await ml.jobTable.filterWithSearchString(JOB_CONFIG.job_id);
+ const rows = await ml.jobTable.parseJobTable();
+ expect(rows.filter((row) => row.id === JOB_CONFIG.job_id)).to.have.length(1);
+
+ await ml.jobTable.clickOpenJobInSingleMetricViewerButton(JOB_CONFIG.job_id);
+ await ml.common.waitForMlLoadingIndicatorToDisappear();
+ });
+
+ it('pre-fills the job selection', async () => {
+ await ml.jobSelection.assertJobSelection([JOB_CONFIG.job_id]);
+ });
+
+ it('pre-fills the detector input', async () => {
+ await ml.singleMetricViewer.assertDetectorInputExsist();
+ await ml.singleMetricViewer.assertDetectorInputValue('0');
+ });
+
+ it('should display the annotations section showing an error', async () => {
+ await ml.singleMetricViewer.assertAnnotationsExists('error');
+ });
+
+ it('should navigate to anomaly explorer', async () => {
+ await ml.navigation.navigateToAnomalyExplorerViaSingleMetricViewer();
+ });
+
+ it('should display the annotations section showing an error', async () => {
+ await ml.anomalyExplorer.assertAnnotationsPanelExists('error');
+ });
+
+ it('should display the annotations section without an error', async () => {
+ // restores the aliases to point to the original working annotations index
+ // so we can run tests against successfully loaded annotations sections.
+ await ml.testResources.restoreAnnotationsIndexState();
+ await ml.anomalyExplorer.refreshPage();
+ await ml.anomalyExplorer.assertAnnotationsPanelExists('loaded');
+ });
+
+ it('should navigate to single metric viewer', async () => {
+ await ml.navigation.navigateToSingleMetricViewerViaAnomalyExplorer();
+ });
+
+ it('should display the annotations section without an error', async () => {
+ await ml.singleMetricViewer.assertAnnotationsExists('loaded');
+ });
+ });
+}
diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/anomaly_explorer.ts b/x-pack/test/functional/apps/ml/anomaly_detection/anomaly_explorer.ts
index 89308938cfab0..cbee36abef78d 100644
--- a/x-pack/test/functional/apps/ml/anomaly_detection/anomaly_explorer.ts
+++ b/x-pack/test/functional/apps/ml/anomaly_detection/anomaly_explorer.ts
@@ -118,6 +118,10 @@ export default function ({ getService }: FtrProviderContext) {
await ml.anomalyExplorer.assertSwimlaneViewByExists();
});
+ it('should display the annotations panel', async () => {
+ await ml.anomalyExplorer.assertAnnotationsPanelExists('loaded');
+ });
+
it('displays the anomalies table', async () => {
await ml.anomaliesTable.assertTableExists();
});
diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/index.ts b/x-pack/test/functional/apps/ml/anomaly_detection/index.ts
index b5e6e426e2ead..0983ebd79dd90 100644
--- a/x-pack/test/functional/apps/ml/anomaly_detection/index.ts
+++ b/x-pack/test/functional/apps/ml/anomaly_detection/index.ts
@@ -18,5 +18,6 @@ export default function ({ loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./anomaly_explorer'));
loadTestFile(require.resolve('./categorization_job'));
loadTestFile(require.resolve('./date_nanos_job'));
+ loadTestFile(require.resolve('./annotations'));
});
}
diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/single_metric_viewer.ts b/x-pack/test/functional/apps/ml/anomaly_detection/single_metric_viewer.ts
index db511c5d75f39..3855bd0c884cd 100644
--- a/x-pack/test/functional/apps/ml/anomaly_detection/single_metric_viewer.ts
+++ b/x-pack/test/functional/apps/ml/anomaly_detection/single_metric_viewer.ts
@@ -79,6 +79,10 @@ export default function ({ getService }: FtrProviderContext) {
await ml.singleMetricViewer.assertChartExsist();
});
+ it('should display the annotations section', async () => {
+ await ml.singleMetricViewer.assertAnnotationsExists('loaded');
+ });
+
it('displays the anomalies table', async () => {
await ml.anomaliesTable.assertTableExists();
});
diff --git a/x-pack/test/functional/services/ml/anomaly_explorer.ts b/x-pack/test/functional/services/ml/anomaly_explorer.ts
index 80df235bf6ff8..1a6d5cd09f2e2 100644
--- a/x-pack/test/functional/services/ml/anomaly_explorer.ts
+++ b/x-pack/test/functional/services/ml/anomaly_explorer.ts
@@ -67,6 +67,12 @@ export function MachineLearningAnomalyExplorerProvider({ getService }: FtrProvid
await testSubjects.existOrFail('mlAnomalyExplorerSwimlaneViewBy');
},
+ async assertAnnotationsPanelExists(state: string) {
+ await testSubjects.existOrFail(`mlAnomalyExplorerAnnotationsPanel ${state}`, {
+ timeout: 30 * 1000,
+ });
+ },
+
async openAddToDashboardControl() {
await testSubjects.click('mlAnomalyTimelinePanelMenu');
await testSubjects.click('mlAnomalyTimelinePanelAddToDashboardButton');
@@ -89,6 +95,10 @@ export function MachineLearningAnomalyExplorerProvider({ getService }: FtrProvid
);
},
+ async refreshPage() {
+ await testSubjects.click('superDatePickerApplyTimeButton');
+ },
+
async waitForDashboardsToLoad() {
await testSubjects.existOrFail('~mlDashboardSelectionTable', { timeout: 60 * 1000 });
},
diff --git a/x-pack/test/functional/services/ml/navigation.ts b/x-pack/test/functional/services/ml/navigation.ts
index f52197d4b2256..116c9deb7c2dc 100644
--- a/x-pack/test/functional/services/ml/navigation.ts
+++ b/x-pack/test/functional/services/ml/navigation.ts
@@ -103,5 +103,23 @@ export function MachineLearningNavigationProvider({
await testSubjects.existOrFail('mlNoDataFrameAnalyticsFound');
});
},
+
+ async navigateToAnomalyExplorerViaSingleMetricViewer() {
+ // clicks the `Anomaly Explorer` icon on the button group to switch result views
+ await testSubjects.click('mlAnomalyResultsViewSelectorExplorer');
+ await retry.tryForTime(60 * 1000, async () => {
+ // verify that the anomaly explorer page is visible
+ await testSubjects.existOrFail('mlPageAnomalyExplorer');
+ });
+ },
+
+ async navigateToSingleMetricViewerViaAnomalyExplorer() {
+ // clicks the `Single Metric Viewere` icon on the button group to switch result views
+ await testSubjects.click('mlAnomalyResultsViewSelectorSingleMetricViewer');
+ await retry.tryForTime(60 * 1000, async () => {
+ // verify that the single metric viewer page is visible
+ await testSubjects.existOrFail('mlPageSingleMetricViewer');
+ });
+ },
};
}
diff --git a/x-pack/test/functional/services/ml/single_metric_viewer.ts b/x-pack/test/functional/services/ml/single_metric_viewer.ts
index b2c3e19020e62..a65ac09a0b056 100644
--- a/x-pack/test/functional/services/ml/single_metric_viewer.ts
+++ b/x-pack/test/functional/services/ml/single_metric_viewer.ts
@@ -49,5 +49,11 @@ export function MachineLearningSingleMetricViewerProvider({ getService }: FtrPro
async assertChartExsist() {
await testSubjects.existOrFail('mlSingleMetricViewerChart');
},
+
+ async assertAnnotationsExists(state: string) {
+ await testSubjects.existOrFail(`mlAnomalyExplorerAnnotations ${state}`, {
+ timeout: 30 * 1000,
+ });
+ },
};
}
diff --git a/x-pack/test/functional/services/ml/test_resources.ts b/x-pack/test/functional/services/ml/test_resources.ts
index 942dc4a80d4ac..675ec890b9edf 100644
--- a/x-pack/test/functional/services/ml/test_resources.ts
+++ b/x-pack/test/functional/services/ml/test_resources.ts
@@ -20,6 +20,7 @@ export enum SavedObjectType {
export type MlTestResourcesi = ProvidedType;
export function MachineLearningTestResourcesProvider({ getService }: FtrProviderContext) {
+ const es = getService('es');
const kibanaServer = getService('kibanaServer');
const log = getService('log');
const supertest = getService('supertest');
@@ -166,6 +167,91 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider
}
},
+ async setupBrokenAnnotationsIndexState(jobId: string) {
+ // Creates a temporary annotations index with unsupported mappings.
+ await es.indices.create({
+ index: '.ml-annotations-6-wrong-mapping',
+ body: {
+ settings: {
+ number_of_shards: 1,
+ },
+ mappings: {
+ properties: {
+ field1: { type: 'text' },
+ },
+ },
+ },
+ });
+
+ // Ingests an annotation that will cause dynamic mapping to pick up the wrong field type.
+ es.create({
+ id: 'annotation_with_wrong_mapping',
+ index: '.ml-annotations-6-wrong-mapping',
+ body: {
+ annotation: 'Annotation with wrong mapping',
+ create_time: 1597393915910,
+ create_username: '_xpack',
+ timestamp: 1549756800000,
+ end_timestamp: 1549756800000,
+ job_id: jobId,
+ modified_time: 1597393915910,
+ modified_username: '_xpack',
+ type: 'annotation',
+ event: 'user',
+ detector_index: 0,
+ },
+ });
+
+ // Points the read/write aliases for annotations to the broken annotations index
+ // so we can run tests against a state where annotation endpoints return errors.
+ await es.indices.updateAliases({
+ body: {
+ actions: [
+ {
+ add: {
+ index: '.ml-annotations-6-wrong-mapping',
+ alias: '.ml-annotations-read',
+ is_hidden: true,
+ },
+ },
+ { remove: { index: '.ml-annotations-6', alias: '.ml-annotations-read' } },
+ {
+ add: {
+ index: '.ml-annotations-6-wrong-mapping',
+ alias: '.ml-annotations-write',
+ is_hidden: true,
+ },
+ },
+ { remove: { index: '.ml-annotations-6', alias: '.ml-annotations-write' } },
+ ],
+ },
+ });
+ },
+
+ async restoreAnnotationsIndexState() {
+ // restore the original working state of pointing read/write aliases
+ // to the right annotations index.
+ await es.indices.updateAliases({
+ body: {
+ actions: [
+ { add: { index: '.ml-annotations-6', alias: '.ml-annotations-read', is_hidden: true } },
+ { remove: { index: '.ml-annotations-6-wrong-mapping', alias: '.ml-annotations-read' } },
+ {
+ add: { index: '.ml-annotations-6', alias: '.ml-annotations-write', is_hidden: true },
+ },
+ {
+ remove: { index: '.ml-annotations-6-wrong-mapping', alias: '.ml-annotations-write' },
+ },
+ ],
+ },
+ });
+
+ // deletes the temporary annotations index with wrong mappings
+ await es.indices.delete({
+ index: '.ml-annotations-6-wrong-mapping',
+ });
+ },
+
async updateSavedSearchRequestBody(body: object, indexPatternTitle: string): Promise {
const indexPatternId = await this.getIndexPatternId(indexPatternTitle);
if (indexPatternId === undefined) {
From 37f4f28040d657b90c7a9f2119e242e529c41dd9 Mon Sep 17 00:00:00 2001
From: Thomas Watson
Date: Wed, 19 Aug 2020 12:14:54 +0200
Subject: [PATCH 15/85] Update Node.js to version 10.22.0 (#75254)
---
.ci/Dockerfile | 2 +-
.node-version | 2 +-
.nvmrc | 2 +-
package.json | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.ci/Dockerfile b/.ci/Dockerfile
index d90d9f4710b5b..065b7f2aafd61 100644
--- a/.ci/Dockerfile
+++ b/.ci/Dockerfile
@@ -1,7 +1,7 @@
# NOTE: This Dockerfile is ONLY used to run certain tasks in CI. It is not used to run Kibana or as a distributable.
# If you're looking for the Kibana Docker image distributable, please see: src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts
-ARG NODE_VERSION=10.21.0
+ARG NODE_VERSION=10.22.0
FROM node:${NODE_VERSION} AS base
diff --git a/.node-version b/.node-version
index b61c07ffddbd1..b7604b0c8aea6 100644
--- a/.node-version
+++ b/.node-version
@@ -1 +1 @@
-10.21.0
+10.22.0
diff --git a/.nvmrc b/.nvmrc
index b61c07ffddbd1..b7604b0c8aea6 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-10.21.0
+10.22.0
diff --git a/package.json b/package.json
index 941cad4f0fd02..d6b272e85bd20 100644
--- a/package.json
+++ b/package.json
@@ -479,7 +479,7 @@
"zlib": "^1.0.5"
},
"engines": {
- "node": "10.21.0",
+ "node": "10.22.0",
"yarn": "^1.21.1"
}
}
From cebcc3a2295e6a4eb02e790114c18ce4790cbe25 Mon Sep 17 00:00:00 2001
From: Nicolas Chaulet
Date: Wed, 19 Aug 2020 07:49:08 -0400
Subject: [PATCH 16/85] [Ingest Manager] Fix agent config rollout rate limit to
use constants (#75364)
---
.../ingest_manager/common/constants/agent.ts | 4 ++--
.../ingest_manager/server/constants/index.ts | 4 ++--
x-pack/plugins/ingest_manager/server/index.ts | 12 ++++++++++--
.../services/agents/checkin/state_new_actions.ts | 13 ++++++++++---
4 files changed, 24 insertions(+), 9 deletions(-)
diff --git a/x-pack/plugins/ingest_manager/common/constants/agent.ts b/x-pack/plugins/ingest_manager/common/constants/agent.ts
index 7652c6ac87bce..d94f536b5659a 100644
--- a/x-pack/plugins/ingest_manager/common/constants/agent.ts
+++ b/x-pack/plugins/ingest_manager/common/constants/agent.ts
@@ -17,5 +17,5 @@ export const AGENT_POLLING_INTERVAL = 1000;
export const AGENT_UPDATE_LAST_CHECKIN_INTERVAL_MS = 30000;
export const AGENT_UPDATE_ACTIONS_INTERVAL_MS = 5000;
-export const AGENT_CONFIG_ROLLUP_RATE_LIMIT_INTERVAL_MS = 5000;
-export const AGENT_CONFIG_ROLLUP_RATE_LIMIT_REQUEST_PER_INTERVAL = 60;
+export const AGENT_CONFIG_ROLLOUT_RATE_LIMIT_INTERVAL_MS = 5000;
+export const AGENT_CONFIG_ROLLOUT_RATE_LIMIT_REQUEST_PER_INTERVAL = 25;
diff --git a/x-pack/plugins/ingest_manager/server/constants/index.ts b/x-pack/plugins/ingest_manager/server/constants/index.ts
index 1ec13bd80f0fb..730d8cf7b172c 100644
--- a/x-pack/plugins/ingest_manager/server/constants/index.ts
+++ b/x-pack/plugins/ingest_manager/server/constants/index.ts
@@ -10,8 +10,8 @@ export {
AGENT_POLLING_THRESHOLD_MS,
AGENT_POLLING_INTERVAL,
AGENT_UPDATE_LAST_CHECKIN_INTERVAL_MS,
- AGENT_CONFIG_ROLLUP_RATE_LIMIT_REQUEST_PER_INTERVAL,
- AGENT_CONFIG_ROLLUP_RATE_LIMIT_INTERVAL_MS,
+ AGENT_CONFIG_ROLLOUT_RATE_LIMIT_REQUEST_PER_INTERVAL,
+ AGENT_CONFIG_ROLLOUT_RATE_LIMIT_INTERVAL_MS,
AGENT_UPDATE_ACTIONS_INTERVAL_MS,
INDEX_PATTERN_PLACEHOLDER_SUFFIX,
// Routes
diff --git a/x-pack/plugins/ingest_manager/server/index.ts b/x-pack/plugins/ingest_manager/server/index.ts
index e2f659f54d625..9933266a441e2 100644
--- a/x-pack/plugins/ingest_manager/server/index.ts
+++ b/x-pack/plugins/ingest_manager/server/index.ts
@@ -6,6 +6,10 @@
import { schema, TypeOf } from '@kbn/config-schema';
import { PluginInitializerContext } from 'src/core/server';
import { IngestManagerPlugin } from './plugin';
+import {
+ AGENT_CONFIG_ROLLOUT_RATE_LIMIT_INTERVAL_MS,
+ AGENT_CONFIG_ROLLOUT_RATE_LIMIT_REQUEST_PER_INTERVAL,
+} from '../common';
export { AgentService, ESIndexPatternService, getRegistryUrl } from './services';
export {
IngestManagerSetupContract,
@@ -35,8 +39,12 @@ export const config = {
host: schema.maybe(schema.string()),
ca_sha256: schema.maybe(schema.string()),
}),
- agentConfigRolloutRateLimitIntervalMs: schema.number({ defaultValue: 5000 }),
- agentConfigRolloutRateLimitRequestPerInterval: schema.number({ defaultValue: 5 }),
+ agentConfigRolloutRateLimitIntervalMs: schema.number({
+ defaultValue: AGENT_CONFIG_ROLLOUT_RATE_LIMIT_INTERVAL_MS,
+ }),
+ agentConfigRolloutRateLimitRequestPerInterval: schema.number({
+ defaultValue: AGENT_CONFIG_ROLLOUT_RATE_LIMIT_REQUEST_PER_INTERVAL,
+ }),
}),
}),
};
diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts
index 1547b6b5ea053..418b59e1ca2e9 100644
--- a/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts
+++ b/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts
@@ -25,7 +25,12 @@ import {
} from '../../../types';
import { agentConfigService } from '../../agent_config';
import * as APIKeysService from '../../api_keys';
-import { AGENT_SAVED_OBJECT_TYPE, AGENT_UPDATE_ACTIONS_INTERVAL_MS } from '../../../constants';
+import {
+ AGENT_SAVED_OBJECT_TYPE,
+ AGENT_UPDATE_ACTIONS_INTERVAL_MS,
+ AGENT_CONFIG_ROLLOUT_RATE_LIMIT_INTERVAL_MS,
+ AGENT_CONFIG_ROLLOUT_RATE_LIMIT_REQUEST_PER_INTERVAL,
+} from '../../../constants';
import { createAgentAction, getNewActionsSince } from '../actions';
import { appContextService } from '../../app_context';
import { toPromiseAbortable, AbortError, createRateLimiter } from './rxjs_utils';
@@ -135,8 +140,10 @@ export function agentCheckinStateNewActionsFactory() {
const newActions$ = createNewActionsSharedObservable();
// Rx operators
const rateLimiter = createRateLimiter(
- appContextService.getConfig()?.fleet.agentConfigRolloutRateLimitIntervalMs ?? 5000,
- appContextService.getConfig()?.fleet.agentConfigRolloutRateLimitRequestPerInterval ?? 50
+ appContextService.getConfig()?.fleet.agentConfigRolloutRateLimitIntervalMs ??
+ AGENT_CONFIG_ROLLOUT_RATE_LIMIT_INTERVAL_MS,
+ appContextService.getConfig()?.fleet.agentConfigRolloutRateLimitRequestPerInterval ??
+ AGENT_CONFIG_ROLLOUT_RATE_LIMIT_REQUEST_PER_INTERVAL
);
async function subscribeToNewActions(
From 87414ad0d9f82e344b29167fc2f2c1adedf3f0d5 Mon Sep 17 00:00:00 2001
From: Joe Portner <5295965+jportner@users.noreply.github.com>
Date: Wed, 19 Aug 2020 09:54:11 -0400
Subject: [PATCH 17/85] Bump and consolidate dependencies (#75360)
---
package.json | 2 +
yarn.lock | 337 +++------------------------------------------------
2 files changed, 22 insertions(+), 317 deletions(-)
diff --git a/package.json b/package.json
index d6b272e85bd20..7d12af2eb5066 100644
--- a/package.json
+++ b/package.json
@@ -90,11 +90,13 @@
"**/graphql-toolkit/lodash": "^4.17.15",
"**/hoist-non-react-statics": "^3.3.2",
"**/isomorphic-git/**/base64-js": "^1.2.1",
+ "**/istanbul-instrumenter-loader/schema-utils": "1.0.0",
"**/image-diff/gm/debug": "^2.6.9",
"**/load-grunt-config/lodash": "^4.17.20",
"**/react-dom": "^16.12.0",
"**/react": "^16.12.0",
"**/react-test-renderer": "^16.12.0",
+ "**/request": "^2.88.2",
"**/deepmerge": "^4.2.2",
"**/fast-deep-equal": "^3.1.1"
},
diff --git a/yarn.lock b/yarn.lock
index 6dd6a39c1142e..0bc1ef72651e3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5549,7 +5549,7 @@ ajv-keywords@^3.4.1:
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da"
integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==
-ajv@^4.7.0, ajv@^4.9.1:
+ajv@^4.7.0:
version "4.11.8"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536"
integrity sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=
@@ -5557,20 +5557,10 @@ ajv@^4.7.0, ajv@^4.9.1:
co "^4.6.0"
json-stable-stringify "^1.0.1"
-ajv@^5.0.0, ajv@^5.1.0:
- version "5.5.2"
- resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965"
- integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=
- dependencies:
- co "^4.6.0"
- fast-deep-equal "^1.0.0"
- fast-json-stable-stringify "^2.0.0"
- json-schema-traverse "^0.3.0"
-
ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.5.5, ajv@^6.9.1:
- version "6.12.2"
- resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd"
- integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==
+ version "6.12.4"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.4.tgz#0614facc4522127fa713445c6bfd3ebd376e2234"
+ integrity sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==
dependencies:
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
@@ -6426,11 +6416,6 @@ assert-plus@1.0.0, assert-plus@^1.0.0:
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
-assert-plus@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234"
- integrity sha1-104bh+ev/A24qttwIfP+SBAasjQ=
-
assert@^1.1.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91"
@@ -6632,26 +6617,11 @@ await-event@^2.1.0:
resolved "https://registry.yarnpkg.com/await-event/-/await-event-2.1.0.tgz#78e9f92684bae4022f9fa0b5f314a11550f9aa76"
integrity sha1-eOn5JoS65AIvn6C18xShFVD5qnY=
-aws-sign2@~0.6.0:
- version "0.6.0"
- resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
- integrity sha1-FDQt0428yU0OW4fXY81jYSwOeU8=
-
aws-sign2@~0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
-aws4@^1.2.1:
- version "1.9.1"
- resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e"
- integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==
-
-aws4@^1.6.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
- integrity sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=
-
aws4@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
@@ -7490,27 +7460,6 @@ boolbase@^1.0.0, boolbase@~1.0.0:
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
-boom@2.x.x:
- version "2.10.1"
- resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f"
- integrity sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=
- dependencies:
- hoek "2.x.x"
-
-boom@4.x.x:
- version "4.3.1"
- resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31"
- integrity sha1-T4owBctKfjiJ90kDD9JbluAdLjE=
- dependencies:
- hoek "4.x.x"
-
-boom@5.x.x:
- version "5.2.0"
- resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02"
- integrity sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==
- dependencies:
- hoek "4.x.x"
-
boom@7.x.x, boom@^7.1.0, boom@^7.2.0:
version "7.2.2"
resolved "https://registry.yarnpkg.com/boom/-/boom-7.2.2.tgz#ac92101451aa5cea901aed07d881dd32b4f08345"
@@ -9073,14 +9022,7 @@ colorspace@1.1.x:
color "3.0.x"
text-hex "1.0.x"
-combined-stream@^1.0.5:
- version "1.0.8"
- resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
- integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
- dependencies:
- delayed-stream "~1.0.0"
-
-combined-stream@^1.0.6, combined-stream@~1.0.5, combined-stream@~1.0.6:
+combined-stream@^1.0.6, combined-stream@~1.0.6:
version "1.0.7"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828"
integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==
@@ -9791,20 +9733,6 @@ crypt@~0.0.1:
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=
-cryptiles@2.x.x:
- version "2.0.5"
- resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8"
- integrity sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=
- dependencies:
- boom "2.x.x"
-
-cryptiles@3.x.x:
- version "3.1.4"
- resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.4.tgz#769a68c95612b56faadfcebf57ac86479cbe8322"
- integrity sha512-8I1sgZHfVwcSOY6mSGpVU3lw/GSIZvusg8dD2+OGehCJpOhQRLNcH0qb9upQnOH4XhgxxFJSg6E2kx95deb1Tw==
- dependencies:
- boom "5.x.x"
-
cryptiles@4.x.x:
version "4.1.3"
resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-4.1.3.tgz#2461d3390ea0b82c643a6ba79f0ed491b0934c25"
@@ -12772,7 +12700,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
assign-symbols "^1.0.0"
is-extendable "^1.0.1"
-extend@^3.0.0, extend@~3.0.0, extend@~3.0.1, extend@~3.0.2:
+extend@^3.0.0, extend@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
@@ -12895,7 +12823,7 @@ fancy-log@^1.3.2:
color-support "^1.1.3"
time-stamp "^1.0.0"
-fast-deep-equal@^1.0.0, fast-deep-equal@^2.0.1, fast-deep-equal@^3.1.1, fast-deep-equal@~3.1.1:
+fast-deep-equal@^2.0.1, fast-deep-equal@^3.1.1, fast-deep-equal@~3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4"
integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==
@@ -13597,7 +13525,7 @@ form-data-to-object@^0.2.0:
resolved "https://registry.yarnpkg.com/form-data-to-object/-/form-data-to-object-0.2.0.tgz#f7a8e68ddd910a1100a65e25ac6a484143ff8168"
integrity sha1-96jmjd2RChEApl4lrGpIQUP/gWg=
-form-data@^2.3.1, form-data@~2.3.1, form-data@~2.3.2:
+form-data@^2.3.1, form-data@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==
@@ -13615,15 +13543,6 @@ form-data@^2.5.0:
combined-stream "^1.0.6"
mime-types "^2.1.12"
-form-data@~2.1.1:
- version "2.1.4"
- resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1"
- integrity sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=
- dependencies:
- asynckit "^0.4.0"
- combined-stream "^1.0.5"
- mime-types "^2.1.12"
-
format@^0.2.0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
@@ -15208,33 +15127,12 @@ hapi@^17.5.3:
teamwork "3.x.x"
topo "3.x.x"
-har-schema@^1.0.5:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e"
- integrity sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=
-
har-schema@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
-har-validator@~4.2.1:
- version "4.2.1"
- resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a"
- integrity sha1-M0gdDxu/9gDdID11gSpqX7oALio=
- dependencies:
- ajv "^4.9.1"
- har-schema "^1.0.5"
-
-har-validator@~5.0.3:
- version "5.0.3"
- resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd"
- integrity sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=
- dependencies:
- ajv "^5.1.0"
- har-schema "^2.0.0"
-
-har-validator@~5.1.0, har-validator@~5.1.3:
+har-validator@~5.1.3:
version "5.1.3"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==
@@ -15420,26 +15318,6 @@ hat@0.0.3:
resolved "https://registry.yarnpkg.com/hat/-/hat-0.0.3.tgz#bb014a9e64b3788aed8005917413d4ff3d502d8a"
integrity sha1-uwFKnmSzeIrtgAWRdBPU/z1QLYo=
-hawk@~3.1.3:
- version "3.1.3"
- resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
- integrity sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=
- dependencies:
- boom "2.x.x"
- cryptiles "2.x.x"
- hoek "2.x.x"
- sntp "1.x.x"
-
-hawk@~6.0.2:
- version "6.0.2"
- resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038"
- integrity sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==
- dependencies:
- boom "4.x.x"
- cryptiles "3.x.x"
- hoek "4.x.x"
- sntp "2.x.x"
-
he@1.2.0, he@1.2.x, he@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
@@ -15523,16 +15401,6 @@ hmac-drbg@^1.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
-hoek@2.x.x:
- version "2.16.3"
- resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
- integrity sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=
-
-hoek@4.x.x:
- version "4.2.1"
- resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb"
- integrity sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==
-
hoek@5.x.x, hoek@^5.0.4:
version "5.0.4"
resolved "https://registry.yarnpkg.com/hoek/-/hoek-5.0.4.tgz#0f7fa270a1cafeb364a4b2ddfaa33f864e4157da"
@@ -15776,15 +15644,6 @@ http-request-to-url@^1.0.0:
await-event "^2.1.0"
socket-location "^1.0.0"
-http-signature@~1.1.0:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf"
- integrity sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=
- dependencies:
- assert-plus "^0.2.0"
- jsprim "^1.2.2"
- sshpk "^1.7.0"
-
http-signature@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
@@ -18198,11 +18057,6 @@ json-parse-better-errors@^1.0.2:
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
-json-schema-traverse@^0.3.0:
- version "0.3.1"
- resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340"
- integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=
-
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
@@ -20076,7 +19930,7 @@ mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.18, mime-types@~2.1.19:
dependencies:
mime-db "~1.37.0"
-mime-types@^2.1.25, mime-types@~2.1.7:
+mime-types@^2.1.25:
version "2.1.26"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06"
integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==
@@ -21407,11 +21261,6 @@ nyc@^15.0.1:
test-exclude "^6.0.0"
yargs "^15.0.2"
-oauth-sign@~0.8.1, oauth-sign@~0.8.2:
- version "0.8.2"
- resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
- integrity sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=
-
oauth-sign@~0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
@@ -23374,7 +23223,7 @@ q@^1.1.2:
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
-qs@6.5.2, qs@^6.4.0, qs@^6.5.1, qs@~6.5.1, qs@~6.5.2:
+qs@6.5.2, qs@^6.4.0, qs@^6.5.1, qs@~6.5.2:
version "6.5.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
@@ -23389,11 +23238,6 @@ qs@^6.6.0:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.8.0.tgz#87b763f0d37ca54200334cd57bb2ef8f68a1d081"
integrity sha512-tPSkj8y92PfZVbinY1n84i1Qdx75lZjMQYx9WZhnkofyxzw2r7Ho39G3/aEvSUdebxpnnM4LZJCtvE/Aq3+s9w==
-qs@~6.4.0:
- version "6.4.0"
- resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
- integrity sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=
-
query-string@5.1.1, query-string@^5.0.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb"
@@ -25188,115 +25032,7 @@ request-promise@^4.2.2:
stealthy-require "^1.1.1"
tough-cookie "^2.3.3"
-request@2.81.0:
- version "2.81.0"
- resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0"
- integrity sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=
- dependencies:
- aws-sign2 "~0.6.0"
- aws4 "^1.2.1"
- caseless "~0.12.0"
- combined-stream "~1.0.5"
- extend "~3.0.0"
- forever-agent "~0.6.1"
- form-data "~2.1.1"
- har-validator "~4.2.1"
- hawk "~3.1.3"
- http-signature "~1.1.0"
- is-typedarray "~1.0.0"
- isstream "~0.1.2"
- json-stringify-safe "~5.0.1"
- mime-types "~2.1.7"
- oauth-sign "~0.8.1"
- performance-now "^0.2.0"
- qs "~6.4.0"
- safe-buffer "^5.0.1"
- stringstream "~0.0.4"
- tough-cookie "~2.3.0"
- tunnel-agent "^0.6.0"
- uuid "^3.0.0"
-
-request@2.88.0, request@^2.88.0:
- version "2.88.0"
- resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
- integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
- dependencies:
- aws-sign2 "~0.7.0"
- aws4 "^1.8.0"
- caseless "~0.12.0"
- combined-stream "~1.0.6"
- extend "~3.0.2"
- forever-agent "~0.6.1"
- form-data "~2.3.2"
- har-validator "~5.1.0"
- http-signature "~1.2.0"
- is-typedarray "~1.0.0"
- isstream "~0.1.2"
- json-stringify-safe "~5.0.1"
- mime-types "~2.1.19"
- oauth-sign "~0.9.0"
- performance-now "^2.1.0"
- qs "~6.5.2"
- safe-buffer "^5.1.2"
- tough-cookie "~2.4.3"
- tunnel-agent "^0.6.0"
- uuid "^3.3.2"
-
-request@^2.74.0:
- version "2.83.0"
- resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356"
- integrity sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==
- dependencies:
- aws-sign2 "~0.7.0"
- aws4 "^1.6.0"
- caseless "~0.12.0"
- combined-stream "~1.0.5"
- extend "~3.0.1"
- forever-agent "~0.6.1"
- form-data "~2.3.1"
- har-validator "~5.0.3"
- hawk "~6.0.2"
- http-signature "~1.2.0"
- is-typedarray "~1.0.0"
- isstream "~0.1.2"
- json-stringify-safe "~5.0.1"
- mime-types "~2.1.17"
- oauth-sign "~0.8.2"
- performance-now "^2.1.0"
- qs "~6.5.1"
- safe-buffer "^5.1.1"
- stringstream "~0.0.5"
- tough-cookie "~2.3.3"
- tunnel-agent "^0.6.0"
- uuid "^3.1.0"
-
-request@^2.87.0:
- version "2.87.0"
- resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e"
- integrity sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==
- dependencies:
- aws-sign2 "~0.7.0"
- aws4 "^1.6.0"
- caseless "~0.12.0"
- combined-stream "~1.0.5"
- extend "~3.0.1"
- forever-agent "~0.6.1"
- form-data "~2.3.1"
- har-validator "~5.0.3"
- http-signature "~1.2.0"
- is-typedarray "~1.0.0"
- isstream "~0.1.2"
- json-stringify-safe "~5.0.1"
- mime-types "~2.1.17"
- oauth-sign "~0.8.2"
- performance-now "^2.1.0"
- qs "~6.5.1"
- safe-buffer "^5.1.1"
- tough-cookie "~2.3.3"
- tunnel-agent "^0.6.0"
- uuid "^3.1.0"
-
-request@^2.88.2:
+request@2.81.0, request@2.88.0, request@^2.74.0, request@^2.87.0, request@^2.88.0, request@^2.88.2:
version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
@@ -25974,12 +25710,14 @@ scheduler@^0.18.0:
loose-envify "^1.1.0"
object-assign "^4.1.1"
-schema-utils@^0.3.0:
- version "0.3.0"
- resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf"
- integrity sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=
+schema-utils@1.0.0, schema-utils@^0.3.0, schema-utils@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
+ integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==
dependencies:
- ajv "^5.0.0"
+ ajv "^6.1.0"
+ ajv-errors "^1.0.0"
+ ajv-keywords "^3.1.0"
schema-utils@^0.4.5:
version "0.4.7"
@@ -25989,15 +25727,6 @@ schema-utils@^0.4.5:
ajv "^6.1.0"
ajv-keywords "^3.1.0"
-schema-utils@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
- integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==
- dependencies:
- ajv "^6.1.0"
- ajv-errors "^1.0.0"
- ajv-keywords "^3.1.0"
-
schema-utils@^2.0.0, schema-utils@^2.0.1, schema-utils@^2.4.1, schema-utils@^2.5.0, schema-utils@^2.6.0, schema-utils@^2.6.1, schema-utils@^2.6.4, schema-utils@^2.6.6, schema-utils@^2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7"
@@ -26609,20 +26338,6 @@ snapdragon@^0.8.1:
source-map-resolve "^0.5.0"
use "^2.0.0"
-sntp@1.x.x:
- version "1.0.9"
- resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198"
- integrity sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=
- dependencies:
- hoek "2.x.x"
-
-sntp@2.x.x:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8"
- integrity sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==
- dependencies:
- hoek "4.x.x"
-
socket-location@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/socket-location/-/socket-location-1.0.0.tgz#6f0c6f891c9a61c9a750265c14921d12196d266f"
@@ -27370,11 +27085,6 @@ stringify-object@^3.2.1:
is-obj "^1.0.1"
is-regexp "^1.0.0"
-stringstream@~0.0.4, stringstream@~0.0.5:
- version "0.0.6"
- resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72"
- integrity sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==
-
strip-ansi@*, strip-ansi@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
@@ -28515,7 +28225,7 @@ touch@^3.1.0:
dependencies:
nopt "~1.0.10"
-tough-cookie@^2.0.0, tough-cookie@^2.3.3, tough-cookie@~2.4.3:
+tough-cookie@^2.0.0, tough-cookie@^2.3.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==
@@ -28540,13 +28250,6 @@ tough-cookie@^3.0.1:
psl "^1.1.28"
punycode "^2.1.1"
-tough-cookie@~2.3.0, tough-cookie@~2.3.3:
- version "2.3.4"
- resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655"
- integrity sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==
- dependencies:
- punycode "^1.4.1"
-
tr46@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
From 53c765eb2216b7559f96b34417b388feb9a405d6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Loix?=
Date: Wed, 19 Aug 2020 16:13:18 +0200
Subject: [PATCH 18/85] [Index template] Add filters to simulate preview
(#74497)
Co-authored-by: Elastic Machine
---
.../component_templates.scss | 1 +
.../component_templates_selector.scss | 41 +++++------
.../component_templates_selector.tsx | 19 +++--
.../index_templates/shared_imports.ts | 13 ++++
.../simulate_template/index.ts | 2 +-
.../simulate_template/simulate_template.tsx | 63 ++++++++++++++---
.../simulate_template_flyout.tsx | 70 ++++++++++++++++---
.../template_form/steps/step_components.tsx | 2 +-
.../template_form/steps/step_review.tsx | 2 +-
.../template_form/template_form.tsx | 14 +++-
.../template_details/tabs/tab_preview.tsx | 2 +-
.../translations/translations/ja-JP.json | 1 -
.../translations/translations/zh-CN.json | 1 -
13 files changed, 177 insertions(+), 54 deletions(-)
create mode 100644 x-pack/plugins/index_management/public/application/components/index_templates/shared_imports.ts
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates.scss b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates.scss
index 026e63b2b4caa..8e196936e4073 100644
--- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates.scss
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates.scss
@@ -8,6 +8,7 @@ $heightHeader: $euiSizeL * 2;
.componentTemplates {
border: $euiBorderThin;
+ border-radius: $euiBorderRadius;
border-top: none;
height: 100%;
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_selector.scss b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_selector.scss
index 041fc1c8bf9a4..aeccc6a513fe2 100644
--- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_selector.scss
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_selector.scss
@@ -7,30 +7,31 @@
&__selection {
border: $euiBorderThin;
+ border-radius: $euiBorderRadius;
- padding: 0 $euiSize $euiSize;
- color: $euiColorDarkShade;
+ padding: 0 $euiSize $euiSize;
+ color: $euiColorDarkShade;
- &--is-empty {
- align-items: center;
- justify-content: center;
- }
+ &--is-empty {
+ align-items: center;
+ justify-content: center;
+ }
- &__header {
- background-color: $euiColorLightestShade;
- border-bottom: $euiBorderThin;
- color: $euiColorInk;
- height: $euiSizeXXL; // [1]
- line-height: $euiSizeXXL; // [1]
- font-size: $euiSizeM;
- margin-bottom: $euiSizeS;
- margin-left: $euiSize * -1;
- margin-right: $euiSize * -1;
- padding-left: $euiSize;
+ &__header {
+ background-color: $euiColorLightestShade;
+ border-bottom: $euiBorderThin;
+ color: $euiColorInk;
+ height: $euiSizeXXL; // [1]
+ line-height: $euiSizeXXL; // [1]
+ font-size: $euiSizeM;
+ margin-bottom: $euiSizeS;
+ margin-left: $euiSize * -1;
+ margin-right: $euiSize * -1;
+ padding-left: $euiSize;
- &__count {
- font-weight: 600;
- }
+ &__count {
+ font-weight: 600;
+ }
}
&__content {
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_selector.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_selector.tsx
index ccdfaad78fb6b..ff871b8b79247 100644
--- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_selector.tsx
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_selector.tsx
@@ -200,12 +200,19 @@ export const ComponentTemplatesSelector = ({
>
) : (
-
-
-
+
+
+
+
+
+
+
)}
diff --git a/x-pack/plugins/index_management/public/application/components/index_templates/shared_imports.ts b/x-pack/plugins/index_management/public/application/components/index_templates/shared_imports.ts
new file mode 100644
index 0000000000000..cc43b8b48b0c7
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/index_templates/shared_imports.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+export {
+ useForm,
+ Form,
+ getUseField,
+ FormDataProvider,
+} from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib';
+
+export { CheckBoxField } from '../../../../../../../src/plugins/es_ui_shared/static/forms/components';
diff --git a/x-pack/plugins/index_management/public/application/components/index_templates/simulate_template/index.ts b/x-pack/plugins/index_management/public/application/components/index_templates/simulate_template/index.ts
index fb10096ed81d4..de231baf11c50 100644
--- a/x-pack/plugins/index_management/public/application/components/index_templates/simulate_template/index.ts
+++ b/x-pack/plugins/index_management/public/application/components/index_templates/simulate_template/index.ts
@@ -10,4 +10,4 @@ export {
Props as SimulateTemplateProps,
} from './simulate_template_flyout';
-export { SimulateTemplate } from './simulate_template';
+export { SimulateTemplate, Filters as SimulateTemplateFilters } from './simulate_template';
diff --git a/x-pack/plugins/index_management/public/application/components/index_templates/simulate_template/simulate_template.tsx b/x-pack/plugins/index_management/public/application/components/index_templates/simulate_template/simulate_template.tsx
index b362b37d54c49..c2ba4eea56ad8 100644
--- a/x-pack/plugins/index_management/public/application/components/index_templates/simulate_template/simulate_template.tsx
+++ b/x-pack/plugins/index_management/public/application/components/index_templates/simulate_template/simulate_template.tsx
@@ -5,7 +5,8 @@
*/
import React, { useState, useCallback, useEffect } from 'react';
import uuid from 'uuid';
-import { EuiCodeBlock } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiCodeBlock, EuiCallOut } from '@elastic/eui';
import { serializers } from '../../../../shared_imports';
import { TemplateDeserialized } from '../../../../../common';
@@ -14,12 +15,18 @@ import { simulateIndexTemplate } from '../../../services';
const { stripEmptyFields } = serializers;
+export interface Filters {
+ mappings: boolean;
+ settings: boolean;
+ aliases: boolean;
+}
+
interface Props {
template: { [key: string]: any };
- minHeightCodeBlock?: string;
+ filters?: Filters;
}
-export const SimulateTemplate = React.memo(({ template, minHeightCodeBlock }: Props) => {
+export const SimulateTemplate = React.memo(({ template, filters }: Props) => {
const [templatePreview, setTemplatePreview] = useState('{}');
const updatePreview = useCallback(async () => {
@@ -34,26 +41,60 @@ export const SimulateTemplate = React.memo(({ template, minHeightCodeBlock }: Pr
indexTemplate.index_patterns = [uuid.v4()];
const { data, error } = await simulateIndexTemplate(indexTemplate);
+ let filteredTemplate = data;
if (data) {
// "Overlapping" info is only useful when simulating against an index
// which we don't do here.
delete data.overlapping;
+
+ if (data.template && data.template.mappings === undefined) {
+ // Adding some extra logic to return an empty object for "mappings" as ES does not
+ // return one in that case (empty objects _are_ returned for "settings" and "aliases")
+ // Issue: https://github.com/elastic/elasticsearch/issues/60968
+ data.template.mappings = {};
+ }
+
+ if (filters) {
+ filteredTemplate = Object.entries(filters).reduce(
+ (acc, [key, value]) => {
+ if (!value) {
+ delete acc[key];
+ }
+ return acc;
+ },
+ { ...data.template } as any
+ );
+ }
}
- setTemplatePreview(JSON.stringify(data ?? error, null, 2));
- }, [template]);
+ setTemplatePreview(JSON.stringify(filteredTemplate ?? error, null, 2));
+ }, [template, filters]);
useEffect(() => {
updatePreview();
}, [updatePreview]);
- return templatePreview === '{}' ? null : (
-
+ const isEmpty = templatePreview === '{}';
+ const hasFilters = Boolean(filters);
+
+ if (isEmpty && hasFilters) {
+ return (
+
+ }
+ iconType="pin"
+ size="s"
+ />
+ );
+ }
+
+ return isEmpty ? null : (
+
{templatePreview}
);
diff --git a/x-pack/plugins/index_management/public/application/components/index_templates/simulate_template/simulate_template_flyout.tsx b/x-pack/plugins/index_management/public/application/components/index_templates/simulate_template/simulate_template_flyout.tsx
index 63bfe78546041..f818f49d8aa59 100644
--- a/x-pack/plugins/index_management/public/application/components/index_templates/simulate_template/simulate_template_flyout.tsx
+++ b/x-pack/plugins/index_management/public/application/components/index_templates/simulate_template/simulate_template_flyout.tsx
@@ -5,6 +5,7 @@
*/
import React, { useState, useCallback, useEffect, useRef } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
+import { i18n } from '@kbn/i18n';
import {
EuiFlyoutHeader,
EuiTitle,
@@ -19,11 +20,16 @@ import {
EuiSpacer,
} from '@elastic/eui';
-import { SimulateTemplate } from './simulate_template';
+import { useForm, Form, getUseField, CheckBoxField, FormDataProvider } from '../shared_imports';
+import { SimulateTemplate, Filters } from './simulate_template';
+
+const CheckBox = getUseField({ component: CheckBoxField });
export interface Props {
onClose(): void;
getTemplate: () => { [key: string]: any };
+ filters: Filters;
+ onFiltersChange: (filters: Filters) => void;
}
export const defaultFlyoutProps = {
@@ -31,16 +37,39 @@ export const defaultFlyoutProps = {
'aria-labelledby': 'simulateTemplateFlyoutTitle',
};
-export const SimulateTemplateFlyoutContent = ({ onClose, getTemplate }: Props) => {
+const i18nTexts = {
+ filters: {
+ label: i18n.translate('xpack.idxMgmt.simulateTemplate.filters.label', {
+ defaultMessage: 'Include:',
+ }),
+ mappings: i18n.translate('xpack.idxMgmt.simulateTemplate.filters.mappings', {
+ defaultMessage: 'Mappings',
+ }),
+ indexSettings: i18n.translate('xpack.idxMgmt.simulateTemplate.filters.indexSettings', {
+ defaultMessage: 'Index settings',
+ }),
+ aliases: i18n.translate('xpack.idxMgmt.simulateTemplate.filters.aliases', {
+ defaultMessage: 'Aliases',
+ }),
+ },
+};
+
+export const SimulateTemplateFlyoutContent = ({
+ onClose,
+ getTemplate,
+ filters,
+ onFiltersChange,
+}: Props) => {
const isMounted = useRef(false);
- const [heightCodeBlock, setHeightCodeBlock] = useState(0);
const [template, setTemplate] = useState<{ [key: string]: any }>({});
+ const { form } = useForm({ defaultValue: filters });
+ const { subscribe } = form;
useEffect(() => {
- setHeightCodeBlock(
- document.getElementsByClassName('euiFlyoutBody__overflow')[0].clientHeight - 96
- );
- }, []);
+ subscribe((formState) => {
+ onFiltersChange(formState.data.format());
+ });
+ }, [subscribe, onFiltersChange]);
const updatePreview = useCallback(async () => {
const indexTemplate = await getTemplate();
@@ -71,8 +100,8 @@ export const SimulateTemplateFlyoutContent = ({ onClose, getTemplate }: Props) =
@@ -80,7 +109,28 @@ export const SimulateTemplateFlyoutContent = ({ onClose, getTemplate }: Props) =
-
+
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_components.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_components.tsx
index ae831f4acf7ee..a4f64f7881a28 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_components.tsx
+++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_components.tsx
@@ -34,7 +34,7 @@ const i18nTexts = {
description: (
),
};
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_review.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_review.tsx
index 1b4f19dda99f7..3bdcf1f35733e 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_review.tsx
+++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_review.tsx
@@ -66,7 +66,7 @@ const PreviewTab = ({ template }: { template: { [key: string]: any } }) => {
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx
index fb0ba0b68fa6c..151b31b527228 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx
+++ b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx
@@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { useState, useCallback } from 'react';
+import React, { useState, useCallback, useRef } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiSpacer, EuiButton } from '@elastic/eui';
@@ -15,6 +15,7 @@ import {
SimulateTemplateFlyoutContent,
SimulateTemplateProps,
simulateTemplateFlyoutProps,
+ SimulateTemplateFilters,
} from '../index_templates';
import { StepLogisticsContainer, StepComponentContainer, StepReviewContainer } from './steps';
import {
@@ -98,6 +99,11 @@ export const TemplateForm = ({
}: Props) => {
const [wizardContent, setWizardContent] = useState | null>(null);
const { addContent: addContentToGlobalFlyout, closeFlyout } = useGlobalFlyout();
+ const simulateTemplateFilters = useRef({
+ mappings: true,
+ settings: true,
+ aliases: true,
+ });
const indexTemplate = defaultValue ?? {
name: '',
@@ -234,6 +240,10 @@ export const TemplateForm = ({
return template;
}, [buildTemplateObject, indexTemplate, wizardContent]);
+ const onSimulateTemplateFiltersChange = useCallback((filters: SimulateTemplateFilters) => {
+ simulateTemplateFilters.current = filters;
+ }, []);
+
const showPreviewFlyout = () => {
addContentToGlobalFlyout({
id: 'simulateTemplate',
@@ -241,6 +251,8 @@ export const TemplateForm = ({
props: {
getTemplate: getSimulateTemplate,
onClose: closeFlyout,
+ filters: simulateTemplateFilters.current,
+ onFiltersChange: onSimulateTemplateFiltersChange,
},
flyoutProps: simulateTemplateFlyoutProps,
});
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_preview.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_preview.tsx
index ec52bcbab3b0b..49b78f3f56c25 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_preview.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_preview.tsx
@@ -21,7 +21,7 @@ export const TabPreview = ({ templateDetails }: Props) => {
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index c958215a2677e..814c532657f60 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -7278,7 +7278,6 @@
"xpack.idxMgmt.componentTemplatesSelector.filters.mappingsLabel": "マッピング",
"xpack.idxMgmt.componentTemplatesSelector.loadingComponentsDescription": "コンポーネントテンプレートを読み込んでいます...",
"xpack.idxMgmt.componentTemplatesSelector.loadingComponentsErrorMessage": "コンポーネントの読み込みエラー",
- "xpack.idxMgmt.componentTemplatesSelector.noComponentSelectedLabel": "コンポーネントテンプレートが選択されていません。",
"xpack.idxMgmt.componentTemplatesSelector.removeItemIconLabel": "削除",
"xpack.idxMgmt.componentTemplatesSelector.searchBox.placeholder": "コンポーネントテンプレートを検索",
"xpack.idxMgmt.componentTemplatesSelector.searchResult.emptyPrompt.clearSearchButtonLabel": "検索のクリア",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index a06e5813796f4..667706c886bc8 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -7280,7 +7280,6 @@
"xpack.idxMgmt.componentTemplatesSelector.filters.mappingsLabel": "映射",
"xpack.idxMgmt.componentTemplatesSelector.loadingComponentsDescription": "正在加载组件模板……",
"xpack.idxMgmt.componentTemplatesSelector.loadingComponentsErrorMessage": "加载组件时出错",
- "xpack.idxMgmt.componentTemplatesSelector.noComponentSelectedLabel": "未选择任何组件模板。",
"xpack.idxMgmt.componentTemplatesSelector.removeItemIconLabel": "移除",
"xpack.idxMgmt.componentTemplatesSelector.searchBox.placeholder": "搜索组件模板",
"xpack.idxMgmt.componentTemplatesSelector.searchResult.emptyPrompt.clearSearchButtonLabel": "清除搜索",
From 774c04a2d5289f57a0f3c742fe19e0b9f5b486c8 Mon Sep 17 00:00:00 2001
From: Chris Roberson
Date: Wed, 19 Aug 2020 10:13:27 -0400
Subject: [PATCH 19/85] [Monitoring] Migrate karma tests (#75301)
* Move to jest
* More tests
---
.../get_color.js => get_color.test.js} | 2 +-
...t_last_value.js => get_last_value.test.js} | 2 +-
.../get_title.js => get_title.test.js} | 2 +-
...egend.js => get_values_for_legend.test.js} | 2 +-
.../{__tests__/config.js => config.test.js} | 4 +-
.../boolean_edge.js => boolean_edge.test.js} | 4 +-
.../graph/{__tests__/edge.js => edge.test.js} | 2 +-
.../edge_factory.js => edge_factory.test.js} | 6 +-
.../if_vertex.js => if_vertex.test.js} | 4 +-
.../{__tests__/index.js => index.test.js} | 8 +-
...plugin_vertex.js => plugin_vertex.test.js} | 4 +-
.../queue_vertex.js => queue_vertex.test.js} | 4 +-
.../{__tests__/vertex.js => vertex.test.js} | 69 +++---
...rtex_factory.js => vertex_factory.test.js} | 8 +-
...peline_state.js => pipeline_state.test.js} | 4 +-
....js => monitoring_main_controller.test.js} | 32 ++-
...format_number.js => format_number.test.js} | 2 +-
.../pipelines.js => pipelines.test.js} | 2 +-
.../public/services/__tests__/executor.js | 111 ----------
.../breadcrumbs.js => breadcrumbs.test.js} | 34 ++-
.../public/views/__tests__/base_controller.js | 209 ------------------
.../views/__tests__/base_table_controller.js | 92 --------
22 files changed, 127 insertions(+), 480 deletions(-)
rename x-pack/plugins/monitoring/public/components/chart/{__tests__/get_color.js => get_color.test.js} (96%)
rename x-pack/plugins/monitoring/public/components/chart/{__tests__/get_last_value.js => get_last_value.test.js} (96%)
rename x-pack/plugins/monitoring/public/components/chart/{__tests__/get_title.js => get_title.test.js} (94%)
rename x-pack/plugins/monitoring/public/components/chart/{__tests__/get_values_for_legend.js => get_values_for_legend.test.js} (99%)
rename x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/{__tests__/config.js => config.test.js} (93%)
rename x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/{__tests__/boolean_edge.js => boolean_edge.test.js} (89%)
rename x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/{__tests__/edge.js => edge.test.js} (96%)
rename x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/{__tests__/edge_factory.js => edge_factory.test.js} (89%)
rename x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/{__tests__/if_vertex.js => if_vertex.test.js} (93%)
rename x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/{__tests__/index.js => index.test.js} (99%)
rename x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/{__tests__/plugin_vertex.js => plugin_vertex.test.js} (98%)
rename x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/{__tests__/queue_vertex.js => queue_vertex.test.js} (89%)
rename x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/{__tests__/vertex.js => vertex.test.js} (81%)
rename x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/{__tests__/vertex_factory.js => vertex_factory.test.js} (86%)
rename x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/{__tests__/pipeline_state.js => pipeline_state.test.js} (92%)
rename x-pack/plugins/monitoring/public/directives/main/{__tests__/monitoring_main_controller.js => monitoring_main_controller.test.js} (93%)
rename x-pack/plugins/monitoring/public/lib/{__tests__/format_number.js => format_number.test.js} (96%)
rename x-pack/plugins/monitoring/public/lib/logstash/{__tests__/pipelines.js => pipelines.test.js} (93%)
delete mode 100644 x-pack/plugins/monitoring/public/services/__tests__/executor.js
rename x-pack/plugins/monitoring/public/services/{__tests__/breadcrumbs.js => breadcrumbs.test.js} (86%)
delete mode 100644 x-pack/plugins/monitoring/public/views/__tests__/base_controller.js
delete mode 100644 x-pack/plugins/monitoring/public/views/__tests__/base_table_controller.js
diff --git a/x-pack/plugins/monitoring/public/components/chart/__tests__/get_color.js b/x-pack/plugins/monitoring/public/components/chart/get_color.test.js
similarity index 96%
rename from x-pack/plugins/monitoring/public/components/chart/__tests__/get_color.js
rename to x-pack/plugins/monitoring/public/components/chart/get_color.test.js
index 7b70f1d1fb224..1029e6c836225 100644
--- a/x-pack/plugins/monitoring/public/components/chart/__tests__/get_color.js
+++ b/x-pack/plugins/monitoring/public/components/chart/get_color.test.js
@@ -5,7 +5,7 @@
*/
import expect from '@kbn/expect';
-import { getColor } from '../get_color';
+import { getColor } from './get_color';
describe('getColors', function () {
it('elasticsearch colors', () => {
diff --git a/x-pack/plugins/monitoring/public/components/chart/__tests__/get_last_value.js b/x-pack/plugins/monitoring/public/components/chart/get_last_value.test.js
similarity index 96%
rename from x-pack/plugins/monitoring/public/components/chart/__tests__/get_last_value.js
rename to x-pack/plugins/monitoring/public/components/chart/get_last_value.test.js
index c089b47408e81..47e6dad3712a7 100644
--- a/x-pack/plugins/monitoring/public/components/chart/__tests__/get_last_value.js
+++ b/x-pack/plugins/monitoring/public/components/chart/get_last_value.test.js
@@ -5,7 +5,7 @@
*/
import expect from '@kbn/expect';
-import { getLastValue } from '../get_last_value';
+import { getLastValue } from './get_last_value';
describe('monitoringChartGetLastValue', function () {
it('getLastValue for single number', () => {
diff --git a/x-pack/plugins/monitoring/public/components/chart/__tests__/get_title.js b/x-pack/plugins/monitoring/public/components/chart/get_title.test.js
similarity index 94%
rename from x-pack/plugins/monitoring/public/components/chart/__tests__/get_title.js
rename to x-pack/plugins/monitoring/public/components/chart/get_title.test.js
index a7f4b48a5862a..7905089b21b9e 100644
--- a/x-pack/plugins/monitoring/public/components/chart/__tests__/get_title.js
+++ b/x-pack/plugins/monitoring/public/components/chart/get_title.test.js
@@ -5,7 +5,7 @@
*/
import expect from '@kbn/expect';
-import { getTitle } from '../get_title';
+import { getTitle } from './get_title';
describe('getTitle', function () {
it('with metric.title', () => {
diff --git a/x-pack/plugins/monitoring/public/components/chart/__tests__/get_values_for_legend.js b/x-pack/plugins/monitoring/public/components/chart/get_values_for_legend.test.js
similarity index 99%
rename from x-pack/plugins/monitoring/public/components/chart/__tests__/get_values_for_legend.js
rename to x-pack/plugins/monitoring/public/components/chart/get_values_for_legend.test.js
index e5c933426efcd..053f88382d9c0 100644
--- a/x-pack/plugins/monitoring/public/components/chart/__tests__/get_values_for_legend.js
+++ b/x-pack/plugins/monitoring/public/components/chart/get_values_for_legend.test.js
@@ -6,7 +6,7 @@
import expect from '@kbn/expect';
import sinon from 'sinon';
-import { findIndexByX, getValuesByX, getValuesForSeriesIndex } from '../get_values_for_legend';
+import { findIndexByX, getValuesByX, getValuesForSeriesIndex } from './get_values_for_legend';
describe('monitoringChartHelpers', function () {
it('getValuesForSeriesIndex sets does not impact callback without series', () => {
diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/__tests__/config.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/config.test.js
similarity index 93%
rename from x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/__tests__/config.js
rename to x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/config.test.js
index 83627a8181f38..f1ddd2b933183 100644
--- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/__tests__/config.js
+++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/config.test.js
@@ -6,8 +6,8 @@
import expect from '@kbn/expect';
import sinon from 'sinon';
-import { Config } from '../config';
-import { Graph } from '../graph';
+import { Config } from './config';
+import { Graph } from './graph';
describe('Config class', () => {
let configJson;
diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/boolean_edge.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/boolean_edge.test.js
similarity index 89%
rename from x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/boolean_edge.js
rename to x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/boolean_edge.test.js
index faf56f48db677..1c20d758242e8 100644
--- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/boolean_edge.js
+++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/boolean_edge.test.js
@@ -5,8 +5,8 @@
*/
import expect from '@kbn/expect';
-import { BooleanEdge } from '../boolean_edge';
-import { Edge } from '../edge';
+import { BooleanEdge } from './boolean_edge';
+import { Edge } from './edge';
describe('BooleanEdge', () => {
let graph;
diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/edge.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/edge.test.js
similarity index 96%
rename from x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/edge.js
rename to x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/edge.test.js
index 8acf0fff7f864..96476aca8cd3b 100644
--- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/edge.js
+++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/edge.test.js
@@ -5,7 +5,7 @@
*/
import expect from '@kbn/expect';
-import { Edge } from '../edge';
+import { Edge } from './edge';
describe('Edge', () => {
let graph;
diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/edge_factory.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/edge_factory.test.js
similarity index 89%
rename from x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/edge_factory.js
rename to x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/edge_factory.test.js
index 80fb22925a38c..80922064122af 100644
--- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/edge_factory.js
+++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/edge_factory.test.js
@@ -5,9 +5,9 @@
*/
import expect from '@kbn/expect';
-import { edgeFactory } from '../edge_factory';
-import { Edge } from '../edge';
-import { BooleanEdge } from '../boolean_edge';
+import { edgeFactory } from './edge_factory';
+import { Edge } from './edge';
+import { BooleanEdge } from './boolean_edge';
describe('edgeFactory', () => {
let graph;
diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/if_vertex.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/if_vertex.test.js
similarity index 93%
rename from x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/if_vertex.js
rename to x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/if_vertex.test.js
index 5a64d12a7f5b0..8bbd8326ddf88 100644
--- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/if_vertex.js
+++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/if_vertex.test.js
@@ -5,8 +5,8 @@
*/
import expect from '@kbn/expect';
-import { IfVertex } from '../if_vertex';
-import { Vertex } from '../vertex';
+import { IfVertex } from './if_vertex';
+import { Vertex } from './vertex';
describe('IfVertex', () => {
let graph;
diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/index.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/index.test.js
similarity index 99%
rename from x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/index.js
rename to x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/index.test.js
index 55d8a0b3e574a..8689d96f1e36a 100644
--- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/index.js
+++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/index.test.js
@@ -5,10 +5,10 @@
*/
import expect from '@kbn/expect';
-import { Graph } from '../';
-import { Vertex } from '../vertex';
-import { PluginVertex } from '../plugin_vertex';
-import { Edge } from '../edge';
+import { Graph } from './';
+import { Vertex } from './vertex';
+import { PluginVertex } from './plugin_vertex';
+import { Edge } from './edge';
describe('Graph', () => {
let graphJson;
diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/plugin_vertex.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/plugin_vertex.test.js
similarity index 98%
rename from x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/plugin_vertex.js
rename to x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/plugin_vertex.test.js
index 3bc8033e283f2..b2931700eb024 100644
--- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/plugin_vertex.js
+++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/plugin_vertex.test.js
@@ -5,8 +5,8 @@
*/
import expect from '@kbn/expect';
-import { Vertex } from '../vertex';
-import { PluginVertex, TIME_CONSUMING_PROCESSOR_THRESHOLD_COEFFICIENT } from '../plugin_vertex';
+import { Vertex } from './vertex';
+import { PluginVertex, TIME_CONSUMING_PROCESSOR_THRESHOLD_COEFFICIENT } from './plugin_vertex';
describe('PluginVertex', () => {
let graph;
diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/queue_vertex.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/queue_vertex.test.js
similarity index 89%
rename from x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/queue_vertex.js
rename to x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/queue_vertex.test.js
index 997306f52cc59..b3b15e6169338 100644
--- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/queue_vertex.js
+++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/queue_vertex.test.js
@@ -5,8 +5,8 @@
*/
import expect from '@kbn/expect';
-import { QueueVertex } from '../queue_vertex';
-import { Vertex } from '../vertex';
+import { QueueVertex } from './queue_vertex';
+import { Vertex } from './vertex';
describe('QueueVertex', () => {
let graph;
diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/vertex.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/vertex.test.js
similarity index 81%
rename from x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/vertex.js
rename to x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/vertex.test.js
index a8761a4c868b1..d54ec354b434b 100644
--- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/vertex.js
+++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/vertex.test.js
@@ -5,7 +5,7 @@
*/
import expect from '@kbn/expect';
-import { Graph } from '../';
+import { Graph } from './';
describe('Vertex', () => {
let graph;
@@ -205,40 +205,39 @@ describe('Vertex', () => {
* \---- v
* F5
*/
- const complexGraphJson = {
- vertices: [
- { id: 'I1', type: 'plugin', explicit_id: false },
- { id: 'my-queue', type: 'queue', config_name: 'some-name' },
- { id: 'IF1', type: 'if', config_name: 'some-name' },
- {
- id: 'IF2',
- type: 'plugin',
- meta: { source_text: 'foobar', source_line: 33, source_column: 4 },
- },
- { id: 'F1', type: 'plugin', explicit_id: true },
- { id: 'F2', type: 'plugin', explicit_id: true },
- { id: 'F3', type: 'plugin', explicit_id: true },
- { id: 'F4', type: 'plugin', explicit_id: true },
- { id: 'F5', type: 'plugin', explicit_id: true },
- ],
- edges: [
- { id: '1', type: 'plain', from: 'I1', to: 'my-queue' },
- { id: '2', type: 'plain', from: 'my-queue', to: 'IF1' },
- { id: '3', type: 'boolean', when: true, from: 'IF1', to: 'IF2' },
- { id: '4', type: 'boolean', when: false, from: 'IF1', to: 'F1' },
- { id: '5', type: 'boolean', when: true, from: 'IF2', to: 'F2' },
- { id: '6', type: 'boolean', when: false, from: 'IF2', to: 'F3' },
- { id: '7', type: 'plain', from: 'F2', to: 'F5' },
- { id: '8', type: 'plain', from: 'F3', to: 'F5' },
- { id: '9', type: 'plain', from: 'F1', to: 'F4' },
- { id: '10', type: 'plain', from: 'F4', to: 'F5' },
- ],
- };
-
- beforeEach(() => {
- graph = new Graph();
- graph.update(complexGraphJson);
- });
+ // const complexGraphJson = {
+ // vertices: [
+ // { id: 'I1', type: 'plugin', explicit_id: false },
+ // { id: 'my-queue', type: 'queue', config_name: 'some-name' },
+ // { id: 'IF1', type: 'if', config_name: 'some-name' },
+ // {
+ // id: 'IF2',
+ // type: 'plugin',
+ // meta: { source_text: 'foobar', source_line: 33, source_column: 4 },
+ // },
+ // { id: 'F1', type: 'plugin', explicit_id: true },
+ // { id: 'F2', type: 'plugin', explicit_id: true },
+ // { id: 'F3', type: 'plugin', explicit_id: true },
+ // { id: 'F4', type: 'plugin', explicit_id: true },
+ // { id: 'F5', type: 'plugin', explicit_id: true },
+ // ],
+ // edges: [
+ // { id: '1', type: 'plain', from: 'I1', to: 'my-queue' },
+ // { id: '2', type: 'plain', from: 'my-queue', to: 'IF1' },
+ // { id: '3', type: 'boolean', when: true, from: 'IF1', to: 'IF2' },
+ // { id: '4', type: 'boolean', when: false, from: 'IF1', to: 'F1' },
+ // { id: '5', type: 'boolean', when: true, from: 'IF2', to: 'F2' },
+ // { id: '6', type: 'boolean', when: false, from: 'IF2', to: 'F3' },
+ // { id: '7', type: 'plain', from: 'F2', to: 'F5' },
+ // { id: '8', type: 'plain', from: 'F3', to: 'F5' },
+ // { id: '9', type: 'plain', from: 'F1', to: 'F4' },
+ // { id: '10', type: 'plain', from: 'F4', to: 'F5' },
+ // ],
+ // };
+ // beforeEach(() => {
+ // graph = new Graph();
+ // graph.update(complexGraphJson);
+ // });
});
});
diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/vertex_factory.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/vertex_factory.test.js
similarity index 86%
rename from x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/vertex_factory.js
rename to x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/vertex_factory.test.js
index 8fbbd72baeb4b..8f8321846aae4 100644
--- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/__tests__/vertex_factory.js
+++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/graph/vertex_factory.test.js
@@ -5,10 +5,10 @@
*/
import expect from '@kbn/expect';
-import { vertexFactory } from '../vertex_factory';
-import { PluginVertex } from '../plugin_vertex';
-import { IfVertex } from '../if_vertex';
-import { QueueVertex } from '../queue_vertex';
+import { vertexFactory } from './vertex_factory';
+import { PluginVertex } from './plugin_vertex';
+import { IfVertex } from './if_vertex';
+import { QueueVertex } from './queue_vertex';
describe('vertexFactory', () => {
let graph;
diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/__tests__/pipeline_state.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/pipeline_state.test.js
similarity index 92%
rename from x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/__tests__/pipeline_state.js
rename to x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/pipeline_state.test.js
index b47f6b2fa3cee..f2aff4a0413da 100644
--- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/__tests__/pipeline_state.js
+++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/models/pipeline_state.test.js
@@ -6,8 +6,8 @@
import expect from '@kbn/expect';
import sinon from 'sinon';
-import { PipelineState } from '../pipeline_state';
-import { Config } from '../config';
+import { PipelineState } from './pipeline_state';
+import { Config } from './config';
describe('PipelineState class', () => {
let configJson;
diff --git a/x-pack/plugins/monitoring/public/directives/main/__tests__/monitoring_main_controller.js b/x-pack/plugins/monitoring/public/directives/main/monitoring_main_controller.test.js
similarity index 93%
rename from x-pack/plugins/monitoring/public/directives/main/__tests__/monitoring_main_controller.js
rename to x-pack/plugins/monitoring/public/directives/main/monitoring_main_controller.test.js
index e2fb2adccb4b9..a12d18425f923 100644
--- a/x-pack/plugins/monitoring/public/directives/main/__tests__/monitoring_main_controller.js
+++ b/x-pack/plugins/monitoring/public/directives/main/monitoring_main_controller.test.js
@@ -6,12 +6,42 @@
import { noop } from 'lodash';
import expect from '@kbn/expect';
-import { MonitoringMainController } from '../';
+import { Legacy } from '../../legacy_shims';
+import { MonitoringMainController } from './';
const getMockLicenseService = (options) => ({ mlIsSupported: () => options.mlIsSupported });
const getMockBreadcrumbsService = () => noop; // breadcrumb service has its own test
describe('Monitoring Main Directive Controller', () => {
+ const core = {
+ notifications: {},
+ application: {},
+ i18n: {},
+ chrome: {},
+ };
+ const data = {
+ query: {
+ timefilter: {
+ timefilter: {
+ isTimeRangeSelectorEnabled: () => true,
+ getTime: () => 1,
+ getRefreshInterval: () => 1,
+ },
+ },
+ },
+ };
+ const isCloud = false;
+ const triggersActionsUi = {};
+
+ beforeAll(() => {
+ Legacy.init({
+ core,
+ data,
+ isCloud,
+ triggersActionsUi,
+ });
+ });
+
/*
* Simulates calling the monitoringMain directive the way Cluster Listing
* does:
diff --git a/x-pack/plugins/monitoring/public/lib/__tests__/format_number.js b/x-pack/plugins/monitoring/public/lib/format_number.test.js
similarity index 96%
rename from x-pack/plugins/monitoring/public/lib/__tests__/format_number.js
rename to x-pack/plugins/monitoring/public/lib/format_number.test.js
index 199eb5298f0be..6f8d4a3d1587c 100644
--- a/x-pack/plugins/monitoring/public/lib/__tests__/format_number.js
+++ b/x-pack/plugins/monitoring/public/lib/format_number.test.js
@@ -5,7 +5,7 @@
*/
import expect from '@kbn/expect';
-import { formatNumber, formatBytesUsage } from '../format_number';
+import { formatNumber, formatBytesUsage } from './format_number';
describe('format_number', () => {
describe('formatBytesUsage', () => {
diff --git a/x-pack/plugins/monitoring/public/lib/logstash/__tests__/pipelines.js b/x-pack/plugins/monitoring/public/lib/logstash/pipelines.test.js
similarity index 93%
rename from x-pack/plugins/monitoring/public/lib/logstash/__tests__/pipelines.js
rename to x-pack/plugins/monitoring/public/lib/logstash/pipelines.test.js
index 3b0b2b629a4ed..183b15dcb8ea6 100644
--- a/x-pack/plugins/monitoring/public/lib/logstash/__tests__/pipelines.js
+++ b/x-pack/plugins/monitoring/public/lib/logstash/pipelines.test.js
@@ -5,7 +5,7 @@
*/
import expect from '@kbn/expect';
-import { isPipelineMonitoringSupportedInVersion } from '../pipelines';
+import { isPipelineMonitoringSupportedInVersion } from './pipelines';
describe('pipelines', () => {
describe('isPipelineMonitoringSupportedInVersion', () => {
diff --git a/x-pack/plugins/monitoring/public/services/__tests__/executor.js b/x-pack/plugins/monitoring/public/services/__tests__/executor.js
deleted file mode 100644
index 802378db2740b..0000000000000
--- a/x-pack/plugins/monitoring/public/services/__tests__/executor.js
+++ /dev/null
@@ -1,111 +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 ngMock from 'ng_mock';
-import expect from '@kbn/expect';
-import sinon from 'sinon';
-import { executorProvider } from '../executor';
-import Bluebird from 'bluebird';
-import { Legacy } from '../../legacy_shims';
-import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks';
-
-describe('$executor service', () => {
- let scope;
- let executor;
- let $timeout;
- let timefilter;
-
- beforeEach(() => {
- const data = dataPluginMock.createStartContract();
- Legacy._shims = { timefilter };
- timefilter = data.query.timefilter.timefilter;
- });
-
- beforeEach(ngMock.module('kibana'));
-
- beforeEach(
- ngMock.inject(function (_$rootScope_) {
- scope = _$rootScope_.$new();
- })
- );
-
- beforeEach(() => {
- $timeout = sinon.spy(setTimeout);
- $timeout.cancel = (id) => clearTimeout(id);
-
- timefilter.setRefreshInterval({
- value: 0,
- });
-
- executor = executorProvider(Bluebird, $timeout);
- });
-
- afterEach(() => executor.destroy());
-
- it('should not call $timeout if the timefilter is not paused and set to zero', () => {
- executor.start(scope);
- expect($timeout.callCount).to.equal(0);
- });
-
- it('should call $timeout if the timefilter is not paused and set to 1000ms', () => {
- timefilter.setRefreshInterval({
- pause: false,
- value: 1000,
- });
- executor.start(scope);
- expect($timeout.callCount).to.equal(1);
- });
-
- it('should execute function if timefilter is not paused and interval set to 1000ms', (done) => {
- timefilter.setRefreshInterval({
- pause: false,
- value: 1000,
- });
- executor.register({ execute: () => Bluebird.resolve().then(() => done(), done) });
- executor.start(scope);
- });
-
- it('should execute function multiple times', (done) => {
- let calls = 0;
- timefilter.setRefreshInterval({
- pause: false,
- value: 10,
- });
- executor.register({
- execute: () => {
- if (calls++ > 1) {
- done();
- }
- return Bluebird.resolve();
- },
- });
- executor.start(scope);
- });
-
- it('should call handleResponse', (done) => {
- timefilter.setRefreshInterval({
- pause: false,
- value: 10,
- });
- executor.register({
- execute: () => Bluebird.resolve(),
- handleResponse: () => done(),
- });
- executor.start(scope);
- });
-
- it('should call handleError', (done) => {
- timefilter.setRefreshInterval({
- pause: false,
- value: 10,
- });
- executor.register({
- execute: () => Bluebird.reject(new Error('reject test')),
- handleError: () => done(),
- });
- executor.start(scope);
- });
-});
diff --git a/x-pack/plugins/monitoring/public/services/__tests__/breadcrumbs.js b/x-pack/plugins/monitoring/public/services/breadcrumbs.test.js
similarity index 86%
rename from x-pack/plugins/monitoring/public/services/__tests__/breadcrumbs.js
rename to x-pack/plugins/monitoring/public/services/breadcrumbs.test.js
index d4493d4d39e58..25f8a9e0cd4b1 100644
--- a/x-pack/plugins/monitoring/public/services/__tests__/breadcrumbs.js
+++ b/x-pack/plugins/monitoring/public/services/breadcrumbs.test.js
@@ -5,10 +5,40 @@
*/
import expect from '@kbn/expect';
-import { breadcrumbsProvider } from '../breadcrumbs';
-import { MonitoringMainController } from '../../directives/main';
+import { breadcrumbsProvider } from './breadcrumbs';
+import { MonitoringMainController } from '../directives/main';
+import { Legacy } from '../legacy_shims';
describe('Monitoring Breadcrumbs Service', () => {
+ const core = {
+ notifications: {},
+ application: {},
+ i18n: {},
+ chrome: {},
+ };
+ const data = {
+ query: {
+ timefilter: {
+ timefilter: {
+ isTimeRangeSelectorEnabled: () => true,
+ getTime: () => 1,
+ getRefreshInterval: () => 1,
+ },
+ },
+ },
+ };
+ const isCloud = false;
+ const triggersActionsUi = {};
+
+ beforeAll(() => {
+ Legacy.init({
+ core,
+ data,
+ isCloud,
+ triggersActionsUi,
+ });
+ });
+
it('in Cluster Alerts', () => {
const controller = new MonitoringMainController();
controller.setup({
diff --git a/x-pack/plugins/monitoring/public/views/__tests__/base_controller.js b/x-pack/plugins/monitoring/public/views/__tests__/base_controller.js
deleted file mode 100644
index efb66f3e251ac..0000000000000
--- a/x-pack/plugins/monitoring/public/views/__tests__/base_controller.js
+++ /dev/null
@@ -1,209 +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 { spy, stub } from 'sinon';
-import expect from '@kbn/expect';
-import { MonitoringViewBaseController } from '../';
-import { Legacy } from '../../legacy_shims';
-import { PromiseWithCancel, Status } from '../../../common/cancel_promise';
-import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks';
-
-/*
- * Mostly copied from base_table_controller test, with modifications
- */
-describe('MonitoringViewBaseController', function () {
- let ctrl;
- let $injector;
- let $scope;
- let opts;
- let titleService;
- let executorService;
- let configService;
- let timefilter;
- const httpCall = (ms) => new Promise((resolve) => setTimeout(() => resolve(), ms));
-
- before(() => {
- const data = dataPluginMock.createStartContract();
- Legacy._shims = { timefilter };
- timefilter = data.query.timefilter.timefilter;
- titleService = spy();
- executorService = {
- register: spy(),
- start: spy(),
- cancel: spy(),
- run: spy(),
- };
- configService = {
- get: spy(),
- };
-
- const windowMock = () => {
- const events = {};
- const targetEvent = 'popstate';
- return {
- removeEventListener: stub(),
- addEventListener: (name, handler) => name === targetEvent && (events[name] = handler),
- history: {
- back: () => events[targetEvent] && events[targetEvent](),
- },
- };
- };
-
- const injectorGetStub = stub();
- injectorGetStub.withArgs('title').returns(titleService);
- injectorGetStub.withArgs('$executor').returns(executorService);
- injectorGetStub
- .withArgs('localStorage')
- .throws('localStorage should not be used by this class');
- injectorGetStub.withArgs('$window').returns(windowMock());
- injectorGetStub.withArgs('config').returns(configService);
- $injector = { get: injectorGetStub };
-
- $scope = {
- cluster: { cluster_uuid: 'foo' },
- $on: stub(),
- $apply: stub(),
- };
-
- opts = {
- title: 'testo',
- getPageData: () => Promise.resolve({ data: { test: true } }),
- $injector,
- $scope,
- };
-
- ctrl = new MonitoringViewBaseController(opts);
- });
-
- it('show/hide zoom-out button based on interaction', (done) => {
- const xaxis = { from: 1562089923880, to: 1562090159676 };
- const timeRange = { xaxis };
- const { zoomInfo } = ctrl;
-
- ctrl.onBrush(timeRange);
-
- expect(zoomInfo.showZoomOutBtn()).to.be(true);
-
- /*
- Need to do this async, since we are delaying event adding
- */
- setTimeout(() => {
- zoomInfo.zoomOutHandler();
- expect(zoomInfo.showZoomOutBtn()).to.be(false);
- done();
- }, 15);
- });
-
- it('creates functions for fetching data', () => {
- expect(ctrl.updateData).to.be.a('function');
- expect(ctrl.onBrush).to.be.a('function');
- });
-
- it('sets page title', () => {
- expect(titleService.calledOnce).to.be(true);
- const { args } = titleService.getCall(0);
- expect(args).to.eql([{ cluster_uuid: 'foo' }, 'testo']);
- });
-
- it('starts data poller', () => {
- expect(executorService.register.calledOnce).to.be(true);
- expect(executorService.start.calledOnce).to.be(true);
- });
-
- it('does not allow for a new request if one is inflight', (done) => {
- let counter = 0;
- const opts = {
- title: 'testo',
- getPageData: (ms) => httpCall(ms),
- $injector,
- $scope,
- };
-
- const ctrl = new MonitoringViewBaseController(opts);
- ctrl.updateData(30).then(() => ++counter);
- ctrl.updateData(60).then(() => {
- expect(counter).to.be(0);
- done();
- });
- });
-
- describe('time filter', () => {
- it('enables timepicker and auto refresh #1', () => {
- expect(timefilter.isTimeRangeSelectorEnabled()).to.be(true);
- expect(timefilter.isAutoRefreshSelectorEnabled()).to.be(true);
- });
-
- it('enables timepicker and auto refresh #2', () => {
- opts = {
- ...opts,
- options: {},
- };
- ctrl = new MonitoringViewBaseController(opts);
-
- expect(timefilter.isTimeRangeSelectorEnabled()).to.be(true);
- expect(timefilter.isAutoRefreshSelectorEnabled()).to.be(true);
- });
-
- it('disables timepicker and enables auto refresh', () => {
- opts = {
- ...opts,
- options: { enableTimeFilter: false },
- };
- ctrl = new MonitoringViewBaseController(opts);
-
- expect(timefilter.isTimeRangeSelectorEnabled()).to.be(false);
- expect(timefilter.isAutoRefreshSelectorEnabled()).to.be(true);
- });
-
- it('enables timepicker and disables auto refresh', () => {
- opts = {
- ...opts,
- options: { enableAutoRefresh: false },
- };
- ctrl = new MonitoringViewBaseController(opts);
-
- expect(timefilter.isTimeRangeSelectorEnabled()).to.be(true);
- expect(timefilter.isAutoRefreshSelectorEnabled()).to.be(false);
- });
-
- it('disables timepicker and auto refresh', () => {
- opts = {
- ...opts,
- options: {
- enableTimeFilter: false,
- enableAutoRefresh: false,
- },
- };
- ctrl = new MonitoringViewBaseController(opts);
-
- expect(timefilter.isTimeRangeSelectorEnabled()).to.be(false);
- expect(timefilter.isAutoRefreshSelectorEnabled()).to.be(false);
- });
-
- it('disables timepicker and auto refresh', (done) => {
- opts = {
- title: 'test',
- getPageData: () => httpCall(60),
- $injector,
- $scope,
- };
-
- ctrl = new MonitoringViewBaseController({ ...opts });
- ctrl.updateDataPromise = new PromiseWithCancel(httpCall(50));
-
- let shouldBeFalse = false;
- ctrl.updateDataPromise.promise().then(() => (shouldBeFalse = true));
-
- const lastUpdateDataPromise = ctrl.updateDataPromise;
-
- ctrl.updateData().then(() => {
- expect(shouldBeFalse).to.be(false);
- expect(lastUpdateDataPromise.status()).to.be(Status.Canceled);
- done();
- });
- });
- });
-});
diff --git a/x-pack/plugins/monitoring/public/views/__tests__/base_table_controller.js b/x-pack/plugins/monitoring/public/views/__tests__/base_table_controller.js
deleted file mode 100644
index 8d66429349cf4..0000000000000
--- a/x-pack/plugins/monitoring/public/views/__tests__/base_table_controller.js
+++ /dev/null
@@ -1,92 +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 { spy, stub } from 'sinon';
-import expect from '@kbn/expect';
-import { MonitoringViewBaseTableController } from '../';
-
-describe('MonitoringViewBaseTableController', function () {
- let ctrl;
- let $injector;
- let $scope;
- let opts;
- let timefilter;
- let titleService;
- let executorService;
-
- before(() => {
- timefilter = {
- enableTimeRangeSelector: spy(),
- enableAutoRefreshSelector: spy(),
- isTimeRangeSelectorEnabled: () => true,
- isAutoRefreshSelectorEnabled: () => true,
- };
- titleService = spy();
- executorService = {
- register: spy(),
- start: spy(),
- };
-
- const injectorGetStub = stub();
- injectorGetStub.withArgs('title').returns(titleService);
- injectorGetStub.withArgs('timefilter').returns(timefilter);
- injectorGetStub.withArgs('$executor').returns(executorService);
- injectorGetStub.withArgs('localStorage').returns({
- get: stub().returns({
- testoStorageKey: {
- pageIndex: 9000,
- filterText: 'table-ctrl-testoStorageKey',
- sortKey: 'test.testoStorageKey',
- sortOrder: -1,
- },
- }),
- });
- $injector = { get: injectorGetStub };
-
- $scope = {
- cluster: { cluster_uuid: 'foo' },
- $on: stub(),
- };
-
- opts = {
- title: 'testoTitle',
- getPageData: () => Promise.resolve({ data: { test: true } }),
- storageKey: 'testoStorageKey',
- $injector,
- $scope,
- };
-
- ctrl = new MonitoringViewBaseTableController(opts);
- });
-
- it('initializes scope data from storage', () => {
- expect(ctrl.pageIndex).to.be(9000);
- expect(ctrl.filterText).to.be('table-ctrl-testoStorageKey');
- expect(ctrl.sortKey).to.be('test.testoStorageKey');
- expect(ctrl.sortOrder).to.be(-1);
- });
-
- it('creates functions for event handling and fetching data', () => {
- expect(ctrl.onNewState).to.be.a('function');
- expect(ctrl.updateData).to.be.a('function');
- });
-
- it('enables timepicker', () => {
- expect(timefilter.isTimeRangeSelectorEnabled()).to.be(true);
- expect(timefilter.isAutoRefreshSelectorEnabled()).to.be(true);
- });
-
- it('sets page title', () => {
- expect(titleService.calledOnce).to.be(true);
- const { args } = titleService.getCall(0);
- expect(args).to.eql([{ cluster_uuid: 'foo' }, 'testoTitle']);
- });
-
- it('starts data poller', () => {
- expect(executorService.register.calledOnce).to.be(true);
- expect(executorService.start.calledOnce).to.be(true);
- });
-});
From 8f7d213944b7112669bbfe11133e782440e1455f Mon Sep 17 00:00:00 2001
From: Dima Arnautov
Date: Wed, 19 Aug 2020 16:22:26 +0200
Subject: [PATCH 20/85] [ML] Inference models management (#74978)
* [ML] init tabs
* [ML] init inference API service in UI
* [ML] server-side routes
* [ML] basic table
* [ML] support deletion
* [ML] delete multiple models
* [ML] WIP expanded row
* [ML] fix types
* [ML] expanded row
* [ML] fix types
* [ML] fix i18n id
* [ML] change server-side permission check
* [ML] refactor types
* [ML] show success toast on model deletion, fix models counter
* [ML] update expanded row
* [ML] pipelines stats
* [ML] use refresh observable
* [ML] endpoint to fetch associated pipelines
* [ML] update the endpoint to fetch associated pipelines
* [ML] show pipelines definition in expanded row
* [ML] change stats layout
* [ML] fix headers
* [ML] change breadcrumb title
* [ML] fetch models config with pipelines
* [ML] change default size to 1000
* [ML] fix collections keys, fix double fetch on initial page load
* [ML] adjust models deletion text
* [ML] fix DFA jobs on the management page
* [ML] small tabs in expanded row
* [ML] fix headers text
* [ML] fix models fetching without pipelines get permissions
* [ML] stats rendering as a description list
* [ML] fix i18n id
* [ML] remove an extra copyright comment, add selectable messages
* [ML] update stats on refresh
---
.../ml/common/types/data_frame_analytics.ts | 70 +++
x-pack/plugins/ml/common/types/inference.ts | 81 +++
.../application/components/stats_bar/index.ts | 2 +-
.../components/stats_bar/stats_bar.tsx | 6 +-
.../data_frame_analytics/common/analytics.ts | 89 +--
.../data_frame_analytics/common/fields.ts | 2 +-
.../common/get_index_data.ts | 3 +-
.../data_frame_analytics/common/index.ts | 6 +-
.../pages/analytics_creation/page.tsx | 2 +-
.../analytics_list/analytics_list.tsx | 12 +-
.../analytics_navigation_bar.tsx | 60 +++
.../analytics_navigation_bar/index.ts | 7 +
.../models_management/delete_models_modal.tsx | 93 ++++
.../models_management/expanded_row.tsx | 370 +++++++++++++
.../components/models_management/index.ts | 13 +
.../models_management/models_list.tsx | 509 ++++++++++++++++++
.../hooks/use_create_analytics_form/state.ts | 9 +-
.../pages/analytics_management/page.tsx | 17 +-
.../routes/data_frame_analytics/index.ts | 1 +
.../data_frame_analytics/models_list.tsx | 40 ++
.../services/ml_api_service/inference.ts | 135 +++++
.../plugins/ml/server/client/error_wrapper.ts | 4 +-
.../{index.js => index.ts} | 1 +
.../data_frame_analytics/models_provider.ts | 42 ++
x-pack/plugins/ml/server/plugin.ts | 3 +
x-pack/plugins/ml/server/routes/inference.ts | 163 ++++++
.../server/routes/schemas/inference_schema.ts | 26 +
.../ml/data_frame_analytics_creation.ts | 8 +-
28 files changed, 1678 insertions(+), 96 deletions(-)
create mode 100644 x-pack/plugins/ml/common/types/inference.ts
create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_navigation_bar/analytics_navigation_bar.tsx
create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_navigation_bar/index.ts
create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx
create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx
create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/index.ts
create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx
create mode 100644 x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx
create mode 100644 x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts
rename x-pack/plugins/ml/server/models/data_frame_analytics/{index.js => index.ts} (86%)
create mode 100644 x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.ts
create mode 100644 x-pack/plugins/ml/server/routes/inference.ts
create mode 100644 x-pack/plugins/ml/server/routes/schemas/inference_schema.ts
diff --git a/x-pack/plugins/ml/common/types/data_frame_analytics.ts b/x-pack/plugins/ml/common/types/data_frame_analytics.ts
index 5ba7f9c191a7f..f0aac75047585 100644
--- a/x-pack/plugins/ml/common/types/data_frame_analytics.ts
+++ b/x-pack/plugins/ml/common/types/data_frame_analytics.ts
@@ -5,7 +5,77 @@
*/
import { CustomHttpResponseOptions, ResponseError } from 'kibana/server';
+
export interface DeleteDataFrameAnalyticsWithIndexStatus {
success: boolean;
error?: CustomHttpResponseOptions;
}
+
+export type IndexName = string;
+export type DataFrameAnalyticsId = string;
+
+export interface OutlierAnalysis {
+ [key: string]: {};
+
+ outlier_detection: {};
+}
+
+interface Regression {
+ dependent_variable: string;
+ training_percent?: number;
+ num_top_feature_importance_values?: number;
+ prediction_field_name?: string;
+}
+
+interface Classification {
+ dependent_variable: string;
+ training_percent?: number;
+ num_top_classes?: string;
+ num_top_feature_importance_values?: number;
+ prediction_field_name?: string;
+}
+
+export interface RegressionAnalysis {
+ [key: string]: Regression;
+
+ regression: Regression;
+}
+
+export interface ClassificationAnalysis {
+ [key: string]: Classification;
+
+ classification: Classification;
+}
+
+interface GenericAnalysis {
+ [key: string]: Record;
+}
+
+export type AnalysisConfig =
+ | OutlierAnalysis
+ | RegressionAnalysis
+ | ClassificationAnalysis
+ | GenericAnalysis;
+
+export interface DataFrameAnalyticsConfig {
+ id: DataFrameAnalyticsId;
+ description?: string;
+ dest: {
+ index: IndexName;
+ results_field: string;
+ };
+ source: {
+ index: IndexName | IndexName[];
+ query?: any;
+ };
+ analysis: AnalysisConfig;
+ analyzed_fields: {
+ includes: string[];
+ excludes: string[];
+ };
+ model_memory_limit: string;
+ max_num_threads?: number;
+ create_time: number;
+ version: string;
+ allow_lazy_start?: boolean;
+}
diff --git a/x-pack/plugins/ml/common/types/inference.ts b/x-pack/plugins/ml/common/types/inference.ts
new file mode 100644
index 0000000000000..c70ee264e6fc8
--- /dev/null
+++ b/x-pack/plugins/ml/common/types/inference.ts
@@ -0,0 +1,81 @@
+/*
+ * 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 { DataFrameAnalyticsConfig } from './data_frame_analytics';
+
+export interface IngestStats {
+ count: number;
+ time_in_millis: number;
+ current: number;
+ failed: number;
+}
+
+export interface TrainedModelStat {
+ model_id: string;
+ pipeline_count: number;
+ inference_stats?: {
+ failure_count: number;
+ inference_count: number;
+ cache_miss_count: number;
+ missing_all_fields_count: number;
+ timestamp: number;
+ };
+ ingest?: {
+ total: IngestStats;
+ pipelines: Record<
+ string,
+ IngestStats & {
+ processors: Array<
+ Record<
+ string,
+ {
+ // TODO use type from ingest_pipelines plugin
+ type: string;
+ stats: IngestStats;
+ }
+ >
+ >;
+ }
+ >;
+ };
+}
+
+export interface ModelConfigResponse {
+ created_by: string;
+ create_time: string;
+ default_field_map: Record;
+ estimated_heap_memory_usage_bytes: number;
+ estimated_operations: number;
+ license_level: string;
+ metadata?:
+ | {
+ analytics_config: DataFrameAnalyticsConfig;
+ input: any;
+ }
+ | Record;
+ model_id: string;
+ tags: string;
+ version: string;
+ inference_config?: Record;
+ pipelines?: Record | null;
+}
+
+export interface PipelineDefinition {
+ processors?: Array>;
+ description?: string;
+}
+
+export interface ModelPipelines {
+ model_id: string;
+ pipelines: Record;
+}
+
+/**
+ * Get inference response from the ES endpoint
+ */
+export interface InferenceConfigResponse {
+ trained_model_configs: ModelConfigResponse[];
+}
diff --git a/x-pack/plugins/ml/public/application/components/stats_bar/index.ts b/x-pack/plugins/ml/public/application/components/stats_bar/index.ts
index 597975d0b150b..c8023b13cf74e 100644
--- a/x-pack/plugins/ml/public/application/components/stats_bar/index.ts
+++ b/x-pack/plugins/ml/public/application/components/stats_bar/index.ts
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { StatsBar, AnalyticStatsBarStats, JobStatsBarStats } from './stats_bar';
+export { StatsBar, AnalyticStatsBarStats, JobStatsBarStats, ModelsBarStats } from './stats_bar';
diff --git a/x-pack/plugins/ml/public/application/components/stats_bar/stats_bar.tsx b/x-pack/plugins/ml/public/application/components/stats_bar/stats_bar.tsx
index 0bd33a8c99f49..c4d64e48151b3 100644
--- a/x-pack/plugins/ml/public/application/components/stats_bar/stats_bar.tsx
+++ b/x-pack/plugins/ml/public/application/components/stats_bar/stats_bar.tsx
@@ -23,7 +23,11 @@ export interface AnalyticStatsBarStats extends Stats {
stopped: StatsBarStat;
}
-export type StatsBarStats = JobStatsBarStats | AnalyticStatsBarStats;
+export interface ModelsBarStats {
+ total: StatsBarStat;
+}
+
+export type StatsBarStats = JobStatsBarStats | AnalyticStatsBarStats | ModelsBarStats;
type StatsKey = keyof StatsBarStats;
interface StatsBarProps {
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts
index 561e8642772f0..8ad861e616b7a 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts
@@ -5,18 +5,21 @@
*/
import { useEffect } from 'react';
-import { BehaviorSubject } from 'rxjs';
-import { filter, distinctUntilChanged } from 'rxjs/operators';
-import { Subscription } from 'rxjs';
+import { BehaviorSubject, Subscription } from 'rxjs';
+import { distinctUntilChanged, filter } from 'rxjs/operators';
import { cloneDeep } from 'lodash';
import { ml } from '../../services/ml_api_service';
import { Dictionary } from '../../../../common/types/common';
import { getErrorMessage } from '../../../../common/util/errors';
import { SavedSearchQuery } from '../../contexts/ml';
+import {
+ AnalysisConfig,
+ ClassificationAnalysis,
+ OutlierAnalysis,
+ RegressionAnalysis,
+} from '../../../../common/types/data_frame_analytics';
-export type IndexName = string;
export type IndexPattern = string;
-export type DataFrameAnalyticsId = string;
export enum ANALYSIS_CONFIG_TYPE {
OUTLIER_DETECTION = 'outlier_detection',
@@ -46,34 +49,6 @@ export enum OUTLIER_ANALYSIS_METHOD {
DISTANCE_KNN = 'distance_knn',
}
-interface OutlierAnalysis {
- [key: string]: {};
- outlier_detection: {};
-}
-
-interface Regression {
- dependent_variable: string;
- training_percent?: number;
- num_top_feature_importance_values?: number;
- prediction_field_name?: string;
-}
-export interface RegressionAnalysis {
- [key: string]: Regression;
- regression: Regression;
-}
-
-interface Classification {
- dependent_variable: string;
- training_percent?: number;
- num_top_classes?: string;
- num_top_feature_importance_values?: number;
- prediction_field_name?: string;
-}
-export interface ClassificationAnalysis {
- [key: string]: Classification;
- classification: Classification;
-}
-
export interface LoadExploreDataArg {
filterByIsTraining?: boolean;
searchQuery: SavedSearchQuery;
@@ -165,22 +140,12 @@ export interface ClassificationEvaluateResponse {
};
}
-interface GenericAnalysis {
- [key: string]: Record;
-}
-
interface LoadEvaluateResult {
success: boolean;
eval: RegressionEvaluateResponse | ClassificationEvaluateResponse | null;
error: string | null;
}
-type AnalysisConfig =
- | OutlierAnalysis
- | RegressionAnalysis
- | ClassificationAnalysis
- | GenericAnalysis;
-
export const getAnalysisType = (analysis: AnalysisConfig): string => {
const keys = Object.keys(analysis);
@@ -342,29 +307,6 @@ export interface UpdateDataFrameAnalyticsConfig {
max_num_threads?: number;
}
-export interface DataFrameAnalyticsConfig {
- id: DataFrameAnalyticsId;
- description?: string;
- dest: {
- index: IndexName;
- results_field: string;
- };
- source: {
- index: IndexName | IndexName[];
- query?: any;
- };
- analysis: AnalysisConfig;
- analyzed_fields: {
- includes: string[];
- excludes: string[];
- };
- model_memory_limit: string;
- max_num_threads?: number;
- create_time: number;
- version: string;
- allow_lazy_start?: boolean;
-}
-
export enum REFRESH_ANALYTICS_LIST_STATE {
ERROR = 'error',
IDLE = 'idle',
@@ -379,7 +321,8 @@ export const useRefreshAnalyticsList = (
callback: {
isLoading?(d: boolean): void;
onRefresh?(): void;
- } = {}
+ } = {},
+ isManagementTable = false
) => {
useEffect(() => {
const distinct$ = refreshAnalyticsList$.pipe(distinctUntilChanged());
@@ -387,13 +330,17 @@ export const useRefreshAnalyticsList = (
const subscriptions: Subscription[] = [];
if (typeof callback.onRefresh === 'function') {
- // initial call to refresh
- callback.onRefresh();
+ // required in order to fetch the DFA jobs on the management page
+ if (isManagementTable) callback.onRefresh();
subscriptions.push(
distinct$
.pipe(filter((state) => state === REFRESH_ANALYTICS_LIST_STATE.REFRESH))
- .subscribe(() => typeof callback.onRefresh === 'function' && callback.onRefresh())
+ .subscribe(() => {
+ if (typeof callback.onRefresh === 'function') {
+ callback.onRefresh();
+ }
+ })
);
}
@@ -410,7 +357,7 @@ export const useRefreshAnalyticsList = (
return () => {
subscriptions.map((sub) => sub.unsubscribe());
};
- }, []);
+ }, [callback.onRefresh]);
return {
refresh: () => {
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts
index 1b99aac812fcd..847aefefbc6c8 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts
@@ -13,13 +13,13 @@ import {
isClassificationAnalysis,
isOutlierAnalysis,
isRegressionAnalysis,
- DataFrameAnalyticsConfig,
} from './analytics';
import { Field } from '../../../../common/types/fields';
import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '../../../../../../../src/plugins/data/public';
import { newJobCapsService } from '../../services/new_job_capabilities_service';
import { FEATURE_IMPORTANCE, FEATURE_INFLUENCE, OUTLIER_SCORE, TOP_CLASSES } from './constants';
+import { DataFrameAnalyticsConfig } from '../../../../common/types/data_frame_analytics';
export type EsId = string;
export type EsDocSource = Record;
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts
index eb38a23d10eef..c162cb2754c10 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts
@@ -12,7 +12,8 @@ import { ml } from '../../services/ml_api_service';
import { isKeywordAndTextType } from '../common/fields';
import { SavedSearchQuery } from '../../contexts/ml';
-import { DataFrameAnalyticsConfig, INDEX_STATUS } from './analytics';
+import { INDEX_STATUS } from './analytics';
+import { DataFrameAnalyticsConfig } from '../../../../common/types/data_frame_analytics';
export const getIndexData = async (
jobConfig: DataFrameAnalyticsConfig | undefined,
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts
index 65531009e4436..00d735d9a866e 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts
@@ -11,10 +11,7 @@ export {
isOutlierAnalysis,
refreshAnalyticsList$,
useRefreshAnalyticsList,
- DataFrameAnalyticsId,
- DataFrameAnalyticsConfig,
UpdateDataFrameAnalyticsConfig,
- IndexName,
IndexPattern,
REFRESH_ANALYTICS_LIST_STATE,
ANALYSIS_CONFIG_TYPE,
@@ -45,3 +42,6 @@ export { getIndexData } from './get_index_data';
export { getIndexFields } from './get_index_fields';
export { useResultsViewConfig } from './use_results_view_config';
+export { DataFrameAnalyticsConfig } from '../../../../common/types/data_frame_analytics';
+export { DataFrameAnalyticsId } from '../../../../common/types/data_frame_analytics';
+export { IndexName } from '../../../../common/types/data_frame_analytics';
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx
index 2f0e2ed3428c0..da5caf8e3875a 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx
@@ -23,10 +23,10 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { useMlContext } from '../../../contexts/ml';
import { newJobCapsService } from '../../../services/new_job_capabilities_service';
import { ml } from '../../../services/ml_api_service';
-import { DataFrameAnalyticsId } from '../../common/analytics';
import { useCreateAnalyticsForm } from '../analytics_management/hooks/use_create_analytics_form';
import { CreateAnalyticsAdvancedEditor } from './components/create_analytics_advanced_editor';
import { AdvancedStep, ConfigurationStep, CreateStep, DetailsStep } from './components';
+import { DataFrameAnalyticsId } from '../../../../../common/types/data_frame_analytics';
export enum ANALYTICS_STEPS {
CONFIGURATION,
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx
index 0652ec5f8acb1..81494a43193dc 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx
@@ -120,10 +120,13 @@ export const DataFrameAnalyticsList: FC = ({
}, [selectedIdFromUrlInitialized, analytics]);
// Subscribe to the refresh observable to trigger reloading the analytics list.
- useRefreshAnalyticsList({
- isLoading: setIsLoading,
- onRefresh: () => getAnalytics(true),
- });
+ useRefreshAnalyticsList(
+ {
+ isLoading: setIsLoading,
+ onRefresh: () => getAnalytics(true),
+ },
+ isManagementTable
+ );
const { columns, modals } = useColumns(
expandedRowItemIds,
@@ -271,6 +274,7 @@ export const DataFrameAnalyticsList: FC = ({
return (
<>
{modals}
+
{analyticsStats && (
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_navigation_bar/analytics_navigation_bar.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_navigation_bar/analytics_navigation_bar.tsx
new file mode 100644
index 0000000000000..bd59749517052
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_navigation_bar/analytics_navigation_bar.tsx
@@ -0,0 +1,60 @@
+/*
+ * 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, { FC, useCallback, useMemo } from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiTab, EuiTabs } from '@elastic/eui';
+import { useNavigateToPath } from '../../../../../contexts/kibana';
+
+interface Tab {
+ id: string;
+ name: string;
+ path: string;
+}
+
+export const AnalyticsNavigationBar: FC<{ selectedTabId?: string }> = ({ selectedTabId }) => {
+ const navigateToPath = useNavigateToPath();
+
+ const tabs = useMemo(
+ () => [
+ {
+ id: 'data_frame_analytics',
+ name: i18n.translate('xpack.ml.dataframe.jobsTabLabel', {
+ defaultMessage: 'Jobs',
+ }),
+ path: '/data_frame_analytics',
+ },
+ {
+ id: 'models',
+ name: i18n.translate('xpack.ml.dataframe.modelsTabLabel', {
+ defaultMessage: 'Models',
+ }),
+ path: '/data_frame_analytics/models',
+ },
+ ],
+ []
+ );
+
+ const onTabClick = useCallback(async (tab: Tab) => {
+ await navigateToPath(tab.path);
+ }, []);
+
+ return (
+
+ {tabs.map((tab) => {
+ return (
+
+ {tab.name}
+
+ );
+ })}
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_navigation_bar/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_navigation_bar/index.ts
new file mode 100644
index 0000000000000..594033f3f8d2c
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_navigation_bar/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export * from './analytics_navigation_bar';
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx
new file mode 100644
index 0000000000000..83010c684473e
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx
@@ -0,0 +1,93 @@
+/*
+ * 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, { FC } from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
+import {
+ EuiOverlayMask,
+ EuiModal,
+ EuiModalHeader,
+ EuiModalHeaderTitle,
+ EuiModalBody,
+ EuiModalFooter,
+ EuiButtonEmpty,
+ EuiButton,
+ EuiSpacer,
+ EuiCallOut,
+} from '@elastic/eui';
+import { ModelItemFull } from './models_list';
+
+interface DeleteModelsModalProps {
+ models: ModelItemFull[];
+ onClose: (deletionApproved?: boolean) => void;
+}
+
+export const DeleteModelsModal: FC = ({ models, onClose }) => {
+ const modelsWithPipelines = models
+ .filter((model) => !!model.pipelines)
+ .map((model) => model.model_id);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {modelsWithPipelines.length > 0 && (
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx
new file mode 100644
index 0000000000000..7b9329fee783b
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/expanded_row.tsx
@@ -0,0 +1,370 @@
+/*
+ * 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, { FC, Fragment } from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
+import {
+ EuiDescriptionList,
+ EuiPanel,
+ EuiSpacer,
+ EuiTabbedContent,
+ EuiTitle,
+ EuiNotificationBadge,
+ EuiFlexGrid,
+ EuiFlexItem,
+ EuiCodeBlock,
+ EuiText,
+ EuiHorizontalRule,
+ EuiFlexGroup,
+ EuiTextColor,
+} from '@elastic/eui';
+// @ts-ignore
+import { formatDate } from '@elastic/eui/lib/services/format';
+import { ModelItemFull } from './models_list';
+import { TIME_FORMAT } from '../../../../../../../common/constants/time_format';
+
+interface ExpandedRowProps {
+ item: ModelItemFull;
+}
+
+export const ExpandedRow: FC = ({ item }) => {
+ const {
+ inference_config: inferenceConfig,
+ stats,
+ metadata,
+ tags,
+ version,
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ estimated_operations,
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ estimated_heap_memory_usage_bytes,
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ default_field_map,
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ license_level,
+ pipelines,
+ } = item;
+
+ const details = {
+ tags,
+ version,
+ estimated_operations,
+ estimated_heap_memory_usage_bytes,
+ default_field_map,
+ license_level,
+ };
+
+ function formatToListItems(items: Record) {
+ return Object.entries(items)
+ .map(([title, value]) => {
+ if (title.includes('timestamp')) {
+ value = formatDate(value, TIME_FORMAT);
+ }
+ return { title, description: typeof value === 'object' ? JSON.stringify(value) : value };
+ })
+ .filter(({ description }) => {
+ return description !== undefined;
+ });
+ }
+
+ const tabs = [
+ {
+ id: 'details',
+ name: (
+
+ ),
+ content: (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ ),
+ },
+ ...(inferenceConfig
+ ? [
+ {
+ id: 'config',
+ name: (
+
+ ),
+ content: (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {metadata?.analytics_config && (
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ >
+ ),
+ },
+ ]
+ : []),
+ {
+ id: 'stats',
+ name: (
+
+ ),
+ content: (
+ <>
+
+
+ {stats.inference_stats && (
+
+
+
+
+
+
+
+
+
+
+
+ )}
+ {stats.ingest?.total && (
+
+
+
+
+
+
+
+
+
+
+ {stats.ingest?.pipelines && (
+ <>
+
+
+
+
+
+
+
+ {Object.entries(stats.ingest.pipelines).map(
+ ([pipelineName, { processors, ...pipelineStats }], i) => {
+ return (
+
+
+
+
+
+
+ {i + 1}. {pipelineName}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <>
+ {processors.map((processor) => {
+ const name = Object.keys(processor)[0];
+ const { stats: processorStats } = processor[name];
+ return (
+
+
+
+
+
+ {name}
+
+
+
+
+
+
+
+
+
+
+ );
+ })}
+ >
+
+ );
+ }
+ )}
+ >
+ )}
+
+
+ )}
+
+ >
+ ),
+ },
+ ...(pipelines && Object.keys(pipelines).length > 0
+ ? [
+ {
+ id: 'pipelines',
+ name: (
+ <>
+ {' '}
+ {stats.pipeline_count}
+ >
+ ),
+ content: (
+ <>
+
+
+ {Object.entries(pipelines).map(([pipelineName, { processors, description }]) => {
+ return (
+
+
+
+ {pipelineName}
+
+ {description && {description} }
+
+
+
+
+
+
+
+ {JSON.stringify(processors, null, 2)}
+
+
+
+ );
+ })}
+
+ >
+ ),
+ },
+ ]
+ : []),
+ ];
+
+ return (
+ {}}
+ />
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/index.ts
new file mode 100644
index 0000000000000..7c70a25071640
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export * from './models_list';
+
+export enum ModelsTableToConfigMapping {
+ id = 'model_id',
+ createdAt = 'create_time',
+ type = 'type',
+}
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx
new file mode 100644
index 0000000000000..3104ec55c3a6d
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx
@@ -0,0 +1,509 @@
+/*
+ * 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, { FC, useState, useCallback, useMemo } from 'react';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import {
+ Direction,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiInMemoryTable,
+ EuiTitle,
+ EuiButton,
+ EuiSearchBarProps,
+ EuiSpacer,
+ EuiButtonIcon,
+ EuiBadge,
+} from '@elastic/eui';
+// @ts-ignore
+import { formatDate } from '@elastic/eui/lib/services/format';
+import { EuiBasicTableColumn } from '@elastic/eui/src/components/basic_table/basic_table';
+import { EuiTableSelectionType } from '@elastic/eui/src/components/basic_table/table_types';
+import { Action } from '@elastic/eui/src/components/basic_table/action_types';
+import { StatsBar, ModelsBarStats } from '../../../../../components/stats_bar';
+import { useInferenceApiService } from '../../../../../services/ml_api_service/inference';
+import { ModelsTableToConfigMapping } from './index';
+import { TIME_FORMAT } from '../../../../../../../common/constants/time_format';
+import { DeleteModelsModal } from './delete_models_modal';
+import { useMlKibana, useNotifications } from '../../../../../contexts/kibana';
+import { ExpandedRow } from './expanded_row';
+import { getResultsUrl } from '../analytics_list/common';
+import {
+ ModelConfigResponse,
+ ModelPipelines,
+ TrainedModelStat,
+} from '../../../../../../../common/types/inference';
+import {
+ REFRESH_ANALYTICS_LIST_STATE,
+ refreshAnalyticsList$,
+ useRefreshAnalyticsList,
+} from '../../../../common';
+
+type Stats = Omit;
+
+export type ModelItem = ModelConfigResponse & {
+ type?: string;
+ stats?: Stats;
+ pipelines?: ModelPipelines['pipelines'] | null;
+};
+
+export type ModelItemFull = Required;
+
+export const ModelsList: FC = () => {
+ const {
+ services: {
+ application: { navigateToUrl, capabilities },
+ },
+ } = useMlKibana();
+
+ const canDeleteDataFrameAnalytics = capabilities.ml.canDeleteDataFrameAnalytics as boolean;
+
+ const inferenceApiService = useInferenceApiService();
+ const { toasts } = useNotifications();
+
+ const [searchQueryText, setSearchQueryText] = useState('');
+
+ const [pageIndex, setPageIndex] = useState(0);
+ const [pageSize, setPageSize] = useState(10);
+ const [sortField, setSortField] = useState(ModelsTableToConfigMapping.id);
+ const [sortDirection, setSortDirection] = useState('asc');
+
+ const [isLoading, setIsLoading] = useState(false);
+ const [items, setItems] = useState([]);
+ const [selectedModels, setSelectedModels] = useState([]);
+
+ const [modelsToDelete, setModelsToDelete] = useState([]);
+
+ const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState>(
+ {}
+ );
+
+ /**
+ * Fetches inference trained models.
+ */
+ const fetchData = useCallback(async () => {
+ try {
+ const response = await inferenceApiService.getInferenceModel(undefined, {
+ with_pipelines: true,
+ size: 1000,
+ });
+
+ const newItems = [];
+ const expandedItemsToRefresh = [];
+
+ for (const model of response) {
+ const tableItem = {
+ ...model,
+ ...(typeof model.inference_config === 'object'
+ ? { type: Object.keys(model.inference_config)[0] }
+ : {}),
+ };
+ newItems.push(tableItem);
+
+ if (itemIdToExpandedRowMap[model.model_id]) {
+ expandedItemsToRefresh.push(tableItem);
+ }
+ }
+
+ setItems(newItems);
+
+ if (expandedItemsToRefresh.length > 0) {
+ await fetchModelsStats(expandedItemsToRefresh);
+
+ setItemIdToExpandedRowMap(
+ expandedItemsToRefresh.reduce((acc, item) => {
+ acc[item.model_id] = ;
+ return acc;
+ }, {} as Record)
+ );
+ }
+ } catch (error) {
+ toasts.addError(new Error(error.body?.message), {
+ title: i18n.translate('xpack.ml.inference.modelsList.fetchFailedErrorMessage', {
+ defaultMessage: 'Models fetch failed',
+ }),
+ });
+ }
+ setIsLoading(false);
+ refreshAnalyticsList$.next(REFRESH_ANALYTICS_LIST_STATE.IDLE);
+ }, [itemIdToExpandedRowMap]);
+
+ // Subscribe to the refresh observable to trigger reloading the model list.
+ useRefreshAnalyticsList({
+ isLoading: setIsLoading,
+ onRefresh: fetchData,
+ });
+
+ const modelsStats: ModelsBarStats = useMemo(() => {
+ return {
+ total: {
+ show: true,
+ value: items.length,
+ label: i18n.translate('xpack.ml.inference.modelsList.totalAmountLabel', {
+ defaultMessage: 'Total inference trained models',
+ }),
+ },
+ };
+ }, [items]);
+
+ /**
+ * Fetches models stats and update the original object
+ */
+ const fetchModelsStats = useCallback(async (models: ModelItem[]) => {
+ const modelIdsToFetch = models.map((model) => model.model_id);
+
+ try {
+ const {
+ trained_model_stats: modelsStatsResponse,
+ } = await inferenceApiService.getInferenceModelStats(modelIdsToFetch);
+
+ for (const { model_id: id, ...stats } of modelsStatsResponse) {
+ const model = models.find((m) => m.model_id === id);
+ model!.stats = stats;
+ }
+ return true;
+ } catch (error) {
+ toasts.addError(new Error(error.body.message), {
+ title: i18n.translate('xpack.ml.inference.modelsList.fetchModelStatsErrorMessage', {
+ defaultMessage: 'Fetch model stats failed',
+ }),
+ });
+ }
+ }, []);
+
+ /**
+ * Unique inference types from models
+ */
+ const inferenceTypesOptions = useMemo(() => {
+ const result = items.reduce((acc, item) => {
+ const type = item.inference_config && Object.keys(item.inference_config)[0];
+ if (type) {
+ acc.add(type);
+ }
+ return acc;
+ }, new Set());
+ return [...result].map((v) => ({
+ value: v,
+ name: v,
+ }));
+ }, [items]);
+
+ async function prepareModelsForDeletion(models: ModelItem[]) {
+ // Fetch model stats to check associated pipelines
+ if (await fetchModelsStats(models)) {
+ setModelsToDelete(models as ModelItemFull[]);
+ } else {
+ toasts.addDanger(
+ i18n.translate('xpack.ml.inference.modelsList.unableToDeleteModelsErrorMessage', {
+ defaultMessage: 'Unable to delete models',
+ })
+ );
+ }
+ }
+
+ /**
+ * Deletes the models marked for deletion.
+ */
+ async function deleteModels() {
+ const modelsToDeleteIds = modelsToDelete.map((model) => model.model_id);
+
+ try {
+ await Promise.all(
+ modelsToDeleteIds.map((modelId) => inferenceApiService.deleteInferenceModel(modelId))
+ );
+ setItems(
+ items.filter(
+ (model) => !modelsToDelete.some((toDelete) => toDelete.model_id === model.model_id)
+ )
+ );
+ toasts.addSuccess(
+ i18n.translate('xpack.ml.inference.modelsList.successfullyDeletedMessage', {
+ defaultMessage:
+ '{modelsCount, plural, one {Model {modelsToDeleteIds}} other {# models}} {modelsCount, plural, one {has} other {have}} been successfully deleted',
+ values: {
+ modelsCount: modelsToDeleteIds.length,
+ modelsToDeleteIds: modelsToDeleteIds.join(', '),
+ },
+ })
+ );
+ } catch (error) {
+ toasts.addError(new Error(error?.body?.message), {
+ title: i18n.translate('xpack.ml.inference.modelsList.fetchDeletionErrorMessage', {
+ defaultMessage: '{modelsCount, plural, one {Model} other {Models}} deletion failed',
+ values: {
+ modelsCount: modelsToDeleteIds.length,
+ },
+ }),
+ });
+ }
+ }
+
+ /**
+ * Table actions
+ */
+ const actions: Array> = [
+ {
+ name: i18n.translate('xpack.ml.inference.modelsList.viewTrainingDataActionLabel', {
+ defaultMessage: 'View training data',
+ }),
+ description: i18n.translate('xpack.ml.inference.modelsList.viewTrainingDataActionLabel', {
+ defaultMessage: 'View training data',
+ }),
+ icon: 'list',
+ type: 'icon',
+ available: (item) => item.metadata?.analytics_config?.id,
+ onClick: async (item) => {
+ await navigateToUrl(
+ getResultsUrl(
+ item.metadata?.analytics_config.id,
+ Object.keys(item.metadata?.analytics_config.analysis)[0]
+ )
+ );
+ },
+ isPrimary: true,
+ },
+ {
+ name: i18n.translate('xpack.ml.inference.modelsList.deleteModelActionLabel', {
+ defaultMessage: 'Delete model',
+ }),
+ description: i18n.translate('xpack.ml.inference.modelsList.deleteModelActionLabel', {
+ defaultMessage: 'Delete model',
+ }),
+ icon: 'trash',
+ type: 'icon',
+ color: 'danger',
+ isPrimary: false,
+ onClick: async (model) => {
+ await prepareModelsForDeletion([model]);
+ },
+ available: (item) => canDeleteDataFrameAnalytics,
+ enabled: (item) => {
+ // TODO check for permissions to delete ingest pipelines.
+ // ATM undefined means pipelines fetch failed server-side.
+ return !item.pipelines;
+ },
+ },
+ ];
+
+ const toggleDetails = async (item: ModelItem) => {
+ const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap };
+ if (itemIdToExpandedRowMapValues[item.model_id]) {
+ delete itemIdToExpandedRowMapValues[item.model_id];
+ } else {
+ await fetchModelsStats([item]);
+ itemIdToExpandedRowMapValues[item.model_id] = ;
+ }
+ setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues);
+ };
+
+ const columns: Array> = [
+ {
+ align: 'left',
+ width: '40px',
+ isExpander: true,
+ render: (item: ModelItem) => (
+
+ ),
+ },
+ {
+ field: ModelsTableToConfigMapping.id,
+ name: i18n.translate('xpack.ml.inference.modelsList.modelIdHeader', {
+ defaultMessage: 'ID',
+ }),
+ sortable: true,
+ truncateText: true,
+ },
+ {
+ field: ModelsTableToConfigMapping.type,
+ name: i18n.translate('xpack.ml.inference.modelsList.typeHeader', {
+ defaultMessage: 'Type',
+ }),
+ sortable: true,
+ align: 'left',
+ render: (type: string) => {type} ,
+ },
+ {
+ field: ModelsTableToConfigMapping.createdAt,
+ name: i18n.translate('xpack.ml.inference.modelsList.createdAtHeader', {
+ defaultMessage: 'Created at',
+ }),
+ dataType: 'date',
+ render: (date: string) => formatDate(date, TIME_FORMAT),
+ sortable: true,
+ },
+ {
+ name: i18n.translate('xpack.ml.inference.modelsList.actionsHeader', {
+ defaultMessage: 'Actions',
+ }),
+ actions,
+ },
+ ];
+
+ const pagination = {
+ initialPageIndex: pageIndex,
+ initialPageSize: pageSize,
+ totalItemCount: items.length,
+ pageSizeOptions: [10, 20, 50],
+ hidePerPageOptions: false,
+ };
+
+ const sorting = {
+ sort: {
+ field: sortField,
+ direction: sortDirection,
+ },
+ };
+ const search: EuiSearchBarProps = {
+ query: searchQueryText,
+ onChange: (searchChange) => {
+ if (searchChange.error !== null) {
+ return false;
+ }
+ setSearchQueryText(searchChange.queryText);
+ return true;
+ },
+ box: {
+ incremental: true,
+ },
+ ...(inferenceTypesOptions && inferenceTypesOptions.length > 0
+ ? {
+ filters: [
+ {
+ type: 'field_value_selection',
+ field: 'type',
+ name: i18n.translate('xpack.ml.dataframe.analyticsList.typeFilter', {
+ defaultMessage: 'Type',
+ }),
+ multiSelect: 'or',
+ options: inferenceTypesOptions,
+ },
+ ],
+ }
+ : {}),
+ ...(selectedModels.length > 0
+ ? {
+ toolsLeft: (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ),
+ }
+ : {}),
+ };
+
+ const onTableChange: EuiInMemoryTable['onTableChange'] = ({
+ page = { index: 0, size: 10 },
+ sort = { field: ModelsTableToConfigMapping.id, direction: 'asc' },
+ }) => {
+ const { index, size } = page;
+ setPageIndex(index);
+ setPageSize(size);
+
+ const { field, direction } = sort;
+ setSortField(field);
+ setSortDirection(direction);
+ };
+
+ const isSelectionAllowed = canDeleteDataFrameAnalytics;
+
+ const selection: EuiTableSelectionType | undefined = isSelectionAllowed
+ ? {
+ selectableMessage: (selectable, item) => {
+ return selectable
+ ? i18n.translate('xpack.ml.inference.modelsList.selectableMessage', {
+ defaultMessage: 'Select a model',
+ })
+ : i18n.translate('xpack.ml.inference.modelsList.disableSelectableMessage', {
+ defaultMessage: 'Model has associated pipelines',
+ });
+ },
+ selectable: (item) => !item.pipelines,
+ onSelectionChange: (selectedItems) => {
+ setSelectedModels(selectedItems);
+ },
+ }
+ : undefined;
+
+ return (
+ <>
+
+
+ {modelsStats && (
+
+
+
+ )}
+
+
+
+ ({
+ 'data-test-subj': `mlModelsTableRow row-${item.model_id}`,
+ })}
+ />
+
+ {modelsToDelete.length > 0 && (
+ {
+ if (deletionApproved) {
+ await deleteModels();
+ }
+ setModelsToDelete([]);
+ }}
+ models={modelsToDelete}
+ />
+ )}
+ >
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts
index f932e4d0db7d7..4926decaa7f9c 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts
@@ -8,13 +8,12 @@ import { DeepPartial, DeepReadonly } from '../../../../../../../common/types/com
import { checkPermission } from '../../../../../capabilities/check_capabilities';
import { mlNodesAvailable } from '../../../../../ml_nodes_check';
+import { ANALYSIS_CONFIG_TYPE, defaultSearchQuery } from '../../../../common/analytics';
+import { CloneDataFrameAnalyticsConfig } from '../../components/action_clone';
import {
- DataFrameAnalyticsId,
DataFrameAnalyticsConfig,
- ANALYSIS_CONFIG_TYPE,
- defaultSearchQuery,
-} from '../../../../common/analytics';
-import { CloneDataFrameAnalyticsConfig } from '../../components/action_clone';
+ DataFrameAnalyticsId,
+} from '../../../../../../../common/types/data_frame_analytics';
export enum DEFAULT_MODEL_MEMORY_LIMIT {
regression = '100mb',
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/page.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/page.tsx
index 1e83e6c7d0e03..7ffd477039e78 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/page.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/page.tsx
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { FC, Fragment, useState } from 'react';
+import React, { FC, Fragment, useMemo, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
@@ -21,6 +21,7 @@ import {
EuiTitle,
} from '@elastic/eui';
+import { useLocation } from 'react-router-dom';
import { NavigationMenu } from '../../../components/navigation_menu';
import { DatePickerWrapper } from '../../../components/navigation_menu/date_picker_wrapper';
import { DataFrameAnalyticsList } from './components/analytics_list';
@@ -28,12 +29,17 @@ import { useRefreshInterval } from './components/analytics_list/use_refresh_inte
import { RefreshAnalyticsListButton } from './components/refresh_analytics_list_button';
import { NodeAvailableWarning } from '../../../components/node_available_warning';
import { UpgradeWarning } from '../../../components/upgrade';
+import { AnalyticsNavigationBar } from './components/analytics_navigation_bar';
+import { ModelsList } from './components/models_management';
export const Page: FC = () => {
const [blockRefresh, setBlockRefresh] = useState(false);
useRefreshInterval(setBlockRefresh);
+ const location = useLocation();
+ const selectedTabId = useMemo(() => location.pathname.split('/').pop(), [location]);
+
return (
@@ -45,7 +51,7 @@ export const Page: FC = () => {
{
-
+
+
+ {selectedTabId === 'data_frame_analytics' && (
+
+ )}
+ {selectedTabId === 'models' && }
diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/index.ts b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/index.ts
index 9b6bcc25c8c7e..c75a8240d28fb 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/index.ts
+++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/index.ts
@@ -7,3 +7,4 @@
export * from './analytics_jobs_list';
export * from './analytics_job_exploration';
export * from './analytics_job_creation';
+export * from './models_list';
diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx
new file mode 100644
index 0000000000000..7bf7784d1b559
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx
@@ -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 React, { FC } from 'react';
+import { i18n } from '@kbn/i18n';
+
+import { NavigateToPath } from '../../../contexts/kibana';
+
+import { MlRoute, PageLoader, PageProps } from '../../router';
+import { useResolver } from '../../use_resolver';
+import { basicResolvers } from '../../resolvers';
+import { Page } from '../../../data_frame_analytics/pages/analytics_management';
+import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
+
+export const modelsListRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+ path: '/data_frame_analytics/models',
+ render: (props, deps) => ,
+ breadcrumbs: [
+ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath),
+ getBreadcrumbWithUrlForApp('DATA_FRAME_ANALYTICS_BREADCRUMB', navigateToPath),
+ {
+ text: i18n.translate('xpack.ml.dataFrameAnalyticsBreadcrumbs.modelsListLabel', {
+ defaultMessage: 'Model Management',
+ }),
+ href: '',
+ },
+ ],
+});
+
+const PageWrapper: FC = ({ location, deps }) => {
+ const { context } = useResolver('', undefined, deps.config, basicResolvers(deps));
+ return (
+
+
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts
new file mode 100644
index 0000000000000..0206037b680bb
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/services/ml_api_service/inference.ts
@@ -0,0 +1,135 @@
+/*
+ * 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 { useMemo } from 'react';
+import { HttpFetchQuery } from 'kibana/public';
+import { HttpService } from '../http_service';
+import { basePath } from './index';
+import { useMlKibana } from '../../contexts/kibana';
+import {
+ ModelConfigResponse,
+ ModelPipelines,
+ TrainedModelStat,
+} from '../../../../common/types/inference';
+
+export interface InferenceQueryParams {
+ decompress_definition?: boolean;
+ from?: number;
+ include_model_definition?: boolean;
+ size?: number;
+ tags?: string;
+ // Custom kibana endpoint query params
+ with_pipelines?: boolean;
+}
+
+export interface InferenceStatsQueryParams {
+ from?: number;
+ size?: number;
+}
+
+export interface IngestStats {
+ count: number;
+ time_in_millis: number;
+ current: number;
+ failed: number;
+}
+
+export interface InferenceStatsResponse {
+ count: number;
+ trained_model_stats: TrainedModelStat[];
+}
+
+/**
+ * Service with APIs calls to perform inference operations.
+ * @param httpService
+ */
+export function inferenceApiProvider(httpService: HttpService) {
+ const apiBasePath = basePath();
+
+ return {
+ /**
+ * Fetches configuration information for a trained inference model.
+ *
+ * @param modelId - Model ID, collection of Model IDs or Model ID pattern.
+ * Fetches all In case nothing is provided.
+ * @param params - Optional query params
+ */
+ getInferenceModel(modelId?: string | string[], params?: InferenceQueryParams) {
+ let model = modelId ?? '';
+ if (Array.isArray(modelId)) {
+ model = modelId.join(',');
+ }
+
+ return httpService.http({
+ path: `${apiBasePath}/inference${model && `/${model}`}`,
+ method: 'GET',
+ ...(params ? { query: params as HttpFetchQuery } : {}),
+ });
+ },
+
+ /**
+ * Fetches usage information for trained inference models.
+ *
+ * @param modelId - Model ID, collection of Model IDs or Model ID pattern.
+ * Fetches all In case nothing is provided.
+ * @param params - Optional query params
+ */
+ getInferenceModelStats(modelId?: string | string[], params?: InferenceStatsQueryParams) {
+ let model = modelId ?? '_all';
+ if (Array.isArray(modelId)) {
+ model = modelId.join(',');
+ }
+
+ return httpService.http({
+ path: `${apiBasePath}/inference/${model}/_stats`,
+ method: 'GET',
+ });
+ },
+
+ /**
+ * Fetches pipelines associated with provided models
+ *
+ * @param modelId - Model ID, collection of Model IDs.
+ */
+ getInferenceModelPipelines(modelId: string | string[]) {
+ let model = modelId;
+ if (Array.isArray(modelId)) {
+ model = modelId.join(',');
+ }
+
+ return httpService.http({
+ path: `${apiBasePath}/inference/${model}/pipelines`,
+ method: 'GET',
+ });
+ },
+
+ /**
+ * Deletes an existing trained inference model.
+ *
+ * @param modelId - Model ID
+ */
+ deleteInferenceModel(modelId: string) {
+ return httpService.http({
+ path: `${apiBasePath}/inference/${modelId}`,
+ method: 'DELETE',
+ });
+ },
+ };
+}
+
+type InferenceApiService = ReturnType;
+
+/**
+ * Hooks for accessing {@link InferenceApiService} in React components.
+ */
+export function useInferenceApiService(): InferenceApiService {
+ const {
+ services: {
+ mlServices: { httpService },
+ },
+ } = useMlKibana();
+ return useMemo(() => inferenceApiProvider(httpService), [httpService]);
+}
diff --git a/x-pack/plugins/ml/server/client/error_wrapper.ts b/x-pack/plugins/ml/server/client/error_wrapper.ts
index de53e4d4345a9..c635eb74e1f19 100644
--- a/x-pack/plugins/ml/server/client/error_wrapper.ts
+++ b/x-pack/plugins/ml/server/client/error_wrapper.ts
@@ -8,7 +8,9 @@ import { boomify, isBoom } from 'boom';
import { ResponseError, CustomHttpResponseOptions } from 'kibana/server';
export function wrapError(error: any): CustomHttpResponseOptions {
- const boom = isBoom(error) ? error : boomify(error, { statusCode: error.status });
+ const boom = isBoom(error)
+ ? error
+ : boomify(error, { statusCode: error.status ?? error.statusCode });
const statusCode = boom.output.statusCode;
return {
body: {
diff --git a/x-pack/plugins/ml/server/models/data_frame_analytics/index.js b/x-pack/plugins/ml/server/models/data_frame_analytics/index.ts
similarity index 86%
rename from x-pack/plugins/ml/server/models/data_frame_analytics/index.js
rename to x-pack/plugins/ml/server/models/data_frame_analytics/index.ts
index 1d6b05e4a5759..fc18436ff5216 100644
--- a/x-pack/plugins/ml/server/models/data_frame_analytics/index.js
+++ b/x-pack/plugins/ml/server/models/data_frame_analytics/index.ts
@@ -5,3 +5,4 @@
*/
export { analyticsAuditMessagesProvider } from './analytics_audit_messages';
+export { modelsProvider } from './models_provider';
diff --git a/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.ts b/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.ts
new file mode 100644
index 0000000000000..b2a4ccdab43dc
--- /dev/null
+++ b/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.ts
@@ -0,0 +1,42 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { IScopedClusterClient } from 'kibana/server';
+import { PipelineDefinition } from '../../../common/types/inference';
+
+export function modelsProvider(client: IScopedClusterClient) {
+ return {
+ /**
+ * Retrieves the map of model ids and associated pipelines.
+ * @param modelIds
+ */
+ async getModelsPipelines(modelIds: string[]) {
+ const modelIdsMap = new Map | null>(
+ modelIds.map((id: string) => [id, null])
+ );
+
+ const { body } = await client.asCurrentUser.ingest.getPipeline();
+
+ for (const [pipelineName, pipelineDefinition] of Object.entries(body)) {
+ const { processors } = pipelineDefinition as { processors: Array> };
+
+ for (const processor of processors) {
+ const id = processor.inference?.model_id;
+ if (modelIdsMap.has(id)) {
+ const obj = modelIdsMap.get(id);
+ if (obj === null) {
+ modelIdsMap.set(id, { [pipelineName]: pipelineDefinition });
+ } else {
+ obj![pipelineName] = pipelineDefinition;
+ }
+ }
+ }
+ }
+
+ return modelIdsMap;
+ },
+ };
+}
diff --git a/x-pack/plugins/ml/server/plugin.ts b/x-pack/plugins/ml/server/plugin.ts
index 3c3824a785032..f6d47639ef7c5 100644
--- a/x-pack/plugins/ml/server/plugin.ts
+++ b/x-pack/plugins/ml/server/plugin.ts
@@ -48,6 +48,7 @@ import { createSharedServices, SharedServices } from './shared_services';
import { getPluginPrivileges } from '../common/types/capabilities';
import { setupCapabilitiesSwitcher } from './lib/capabilities';
import { registerKibanaSettings } from './lib/register_settings';
+import { inferenceRoutes } from './routes/inference';
declare module 'kibana/server' {
interface RequestHandlerContext {
@@ -172,6 +173,8 @@ export class MlServerPlugin implements Plugin {
+ try {
+ const { modelId } = request.params;
+ const { with_pipelines: withPipelines, ...query } = request.query;
+ const { body } = await client.asInternalUser.ml.getTrainedModels({
+ size: 1000,
+ ...query,
+ ...(modelId ? { model_id: modelId } : {}),
+ });
+ const result = body.trained_model_configs;
+ try {
+ if (withPipelines) {
+ const pipelinesResponse = await modelsProvider(client).getModelsPipelines(
+ result.map(({ model_id: id }: { model_id: string }) => id)
+ );
+ for (const model of result) {
+ model.pipelines = pipelinesResponse.get(model.model_id)!;
+ }
+ }
+ } catch (e) {
+ // the user might not have required permissions to fetch pipelines
+ // eslint-disable-next-line no-console
+ console.log(e);
+ }
+
+ return response.ok({
+ body: result,
+ });
+ } catch (e) {
+ return response.customError(wrapError(e));
+ }
+ })
+ );
+
+ /**
+ * @apiGroup Inference
+ *
+ * @api {get} /api/ml/inference/:modelId/_stats Get stats of a trained inference model
+ * @apiName GetInferenceModelStats
+ * @apiDescription Retrieves usage information for trained inference models.
+ */
+ router.get(
+ {
+ path: '/api/ml/inference/{modelId}/_stats',
+ validate: {
+ params: modelIdSchema,
+ },
+ options: {
+ tags: ['access:ml:canGetDataFrameAnalytics'],
+ },
+ },
+ mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => {
+ try {
+ const { modelId } = request.params;
+ const { body } = await client.asInternalUser.ml.getTrainedModelsStats({
+ ...(modelId ? { model_id: modelId } : {}),
+ });
+ return response.ok({
+ body,
+ });
+ } catch (e) {
+ return response.customError(wrapError(e));
+ }
+ })
+ );
+
+ /**
+ * @apiGroup Inference
+ *
+ * @api {get} /api/ml/inference/:modelId/pipelines Get model pipelines
+ * @apiName GetModelPipelines
+ * @apiDescription Retrieves pipelines associated with a model
+ */
+ router.get(
+ {
+ path: '/api/ml/inference/{modelId}/pipelines',
+ validate: {
+ params: modelIdSchema,
+ },
+ options: {
+ tags: ['access:ml:canGetDataFrameAnalytics'],
+ },
+ },
+ mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => {
+ try {
+ const { modelId } = request.params;
+ const result = await modelsProvider(client).getModelsPipelines(modelId.split(','));
+ return response.ok({
+ body: [...result].map(([id, pipelines]) => ({ model_id: id, pipelines })),
+ });
+ } catch (e) {
+ return response.customError(wrapError(e));
+ }
+ })
+ );
+
+ /**
+ * @apiGroup Inference
+ *
+ * @api {delete} /api/ml/inference/:modelId Get stats of a trained inference model
+ * @apiName DeleteInferenceModel
+ * @apiDescription Deletes an existing trained inference model that is currently not referenced by an ingest pipeline.
+ */
+ router.delete(
+ {
+ path: '/api/ml/inference/{modelId}',
+ validate: {
+ params: modelIdSchema,
+ },
+ options: {
+ tags: ['access:ml:canDeleteDataFrameAnalytics'],
+ },
+ },
+ mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => {
+ try {
+ const { modelId } = request.params;
+ const { body } = await client.asInternalUser.ml.deleteTrainedModel({
+ model_id: modelId,
+ });
+ return response.ok({
+ body,
+ });
+ } catch (e) {
+ return response.customError(wrapError(e));
+ }
+ })
+ );
+}
diff --git a/x-pack/plugins/ml/server/routes/schemas/inference_schema.ts b/x-pack/plugins/ml/server/routes/schemas/inference_schema.ts
new file mode 100644
index 0000000000000..896449be7896a
--- /dev/null
+++ b/x-pack/plugins/ml/server/routes/schemas/inference_schema.ts
@@ -0,0 +1,26 @@
+/*
+ * 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 modelIdSchema = schema.object({
+ /**
+ * Model ID
+ */
+ modelId: schema.string(),
+});
+
+export const optionalModelIdSchema = schema.object({
+ /**
+ * Model ID
+ */
+ modelId: schema.maybe(schema.string()),
+});
+
+export const getInferenceQuerySchema = schema.object({
+ size: schema.maybe(schema.string()),
+ with_pipelines: schema.maybe(schema.string()),
+});
diff --git a/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts b/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts
index 474d6845ef3ec..cdd26b60d3be0 100644
--- a/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts
+++ b/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts
@@ -5,14 +5,14 @@
*/
import expect from '@kbn/expect';
import { DataFrameAnalyticsConfig } from '../../../../plugins/ml/public/application/data_frame_analytics/common';
-import {
- ClassificationAnalysis,
- RegressionAnalysis,
-} from '../../../../plugins/ml/public/application/data_frame_analytics/common/analytics';
import { FtrProviderContext } from '../../ftr_provider_context';
import { MlCommon } from './common';
import { MlApi } from './api';
+import {
+ ClassificationAnalysis,
+ RegressionAnalysis,
+} from '../../../../plugins/ml/common/types/data_frame_analytics';
enum ANALYSIS_CONFIG_TYPE {
OUTLIER_DETECTION = 'outlier_detection',
From e61a4b63a6e7a626dbb53f371dd9752a7e66c696 Mon Sep 17 00:00:00 2001
From: Walter Rafelsberger
Date: Wed, 19 Aug 2020 16:25:00 +0200
Subject: [PATCH 21/85] [ML] DF Analytics / Transforms: Fix job row actions
menu invalid DOM nesting warning (#74499)
Refactors the action buttons for transforms and analytics job list to no longer produce nested button elements and throw a React error.
---
.../annotations_table/annotations_table.js | 2 +-
.../components/anomalies_table/links_menu.js | 2 +-
.../anomaly_results_view_selector.tsx | 4 +-
.../view_results_panel/view_results_panel.tsx | 2 +-
...tton.test.ts => clone_action_name.test.ts} | 2 +-
...clone_button.tsx => clone_action_name.tsx} | 61 ++------
.../components/action_clone/index.ts | 5 +-
.../action_clone/use_clone_action.tsx | 41 +++++
...tton_modal.tsx => delete_action_modal.tsx} | 2 +-
...e.test.tsx => delete_action_name.test.tsx} | 35 +++--
...lete_button.tsx => delete_action_name.tsx} | 35 ++---
.../components/action_delete/index.ts | 4 +-
...delete_action.ts => use_delete_action.tsx} | 42 +++--
...tton_flyout.tsx => edit_action_flyout.tsx} | 2 +-
.../{edit_button.tsx => edit_action_name.tsx} | 35 ++---
.../components/action_edit/index.ts | 3 +-
...use_edit_action.ts => use_edit_action.tsx} | 22 ++-
.../components/action_start/index.ts | 3 +-
...utton_modal.tsx => start_action_modal.tsx} | 2 +-
...start_button.tsx => start_action_name.tsx} | 36 ++---
.../action_start/use_start_action.ts | 41 -----
.../action_start/use_start_action.tsx | 80 ++++++++++
.../components/action_stop/index.ts | 5 +-
...button_modal.tsx => stop_action_modal.tsx} | 8 +-
.../action_stop/stop_action_name.tsx | 38 +++++
.../components/action_stop/stop_button.tsx | 54 -------
.../action_stop/use_force_stop_action.ts | 38 -----
.../action_stop/use_stop_action.tsx | 67 ++++++++
.../action_view/get_view_action.tsx | 20 ---
.../components/action_view/index.ts | 2 +-
.../action_view/use_view_action.tsx | 45 ++++++
.../components/action_view/view_button.tsx | 42 ++---
.../components/analytics_list/common.ts | 10 +-
.../components/analytics_list/use_actions.tsx | 148 +++---------------
.../use_create_analytics_form/reducer.ts | 2 +-
.../analytics_service/delete_analytics.ts | 23 ++-
.../explorer_charts_container.js | 2 +-
.../components/job_actions/results.js | 4 +-
.../forecasts_table/forecasts_table.js | 2 +-
.../components/analytics_panel/actions.tsx | 54 +++++++
.../components/analytics_panel/table.tsx | 9 +-
.../anomaly_detection_panel/actions.tsx | 6 +-
.../services/toast_notification_service.ts | 4 +
.../forecasting_modal/forecasts_list.js | 2 +-
.../open_in_anomaly_explorer_action.tsx | 2 +-
.../transform/public/app/common/index.ts | 2 +-
.../public/app/common/transform_list.ts | 10 ++
.../action_clone/clone_action_name.tsx | 34 ++++
.../components/action_clone/clone_button.tsx | 62 --------
.../components/action_clone/index.ts | 2 +-
.../action_clone/use_clone_action.tsx | 43 +++++
.../delete_action_name.test.tsx.snap | 7 +
.../__snapshots__/delete_button.test.tsx.snap | 22 ---
...tton_modal.tsx => delete_action_modal.tsx} | 2 +-
...n.test.tsx => delete_action_name.test.tsx} | 18 +--
...lete_button.tsx => delete_action_name.tsx} | 66 ++++----
.../components/action_delete/index.ts | 4 +-
...delete_action.ts => use_delete_action.tsx} | 38 ++++-
.../{edit_button.tsx => edit_action_name.tsx} | 39 ++---
.../components/action_edit/index.ts | 1 -
.../components/action_edit/use_edit_action.ts | 26 ---
.../action_edit/use_edit_action.tsx | 45 ++++++
...x.snap => start_action_name.test.tsx.snap} | 13 +-
.../components/action_start/index.ts | 4 +-
...utton_modal.tsx => start_action_modal.tsx} | 7 +-
...on.test.tsx => start_action_name.test.tsx} | 9 +-
...start_button.tsx => start_action_name.tsx} | 60 +++----
.../action_start/use_start_action.ts | 42 -----
.../action_start/use_start_action.tsx | 66 ++++++++
...sx.snap => stop_action_name.test.tsx.snap} | 13 +-
.../components/action_stop/index.ts | 3 +-
...ton.test.tsx => stop_action_name.test.tsx} | 8 +-
.../{stop_button.tsx => stop_action_name.tsx} | 57 +++----
.../action_stop/use_stop_action.tsx | 45 ++++++
.../transform_list/transform_list.tsx | 38 +++--
.../transform_list/use_actions.test.tsx | 17 +-
.../components/transform_list/use_actions.tsx | 71 +++------
.../translations/translations/ja-JP.json | 11 --
.../translations/translations/zh-CN.json | 11 --
79 files changed, 1002 insertions(+), 942 deletions(-)
rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/{clone_button.test.ts => clone_action_name.test.ts} (99%)
rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/{clone_button.tsx => clone_action_name.tsx} (88%)
create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/use_clone_action.tsx
rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/{delete_button_modal.tsx => delete_action_modal.tsx} (98%)
rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/{action_delete.test.tsx => delete_action_name.test.tsx} (73%)
rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/{delete_button.tsx => delete_action_name.tsx} (62%)
rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/{use_delete_action.ts => use_delete_action.tsx} (77%)
rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/{edit_button_flyout.tsx => edit_action_flyout.tsx} (99%)
rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/{edit_button.tsx => edit_action_name.tsx} (50%)
rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/{use_edit_action.ts => use_edit_action.tsx} (56%)
rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/{start_button_modal.tsx => start_action_modal.tsx} (96%)
rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/{start_button.tsx => start_action_name.tsx} (65%)
delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/use_start_action.ts
create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/use_start_action.tsx
rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/{stop_button_modal.tsx => stop_action_modal.tsx} (91%)
create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/stop_action_name.tsx
delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/stop_button.tsx
delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/use_force_stop_action.ts
create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/use_stop_action.tsx
delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/get_view_action.tsx
create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/use_view_action.tsx
create mode 100644 x-pack/plugins/ml/public/application/overview/components/analytics_panel/actions.tsx
create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/clone_action_name.tsx
delete mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/clone_button.tsx
create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/use_clone_action.tsx
create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/__snapshots__/delete_action_name.test.tsx.snap
delete mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/__snapshots__/delete_button.test.tsx.snap
rename x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/{delete_button_modal.tsx => delete_action_modal.tsx} (99%)
rename x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/{delete_button.test.tsx => delete_action_name.test.tsx} (53%)
rename x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/{delete_button.tsx => delete_action_name.tsx} (51%)
rename x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/{use_delete_action.ts => use_delete_action.tsx} (66%)
rename x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/{edit_button.tsx => edit_action_name.tsx} (52%)
delete mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.ts
create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.tsx
rename x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/__snapshots__/{start_button.test.tsx.snap => start_action_name.test.tsx.snap} (56%)
rename x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/{start_button_modal.tsx => start_action_modal.tsx} (94%)
rename x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/{start_button.test.tsx => start_action_name.test.tsx} (77%)
rename x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/{start_button.tsx => start_action_name.tsx} (73%)
delete mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/use_start_action.ts
create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/use_start_action.tsx
rename x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/__snapshots__/{stop_button.test.tsx.snap => stop_action_name.test.tsx.snap} (56%)
rename x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/{stop_button.test.tsx => stop_action_name.test.tsx} (79%)
rename x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/{stop_button.tsx => stop_action_name.tsx} (67%)
create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/use_stop_action.tsx
diff --git a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js
index c6ca4fb821984..9dabfce163dbb 100644
--- a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js
+++ b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js
@@ -506,7 +506,7 @@ export class AnnotationsTable extends Component {
this.openSingleMetricView(annotation)}
disabled={!isDrillDownAvailable}
- iconType="stats"
+ iconType="visLine"
aria-label={openInSingleMetricViewerAriaLabelText}
/>
diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js
index 0e4d736a01e47..5a7d2a9c3ddaa 100644
--- a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js
+++ b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js
@@ -415,7 +415,7 @@ class LinksMenuUI extends Component {
items.push(
{
this.closePopover();
this.viewSeries();
diff --git a/x-pack/plugins/ml/public/application/components/anomaly_results_view_selector/anomaly_results_view_selector.tsx b/x-pack/plugins/ml/public/application/components/anomaly_results_view_selector/anomaly_results_view_selector.tsx
index b9eaf6c5d56ec..78acb422851e3 100644
--- a/x-pack/plugins/ml/public/application/components/anomaly_results_view_selector/anomaly_results_view_selector.tsx
+++ b/x-pack/plugins/ml/public/application/components/anomaly_results_view_selector/anomaly_results_view_selector.tsx
@@ -27,7 +27,7 @@ export const AnomalyResultsViewSelector: FC = ({ viewId }) => {
label: i18n.translate('xpack.ml.anomalyResultsViewSelector.singleMetricViewerLabel', {
defaultMessage: 'View results in the Single Metric Viewer',
}),
- iconType: 'stats',
+ iconType: 'visLine',
value: 'timeseriesexplorer',
'data-test-subj': 'mlAnomalyResultsViewSelectorSingleMetricViewer',
},
@@ -36,7 +36,7 @@ export const AnomalyResultsViewSelector: FC = ({ viewId }) => {
label: i18n.translate('xpack.ml.anomalyResultsViewSelector.anomalyExplorerLabel', {
defaultMessage: 'View results in the Anomaly Explorer',
}),
- iconType: 'tableOfContents',
+ iconType: 'visTable',
value: 'explorer',
'data-test-subj': 'mlAnomalyResultsViewSelectorExplorer',
},
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/view_results_panel/view_results_panel.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/view_results_panel/view_results_panel.tsx
index 13706eb548ec8..23a16ba84ef92 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/view_results_panel/view_results_panel.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/view_results_panel/view_results_panel.tsx
@@ -28,7 +28,7 @@ export const ViewResultsPanel: FC = ({ jobId, analysisType }) => {
}
+ icon={ }
title={i18n.translate('xpack.ml.dataframe.analytics.create.viewResultsCardTitle', {
defaultMessage: 'View Results',
})}
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.test.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_action_name.test.ts
similarity index 99%
rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.test.ts
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_action_name.test.ts
index 9db32e298691e..ee59b0c6c6388 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.test.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_action_name.test.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { isAdvancedConfig } from './clone_button';
+import { isAdvancedConfig } from './clone_action_name';
describe('Analytics job clone action', () => {
describe('isAdvancedConfig', () => {
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_action_name.tsx
similarity index 88%
rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.tsx
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_action_name.tsx
index d78e1bcc1a913..60c699ba0d370 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_action_name.tsx
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
+import { EuiToolTip } from '@elastic/eui';
import React, { FC } from 'react';
import { isEqual, cloneDeep } from 'lodash';
import { i18n } from '@kbn/i18n';
@@ -14,10 +14,7 @@ import { DataFrameAnalyticsConfig, isOutlierAnalysis } from '../../../../common'
import { isClassificationAnalysis, isRegressionAnalysis } from '../../../../common/analytics';
import { DEFAULT_RESULTS_FIELD } from '../../../../common/constants';
import { useMlKibana, useNavigateToPath } from '../../../../../contexts/kibana';
-import {
- CreateAnalyticsFormProps,
- DEFAULT_NUM_TOP_FEATURE_IMPORTANCE_VALUES,
-} from '../../hooks/use_create_analytics_form';
+import { DEFAULT_NUM_TOP_FEATURE_IMPORTANCE_VALUES } from '../../hooks/use_create_analytics_form';
import { State } from '../../hooks/use_create_analytics_form/state';
import { DataFrameAnalyticsListRow } from '../analytics_list/common';
import { extractErrorMessage } from '../../../../../../../common/util/errors';
@@ -328,25 +325,12 @@ export function extractCloningConfig({
}) as unknown) as CloneDataFrameAnalyticsConfig;
}
-const buttonText = i18n.translate('xpack.ml.dataframe.analyticsList.cloneJobButtonLabel', {
- defaultMessage: 'Clone job',
-});
-
-export function getCloneAction(createAnalyticsForm: CreateAnalyticsFormProps) {
- const { actions } = createAnalyticsForm;
-
- const onClick = async (item: DeepReadonly) => {
- await actions.setJobClone(item.config);
- };
-
- return {
- name: buttonText,
- description: buttonText,
- icon: 'copy',
- onClick,
- 'data-test-subj': 'mlAnalyticsJobCloneButton',
- };
-}
+export const cloneActionNameText = i18n.translate(
+ 'xpack.ml.dataframe.analyticsList.cloneActionNameText',
+ {
+ defaultMessage: 'Clone',
+ }
+);
export const useNavigateToWizardWithClonedJob = () => {
const {
@@ -405,32 +389,11 @@ export const useNavigateToWizardWithClonedJob = () => {
};
};
-interface CloneButtonProps {
+interface CloneActionNameProps {
isDisabled: boolean;
- onClick: () => void;
}
-/**
- * Temp component to have Clone job button with the same look as the other actions.
- * Replace with {@link getCloneAction} as soon as all the actions are refactored
- * to support EuiContext with a valid DOM structure without nested buttons.
- */
-export const CloneButton: FC = ({ isDisabled, onClick }) => {
- const button = (
-
- {buttonText}
-
- );
-
+export const CloneActionName: FC = ({ isDisabled }) => {
if (isDisabled) {
return (
= ({ isDisabled, onClick }) => {
defaultMessage: 'You do not have permission to clone analytics jobs.',
})}
>
- {button}
+ <>{cloneActionNameText}>
);
}
- return button;
+ return <>{cloneActionNameText}>;
};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/index.ts
index 4e6357c4ea454..723279d04388e 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/index.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/index.ts
@@ -7,7 +7,6 @@
export {
extractCloningConfig,
isAdvancedConfig,
- useNavigateToWizardWithClonedJob,
- CloneButton,
CloneDataFrameAnalyticsConfig,
-} from './clone_button';
+} from './clone_action_name';
+export { useCloneAction } from './use_clone_action';
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/use_clone_action.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/use_clone_action.tsx
new file mode 100644
index 0000000000000..53043d4e503fe
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/use_clone_action.tsx
@@ -0,0 +1,41 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useCallback, useMemo } from 'react';
+
+import { DataFrameAnalyticsListAction, DataFrameAnalyticsListRow } from '../analytics_list/common';
+
+import {
+ cloneActionNameText,
+ useNavigateToWizardWithClonedJob,
+ CloneActionName,
+} from './clone_action_name';
+
+export type CloneAction = ReturnType;
+export const useCloneAction = (canCreateDataFrameAnalytics: boolean) => {
+ const navigateToWizardWithClonedJob = useNavigateToWizardWithClonedJob();
+
+ const clickHandler = useCallback((item: DataFrameAnalyticsListRow) => {
+ navigateToWizardWithClonedJob(item);
+ }, []);
+
+ const action: DataFrameAnalyticsListAction = useMemo(
+ () => ({
+ name: (item: DataFrameAnalyticsListRow) => (
+
+ ),
+ enabled: () => canCreateDataFrameAnalytics,
+ description: cloneActionNameText,
+ icon: 'copy',
+ type: 'icon',
+ onClick: clickHandler,
+ 'data-test-subj': 'mlAnalyticsJobCloneButton',
+ }),
+ []
+ );
+
+ return { action };
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_button_modal.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_action_modal.tsx
similarity index 98%
rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_button_modal.tsx
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_action_modal.tsx
index f94dccee479bd..5ffa5e304b996 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_button_modal.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_action_modal.tsx
@@ -18,7 +18,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { DeleteAction } from './use_delete_action';
-export const DeleteButtonModal: FC = ({
+export const DeleteActionModal: FC = ({
closeModal,
deleteAndCloseModal,
deleteTargetIndex,
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/action_delete.test.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_action_name.test.tsx
similarity index 73%
rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/action_delete.test.tsx
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_action_name.test.tsx
index 99455a33cf084..e033af6436130 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/action_delete.test.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_action_name.test.tsx
@@ -14,8 +14,8 @@ import {
i18nServiceMock,
} from '../../../../../../../../../../src/core/public/mocks';
-import { DeleteButton } from './delete_button';
-import { DeleteButtonModal } from './delete_button_modal';
+import { DeleteActionName } from './delete_action_name';
+import { DeleteActionModal } from './delete_action_modal';
import { useDeleteAction } from './use_delete_action';
jest.mock('../../../../../capabilities/check_capabilities', () => ({
@@ -49,38 +49,39 @@ describe('DeleteAction', () => {
jest.clearAllMocks();
});
- test('When isDisabled prop is true, inner button should be disabled.', () => {
- const { getByTestId } = render(
- {}} />
+ it('should display a tooltip when isDisabled prop is true.', () => {
+ const { container } = render(
+
);
- expect(getByTestId('mlAnalyticsJobDeleteButton')).toHaveAttribute('disabled');
+ expect(container.querySelector('.euiToolTipAnchor')).toBeInTheDocument();
});
- test('When isDisabled prop is true, inner button should not be disabled.', () => {
- const { getByTestId } = render(
- {}} />
+ it('should not display a tooltip when isDisabled prop is false.', () => {
+ const { container } = render(
+
);
- expect(getByTestId('mlAnalyticsJobDeleteButton')).not.toHaveAttribute('disabled');
+ expect(container.querySelector('.euiToolTipAnchor')).not.toBeInTheDocument();
});
describe('When delete model is open', () => {
- test('should allow to delete target index by default.', () => {
+ it('should allow to delete target index by default.', () => {
const mock = jest.spyOn(CheckPrivilige, 'checkPermission');
mock.mockImplementation((p) => p === 'canDeleteDataFrameAnalytics');
const TestComponent = () => {
- const deleteAction = useDeleteAction();
+ const deleteAction = useDeleteAction(true);
return (
<>
- {deleteAction.isModalVisible && }
- }
+ deleteAction.openModal(mockAnalyticsListItem)}
- />
+ >
+
+
>
);
};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_button.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_action_name.tsx
similarity index 62%
rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_button.tsx
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_action_name.tsx
index 2bc3935c3b9f1..ccf45da81670c 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_button.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_action_name.tsx
@@ -6,36 +6,23 @@
import React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
-import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
+import { EuiToolTip } from '@elastic/eui';
import { createPermissionFailureMessage } from '../../../../../capabilities/check_capabilities';
import { isDataFrameAnalyticsRunning, DataFrameAnalyticsListRow } from '../analytics_list/common';
-const buttonText = i18n.translate('xpack.ml.dataframe.analyticsList.deleteActionName', {
- defaultMessage: 'Delete',
-});
+export const deleteActionNameText = i18n.translate(
+ 'xpack.ml.dataframe.analyticsList.deleteActionNameText',
+ {
+ defaultMessage: 'Delete',
+ }
+);
-interface DeleteButtonProps {
+interface DeleteActionNameProps {
isDisabled: boolean;
item: DataFrameAnalyticsListRow;
- onClick: () => void;
}
-export const DeleteButton: FC = ({ isDisabled, item, onClick }) => {
- const button = (
-
- {buttonText}
-
- );
-
+export const DeleteActionName: FC = ({ isDisabled, item }) => {
if (isDisabled) {
return (
= ({ isDisabled, item, onClick
: createPermissionFailureMessage('canStartStopDataFrameAnalytics')
}
>
- {button}
+ <>{deleteActionNameText}>
);
}
- return button;
+ return <>{deleteActionNameText}>;
};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/index.ts
index ef891d7c4a128..640cc39c90662 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/index.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/index.ts
@@ -4,6 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { DeleteButton } from './delete_button';
-export { DeleteButtonModal } from './delete_button_modal';
+export { DeleteActionName } from './delete_action_name';
+export { DeleteActionModal } from './delete_action_modal';
export { useDeleteAction } from './use_delete_action';
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/use_delete_action.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/use_delete_action.tsx
similarity index 77%
rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/use_delete_action.ts
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/use_delete_action.tsx
index 461b1749c7936..60a7267db1e25 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/use_delete_action.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/use_delete_action.tsx
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { useEffect, useState } from 'react';
+import React, { useEffect, useMemo, useState } from 'react';
import { i18n } from '@kbn/i18n';
@@ -21,10 +21,16 @@ import {
canDeleteIndex,
} from '../../services/analytics_service';
-import { DataFrameAnalyticsListRow } from '../analytics_list/common';
+import {
+ isDataFrameAnalyticsRunning,
+ DataFrameAnalyticsListAction,
+ DataFrameAnalyticsListRow,
+} from '../analytics_list/common';
+
+import { deleteActionNameText, DeleteActionName } from './delete_action_name';
export type DeleteAction = ReturnType;
-export const useDeleteAction = () => {
+export const useDeleteAction = (canDeleteDataFrameAnalytics: boolean) => {
const [item, setItem] = useState();
const [isModalVisible, setModalVisible] = useState(false);
@@ -33,7 +39,7 @@ export const useDeleteAction = () => {
const [userCanDeleteIndex, setUserCanDeleteIndex] = useState(false);
const [indexPatternExists, setIndexPatternExists] = useState(false);
- const { savedObjects, notifications } = useMlKibana().services;
+ const { savedObjects } = useMlKibana().services;
const savedObjectsClient = savedObjects.client;
const indexName = item?.config.dest.index ?? '';
@@ -58,10 +64,9 @@ export const useDeleteAction = () => {
setIndexPatternExists(false);
}
} catch (e) {
- const { toasts } = notifications;
const error = extractErrorMessage(e);
- toasts.addDanger(
+ toastNotificationService.displayDangerToast(
i18n.translate(
'xpack.ml.dataframe.analyticsList.errorWithCheckingIfIndexPatternExistsNotificationErrorMessage',
{
@@ -75,15 +80,14 @@ export const useDeleteAction = () => {
};
const checkUserIndexPermission = () => {
try {
- const userCanDelete = canDeleteIndex(indexName);
+ const userCanDelete = canDeleteIndex(indexName, toastNotificationService);
if (userCanDelete) {
setUserCanDeleteIndex(true);
}
} catch (e) {
- const { toasts } = notifications;
const error = extractErrorMessage(e);
- toasts.addDanger(
+ toastNotificationService.displayDangerToast(
i18n.translate(
'xpack.ml.dataframe.analyticsList.errorWithCheckingIfUserCanDeleteIndexNotificationErrorMessage',
{
@@ -130,7 +134,27 @@ export const useDeleteAction = () => {
setModalVisible(true);
};
+ const action: DataFrameAnalyticsListAction = useMemo(
+ () => ({
+ name: (i: DataFrameAnalyticsListRow) => (
+
+ ),
+ enabled: (i: DataFrameAnalyticsListRow) =>
+ !isDataFrameAnalyticsRunning(i.stats.state) && canDeleteDataFrameAnalytics,
+ description: deleteActionNameText,
+ icon: 'trash',
+ type: 'icon',
+ onClick: (i: DataFrameAnalyticsListRow) => openModal(i),
+ 'data-test-subj': 'mlAnalyticsJobDeleteButton',
+ }),
+ []
+ );
+
return {
+ action,
closeModal,
deleteAndCloseModal,
deleteTargetIndex,
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/edit_button_flyout.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/edit_action_flyout.tsx
similarity index 99%
rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/edit_button_flyout.tsx
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/edit_action_flyout.tsx
index 14b743997f30a..867f89d9e5e4a 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/edit_button_flyout.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/edit_action_flyout.tsx
@@ -43,7 +43,7 @@ import { EditAction } from './use_edit_action';
let mmLValidator: (value: any) => MemoryInputValidatorResult;
-export const EditButtonFlyout: FC> = ({ closeFlyout, item }) => {
+export const EditActionFlyout: FC> = ({ closeFlyout, item }) => {
const { id: jobId, config } = item;
const { state } = item.stats;
const initialAllowLazyStart =
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/edit_button.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/edit_action_name.tsx
similarity index 50%
rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/edit_button.tsx
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/edit_action_name.tsx
index e17862bf326f1..5b7f40c81f118 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/edit_button.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/edit_action_name.tsx
@@ -8,33 +8,20 @@ import React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
-import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
+import { EuiToolTip } from '@elastic/eui';
-const buttonText = i18n.translate('xpack.ml.dataframe.analyticsList.editActionName', {
- defaultMessage: 'Edit',
-});
+export const editActionNameText = i18n.translate(
+ 'xpack.ml.dataframe.analyticsList.editActionNameText',
+ {
+ defaultMessage: 'Edit',
+ }
+);
-interface EditButtonProps {
+interface EditActionNameProps {
isDisabled: boolean;
- onClick: () => void;
}
-export const EditButton: FC = ({ isDisabled, onClick }) => {
- const button = (
-
- {buttonText}
-
- );
-
+export const EditActionName: FC = ({ isDisabled }) => {
if (isDisabled) {
return (
= ({ isDisabled, onClick }) => {
defaultMessage: 'You do not have permission to edit analytics jobs.',
})}
>
- {button}
+ <>{editActionNameText}>
);
}
- return button;
+ return <>{editActionNameText}>;
};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/index.ts
index cfb0bba16ca18..07b3949717797 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/index.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/index.ts
@@ -4,6 +4,5 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { EditButton } from './edit_button';
-export { EditButtonFlyout } from './edit_button_flyout';
+export { EditActionFlyout } from './edit_action_flyout';
export { isEditActionFlyoutVisible, useEditAction } from './use_edit_action';
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/use_edit_action.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/use_edit_action.tsx
similarity index 56%
rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/use_edit_action.ts
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/use_edit_action.tsx
index 82a7bcc91997a..67f0d9d059570 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/use_edit_action.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/use_edit_action.tsx
@@ -4,9 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { useState } from 'react';
+import React, { useMemo, useState } from 'react';
-import { DataFrameAnalyticsListRow } from '../analytics_list/common';
+import { DataFrameAnalyticsListAction, DataFrameAnalyticsListRow } from '../analytics_list/common';
+
+import { editActionNameText, EditActionName } from './edit_action_name';
export const isEditActionFlyoutVisible = (editAction: any): editAction is Required => {
return editAction.isFlyoutVisible === true && editAction.item !== undefined;
@@ -18,7 +20,7 @@ export interface EditAction {
closeFlyout: () => void;
openFlyout: (newItem: DataFrameAnalyticsListRow) => void;
}
-export const useEditAction = () => {
+export const useEditAction = (canStartStopDataFrameAnalytics: boolean) => {
const [item, setItem] = useState();
const [isFlyoutVisible, setIsFlyoutVisible] = useState(false);
@@ -28,7 +30,21 @@ export const useEditAction = () => {
setIsFlyoutVisible(true);
};
+ const action: DataFrameAnalyticsListAction = useMemo(
+ () => ({
+ name: () => ,
+ enabled: () => canStartStopDataFrameAnalytics,
+ description: editActionNameText,
+ icon: 'pencil',
+ type: 'icon',
+ onClick: (i: DataFrameAnalyticsListRow) => openFlyout(i),
+ 'data-test-subj': 'mlAnalyticsJobEditButton',
+ }),
+ [canStartStopDataFrameAnalytics]
+ );
+
return {
+ action,
isFlyoutVisible,
item,
closeFlyout,
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/index.ts
index df6bbb7c61908..cda5b0ee1ec79 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/index.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/index.ts
@@ -4,6 +4,5 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { StartButton } from './start_button';
-export { StartButtonModal } from './start_button_modal';
export { useStartAction } from './use_start_action';
+export { StartActionModal } from './start_action_modal';
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_button_modal.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_action_modal.tsx
similarity index 96%
rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_button_modal.tsx
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_action_modal.tsx
index 664dbe5c62b2f..fa559e807f5ea 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_button_modal.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_action_modal.tsx
@@ -10,7 +10,7 @@ import { EuiConfirmModal, EuiOverlayMask, EUI_MODAL_CONFIRM_BUTTON } from '@elas
import { StartAction } from './use_start_action';
-export const StartButtonModal: FC = ({ closeModal, item, startAndCloseModal }) => {
+export const StartActionModal: FC = ({ closeModal, item, startAndCloseModal }) => {
return (
<>
{item !== undefined && (
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_button.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_action_name.tsx
similarity index 65%
rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_button.tsx
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_action_name.tsx
index 98b9279d8469a..1b39f3c8a7167 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_button.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_action_name.tsx
@@ -6,44 +6,30 @@
import React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
-import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
+import { EuiToolTip } from '@elastic/eui';
import { createPermissionFailureMessage } from '../../../../../capabilities/check_capabilities';
import { DataFrameAnalyticsListRow } from '../analytics_list/common';
-const buttonText = i18n.translate('xpack.ml.dataframe.analyticsList.startActionName', {
- defaultMessage: 'Start',
-});
+export const startActionNameText = i18n.translate(
+ 'xpack.ml.dataframe.analyticsList.startActionNameText',
+ {
+ defaultMessage: 'Start',
+ }
+);
-interface StartButtonProps {
+interface StartActionNameProps {
canStartStopDataFrameAnalytics: boolean;
isDisabled: boolean;
item: DataFrameAnalyticsListRow;
- onClick: () => void;
}
-export const StartButton: FC = ({
+export const StartActionName: FC = ({
canStartStopDataFrameAnalytics,
isDisabled,
item,
- onClick,
}) => {
- const button = (
-
- {buttonText}
-
- );
-
if (isDisabled) {
return (
= ({
})
}
>
- {button}
+ <>{startActionNameText}>
);
}
- return button;
+ return <>{startActionNameText}>;
};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/use_start_action.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/use_start_action.ts
deleted file mode 100644
index 3c1087ff587d8..0000000000000
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/use_start_action.ts
+++ /dev/null
@@ -1,41 +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 { useState } from 'react';
-
-import { DataFrameAnalyticsListRow } from '../analytics_list/common';
-import { startAnalytics } from '../../services/analytics_service';
-import { useToastNotificationService } from '../../../../../services/toast_notification_service';
-
-export type StartAction = ReturnType;
-export const useStartAction = () => {
- const [isModalVisible, setModalVisible] = useState(false);
-
- const [item, setItem] = useState();
-
- const toastNotificationService = useToastNotificationService();
-
- const closeModal = () => setModalVisible(false);
- const startAndCloseModal = () => {
- if (item !== undefined) {
- setModalVisible(false);
- startAnalytics(item, toastNotificationService);
- }
- };
-
- const openModal = (newItem: DataFrameAnalyticsListRow) => {
- setItem(newItem);
- setModalVisible(true);
- };
-
- return {
- closeModal,
- isModalVisible,
- item,
- openModal,
- startAndCloseModal,
- };
-};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/use_start_action.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/use_start_action.tsx
new file mode 100644
index 0000000000000..f72c4d8a1f556
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/use_start_action.tsx
@@ -0,0 +1,80 @@
+/*
+ * 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, { useMemo, useState } from 'react';
+
+import {
+ isCompletedAnalyticsJob,
+ isDataFrameAnalyticsFailed,
+ isDataFrameAnalyticsRunning,
+ DataFrameAnalyticsListAction,
+ DataFrameAnalyticsListRow,
+} from '../analytics_list/common';
+import { startAnalytics } from '../../services/analytics_service';
+import { useToastNotificationService } from '../../../../../services/toast_notification_service';
+
+import { startActionNameText, StartActionName } from './start_action_name';
+
+export type StartAction = ReturnType;
+export const useStartAction = (canStartStopDataFrameAnalytics: boolean) => {
+ const [isModalVisible, setModalVisible] = useState(false);
+
+ const [item, setItem] = useState();
+
+ const toastNotificationService = useToastNotificationService();
+
+ const closeModal = () => setModalVisible(false);
+ const startAndCloseModal = () => {
+ if (item !== undefined) {
+ setModalVisible(false);
+ startAnalytics(item, toastNotificationService);
+ }
+ };
+
+ const openModal = (newItem: DataFrameAnalyticsListRow) => {
+ setItem(newItem);
+ setModalVisible(true);
+ };
+
+ const startButtonEnabled = (i: DataFrameAnalyticsListRow) => {
+ if (!isDataFrameAnalyticsRunning(i.stats.state)) {
+ // Disable start for analytics jobs which have completed.
+ const completeAnalytics = isCompletedAnalyticsJob(i.stats);
+ return canStartStopDataFrameAnalytics && !completeAnalytics;
+ }
+ return canStartStopDataFrameAnalytics;
+ };
+
+ const action: DataFrameAnalyticsListAction = useMemo(
+ () => ({
+ name: (i: DataFrameAnalyticsListRow) => (
+
+ ),
+ available: (i: DataFrameAnalyticsListRow) =>
+ !isDataFrameAnalyticsRunning(i.stats.state) && !isDataFrameAnalyticsFailed(i.stats.state),
+ enabled: startButtonEnabled,
+ description: startActionNameText,
+ icon: 'play',
+ type: 'icon',
+ onClick: openModal,
+ 'data-test-subj': 'mlAnalyticsJobStartButton',
+ }),
+ []
+ );
+
+ return {
+ action,
+ closeModal,
+ isModalVisible,
+ item,
+ openModal,
+ startAndCloseModal,
+ };
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/index.ts
index ce03305e3d859..f45b90222611b 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/index.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/index.ts
@@ -4,6 +4,5 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { StopButton } from './stop_button';
-export { StopButtonModal } from './stop_button_modal';
-export { useForceStopAction } from './use_force_stop_action';
+export { useStopAction } from './use_stop_action';
+export { StopActionModal } from './stop_action_modal';
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/stop_button_modal.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/stop_action_modal.tsx
similarity index 91%
rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/stop_button_modal.tsx
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/stop_action_modal.tsx
index 387f42cc85ef3..0b483ce4cad16 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/stop_button_modal.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/stop_action_modal.tsx
@@ -9,13 +9,9 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiConfirmModal, EuiOverlayMask, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui';
-import { ForceStopAction } from './use_force_stop_action';
+import { StopAction } from './use_stop_action';
-export const StopButtonModal: FC = ({
- closeModal,
- item,
- forceStopAndCloseModal,
-}) => {
+export const StopActionModal: FC = ({ closeModal, item, forceStopAndCloseModal }) => {
return (
<>
{item !== undefined && (
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/stop_action_name.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/stop_action_name.tsx
new file mode 100644
index 0000000000000..99c8d960cf3b9
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/stop_action_name.tsx
@@ -0,0 +1,38 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC } from 'react';
+import { i18n } from '@kbn/i18n';
+
+import { EuiToolTip } from '@elastic/eui';
+
+import { createPermissionFailureMessage } from '../../../../../capabilities/check_capabilities';
+
+export const stopActionNameText = i18n.translate(
+ 'xpack.ml.dataframe.analyticsList.stopActionNameText',
+ {
+ defaultMessage: 'Stop',
+ }
+);
+
+interface StopActionNameProps {
+ isDisabled: boolean;
+}
+
+export const StopActionName: FC = ({ isDisabled }) => {
+ if (isDisabled) {
+ return (
+
+ <>{stopActionNameText}>
+
+ );
+ }
+
+ return <>{stopActionNameText}>;
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/stop_button.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/stop_button.tsx
deleted file mode 100644
index 3bac183d9f391..0000000000000
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/stop_button.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import React, { FC } from 'react';
-import { i18n } from '@kbn/i18n';
-
-import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
-
-import { createPermissionFailureMessage } from '../../../../../capabilities/check_capabilities';
-
-import { DataFrameAnalyticsListRow } from '../analytics_list/common';
-
-const buttonText = i18n.translate('xpack.ml.dataframe.analyticsList.stopActionName', {
- defaultMessage: 'Stop',
-});
-
-interface StopButtonProps {
- isDisabled: boolean;
- item: DataFrameAnalyticsListRow;
- onClick: () => void;
-}
-
-export const StopButton: FC = ({ isDisabled, item, onClick }) => {
- const button = (
-
- {buttonText}
-
- );
-
- if (isDisabled) {
- return (
-
- {button}
-
- );
- }
-
- return button;
-};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/use_force_stop_action.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/use_force_stop_action.ts
deleted file mode 100644
index 5a4e748948731..0000000000000
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/use_force_stop_action.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { useState } from 'react';
-
-import { DataFrameAnalyticsListRow } from '../analytics_list/common';
-import { stopAnalytics } from '../../services/analytics_service';
-
-export type ForceStopAction = ReturnType;
-export const useForceStopAction = () => {
- const [isModalVisible, setModalVisible] = useState(false);
-
- const [item, setItem] = useState();
-
- const closeModal = () => setModalVisible(false);
- const forceStopAndCloseModal = () => {
- if (item !== undefined) {
- setModalVisible(false);
- stopAnalytics(item);
- }
- };
-
- const openModal = (newItem: DataFrameAnalyticsListRow) => {
- setItem(newItem);
- setModalVisible(true);
- };
-
- return {
- closeModal,
- isModalVisible,
- item,
- openModal,
- forceStopAndCloseModal,
- };
-};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/use_stop_action.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/use_stop_action.tsx
new file mode 100644
index 0000000000000..546744c992709
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/use_stop_action.tsx
@@ -0,0 +1,67 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useCallback, useMemo, useState } from 'react';
+
+import {
+ isDataFrameAnalyticsFailed,
+ isDataFrameAnalyticsRunning,
+ DataFrameAnalyticsListAction,
+ DataFrameAnalyticsListRow,
+} from '../analytics_list/common';
+import { stopAnalytics } from '../../services/analytics_service';
+
+import { stopActionNameText, StopActionName } from './stop_action_name';
+
+export type StopAction = ReturnType;
+export const useStopAction = (canStartStopDataFrameAnalytics: boolean) => {
+ const [isModalVisible, setModalVisible] = useState(false);
+
+ const [item, setItem] = useState();
+
+ const closeModal = () => setModalVisible(false);
+ const forceStopAndCloseModal = () => {
+ if (item !== undefined) {
+ setModalVisible(false);
+ stopAnalytics(item);
+ }
+ };
+
+ const openModal = (newItem: DataFrameAnalyticsListRow) => {
+ setItem(newItem);
+ setModalVisible(true);
+ };
+
+ const clickHandler = useCallback(
+ (i: DataFrameAnalyticsListRow) => {
+ if (canStartStopDataFrameAnalytics) {
+ if (isDataFrameAnalyticsFailed(i.stats.state)) {
+ openModal(i);
+ } else {
+ stopAnalytics(i);
+ }
+ }
+ },
+ [stopAnalytics]
+ );
+
+ const action: DataFrameAnalyticsListAction = useMemo(
+ () => ({
+ name: () => ,
+ available: (i: DataFrameAnalyticsListRow) =>
+ isDataFrameAnalyticsRunning(i.stats.state) || isDataFrameAnalyticsFailed(i.stats.state),
+ enabled: () => canStartStopDataFrameAnalytics,
+ description: stopActionNameText,
+ icon: 'stop',
+ type: 'icon',
+ onClick: clickHandler,
+ 'data-test-subj': 'mlAnalyticsJobStopButton',
+ }),
+ [clickHandler]
+ );
+
+ return { action, closeModal, isModalVisible, item, openModal, forceStopAndCloseModal };
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/get_view_action.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/get_view_action.tsx
deleted file mode 100644
index e123af204b515..0000000000000
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/get_view_action.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import React from 'react';
-
-import { EuiTableActionsColumnType } from '@elastic/eui';
-
-import { DataFrameAnalyticsListRow } from '../analytics_list/common';
-
-import { ViewButton } from './view_button';
-
-export const getViewAction = (): EuiTableActionsColumnType<
- DataFrameAnalyticsListRow
->['actions'][number] => ({
- isPrimary: true,
- render: (item: DataFrameAnalyticsListRow) => ,
-});
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/index.ts
index 5ac12c12071fd..076c07abfe920 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/index.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/index.ts
@@ -4,5 +4,5 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { getViewAction } from './get_view_action';
+export { useViewAction } from './use_view_action';
export { ViewButton } from './view_button';
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/use_view_action.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/use_view_action.tsx
new file mode 100644
index 0000000000000..a3595b51d0a59
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/use_view_action.tsx
@@ -0,0 +1,45 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useCallback, useMemo } from 'react';
+
+import { getAnalysisType } from '../../../../common/analytics';
+import { useNavigateToPath } from '../../../../../contexts/kibana';
+
+import {
+ getResultsUrl,
+ DataFrameAnalyticsListAction,
+ DataFrameAnalyticsListRow,
+} from '../analytics_list/common';
+
+import { getViewLinkStatus } from './get_view_link_status';
+import { viewActionButtonText, ViewButton } from './view_button';
+
+export type ViewAction = ReturnType;
+export const useViewAction = () => {
+ const navigateToPath = useNavigateToPath();
+
+ const clickHandler = useCallback((item: DataFrameAnalyticsListRow) => {
+ const analysisType = getAnalysisType(item.config.analysis);
+ navigateToPath(getResultsUrl(item.id, analysisType));
+ }, []);
+
+ const action: DataFrameAnalyticsListAction = useMemo(
+ () => ({
+ isPrimary: true,
+ name: (item: DataFrameAnalyticsListRow) => ,
+ enabled: (item: DataFrameAnalyticsListRow) => !getViewLinkStatus(item).disabled,
+ description: viewActionButtonText,
+ icon: 'visTable',
+ type: 'icon',
+ onClick: clickHandler,
+ 'data-test-subj': 'mlAnalyticsJobViewButton',
+ }),
+ [clickHandler]
+ );
+
+ return { action };
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/view_button.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/view_button.tsx
index 9472a3af852fc..d46ddf1fcaaba 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/view_button.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/view_button.tsx
@@ -6,53 +6,33 @@
import React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
-import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
+import { EuiToolTip } from '@elastic/eui';
-import { getAnalysisType } from '../../../../common/analytics';
-import { useNavigateToPath } from '../../../../../contexts/kibana';
-
-import { getResultsUrl, DataFrameAnalyticsListRow } from '../analytics_list/common';
+import { DataFrameAnalyticsListRow } from '../analytics_list/common';
import { getViewLinkStatus } from './get_view_link_status';
+export const viewActionButtonText = i18n.translate(
+ 'xpack.ml.dataframe.analyticsList.viewActionName',
+ {
+ defaultMessage: 'View',
+ }
+);
+
interface ViewButtonProps {
item: DataFrameAnalyticsListRow;
}
export const ViewButton: FC = ({ item }) => {
- const navigateToPath = useNavigateToPath();
-
const { disabled, tooltipContent } = getViewLinkStatus(item);
- const analysisType = getAnalysisType(item.config.analysis);
-
- const onClickHandler = () => navigateToPath(getResultsUrl(item.id, analysisType));
-
- const buttonText = i18n.translate('xpack.ml.dataframe.analyticsList.viewActionName', {
- defaultMessage: 'View',
- });
-
- const button = (
-
- {buttonText}
-
- );
if (disabled) {
return (
- {button}
+ <>{viewActionButtonText}>
);
}
- return button;
+ return <>{viewActionButtonText}>;
};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts
index 39489836773b3..774864ae964a8 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { Query, Ast } from '@elastic/eui';
+import { EuiTableActionsColumnType, Query, Ast } from '@elastic/eui';
import { DATA_FRAME_TASK_STATE } from './data_frame_task_state';
export { DATA_FRAME_TASK_STATE };
@@ -139,3 +139,11 @@ export function isCompletedAnalyticsJob(stats: DataFrameAnalyticsStats) {
export function getResultsUrl(jobId: string, analysisType: ANALYSIS_CONFIG_TYPE | string) {
return `#/data_frame_analytics/exploration?_g=(ml:(jobId:${jobId},analysisType:${analysisType}))`;
}
+
+// The single Action type is not exported as is
+// from EUI so we use that code to get the single
+// Action type from the array of actions.
+type ArrayElement = ArrayType[number];
+export type DataFrameAnalyticsListAction = ArrayElement<
+ EuiTableActionsColumnType['actions']
+>;
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_actions.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_actions.tsx
index d355335039085..20ae48a12ecf9 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_actions.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_actions.tsx
@@ -10,26 +10,14 @@ import { EuiTableActionsColumnType } from '@elastic/eui';
import { checkPermission } from '../../../../../capabilities/check_capabilities';
-import { stopAnalytics } from '../../services/analytics_service';
+import { useCloneAction } from '../action_clone';
+import { useDeleteAction, DeleteActionModal } from '../action_delete';
+import { isEditActionFlyoutVisible, useEditAction, EditActionFlyout } from '../action_edit';
+import { useStartAction, StartActionModal } from '../action_start';
+import { useStopAction, StopActionModal } from '../action_stop';
+import { useViewAction } from '../action_view';
-import { useNavigateToWizardWithClonedJob, CloneButton } from '../action_clone';
-import { useDeleteAction, DeleteButton, DeleteButtonModal } from '../action_delete';
-import {
- isEditActionFlyoutVisible,
- useEditAction,
- EditButton,
- EditButtonFlyout,
-} from '../action_edit';
-import { useStartAction, StartButton, StartButtonModal } from '../action_start';
-import { StopButton, useForceStopAction, StopButtonModal } from '../action_stop';
-import { getViewAction } from '../action_view';
-
-import {
- isCompletedAnalyticsJob,
- isDataFrameAnalyticsRunning,
- isDataFrameAnalyticsFailed,
- DataFrameAnalyticsListRow,
-} from './common';
+import { DataFrameAnalyticsListRow } from './common';
export const useActions = (
isManagementTable: boolean
@@ -41,126 +29,38 @@ export const useActions = (
const canDeleteDataFrameAnalytics: boolean = checkPermission('canDeleteDataFrameAnalytics');
const canStartStopDataFrameAnalytics: boolean = checkPermission('canStartStopDataFrameAnalytics');
+ const viewAction = useViewAction();
+ const cloneAction = useCloneAction(canCreateDataFrameAnalytics);
+ const deleteAction = useDeleteAction(canDeleteDataFrameAnalytics);
+ const editAction = useEditAction(canStartStopDataFrameAnalytics);
+ const startAction = useStartAction(canStartStopDataFrameAnalytics);
+ const stopAction = useStopAction(canStartStopDataFrameAnalytics);
+
let modals: JSX.Element | null = null;
const actions: EuiTableActionsColumnType['actions'] = [
- getViewAction(),
+ viewAction.action,
];
// isManagementTable will be the same for the lifecycle of the component
// Disabling lint error to fix console error in management list due to action hooks using deps not initialized in management
if (isManagementTable === false) {
- /* eslint-disable react-hooks/rules-of-hooks */
- const deleteAction = useDeleteAction();
- const editAction = useEditAction();
- const startAction = useStartAction();
- const stopAction = useForceStopAction();
- /* eslint-disable react-hooks/rules-of-hooks */
-
modals = (
<>
- {startAction.isModalVisible && }
- {stopAction.isModalVisible && }
- {deleteAction.isModalVisible && }
- {isEditActionFlyoutVisible(editAction) && }
+ {startAction.isModalVisible && }
+ {stopAction.isModalVisible && }
+ {deleteAction.isModalVisible && }
+ {isEditActionFlyoutVisible(editAction) && }
>
);
- const startButtonEnabled = (item: DataFrameAnalyticsListRow) => {
- if (!isDataFrameAnalyticsRunning(item.stats.state)) {
- // Disable start for analytics jobs which have completed.
- const completeAnalytics = isCompletedAnalyticsJob(item.stats);
- return canStartStopDataFrameAnalytics && !completeAnalytics;
- }
- return canStartStopDataFrameAnalytics;
- };
-
- const navigateToWizardWithClonedJob = useNavigateToWizardWithClonedJob();
-
actions.push(
...[
- {
- render: (item: DataFrameAnalyticsListRow) => {
- if (
- !isDataFrameAnalyticsRunning(item.stats.state) &&
- !isDataFrameAnalyticsFailed(item.stats.state)
- ) {
- return (
- {
- if (startButtonEnabled(item)) {
- startAction.openModal(item);
- }
- }}
- />
- );
- }
-
- return (
- {
- if (canStartStopDataFrameAnalytics) {
- if (isDataFrameAnalyticsFailed(item.stats.state)) {
- stopAction.openModal(item);
- } else {
- stopAnalytics(item);
- }
- }
- }}
- />
- );
- },
- },
- {
- render: (item: DataFrameAnalyticsListRow) => {
- return (
- {
- if (canStartStopDataFrameAnalytics) {
- editAction.openFlyout(item);
- }
- }}
- />
- );
- },
- },
- {
- render: (item: DataFrameAnalyticsListRow) => {
- return (
- {
- if (canStartStopDataFrameAnalytics) {
- deleteAction.openModal(item);
- }
- }}
- />
- );
- },
- },
- {
- render: (item: DataFrameAnalyticsListRow) => {
- return (
- {
- if (canCreateDataFrameAnalytics) {
- navigateToWizardWithClonedJob(item);
- }
- }}
- />
- );
- },
- },
+ startAction.action,
+ stopAction.action,
+ editAction.action,
+ cloneAction.action,
+ deleteAction.action,
]
);
}
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts
index 8d8421a116b91..7cd9fcc052f1a 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts
@@ -45,7 +45,7 @@ import {
TRAINING_PERCENT_MAX,
} from '../../../../common/analytics';
import { indexPatterns } from '../../../../../../../../../../src/plugins/data/public';
-import { isAdvancedConfig } from '../../components/action_clone/clone_button';
+import { isAdvancedConfig } from '../../components/action_clone/clone_action_name';
const mmlAllowedUnitsStr = `${ALLOWED_DATA_UNITS.slice(0, ALLOWED_DATA_UNITS.length - 1).join(
', '
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 7d3ee986a4ef1..9de859742438e 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
@@ -5,7 +5,6 @@
*/
import { i18n } from '@kbn/i18n';
import { extractErrorMessage } from '../../../../../../../common/util/errors';
-import { getToastNotifications } from '../../../../../util/dependency_cache';
import { ml } from '../../../../../services/ml_api_service';
import { ToastNotificationService } from '../../../../../services/toast_notification_service';
import { refreshAnalyticsList$, REFRESH_ANALYTICS_LIST_STATE } from '../../../../common';
@@ -18,13 +17,12 @@ export const deleteAnalytics = async (
d: DataFrameAnalyticsListRow,
toastNotificationService: ToastNotificationService
) => {
- const toastNotifications = getToastNotifications();
try {
if (isDataFrameAnalyticsFailed(d.stats.state)) {
await ml.dataFrameAnalytics.stopDataFrameAnalytics(d.config.id, true);
}
await ml.dataFrameAnalytics.deleteDataFrameAnalytics(d.config.id);
- toastNotifications.addSuccess(
+ toastNotificationService.displaySuccessToast(
i18n.translate('xpack.ml.dataframe.analyticsList.deleteAnalyticsSuccessMessage', {
defaultMessage: 'Request to delete data frame analytics job {analyticsId} acknowledged.',
values: { analyticsId: d.config.id },
@@ -48,7 +46,6 @@ export const deleteAnalyticsAndDestIndex = async (
deleteDestIndexPattern: boolean,
toastNotificationService: ToastNotificationService
) => {
- const toastNotifications = getToastNotifications();
const destinationIndex = Array.isArray(d.config.dest.index)
? d.config.dest.index[0]
: d.config.dest.index;
@@ -62,7 +59,7 @@ export const deleteAnalyticsAndDestIndex = async (
deleteDestIndexPattern
);
if (status.analyticsJobDeleted?.success) {
- toastNotifications.addSuccess(
+ toastNotificationService.displaySuccessToast(
i18n.translate('xpack.ml.dataframe.analyticsList.deleteAnalyticsSuccessMessage', {
defaultMessage: 'Request to delete data frame analytics job {analyticsId} acknowledged.',
values: { analyticsId: d.config.id },
@@ -80,7 +77,7 @@ export const deleteAnalyticsAndDestIndex = async (
}
if (status.destIndexDeleted?.success) {
- toastNotifications.addSuccess(
+ toastNotificationService.displaySuccessToast(
i18n.translate('xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexSuccessMessage', {
defaultMessage: 'Request to delete destination index {destinationIndex} acknowledged.',
values: { destinationIndex },
@@ -89,7 +86,7 @@ export const deleteAnalyticsAndDestIndex = async (
}
if (status.destIndexDeleted?.error) {
const error = extractErrorMessage(status.destIndexDeleted.error);
- toastNotifications.addDanger(
+ toastNotificationService.displayDangerToast(
i18n.translate('xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexErrorMessage', {
defaultMessage:
'An error occurred deleting destination index {destinationIndex}: {error}',
@@ -99,7 +96,7 @@ export const deleteAnalyticsAndDestIndex = async (
}
if (status.destIndexPatternDeleted?.success) {
- toastNotifications.addSuccess(
+ toastNotificationService.displaySuccessToast(
i18n.translate(
'xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexPatternSuccessMessage',
{
@@ -111,7 +108,7 @@ export const deleteAnalyticsAndDestIndex = async (
}
if (status.destIndexPatternDeleted?.error) {
const error = extractErrorMessage(status.destIndexPatternDeleted.error);
- toastNotifications.addDanger(
+ toastNotificationService.displayDangerToast(
i18n.translate(
'xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexPatternErrorMessage',
{
@@ -133,8 +130,10 @@ export const deleteAnalyticsAndDestIndex = async (
refreshAnalyticsList$.next(REFRESH_ANALYTICS_LIST_STATE.REFRESH);
};
-export const canDeleteIndex = async (indexName: string) => {
- const toastNotifications = getToastNotifications();
+export const canDeleteIndex = async (
+ indexName: string,
+ toastNotificationService: ToastNotificationService
+) => {
try {
const privilege = await ml.hasPrivileges({
index: [
@@ -150,7 +149,7 @@ export const canDeleteIndex = async (indexName: string) => {
return privilege.securityDisabled === true || privilege.has_all_requested === true;
} catch (e) {
const error = extractErrorMessage(e);
- toastNotifications.addDanger(
+ toastNotificationService.displayDangerToast(
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/explorer/explorer_charts/explorer_charts_container.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js
index 9988298c25c51..4fb783bfb6006 100644
--- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js
+++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js
@@ -104,7 +104,7 @@ function ExplorerChartContainer({ series, severity, tooManyBuckets, wrapLabel })
window.open(getExploreSeriesLink(series), '_blank')}
>
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/results.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/results.js
index 781b9126b7cbe..74072aa7e9638 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/results.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/results.js
@@ -51,7 +51,7 @@ export function ResultLinks({ jobs }) {
this.openSingleMetricView(forecast)}
isDisabled={forecast.forecast_status !== FORECAST_REQUEST_STATE.FINISHED}
- iconType="stats"
+ iconType="visLine"
aria-label={viewForecastAriaLabel}
/>
);
diff --git a/x-pack/plugins/ml/public/application/overview/components/analytics_panel/actions.tsx b/x-pack/plugins/ml/public/application/overview/components/analytics_panel/actions.tsx
new file mode 100644
index 0000000000000..081101e241990
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/overview/components/analytics_panel/actions.tsx
@@ -0,0 +1,54 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useCallback, FC } from 'react';
+import { EuiToolTip, EuiButtonEmpty } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
+import { useNavigateToPath } from '../../../contexts/kibana';
+import { getAnalysisType } from '../../../data_frame_analytics/common/analytics';
+import {
+ getResultsUrl,
+ DataFrameAnalyticsListRow,
+} from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/common';
+
+interface Props {
+ item: DataFrameAnalyticsListRow;
+}
+
+export const ViewLink: FC = ({ item }) => {
+ const navigateToPath = useNavigateToPath();
+
+ const clickHandler = useCallback(() => {
+ const analysisType = getAnalysisType(item.config.analysis);
+ navigateToPath(getResultsUrl(item.id, analysisType));
+ }, []);
+
+ const openJobsInAnomalyExplorerText = i18n.translate(
+ 'xpack.ml.overview.analytics.resultActions.openJobText',
+ {
+ defaultMessage: 'View job results',
+ }
+ );
+
+ return (
+
+
+ {i18n.translate('xpack.ml.overview.analytics.viewActionName', {
+ defaultMessage: 'View',
+ })}
+
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/overview/components/analytics_panel/table.tsx b/x-pack/plugins/ml/public/application/overview/components/analytics_panel/table.tsx
index b28729dcf157f..fc0645a0e9498 100644
--- a/x-pack/plugins/ml/public/application/overview/components/analytics_panel/table.tsx
+++ b/x-pack/plugins/ml/public/application/overview/components/analytics_panel/table.tsx
@@ -23,9 +23,10 @@ import {
getTaskStateBadge,
progressColumn,
} from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns';
-import { getViewAction } from '../../../data_frame_analytics/pages/analytics_management/components/action_view';
import { formatHumanReadableDateTimeSeconds } from '../../../util/date_utils';
+import { ViewLink } from './actions';
+
type DataFrameAnalyticsTableColumns = [
EuiTableFieldDataColumnType,
EuiTableComputedColumnType,
@@ -89,7 +90,11 @@ export const AnalyticsTable: FC = ({ items }) => {
name: i18n.translate('xpack.ml.overview.analyticsList.tableActionLabel', {
defaultMessage: 'Actions',
}),
- actions: [getViewAction()],
+ actions: [
+ {
+ render: (item: DataFrameAnalyticsListRow) => ,
+ },
+ ],
width: '100px',
},
];
diff --git a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/actions.tsx b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/actions.tsx
index 3a64c623dd129..07a555c13dbf5 100644
--- a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/actions.tsx
+++ b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/actions.tsx
@@ -30,13 +30,13 @@ export const ExplorerLink: FC = ({ jobsList }) => {
color="text"
size="xs"
href={getLink('explorer', jobsList)}
- iconType="tableOfContents"
+ iconType="visTable"
aria-label={openJobsInAnomalyExplorerText}
className="results-button"
data-test-subj={`openOverviewJobsInAnomalyExplorer`}
>
- {i18n.translate('xpack.ml.overview.anomalyDetection.exploreActionName', {
- defaultMessage: 'Explore',
+ {i18n.translate('xpack.ml.overview.anomalyDetection.viewActionName', {
+ defaultMessage: 'View',
})}
diff --git a/x-pack/plugins/ml/public/application/services/toast_notification_service.ts b/x-pack/plugins/ml/public/application/services/toast_notification_service.ts
index d93d6833c7cb4..94381ae3f1e51 100644
--- a/x-pack/plugins/ml/public/application/services/toast_notification_service.ts
+++ b/x-pack/plugins/ml/public/application/services/toast_notification_service.ts
@@ -20,6 +20,10 @@ export type ToastNotificationService = ReturnType viewForecast(forecast.forecast_id)}
- iconType="stats"
+ iconType="visLine"
aria-label={viewForecastAriaLabel}
/>
);
diff --git a/x-pack/plugins/ml/public/ui_actions/open_in_anomaly_explorer_action.tsx b/x-pack/plugins/ml/public/ui_actions/open_in_anomaly_explorer_action.tsx
index e18f593145f9c..e327befcf7293 100644
--- a/x-pack/plugins/ml/public/ui_actions/open_in_anomaly_explorer_action.tsx
+++ b/x-pack/plugins/ml/public/ui_actions/open_in_anomaly_explorer_action.tsx
@@ -17,7 +17,7 @@ export function createOpenInExplorerAction(getStartServices: MlCoreSetup['getSta
id: 'open-in-anomaly-explorer',
type: OPEN_IN_ANOMALY_EXPLORER_ACTION,
getIconType(context: ActionContextMapping[typeof OPEN_IN_ANOMALY_EXPLORER_ACTION]): string {
- return 'tableOfContents';
+ return 'visTable';
},
getDisplayName() {
return i18n.translate('xpack.ml.actions.openInAnomalyExplorerTitle', {
diff --git a/x-pack/plugins/transform/public/app/common/index.ts b/x-pack/plugins/transform/public/app/common/index.ts
index 009c8c7a2a9f5..45ddc440057b2 100644
--- a/x-pack/plugins/transform/public/app/common/index.ts
+++ b/x-pack/plugins/transform/public/app/common/index.ts
@@ -31,7 +31,7 @@ export {
IndexPattern,
REFRESH_TRANSFORM_LIST_STATE,
} from './transform';
-export { TRANSFORM_LIST_COLUMN, TransformListRow } from './transform_list';
+export { TRANSFORM_LIST_COLUMN, TransformListAction, TransformListRow } from './transform_list';
export {
getTransformProgress,
isCompletedBatchTransform,
diff --git a/x-pack/plugins/transform/public/app/common/transform_list.ts b/x-pack/plugins/transform/public/app/common/transform_list.ts
index a27fc0b3e0dbb..a2a762a7e2dfb 100644
--- a/x-pack/plugins/transform/public/app/common/transform_list.ts
+++ b/x-pack/plugins/transform/public/app/common/transform_list.ts
@@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { EuiTableActionsColumnType } from '@elastic/eui';
+
import { TransformId } from '../../../common';
import { TransformPivotConfig } from './transform';
import { TransformStats } from './transform_stats';
@@ -20,3 +22,11 @@ export interface TransformListRow {
mode?: string; // added property on client side to allow filtering by this field
stats: TransformStats;
}
+
+// The single Action type is not exported as is
+// from EUI so we use that code to get the single
+// Action type from the array of actions.
+type ArrayElement = ArrayType[number];
+export type TransformListAction = ArrayElement<
+ EuiTableActionsColumnType['actions']
+>;
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/clone_action_name.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/clone_action_name.tsx
new file mode 100644
index 0000000000000..7ef634514de80
--- /dev/null
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/clone_action_name.tsx
@@ -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 React, { FC } from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiToolTip } from '@elastic/eui';
+
+import { createCapabilityFailureMessage } from '../../../../lib/authorization';
+
+export const cloneActionNameText = i18n.translate(
+ 'xpack.transform.transformList.cloneActionNameText',
+ {
+ defaultMessage: 'Clone',
+ }
+);
+
+interface CloneActionNameProps {
+ disabled: boolean;
+}
+
+export const CloneActionName: FC = ({ disabled }) => {
+ if (disabled) {
+ return (
+
+ <>{cloneActionNameText}>
+
+ );
+ }
+
+ return <>{cloneActionNameText}>;
+};
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/clone_button.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/clone_button.tsx
deleted file mode 100644
index 7b371d35f8d4c..0000000000000
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/clone_button.tsx
+++ /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 React, { FC, useContext } from 'react';
-import { useHistory } from 'react-router-dom';
-import { i18n } from '@kbn/i18n';
-import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
-
-import {
- createCapabilityFailureMessage,
- AuthorizationContext,
-} from '../../../../lib/authorization';
-
-import { SECTION_SLUG } from '../../../../constants';
-
-interface CloneActionProps {
- itemId: string;
-}
-
-export const CloneButton: FC = ({ itemId }) => {
- const history = useHistory();
-
- const { canCreateTransform } = useContext(AuthorizationContext).capabilities;
-
- const buttonText = i18n.translate('xpack.transform.transformList.cloneActionName', {
- defaultMessage: 'Clone',
- });
-
- function clickHandler() {
- history.push(`/${SECTION_SLUG.CLONE_TRANSFORM}/${itemId}`);
- }
-
- const buttonDisabled = !canCreateTransform;
-
- const button = (
-
- {buttonText}
-
- );
-
- if (buttonDisabled) {
- return (
-
- {button}
-
- );
- }
-
- return button;
-};
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/index.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/index.ts
index 727cc87c70f2c..fca9e156d1e9d 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/index.ts
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/index.ts
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { CloneButton } from './clone_button';
+export { useCloneAction } from './use_clone_action';
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/use_clone_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/use_clone_action.tsx
new file mode 100644
index 0000000000000..c2d772322ca06
--- /dev/null
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/use_clone_action.tsx
@@ -0,0 +1,43 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useCallback, useContext, useMemo } from 'react';
+import { useHistory } from 'react-router-dom';
+
+import { AuthorizationContext } from '../../../../lib/authorization';
+import { TransformListAction, TransformListRow } from '../../../../common';
+import { SECTION_SLUG } from '../../../../constants';
+
+import { cloneActionNameText, CloneActionName } from './clone_action_name';
+
+export type CloneAction = ReturnType;
+export const useCloneAction = (forceDisable: boolean) => {
+ const history = useHistory();
+
+ const { canCreateTransform } = useContext(AuthorizationContext).capabilities;
+
+ const clickHandler = useCallback(
+ (item: TransformListRow) => {
+ history.push(`/${SECTION_SLUG.CLONE_TRANSFORM}/${item.id}`);
+ },
+ [history]
+ );
+
+ const action: TransformListAction = useMemo(
+ () => ({
+ name: (item: TransformListRow) => ,
+ enabled: () => canCreateTransform && !forceDisable,
+ description: cloneActionNameText,
+ icon: 'copy',
+ type: 'icon',
+ onClick: clickHandler,
+ 'data-test-subj': 'transformActionClone',
+ }),
+ [canCreateTransform, forceDisable, clickHandler]
+ );
+
+ return { action };
+};
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/__snapshots__/delete_action_name.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/__snapshots__/delete_action_name.test.tsx.snap
new file mode 100644
index 0000000000000..fe1e813ca9843
--- /dev/null
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/__snapshots__/delete_action_name.test.tsx.snap
@@ -0,0 +1,7 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Transform: Transform List Actions Minimal initialization 1`] = `
+
+ Delete
+
+`;
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/__snapshots__/delete_button.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/__snapshots__/delete_button.test.tsx.snap
deleted file mode 100644
index 8d4568c5ce20e..0000000000000
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/__snapshots__/delete_button.test.tsx.snap
+++ /dev/null
@@ -1,22 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Transform: Transform List Actions Minimal initialization 1`] = `
-
-
- Delete
-
-
-`;
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_button_modal.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_modal.tsx
similarity index 99%
rename from x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_button_modal.tsx
rename to x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_modal.tsx
index 668e535198649..3c06af1eecb59 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_button_modal.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_modal.tsx
@@ -20,7 +20,7 @@ import { TRANSFORM_STATE } from '../../../../../../common';
import { DeleteAction } from './use_delete_action';
-export const DeleteButtonModal: FC = ({
+export const DeleteActionModal: FC = ({
closeModal,
deleteAndCloseModal,
deleteDestIndex,
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_button.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_name.test.tsx
similarity index 53%
rename from x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_button.test.tsx
rename to x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_name.test.tsx
index 63f8243b403d3..ecba7a0a24536 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_button.test.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_name.test.tsx
@@ -5,26 +5,22 @@
*/
import { shallow } from 'enzyme';
-import React, { ComponentProps } from 'react';
+import React from 'react';
-import { TransformListRow } from '../../../../common';
-import { DeleteButton } from './delete_button';
-
-import transformListRow from '../../../../common/__mocks__/transform_list_row.json';
+import { DeleteActionName, DeleteActionNameProps } from './delete_action_name';
jest.mock('../../../../../shared_imports');
jest.mock('../../../../../app/app_dependencies');
describe('Transform: Transform List Actions ', () => {
test('Minimal initialization', () => {
- const item: TransformListRow = transformListRow;
- const props: ComponentProps = {
- forceDisable: false,
- items: [item],
- onClick: () => {},
+ const props: DeleteActionNameProps = {
+ canDeleteTransform: true,
+ disabled: false,
+ isBulkAction: false,
};
- const wrapper = shallow( );
+ const wrapper = shallow( );
expect(wrapper).toMatchSnapshot();
});
});
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_button.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_name.tsx
similarity index 51%
rename from x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_button.tsx
rename to x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_name.tsx
index dc6ddcfc45a11..d8ab72f15c59c 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_button.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_name.tsx
@@ -4,34 +4,39 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { FC, useContext } from 'react';
+import React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
-import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
+import { EuiToolTip } from '@elastic/eui';
import { TRANSFORM_STATE } from '../../../../../../common';
-import {
- AuthorizationContext,
- createCapabilityFailureMessage,
-} from '../../../../lib/authorization';
+import { createCapabilityFailureMessage } from '../../../../lib/authorization';
import { TransformListRow } from '../../../../common';
-interface DeleteButtonProps {
- items: TransformListRow[];
- forceDisable?: boolean;
- onClick: (items: TransformListRow[]) => void;
-}
-
-const transformCanNotBeDeleted = (i: TransformListRow) =>
- ![TRANSFORM_STATE.STOPPED, TRANSFORM_STATE.FAILED].includes(i.stats.state);
+export const deleteActionNameText = i18n.translate(
+ 'xpack.transform.transformList.deleteActionNameText',
+ {
+ defaultMessage: 'Delete',
+ }
+);
-export const DeleteButton: FC = ({ items, forceDisable, onClick }) => {
- const isBulkAction = items.length > 1;
+const transformCanNotBeDeleted = (item: TransformListRow) =>
+ ![TRANSFORM_STATE.STOPPED, TRANSFORM_STATE.FAILED].includes(item.stats.state);
+export const isDeleteActionDisabled = (items: TransformListRow[], forceDisable: boolean) => {
const disabled = items.some(transformCanNotBeDeleted);
- const { canDeleteTransform } = useContext(AuthorizationContext).capabilities;
+ return forceDisable === true || disabled;
+};
- const buttonText = i18n.translate('xpack.transform.transformList.deleteActionName', {
- defaultMessage: 'Delete',
- });
+export interface DeleteActionNameProps {
+ canDeleteTransform: boolean;
+ disabled: boolean;
+ isBulkAction: boolean;
+}
+
+export const DeleteActionName: FC = ({
+ canDeleteTransform,
+ disabled,
+ isBulkAction,
+}) => {
const bulkDeleteButtonDisabledText = i18n.translate(
'xpack.transform.transformList.deleteBulkActionDisabledToolTipContent',
{
@@ -45,23 +50,6 @@ export const DeleteButton: FC = ({ items, forceDisable, onCli
}
);
- const buttonDisabled = forceDisable === true || disabled || !canDeleteTransform;
-
- const button = (
- onClick(items)}
- size="xs"
- >
- {buttonText}
-
- );
-
if (disabled || !canDeleteTransform) {
let content;
if (disabled) {
@@ -72,10 +60,10 @@ export const DeleteButton: FC = ({ items, forceDisable, onCli
return (
- {button}
+ <>{deleteActionNameText}>
);
}
- return button;
+ return <>{deleteActionNameText}>;
};
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/index.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/index.ts
index ef891d7c4a128..efc849e84add3 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/index.ts
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/index.ts
@@ -4,6 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { DeleteButton } from './delete_button';
-export { DeleteButtonModal } from './delete_button_modal';
export { useDeleteAction } from './use_delete_action';
+export { DeleteActionModal } from './delete_action_modal';
+export { isDeleteActionDisabled, DeleteActionName } from './delete_action_name';
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/use_delete_action.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/use_delete_action.tsx
similarity index 66%
rename from x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/use_delete_action.ts
rename to x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/use_delete_action.tsx
index d76eebe954d7b..e573709fa6e63 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/use_delete_action.ts
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/use_delete_action.tsx
@@ -4,15 +4,24 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { useMemo, useState } from 'react';
+import React, { useContext, useMemo, useState } from 'react';
import { TRANSFORM_STATE } from '../../../../../../common';
-import { TransformListRow } from '../../../../common';
+import { TransformListAction, TransformListRow } from '../../../../common';
import { useDeleteIndexAndTargetIndex, useDeleteTransforms } from '../../../../hooks';
+import { AuthorizationContext } from '../../../../lib/authorization';
+
+import {
+ deleteActionNameText,
+ isDeleteActionDisabled,
+ DeleteActionName,
+} from './delete_action_name';
export type DeleteAction = ReturnType;
-export const useDeleteAction = () => {
+export const useDeleteAction = (forceDisable: boolean) => {
+ const { canDeleteTransform } = useContext(AuthorizationContext).capabilities;
+
const deleteTransforms = useDeleteTransforms();
const [isModalVisible, setModalVisible] = useState(false);
@@ -58,7 +67,30 @@ export const useDeleteAction = () => {
}
};
+ const action: TransformListAction = useMemo(
+ () => ({
+ name: (item: TransformListRow) => (
+
+ ),
+ enabled: (item: TransformListRow) =>
+ !isDeleteActionDisabled([item], forceDisable) && canDeleteTransform,
+ description: deleteActionNameText,
+ icon: 'trash',
+ type: 'icon',
+ onClick: (item: TransformListRow) => openModal([item]),
+ 'data-test-subj': 'transformActionDelete',
+ }),
+ [canDeleteTransform, forceDisable]
+ );
+
return {
+ action,
closeModal,
deleteAndCloseModal,
deleteDestIndex,
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/edit_button.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/edit_action_name.tsx
similarity index 52%
rename from x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/edit_button.tsx
rename to x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/edit_action_name.tsx
index 7bae8807425fc..04c6ebe43c8c3 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/edit_button.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/edit_action_name.tsx
@@ -8,47 +8,30 @@ import React, { useContext, FC } from 'react';
import { i18n } from '@kbn/i18n';
-import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
+import { EuiToolTip } from '@elastic/eui';
import {
createCapabilityFailureMessage,
AuthorizationContext,
} from '../../../../lib/authorization';
-interface EditButtonProps {
- onClick: () => void;
-}
-export const EditButton: FC = ({ onClick }) => {
- const { canCreateTransform } = useContext(AuthorizationContext).capabilities;
-
- const buttonText = i18n.translate('xpack.transform.transformList.editActionName', {
+export const editActionNameText = i18n.translate(
+ 'xpack.transform.transformList.editActionNameText',
+ {
defaultMessage: 'Edit',
- });
-
- const buttonDisabled = !canCreateTransform;
-
- const button = (
-
- {buttonText}
-
- );
+ }
+);
+
+export const EditActionName: FC = () => {
+ const { canCreateTransform } = useContext(AuthorizationContext).capabilities;
if (!canCreateTransform) {
return (
- {button}
+ <>{editActionNameText}>
);
}
- return button;
+ return <>{editActionNameText}>;
};
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/index.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/index.ts
index 17a2ad9444f8d..a3077a8824045 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/index.ts
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/index.ts
@@ -4,5 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { EditButton } from './edit_button';
export { useEditAction } from './use_edit_action';
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.ts
deleted file mode 100644
index ace3ec8f636e6..0000000000000
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { useState } from 'react';
-
-import { TransformPivotConfig } from '../../../../common';
-
-export const useEditAction = () => {
- const [config, setConfig] = useState();
- const [isFlyoutVisible, setIsFlyoutVisible] = useState(false);
- const closeFlyout = () => setIsFlyoutVisible(false);
- const showFlyout = (newConfig: TransformPivotConfig) => {
- setConfig(newConfig);
- setIsFlyoutVisible(true);
- };
-
- return {
- config,
- closeFlyout,
- isFlyoutVisible,
- showFlyout,
- };
-};
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.tsx
new file mode 100644
index 0000000000000..1fe20f1acae5a
--- /dev/null
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.tsx
@@ -0,0 +1,45 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useContext, useMemo, useState } from 'react';
+
+import { TransformListAction, TransformListRow, TransformPivotConfig } from '../../../../common';
+import { AuthorizationContext } from '../../../../lib/authorization';
+
+import { editActionNameText, EditActionName } from './edit_action_name';
+
+export const useEditAction = (forceDisable: boolean) => {
+ const { canCreateTransform } = useContext(AuthorizationContext).capabilities;
+
+ const [config, setConfig] = useState();
+ const [isFlyoutVisible, setIsFlyoutVisible] = useState(false);
+ const closeFlyout = () => setIsFlyoutVisible(false);
+ const showFlyout = (newConfig: TransformPivotConfig) => {
+ setConfig(newConfig);
+ setIsFlyoutVisible(true);
+ };
+
+ const action: TransformListAction = useMemo(
+ () => ({
+ name: () => ,
+ enabled: () => canCreateTransform || !forceDisable,
+ description: editActionNameText,
+ icon: 'pencil',
+ type: 'icon',
+ onClick: (item: TransformListRow) => showFlyout(item.config),
+ 'data-test-subj': 'transformActionEdit',
+ }),
+ [canCreateTransform, forceDisable]
+ );
+
+ return {
+ action,
+ config,
+ closeFlyout,
+ isFlyoutVisible,
+ showFlyout,
+ };
+};
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/__snapshots__/start_button.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/__snapshots__/start_action_name.test.tsx.snap
similarity index 56%
rename from x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/__snapshots__/start_button.test.tsx.snap
rename to x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/__snapshots__/start_action_name.test.tsx.snap
index 543f8f9dfcffe..5a24e30a0657e 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/__snapshots__/start_button.test.tsx.snap
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/__snapshots__/start_action_name.test.tsx.snap
@@ -6,17 +6,6 @@ exports[`Transform: Transform List Actions Minimal initializatio
delay="regular"
position="top"
>
-
- Start
-
+ Start
`;
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/index.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/index.ts
index df6bbb7c61908..835df35c3c3bc 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/index.ts
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/index.ts
@@ -4,6 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { StartButton } from './start_button';
-export { StartButtonModal } from './start_button_modal';
export { useStartAction } from './use_start_action';
+export { StartActionModal } from './start_action_modal';
+export { StartActionName } from './start_action_name';
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_button_modal.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx
similarity index 94%
rename from x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_button_modal.tsx
rename to x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx
index 2ef0d20c45116..9f7dbee10cef0 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_button_modal.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx
@@ -10,12 +10,7 @@ import { EuiConfirmModal, EuiOverlayMask, EUI_MODAL_CONFIRM_BUTTON } from '@elas
import { StartAction } from './use_start_action';
-export const StartButtonModal: FC = ({
- closeModal,
- isModalVisible,
- items,
- startAndCloseModal,
-}) => {
+export const StartActionModal: FC = ({ closeModal, items, startAndCloseModal }) => {
const isBulkAction = items.length > 1;
const bulkStartModalTitle = i18n.translate('xpack.transform.transformList.bulkStartModalTitle', {
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_button.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_name.test.tsx
similarity index 77%
rename from x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_button.test.tsx
rename to x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_name.test.tsx
index b88e1257f56ad..9c06675d69ca4 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_button.test.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_name.test.tsx
@@ -5,10 +5,10 @@
*/
import { shallow } from 'enzyme';
-import React, { ComponentProps } from 'react';
+import React from 'react';
import { TransformListRow } from '../../../../common';
-import { StartButton } from './start_button';
+import { StartActionName, StartActionNameProps } from './start_action_name';
import transformListRow from '../../../../common/__mocks__/transform_list_row.json';
@@ -18,13 +18,12 @@ jest.mock('../../../../../app/app_dependencies');
describe('Transform: Transform List Actions ', () => {
test('Minimal initialization', () => {
const item: TransformListRow = transformListRow;
- const props: ComponentProps = {
+ const props: StartActionNameProps = {
forceDisable: false,
items: [item],
- onClick: () => {},
};
- const wrapper = shallow( );
+ const wrapper = shallow( );
expect(wrapper).toMatchSnapshot();
});
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_button.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_name.tsx
similarity index 73%
rename from x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_button.tsx
rename to x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_name.tsx
index 7f5595043b775..191df0c16cba0 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_button.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_name.tsx
@@ -6,7 +6,7 @@
import React, { FC, useContext } from 'react';
import { i18n } from '@kbn/i18n';
-import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
+import { EuiToolTip } from '@elastic/eui';
import { TRANSFORM_STATE } from '../../../../../../common';
@@ -16,19 +16,37 @@ import {
} from '../../../../lib/authorization';
import { TransformListRow, isCompletedBatchTransform } from '../../../../common';
-interface StartButtonProps {
+export const startActionNameText = i18n.translate(
+ 'xpack.transform.transformList.startActionNameText',
+ {
+ defaultMessage: 'Start',
+ }
+);
+
+export const isStartActionDisabled = (
+ items: TransformListRow[],
+ canStartStopTransform: boolean
+) => {
+ // Disable start for batch transforms which have completed.
+ const completedBatchTransform = items.some((i: TransformListRow) => isCompletedBatchTransform(i));
+ // Disable start action if one of the transforms is already started or trying to restart will throw error
+ const startedTransform = items.some(
+ (i: TransformListRow) => i.stats.state === TRANSFORM_STATE.STARTED
+ );
+
+ return (
+ !canStartStopTransform || completedBatchTransform || startedTransform || items.length === 0
+ );
+};
+
+export interface StartActionNameProps {
items: TransformListRow[];
forceDisable?: boolean;
- onClick: (items: TransformListRow[]) => void;
}
-export const StartButton: FC = ({ items, forceDisable, onClick }) => {
+export const StartActionName: FC = ({ items, forceDisable }) => {
const { canStartStopTransform } = useContext(AuthorizationContext).capabilities;
const isBulkAction = items.length > 1;
- const buttonText = i18n.translate('xpack.transform.transformList.startActionName', {
- defaultMessage: 'Start',
- });
-
// Disable start for batch transforms which have completed.
const completedBatchTransform = items.some((i: TransformListRow) => isCompletedBatchTransform(i));
// Disable start action if one of the transforms is already started or trying to restart will throw error
@@ -70,8 +88,7 @@ export const StartButton: FC = ({ items, forceDisable, onClick
);
}
- const actionIsDisabled =
- !canStartStopTransform || completedBatchTransform || startedTransform || items.length === 0;
+ const actionIsDisabled = isStartActionDisabled(items, canStartStopTransform);
let content: string | undefined;
if (actionIsDisabled && items.length > 0) {
@@ -84,30 +101,13 @@ export const StartButton: FC = ({ items, forceDisable, onClick
}
}
- const buttonDisabled = forceDisable === true || actionIsDisabled;
-
- const button = (
- onClick(items)}
- size="xs"
- >
- {buttonText}
-
- );
-
- if (buttonDisabled && content !== undefined) {
+ if ((forceDisable === true || actionIsDisabled) && content !== undefined) {
return (
- {button}
+ <>{startActionNameText}>
);
}
- return button;
+ return <>{startActionNameText}>;
};
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/use_start_action.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/use_start_action.ts
deleted file mode 100644
index 32d2dc6dabf86..0000000000000
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/use_start_action.ts
+++ /dev/null
@@ -1,42 +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 { useState } from 'react';
-
-import { TransformListRow } from '../../../../common';
-import { useStartTransforms } from '../../../../hooks';
-
-export type StartAction = ReturnType;
-export const useStartAction = () => {
- const startTransforms = useStartTransforms();
-
- const [isModalVisible, setModalVisible] = useState(false);
- const [items, setItems] = useState([]);
-
- const closeModal = () => setModalVisible(false);
-
- const startAndCloseModal = () => {
- setModalVisible(false);
- startTransforms(items);
- };
-
- const openModal = (newItems: TransformListRow[]) => {
- // EUI issue: Might trigger twice, one time as an array,
- // one time as a single object. See https://github.com/elastic/eui/issues/3679
- if (Array.isArray(newItems)) {
- setItems(newItems);
- setModalVisible(true);
- }
- };
-
- return {
- closeModal,
- isModalVisible,
- items,
- openModal,
- startAndCloseModal,
- };
-};
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/use_start_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/use_start_action.tsx
new file mode 100644
index 0000000000000..8d6a4376c55b3
--- /dev/null
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/use_start_action.tsx
@@ -0,0 +1,66 @@
+/*
+ * 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, { useContext, useMemo, useState } from 'react';
+
+import { TRANSFORM_STATE } from '../../../../../../common';
+
+import { AuthorizationContext } from '../../../../lib/authorization';
+import { TransformListAction, TransformListRow } from '../../../../common';
+import { useStartTransforms } from '../../../../hooks';
+
+import { isStartActionDisabled, startActionNameText, StartActionName } from './start_action_name';
+
+export type StartAction = ReturnType;
+export const useStartAction = (forceDisable: boolean) => {
+ const { canStartStopTransform } = useContext(AuthorizationContext).capabilities;
+
+ const startTransforms = useStartTransforms();
+
+ const [isModalVisible, setModalVisible] = useState(false);
+ const [items, setItems] = useState([]);
+
+ const closeModal = () => setModalVisible(false);
+
+ const startAndCloseModal = () => {
+ setModalVisible(false);
+ startTransforms(items);
+ };
+
+ const openModal = (newItems: TransformListRow[]) => {
+ // EUI issue: Might trigger twice, one time as an array,
+ // one time as a single object. See https://github.com/elastic/eui/issues/3679
+ if (Array.isArray(newItems)) {
+ setItems(newItems);
+ setModalVisible(true);
+ }
+ };
+
+ const action: TransformListAction = useMemo(
+ () => ({
+ name: (item: TransformListRow) => (
+
+ ),
+ available: (item: TransformListRow) => item.stats.state === TRANSFORM_STATE.STOPPED,
+ enabled: (item: TransformListRow) => !isStartActionDisabled([item], canStartStopTransform),
+ description: startActionNameText,
+ icon: 'play',
+ type: 'icon',
+ onClick: (item: TransformListRow) => openModal([item]),
+ 'data-test-subj': 'transformActionStart',
+ }),
+ [canStartStopTransform, forceDisable]
+ );
+
+ return {
+ action,
+ closeModal,
+ isModalVisible,
+ items,
+ openModal,
+ startAndCloseModal,
+ };
+};
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/__snapshots__/stop_button.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/__snapshots__/stop_action_name.test.tsx.snap
similarity index 56%
rename from x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/__snapshots__/stop_button.test.tsx.snap
rename to x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/__snapshots__/stop_action_name.test.tsx.snap
index 646162d370ac6..e3740ae9a0978 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/__snapshots__/stop_button.test.tsx.snap
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/__snapshots__/stop_action_name.test.tsx.snap
@@ -6,17 +6,6 @@ exports[`Transform: Transform List Actions Minimal initialization
delay="regular"
position="top"
>
-
- Stop
-
+ Stop
`;
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/index.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/index.ts
index 858b6c70501b3..3b4da24298a96 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/index.ts
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/index.ts
@@ -4,4 +4,5 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { StopButton } from './stop_button';
+export { useStopAction } from './use_stop_action';
+export { StopActionName } from './stop_action_name';
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_button.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_action_name.test.tsx
similarity index 79%
rename from x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_button.test.tsx
rename to x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_action_name.test.tsx
index d9c07a9dccc8f..643e8c6126f0f 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_button.test.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_action_name.test.tsx
@@ -5,10 +5,10 @@
*/
import { shallow } from 'enzyme';
-import React, { ComponentProps } from 'react';
+import React from 'react';
import { TransformListRow } from '../../../../common';
-import { StopButton } from './stop_button';
+import { StopActionName, StopActionNameProps } from './stop_action_name';
import transformListRow from '../../../../common/__mocks__/transform_list_row.json';
@@ -18,12 +18,12 @@ jest.mock('../../../../../app/app_dependencies');
describe('Transform: Transform List Actions ', () => {
test('Minimal initialization', () => {
const item: TransformListRow = transformListRow;
- const props: ComponentProps = {
+ const props: StopActionNameProps = {
forceDisable: false,
items: [item],
};
- const wrapper = shallow( );
+ const wrapper = shallow( );
expect(wrapper).toMatchSnapshot();
});
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_button.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_action_name.tsx
similarity index 67%
rename from x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_button.tsx
rename to x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_action_name.tsx
index 1c672193dd1ee..e1ea82cb371e8 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_button.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_action_name.tsx
@@ -6,7 +6,7 @@
import React, { FC, useContext } from 'react';
import { i18n } from '@kbn/i18n';
-import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
+import { EuiToolTip } from '@elastic/eui';
import { TRANSFORM_STATE } from '../../../../../../common';
@@ -15,19 +15,34 @@ import {
createCapabilityFailureMessage,
AuthorizationContext,
} from '../../../../lib/authorization';
-import { useStopTransforms } from '../../../../hooks';
-interface StopButtonProps {
+export const stopActionNameText = i18n.translate(
+ 'xpack.transform.transformList.stopActionNameText',
+ {
+ defaultMessage: 'Stop',
+ }
+);
+
+export const isStopActionDisabled = (
+ items: TransformListRow[],
+ canStartStopTransform: boolean,
+ forceDisable: boolean
+) => {
+ // Disable stop action if one of the transforms is stopped already
+ const stoppedTransform = items.some(
+ (i: TransformListRow) => i.stats.state === TRANSFORM_STATE.STOPPED
+ );
+
+ return forceDisable === true || !canStartStopTransform || stoppedTransform === true;
+};
+
+export interface StopActionNameProps {
items: TransformListRow[];
forceDisable?: boolean;
}
-export const StopButton: FC = ({ items, forceDisable }) => {
+export const StopActionName: FC = ({ items, forceDisable }) => {
const isBulkAction = items.length > 1;
const { canStartStopTransform } = useContext(AuthorizationContext).capabilities;
- const stopTransforms = useStopTransforms();
- const buttonText = i18n.translate('xpack.transform.transformList.stopActionName', {
- defaultMessage: 'Stop',
- });
// Disable stop action if one of the transforms is stopped already
const stoppedTransform = items.some(
@@ -52,28 +67,6 @@ export const StopButton: FC = ({ items, forceDisable }) => {
);
}
- const handleStop = () => {
- stopTransforms(items);
- };
-
- const buttonDisabled =
- forceDisable === true || !canStartStopTransform || stoppedTransform === true;
-
- const button = (
-
- {buttonText}
-
- );
-
if (!canStartStopTransform || stoppedTransform) {
return (
= ({ items, forceDisable }) => {
: stoppedTransformMessage
}
>
- {button}
+ <>{stopActionNameText}>
);
}
- return button;
+ return <>{stopActionNameText}>;
};
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/use_stop_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/use_stop_action.tsx
new file mode 100644
index 0000000000000..e0a7e0b489ab6
--- /dev/null
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/use_stop_action.tsx
@@ -0,0 +1,45 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useCallback, useContext, useMemo } from 'react';
+
+import { TRANSFORM_STATE } from '../../../../../../common';
+
+import { AuthorizationContext } from '../../../../lib/authorization';
+import { TransformListAction, TransformListRow } from '../../../../common';
+import { useStopTransforms } from '../../../../hooks';
+
+import { isStopActionDisabled, stopActionNameText, StopActionName } from './stop_action_name';
+
+export type StopAction = ReturnType;
+export const useStopAction = (forceDisable: boolean) => {
+ const { canStartStopTransform } = useContext(AuthorizationContext).capabilities;
+
+ const stopTransforms = useStopTransforms();
+
+ const clickHandler = useCallback((item: TransformListRow) => stopTransforms([item]), [
+ stopTransforms,
+ ]);
+
+ const action: TransformListAction = useMemo(
+ () => ({
+ name: (item: TransformListRow) => (
+
+ ),
+ available: (item: TransformListRow) => item.stats.state !== TRANSFORM_STATE.STOPPED,
+ enabled: (item: TransformListRow) =>
+ !isStopActionDisabled([item], canStartStopTransform, forceDisable),
+ description: stopActionNameText,
+ icon: 'stop',
+ type: 'icon',
+ onClick: clickHandler,
+ 'data-test-subj': 'transformActionStop',
+ }),
+ [canStartStopTransform, forceDisable, clickHandler]
+ );
+
+ return { action };
+};
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx
index a31251943061a..cf32bf0455a1b 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx
@@ -31,13 +31,19 @@ import {
TRANSFORM_MODE,
TRANSFORM_LIST_COLUMN,
} from '../../../../common';
+import { useStopTransforms } from '../../../../hooks';
import { AuthorizationContext } from '../../../../lib/authorization';
import { CreateTransformButton } from '../create_transform_button';
import { RefreshTransformListButton } from '../refresh_transform_list_button';
-import { useDeleteAction, DeleteButton, DeleteButtonModal } from '../action_delete';
-import { useStartAction, StartButton, StartButtonModal } from '../action_start';
-import { StopButton } from '../action_stop';
+import {
+ isDeleteActionDisabled,
+ useDeleteAction,
+ DeleteActionName,
+ DeleteActionModal,
+} from '../action_delete';
+import { useStartAction, StartActionName, StartActionModal } from '../action_start';
+import { StopActionName } from '../action_stop';
import { ItemIdToExpandedRowMap, Clause, TermClause, FieldClause, Value } from './common';
import { getTaskStateBadge, useColumns } from './use_columns';
@@ -89,8 +95,8 @@ export const TransformList: FC = ({
const [transformSelection, setTransformSelection] = useState([]);
const [isActionsMenuOpen, setIsActionsMenuOpen] = useState(false);
- const bulkStartAction = useStartAction();
- const bulkDeleteAction = useDeleteAction();
+ const bulkStartAction = useStartAction(false);
+ const bulkDeleteAction = useDeleteAction(false);
const [searchError, setSearchError] = useState(undefined);
@@ -100,6 +106,8 @@ export const TransformList: FC = ({
const [sortField, setSortField] = useState(TRANSFORM_LIST_COLUMN.ID);
const [sortDirection, setSortDirection] = useState('asc');
+ const stopTransforms = useStopTransforms();
+
const { capabilities } = useContext(AuthorizationContext);
const disabled =
!capabilities.canCreateTransform ||
@@ -257,13 +265,23 @@ export const TransformList: FC = ({
const bulkActionMenuItems = [
-
+ bulkStartAction.openModal(transformSelection)}>
+
+
,
-
+ stopTransforms(transformSelection)}>
+
+
,
-
+ bulkDeleteAction.openModal(transformSelection)}>
+
+
,
];
@@ -381,8 +399,8 @@ export const TransformList: FC = ({
return (
{/* Bulk Action Modals */}
- {bulkStartAction.isModalVisible &&
}
- {bulkDeleteAction.isModalVisible &&
}
+ {bulkStartAction.isModalVisible &&
}
+ {bulkDeleteAction.isModalVisible &&
}
{/* Single Action Modals */}
{singleActionModals}
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.test.tsx
index e58d5ed572667..dafde8dd58123 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.test.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.test.tsx
@@ -14,12 +14,17 @@ jest.mock('../../../../../app/app_dependencies');
describe('Transform: Transform List Actions', () => {
test('useActions()', () => {
const { result } = renderHook(() => useActions({ forceDisable: false }));
- const actions: ReturnType
['actions'] = result.current.actions;
+ const actions = result.current.actions;
- expect(actions).toHaveLength(4);
- expect(typeof actions[0].render).toBe('function');
- expect(typeof actions[1].render).toBe('function');
- expect(typeof actions[2].render).toBe('function');
- expect(typeof actions[3].render).toBe('function');
+ // Using `any` for the callback. Somehow the EUI types don't pass
+ // on the `data-test-subj` attribute correctly. We're interested
+ // in the runtime result here anyway.
+ expect(actions.map((a: any) => a['data-test-subj'])).toStrictEqual([
+ 'transformActionStart',
+ 'transformActionStop',
+ 'transformActionEdit',
+ 'transformActionClone',
+ 'transformActionDelete',
+ ]);
});
});
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx
index a6b1aa1a1b5c5..616e03a61e0ed 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx
@@ -6,74 +6,47 @@
import React from 'react';
-import { EuiTableComputedColumnType } from '@elastic/eui';
-
-import { TRANSFORM_STATE } from '../../../../../../common';
+import { EuiTableActionsColumnType } from '@elastic/eui';
import { TransformListRow } from '../../../../common';
-import { CloneButton } from '../action_clone';
-import { useDeleteAction, DeleteButton, DeleteButtonModal } from '../action_delete';
+import { useCloneAction } from '../action_clone';
+import { useDeleteAction, DeleteActionModal } from '../action_delete';
import { EditTransformFlyout } from '../edit_transform_flyout';
-import { useEditAction, EditButton } from '../action_edit';
-import { useStartAction, StartButton, StartButtonModal } from '../action_start';
-import { StopButton } from '../action_stop';
+import { useEditAction } from '../action_edit';
+import { useStartAction, StartActionModal } from '../action_start';
+import { useStopAction } from '../action_stop';
export const useActions = ({
forceDisable,
}: {
forceDisable: boolean;
-}): { actions: Array>; modals: JSX.Element } => {
- const deleteAction = useDeleteAction();
- const editAction = useEditAction();
- const startAction = useStartAction();
+}): {
+ actions: EuiTableActionsColumnType['actions'];
+ modals: JSX.Element;
+} => {
+ const cloneAction = useCloneAction(forceDisable);
+ const deleteAction = useDeleteAction(forceDisable);
+ const editAction = useEditAction(forceDisable);
+ const startAction = useStartAction(forceDisable);
+ const stopAction = useStopAction(forceDisable);
return {
modals: (
<>
- {startAction.isModalVisible && }
+ {startAction.isModalVisible && }
{editAction.config && editAction.isFlyoutVisible && (
)}
- {deleteAction.isModalVisible && }
+ {deleteAction.isModalVisible && }
>
),
actions: [
- {
- render: (item: TransformListRow) => {
- if (item.stats.state === TRANSFORM_STATE.STOPPED) {
- return (
-
- );
- }
- return ;
- },
- },
- {
- render: (item: TransformListRow) => {
- return editAction.showFlyout(item.config)} />;
- },
- },
- {
- render: (item: TransformListRow) => {
- return ;
- },
- },
- {
- render: (item: TransformListRow) => {
- return (
-
- );
- },
- },
+ startAction.action,
+ stopAction.action,
+ editAction.action,
+ cloneAction.action,
+ deleteAction.action,
],
};
};
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 814c532657f60..6a9909c4291ff 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -11187,11 +11187,9 @@
"xpack.ml.dataframe.analytics.regressionExploration.trainingErrorTitle": "トレーニングエラー",
"xpack.ml.dataframe.analytics.regressionExploration.trainingFilterText": ".テストデータをフィルタリングしています。",
"xpack.ml.dataframe.analyticsList.analyticsDetails.tabs.analyticsMessagesLabel": "ジョブメッセージ",
- "xpack.ml.dataframe.analyticsList.cloneJobButtonLabel": "ジョブのクローンを作成します",
"xpack.ml.dataframe.analyticsList.completeBatchAnalyticsToolTip": "{analyticsId}は完了済みの分析ジョブで、再度開始できません。",
"xpack.ml.dataframe.analyticsList.createDataFrameAnalyticsButton": "ジョブを作成",
"xpack.ml.dataframe.analyticsList.deleteActionDisabledToolTipContent": "削除するにはデータフレーム分析ジョブを停止してください。",
- "xpack.ml.dataframe.analyticsList.deleteActionName": "削除",
"xpack.ml.dataframe.analyticsList.deleteAnalyticsErrorMessage": "データフレーム分析ジョブ{analyticsId}の削除中にエラーが発生しました。",
"xpack.ml.dataframe.analyticsList.deleteAnalyticsPrivilegeErrorMessage": "ユーザーはインデックス{indexName}を削除する権限がありません。{error}",
"xpack.ml.dataframe.analyticsList.deleteAnalyticsSuccessMessage": "データフレーム分析ジョブ{analyticsId}の削除リクエストが受け付けられました。",
@@ -11206,7 +11204,6 @@
"xpack.ml.dataframe.analyticsList.deleteModalTitle": "{analyticsId}の削除",
"xpack.ml.dataframe.analyticsList.description": "説明",
"xpack.ml.dataframe.analyticsList.destinationIndex": "デスティネーションインデックス",
- "xpack.ml.dataframe.analyticsList.editActionName": "編集",
"xpack.ml.dataframe.analyticsList.editActionPermissionTooltip": "分析ジョブを編集する権限がありません。",
"xpack.ml.dataframe.analyticsList.editFlyout.allowLazyStartAriaLabel": "lazy startの許可を更新します。",
"xpack.ml.dataframe.analyticsList.editFlyout.allowLazyStartFalseValue": "False",
@@ -11246,7 +11243,6 @@
"xpack.ml.dataframe.analyticsList.rowExpand": "{analyticsId}の詳細を表示",
"xpack.ml.dataframe.analyticsList.showDetailsColumn.screenReaderDescription": "このカラムには各ジョブの詳細を示すクリック可能なコントロールが含まれます",
"xpack.ml.dataframe.analyticsList.sourceIndex": "ソースインデックス",
- "xpack.ml.dataframe.analyticsList.startActionName": "開始",
"xpack.ml.dataframe.analyticsList.startAnalyticsErrorTitle": "ジョブの開始エラー",
"xpack.ml.dataframe.analyticsList.startAnalyticsSuccessMessage": "データフレーム分析 {analyticsId} の開始リクエストが受け付けられました。",
"xpack.ml.dataframe.analyticsList.startModalBody": "データフレーム分析ジョブは、クラスターの検索とインデックスによる負荷を増やします。過剰な負荷が生じた場合は分析ジョブを停止してください。この分析ジョブを開始してよろしいですか?",
@@ -11255,7 +11251,6 @@
"xpack.ml.dataframe.analyticsList.startModalTitle": "{analyticsId}の開始",
"xpack.ml.dataframe.analyticsList.status": "ステータス",
"xpack.ml.dataframe.analyticsList.statusFilter": "ステータス",
- "xpack.ml.dataframe.analyticsList.stopActionName": "停止",
"xpack.ml.dataframe.analyticsList.stopAnalyticsErrorMessage": "データフレーム分析{analyticsId}の停止中にエラーが発生しました。{error}",
"xpack.ml.dataframe.analyticsList.stopAnalyticsSuccessMessage": "データフレーム分析 {analyticsId} の停止リクエストが受け付けられました。",
"xpack.ml.dataframe.analyticsList.tableActionLabel": "アクション",
@@ -12305,7 +12300,6 @@
"xpack.ml.overview.anomalyDetection.emptyPromptText": "異常検知により、時系列データの異常な動作を検出できます。データに隠れた異常を自動的に検出して問題をより素早く解決しましょう。",
"xpack.ml.overview.anomalyDetection.errorPromptTitle": "異常検出ジョブリストの取得中にエラーが発生しました。",
"xpack.ml.overview.anomalyDetection.errorWithFetchingAnomalyScoreNotificationErrorMessage": "異常スコアの取得中にエラーが発生しました: {error}",
- "xpack.ml.overview.anomalyDetection.exploreActionName": "探索",
"xpack.ml.overview.anomalyDetection.manageJobsButtonText": "ジョブの管理",
"xpack.ml.overview.anomalyDetection.panelTitle": "異常検知",
"xpack.ml.overview.anomalyDetection.refreshJobsButtonText": "更新",
@@ -18244,12 +18238,10 @@
"xpack.transform.transformList.bulkDeleteTransformSuccessMessage": "{count} {count, plural, one {個の変換} other {個の変換}}を正常に削除しました。",
"xpack.transform.transformList.bulkForceDeleteModalBody": "{count, plural, one {この} other {これらの}} {count} {count, plural, one {変換} other {変換}}を強制的に削除しますか?{count, plural, one {} other {}}現在の状態に関係なく、{count, plural, one {個の変換} other {個の変換}}は削除されます。",
"xpack.transform.transformList.bulkStartModalTitle": "{count} 件の{count, plural, one {変換} other {変換}}を開始",
- "xpack.transform.transformList.cloneActionName": "クローンを作成",
"xpack.transform.transformList.completeBatchTransformBulkActionToolTip": "1 つまたは複数の変換が完了済みの一斉変換で、再度開始できません。",
"xpack.transform.transformList.completeBatchTransformToolTip": "{transformId} は完了済みの一斉変換で、再度開始できません。",
"xpack.transform.transformList.createTransformButton": "変換の作成",
"xpack.transform.transformList.deleteActionDisabledToolTipContent": "削除するにはデータフレームジョブを停止してください。",
- "xpack.transform.transformList.deleteActionName": "削除",
"xpack.transform.transformList.deleteBulkActionDisabledToolTipContent": "削除するには、選択された変換のうちの 1 つまたは複数を停止する必要があります。",
"xpack.transform.transformList.deleteModalBody": "この変換を削除してよろしいですか?",
"xpack.transform.transformList.deleteModalCancelButton": "キャンセル",
@@ -18258,7 +18250,6 @@
"xpack.transform.transformList.deleteTransformErrorMessage": "変換 {transformId} の削除中にエラーが発生しました",
"xpack.transform.transformList.deleteTransformGenericErrorMessage": "変換を削除するための API エンドポイントの呼び出し中にエラーが発生しました。",
"xpack.transform.transformList.deleteTransformSuccessMessage": "変換 {transformId} の削除リクエストが受け付けられました。",
- "xpack.transform.transformList.editActionName": "編集",
"xpack.transform.transformList.editFlyoutCalloutDocs": "ドキュメントを表示",
"xpack.transform.transformList.editFlyoutCalloutText": "このフォームでは、変換を更新できます。更新できるプロパティのリストは、変換を作成するときに定義できるリストのサブセットです。",
"xpack.transform.transformList.editFlyoutCancelButtonText": "キャンセル",
@@ -18280,7 +18271,6 @@
"xpack.transform.transformList.rowCollapse": "{transformId} の詳細を非表示",
"xpack.transform.transformList.rowExpand": "{transformId} の詳細を表示",
"xpack.transform.transformList.showDetailsColumn.screenReaderDescription": "このカラムには変換ごとの詳細を示すクリック可能なコントロールが含まれます",
- "xpack.transform.transformList.startActionName": "開始",
"xpack.transform.transformList.startedTransformBulkToolTip": "1 つまたは複数の変換が既に開始済みです。",
"xpack.transform.transformList.startedTransformToolTip": "{transformId} は既に開始済みです。",
"xpack.transform.transformList.startModalBody": "変換は、クラスターの検索とインデックスによる負荷を増やします。過剰な負荷が生じた場合は変換を停止してください。{count, plural, one {この} other {これら}} {count} 件の{count, plural, one {変換} other {変換}}を開始してよろしいですか?",
@@ -18289,7 +18279,6 @@
"xpack.transform.transformList.startModalTitle": "{transformId} を開始",
"xpack.transform.transformList.startTransformErrorMessage": "変換 {transformId} の開始中にエラーが発生しました",
"xpack.transform.transformList.startTransformSuccessMessage": "変換 {transformId} の開始リクエストが受け付けられました。",
- "xpack.transform.transformList.stopActionName": "停止",
"xpack.transform.transformList.stoppedTransformBulkToolTip": "1 つまたは複数の変換が既に開始済みです。",
"xpack.transform.transformList.stoppedTransformToolTip": "{transformId} は既に停止済みです。",
"xpack.transform.transformList.stopTransformErrorMessage": "データフレーム変換 {transformId} の停止中にエラーが発生しました",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 667706c886bc8..a2565d8af3ec9 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -11190,11 +11190,9 @@
"xpack.ml.dataframe.analytics.regressionExploration.trainingErrorTitle": "训练误差",
"xpack.ml.dataframe.analytics.regressionExploration.trainingFilterText": ".筛留测试数据。",
"xpack.ml.dataframe.analyticsList.analyticsDetails.tabs.analyticsMessagesLabel": "作业消息",
- "xpack.ml.dataframe.analyticsList.cloneJobButtonLabel": "克隆作业",
"xpack.ml.dataframe.analyticsList.completeBatchAnalyticsToolTip": "{analyticsId} 为已完成的分析作业,无法重新启动。",
"xpack.ml.dataframe.analyticsList.createDataFrameAnalyticsButton": "创建作业",
"xpack.ml.dataframe.analyticsList.deleteActionDisabledToolTipContent": "停止数据帧分析作业,以便将其删除。",
- "xpack.ml.dataframe.analyticsList.deleteActionName": "删除",
"xpack.ml.dataframe.analyticsList.deleteAnalyticsErrorMessage": "删除数据帧分析作业 {analyticsId} 时发生错误",
"xpack.ml.dataframe.analyticsList.deleteAnalyticsPrivilegeErrorMessage": "用户无权删除索引 {indexName}:{error}",
"xpack.ml.dataframe.analyticsList.deleteAnalyticsSuccessMessage": "删除的数据帧分析作业 {analyticsId} 的请求已确认。",
@@ -11210,7 +11208,6 @@
"xpack.ml.dataframe.analyticsList.deleteTargetIndexPatternTitle": "删除索引模式 {indexPattern}",
"xpack.ml.dataframe.analyticsList.description": "描述",
"xpack.ml.dataframe.analyticsList.destinationIndex": "目标 IP",
- "xpack.ml.dataframe.analyticsList.editActionName": "编辑",
"xpack.ml.dataframe.analyticsList.editActionPermissionTooltip": "您无权编辑分析作业。",
"xpack.ml.dataframe.analyticsList.editFlyout.allowLazyStartAriaLabel": "更新允许惰性启动。",
"xpack.ml.dataframe.analyticsList.editFlyout.allowLazyStartFalseValue": "False",
@@ -11250,7 +11247,6 @@
"xpack.ml.dataframe.analyticsList.rowExpand": "显示 {analyticsId} 的详情",
"xpack.ml.dataframe.analyticsList.showDetailsColumn.screenReaderDescription": "此列包含可单击控件,用于显示每个作业的更多详情",
"xpack.ml.dataframe.analyticsList.sourceIndex": "源索引",
- "xpack.ml.dataframe.analyticsList.startActionName": "开始",
"xpack.ml.dataframe.analyticsList.startAnalyticsErrorTitle": "启动作业时出错",
"xpack.ml.dataframe.analyticsList.startAnalyticsSuccessMessage": "数据帧分析 {analyticsId} 启动请求已确认。",
"xpack.ml.dataframe.analyticsList.startModalBody": "数据帧分析作业将增加集群的搜索和索引负荷。如果负荷超载,请停止分析作业。是否确定要启动此分析作业?",
@@ -11259,7 +11255,6 @@
"xpack.ml.dataframe.analyticsList.startModalTitle": "启动 {analyticsId}",
"xpack.ml.dataframe.analyticsList.status": "状态",
"xpack.ml.dataframe.analyticsList.statusFilter": "状态",
- "xpack.ml.dataframe.analyticsList.stopActionName": "停止",
"xpack.ml.dataframe.analyticsList.stopAnalyticsErrorMessage": "停止数据帧分析 {analyticsId} 时发生错误:{error}",
"xpack.ml.dataframe.analyticsList.stopAnalyticsSuccessMessage": "数据帧分析 {analyticsId} 停止请求已确认。",
"xpack.ml.dataframe.analyticsList.tableActionLabel": "操作",
@@ -12309,7 +12304,6 @@
"xpack.ml.overview.anomalyDetection.emptyPromptText": "通过异常检测,可发现时序数据中的异常行为。开始自动发现数据中隐藏的异常并更快捷地解决问题。",
"xpack.ml.overview.anomalyDetection.errorPromptTitle": "获取异常检测作业列表时出错。",
"xpack.ml.overview.anomalyDetection.errorWithFetchingAnomalyScoreNotificationErrorMessage": "获取异常分数时出错:{error}",
- "xpack.ml.overview.anomalyDetection.exploreActionName": "浏览",
"xpack.ml.overview.anomalyDetection.manageJobsButtonText": "管理作业",
"xpack.ml.overview.anomalyDetection.panelTitle": "异常检测",
"xpack.ml.overview.anomalyDetection.refreshJobsButtonText": "刷新",
@@ -18251,12 +18245,10 @@
"xpack.transform.transformList.bulkDeleteTransformSuccessMessage": "已成功删除 {count} 个{count, plural, one {转换} other {转换}}。",
"xpack.transform.transformList.bulkForceDeleteModalBody": "确定要强制删除{count, plural, one {这} other {这}} {count} 个{count, plural, one {转换} other {转换}}?无论{count, plural, one {转换} other {转换}}的当前状态如何,都将删除{count, plural, one {} other {}}。",
"xpack.transform.transformList.bulkStartModalTitle": "启动 {count} 个 {count, plural, one {转换} other {转换}}?",
- "xpack.transform.transformList.cloneActionName": "克隆",
"xpack.transform.transformList.completeBatchTransformBulkActionToolTip": "一个或多个转换为已完成批量转换,无法重新启动。",
"xpack.transform.transformList.completeBatchTransformToolTip": "{transformId} 为已完成批量转换,无法重新启动。",
"xpack.transform.transformList.createTransformButton": "创建转换",
"xpack.transform.transformList.deleteActionDisabledToolTipContent": "停止数据帧作业,以便将其删除。",
- "xpack.transform.transformList.deleteActionName": "删除",
"xpack.transform.transformList.deleteBulkActionDisabledToolTipContent": "一个或多个选定数据帧转换必须停止,才能删除。",
"xpack.transform.transformList.deleteModalBody": "是否确定要删除此转换?",
"xpack.transform.transformList.deleteModalCancelButton": "取消",
@@ -18265,7 +18257,6 @@
"xpack.transform.transformList.deleteTransformErrorMessage": "删除转换 {transformId} 时发生错误",
"xpack.transform.transformList.deleteTransformGenericErrorMessage": "调用用于删除转换的 API 终端节点时发生错误。",
"xpack.transform.transformList.deleteTransformSuccessMessage": "删除转换 {transformId} 的请求已确认。",
- "xpack.transform.transformList.editActionName": "编辑",
"xpack.transform.transformList.editFlyoutCalloutDocs": "查看文档",
"xpack.transform.transformList.editFlyoutCalloutText": "此表单允许您更新转换。可以更新的属性列表是创建转换时可以定义的列表子集。",
"xpack.transform.transformList.editFlyoutCancelButtonText": "取消",
@@ -18287,7 +18278,6 @@
"xpack.transform.transformList.rowCollapse": "隐藏 {transformId} 的详情",
"xpack.transform.transformList.rowExpand": "显示 {transformId} 的详情",
"xpack.transform.transformList.showDetailsColumn.screenReaderDescription": "此列包含可单击控件,用于显示每个转换的更多详情",
- "xpack.transform.transformList.startActionName": "开始",
"xpack.transform.transformList.startedTransformBulkToolTip": "一个或多个选定数据帧转换已启动。",
"xpack.transform.transformList.startedTransformToolTip": "{transformId} 已启动。",
"xpack.transform.transformList.startModalBody": "转换将增加集群的搜索和索引负荷。如果负荷超载,请停止转换。是否确定要启动{count, plural, one {这} other {这}} {count} 个 {count, plural, one {转换} other {转换}}?",
@@ -18296,7 +18286,6 @@
"xpack.transform.transformList.startModalTitle": "启动 {transformId}",
"xpack.transform.transformList.startTransformErrorMessage": "启动转换 {transformId} 时发生错误",
"xpack.transform.transformList.startTransformSuccessMessage": "启动转换 {transformId} 的请求已确认。",
- "xpack.transform.transformList.stopActionName": "停止",
"xpack.transform.transformList.stoppedTransformBulkToolTip": "一个或多个选定数据帧转换已停止。",
"xpack.transform.transformList.stoppedTransformToolTip": "{transformId} 已停止。",
"xpack.transform.transformList.stopTransformErrorMessage": "停止数据帧转换 {transformId} 时发生错误",
From 88802cb488278748e576266cea87ecae36a9d257 Mon Sep 17 00:00:00 2001
From: Alexey Antonov
Date: Wed, 19 Aug 2020 17:26:41 +0300
Subject: [PATCH 22/85] [Visualize] Horizontal Bar Percentiles Overlapping
(#75315)
* [Visualize] Horizontal Bar Percentiles Overlapping
Closes: #74986
* add tests
Co-authored-by: Elastic Machine
---
.../point_series/_point_series.js | 6 +-
.../point_series/_point_series.test.js | 114 ++++++++++++++++++
.../point_series/column_chart.js | 9 +-
3 files changed, 124 insertions(+), 5 deletions(-)
create mode 100644 src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/_point_series.test.js
diff --git a/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/_point_series.js b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/_point_series.js
index 1b084e4142445..d3829d45a88d5 100644
--- a/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/_point_series.js
+++ b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/_point_series.js
@@ -54,18 +54,18 @@ export class PointSeries {
}, 0);
}
- getGroupedNum(data) {
+ getGroupedNum(seriesId) {
let i = 0;
const stacks = [];
for (const seri of this.baseChart.chartConfig.series) {
const valueAxis = seri.valueAxis || this.baseChart.handler.valueAxes[0].id;
const isStacked = seri.mode === 'stacked';
if (!isStacked) {
- if (seri.data === data) return i;
+ if (seri.data.rawId === seriesId) return i;
i++;
} else {
if (!(valueAxis in stacks)) stacks[valueAxis] = i++;
- if (seri.data === data) return stacks[valueAxis];
+ if (seri.data.rawId === seriesId) return stacks[valueAxis];
}
}
return 0;
diff --git a/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/_point_series.test.js b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/_point_series.test.js
new file mode 100644
index 0000000000000..cedbe2f443f41
--- /dev/null
+++ b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/_point_series.test.js
@@ -0,0 +1,114 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { PointSeries } from './_point_series';
+
+describe('Point Series', () => {
+ describe('getGroupedNum', () => {
+ let handler;
+
+ beforeEach(() => {
+ handler = {
+ visConfig: {
+ get: jest.fn(),
+ },
+ pointSeries: {
+ chartConfig: {
+ series: [],
+ },
+ handler: {
+ valueAxes: [{ id: 'stackedId' }],
+ },
+ },
+ };
+ });
+
+ describe('normal mode', () => {
+ let pointSeries;
+
+ beforeEach(() => {
+ handler.pointSeries.chartConfig.series = [
+ {
+ mode: 'normal',
+ data: {
+ label: '1st',
+ rawId: 'col-1',
+ },
+ },
+ {
+ mode: 'normal',
+ data: {
+ label: '2nd',
+ rawId: 'col-2',
+ },
+ },
+ ];
+
+ pointSeries = new PointSeries(handler, [{}], {}, {});
+ });
+
+ test('should calculate correct group num', () => {
+ expect(pointSeries.getGroupedNum('col-2')).toBe(1);
+ });
+
+ test('should return "0" for not found id', () => {
+ expect(pointSeries.getGroupedNum('wrong-id')).toBe(0);
+ });
+ });
+
+ describe('stacked mode', () => {
+ let pointSeries;
+
+ beforeEach(() => {
+ handler.pointSeries.chartConfig.series = [
+ {
+ mode: 'normal',
+ data: {
+ label: '1st',
+ rawId: 'col-1',
+ },
+ },
+ {
+ mode: 'stacked',
+ data: {
+ label: '3rd',
+ rawId: 'col-2',
+ },
+ },
+ {
+ mode: 'stacked',
+ data: {
+ label: '2nd',
+ rawId: 'col-3',
+ },
+ },
+ ];
+
+ pointSeries = new PointSeries(handler, [{}], {}, {});
+ });
+
+ test('should calculate correct group num', () => {
+ expect(pointSeries.getGroupedNum('col-2')).toBe(1);
+ });
+
+ test('should return "0" for not found id', () => {
+ expect(pointSeries.getGroupedNum('wrong-id')).toBe(0);
+ });
+ });
+ });
+});
diff --git a/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/column_chart.js b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/column_chart.js
index cc64e0d746fbf..1369bf1dff68a 100644
--- a/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/column_chart.js
+++ b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/column_chart.js
@@ -130,8 +130,9 @@ export class ColumnChart extends PointSeries {
const yMin = yScale.domain()[0];
const gutterSpacingPercentage = 0.15;
const chartData = this.chartData;
+ const getGroupedNum = this.getGroupedNum.bind(this);
const groupCount = this.getGroupedCount();
- const groupNum = this.getGroupedNum(this.chartData);
+
let barWidth;
let gutterWidth;
@@ -145,6 +146,8 @@ export class ColumnChart extends PointSeries {
}
function x(d, i) {
+ const groupNum = getGroupedNum(d.seriesId);
+
if (isTimeScale) {
return (
xScale(d.x) +
@@ -249,12 +252,13 @@ export class ColumnChart extends PointSeries {
const yScale = this.getValueAxis().getScale();
const chartData = this.chartData;
const groupCount = this.getGroupedCount();
- const groupNum = this.getGroupedNum(this.chartData);
const gutterSpacingPercentage = 0.15;
const isTimeScale = this.getCategoryAxis().axisConfig.isTimeDomain();
const isHorizontal = this.getCategoryAxis().axisConfig.isHorizontal();
const isLogScale = this.getValueAxis().axisConfig.isLogScale();
const isLabels = this.labelOptions.show;
+ const getGroupedNum = this.getGroupedNum.bind(this);
+
let barWidth;
let gutterWidth;
@@ -268,6 +272,7 @@ export class ColumnChart extends PointSeries {
}
function x(d, i) {
+ const groupNum = getGroupedNum(d.seriesId);
if (isTimeScale) {
return (
xScale(d.x) +
From 385b570bb551abcaab0b75b14eabff7ba1bfd3f2 Mon Sep 17 00:00:00 2001
From: Mikhail Shustov
Date: Wed, 19 Aug 2020 17:49:34 +0300
Subject: [PATCH 23/85] MOAR RAM (#75423)
---
src/dev/typescript/run_type_check_cli.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/dev/typescript/run_type_check_cli.ts b/src/dev/typescript/run_type_check_cli.ts
index 5d4cf14c1cd95..9eeaeb4da7042 100644
--- a/src/dev/typescript/run_type_check_cli.ts
+++ b/src/dev/typescript/run_type_check_cli.ts
@@ -88,7 +88,7 @@ export function runTypeCheckCli() {
}
execInProjects(log, projects, process.execPath, (project) => [
- ...(project.name.startsWith('x-pack') ? ['--max-old-space-size=4096'] : []),
+ '--max-old-space-size=4096',
require.resolve('typescript/bin/tsc'),
...['--project', project.tsConfigPath],
...tscArgs,
From 9a22ef29d4f0385a7cc6a6c9640b3b25fa47e29a Mon Sep 17 00:00:00 2001
From: Stratoula Kalafateli
Date: Wed, 19 Aug 2020 18:04:03 +0300
Subject: [PATCH 24/85] [Functional]Table Vis increase sleep time in order
filter to be applied (#75138)
* Increase sleep time in order filter to be applied
* revert
Co-authored-by: Elastic Machine
---
test/functional/page_objects/visualize_chart_page.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/functional/page_objects/visualize_chart_page.ts b/test/functional/page_objects/visualize_chart_page.ts
index ade78cbb810d5..cb114866322db 100644
--- a/test/functional/page_objects/visualize_chart_page.ts
+++ b/test/functional/page_objects/visualize_chart_page.ts
@@ -244,7 +244,7 @@ export function VisualizeChartPageProvider({ getService, getPageObjects }: FtrPr
const firstCount = await this.getVisualizationRenderingCount();
log.debug(`-- firstCount=${firstCount}`);
- await common.sleep(1000);
+ await common.sleep(2000);
const secondCount = await this.getVisualizationRenderingCount();
log.debug(`-- secondCount=${secondCount}`);
From 9698a0720d424531aa528a4fe71112d53492b837 Mon Sep 17 00:00:00 2001
From: Gidi Meir Morris
Date: Wed, 19 Aug 2020 16:10:33 +0100
Subject: [PATCH 25/85] [Saved objects] Add support for version on create &
bulkCreate when overwriting a document (#75172)
Adds support for `version` one the SavedObjectsClient's create api.
This sallows us to retain Optimistic concurrency control when using create to overwrite an existing document.
---
...ore-server.savedobjectsbulkcreateobject.md | 1 +
...er.savedobjectsbulkcreateobject.version.md | 11 +++++
...n-core-server.savedobjectscreateoptions.md | 1 +
...erver.savedobjectscreateoptions.version.md | 13 ++++++
.../import/import_saved_objects.ts | 7 ++-
.../import/resolve_import_errors.ts | 12 ++++--
.../service/lib/repository.test.js | 43 ++++++++++++++++++-
.../saved_objects/service/lib/repository.ts | 12 +++++-
.../service/saved_objects_client.ts | 6 +++
src/core/server/server.api.md | 3 ++
.../services/sample_data/routes/install.ts | 2 +-
.../services/epm/kibana/assets/install.ts | 4 +-
12 files changed, 105 insertions(+), 10 deletions(-)
create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.version.md
create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.version.md
diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.md
index 5a9ca36ba56f4..5ccad134248f6 100644
--- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.md
+++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.md
@@ -20,4 +20,5 @@ export interface SavedObjectsBulkCreateObject
| [migrationVersion](./kibana-plugin-core-server.savedobjectsbulkcreateobject.migrationversion.md) | SavedObjectsMigrationVersion
| Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. |
| [references](./kibana-plugin-core-server.savedobjectsbulkcreateobject.references.md) | SavedObjectReference[]
| |
| [type](./kibana-plugin-core-server.savedobjectsbulkcreateobject.type.md) | string
| |
+| [version](./kibana-plugin-core-server.savedobjectsbulkcreateobject.version.md) | string
| |
diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.version.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.version.md
new file mode 100644
index 0000000000000..ca2a38693d036
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.version.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsBulkCreateObject](./kibana-plugin-core-server.savedobjectsbulkcreateobject.md) > [version](./kibana-plugin-core-server.savedobjectsbulkcreateobject.version.md)
+
+## SavedObjectsBulkCreateObject.version property
+
+Signature:
+
+```typescript
+version?: string;
+```
diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.md
index 5e9433c5c9196..c5201efd0608d 100644
--- a/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.md
+++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.md
@@ -20,4 +20,5 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions
| [overwrite](./kibana-plugin-core-server.savedobjectscreateoptions.overwrite.md) | boolean
| Overwrite existing documents (defaults to false) |
| [references](./kibana-plugin-core-server.savedobjectscreateoptions.references.md) | SavedObjectReference[]
| |
| [refresh](./kibana-plugin-core-server.savedobjectscreateoptions.refresh.md) | MutatingOperationRefreshSetting
| The Elasticsearch Refresh setting for this operation |
+| [version](./kibana-plugin-core-server.savedobjectscreateoptions.version.md) | string
| An opaque version number which changes on each successful write operation. Can be used in conjunction with overwrite
for implementing optimistic concurrency control. |
diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.version.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.version.md
new file mode 100644
index 0000000000000..51da57064abb9
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.version.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsCreateOptions](./kibana-plugin-core-server.savedobjectscreateoptions.md) > [version](./kibana-plugin-core-server.savedobjectscreateoptions.version.md)
+
+## SavedObjectsCreateOptions.version property
+
+An opaque version number which changes on each successful write operation. Can be used in conjunction with `overwrite` for implementing optimistic concurrency control.
+
+Signature:
+
+```typescript
+version?: string;
+```
diff --git a/src/core/server/saved_objects/import/import_saved_objects.ts b/src/core/server/saved_objects/import/import_saved_objects.ts
index 6065e03fb1628..4956491a79aa9 100644
--- a/src/core/server/saved_objects/import/import_saved_objects.ts
+++ b/src/core/server/saved_objects/import/import_saved_objects.ts
@@ -25,6 +25,7 @@ import {
SavedObjectsImportOptions,
} from './types';
import { validateReferences } from './validate_references';
+import { SavedObject } from '../types';
/**
* Import saved objects from given stream. See the {@link SavedObjectsImportOptions | options} for more
@@ -67,7 +68,7 @@ export async function importSavedObjectsFromStream({
}
// Create objects in bulk
- const bulkCreateResult = await savedObjectsClient.bulkCreate(filteredObjects, {
+ const bulkCreateResult = await savedObjectsClient.bulkCreate(omitVersion(filteredObjects), {
overwrite,
namespace,
});
@@ -82,3 +83,7 @@ export async function importSavedObjectsFromStream({
...(errorAccumulator.length ? { errors: errorAccumulator } : {}),
};
}
+
+export function omitVersion(objects: SavedObject[]): SavedObject[] {
+ return objects.map(({ version, ...object }) => object);
+}
diff --git a/src/core/server/saved_objects/import/resolve_import_errors.ts b/src/core/server/saved_objects/import/resolve_import_errors.ts
index a5175aa080598..dce044a31a577 100644
--- a/src/core/server/saved_objects/import/resolve_import_errors.ts
+++ b/src/core/server/saved_objects/import/resolve_import_errors.ts
@@ -26,6 +26,7 @@ import {
SavedObjectsResolveImportErrorsOptions,
} from './types';
import { validateReferences } from './validate_references';
+import { omitVersion } from './import_saved_objects';
/**
* Resolve and return saved object import errors.
@@ -91,7 +92,7 @@ export async function resolveSavedObjectsImportErrors({
// Bulk create in two batches, overwrites and non-overwrites
const { objectsToOverwrite, objectsToNotOverwrite } = splitOverwrites(filteredObjects, retries);
if (objectsToOverwrite.length) {
- const bulkCreateResult = await savedObjectsClient.bulkCreate(objectsToOverwrite, {
+ const bulkCreateResult = await savedObjectsClient.bulkCreate(omitVersion(objectsToOverwrite), {
overwrite: true,
namespace,
});
@@ -102,9 +103,12 @@ export async function resolveSavedObjectsImportErrors({
successCount += bulkCreateResult.saved_objects.filter((obj) => !obj.error).length;
}
if (objectsToNotOverwrite.length) {
- const bulkCreateResult = await savedObjectsClient.bulkCreate(objectsToNotOverwrite, {
- namespace,
- });
+ const bulkCreateResult = await savedObjectsClient.bulkCreate(
+ omitVersion(objectsToNotOverwrite),
+ {
+ namespace,
+ }
+ );
errorAccumulator = [
...errorAccumulator,
...extractErrors(bulkCreateResult.saved_objects, objectsToNotOverwrite),
diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js
index 4a9fceb9bf357..941c4091a66a7 100644
--- a/src/core/server/saved_objects/service/lib/repository.test.js
+++ b/src/core/server/saved_objects/service/lib/repository.test.js
@@ -464,8 +464,16 @@ describe('SavedObjectsRepository', () => {
{ method, _index = expect.any(String), getId = () => expect.any(String) }
) => {
const body = [];
- for (const { type, id } of objects) {
- body.push({ [method]: { _index, _id: getId(type, id) } });
+ for (const { type, id, if_primary_term: ifPrimaryTerm, if_seq_no: ifSeqNo } of objects) {
+ body.push({
+ [method]: {
+ _index,
+ _id: getId(type, id),
+ ...(ifPrimaryTerm && ifSeqNo
+ ? { if_primary_term: expect.any(Number), if_seq_no: expect.any(Number) }
+ : {}),
+ },
+ });
body.push(expect.any(Object));
}
expect(client.bulk).toHaveBeenCalledWith(
@@ -525,6 +533,27 @@ describe('SavedObjectsRepository', () => {
expectClientCallArgsAction([obj1, obj2], { method: 'index' });
});
+ it(`should use the ES index method with version if ID and version are defined and overwrite=true`, async () => {
+ await bulkCreateSuccess(
+ [
+ {
+ ...obj1,
+ version: mockVersion,
+ },
+ obj2,
+ ],
+ { overwrite: true }
+ );
+
+ const obj1WithSeq = {
+ ...obj1,
+ if_seq_no: mockVersionProps._seq_no,
+ if_primary_term: mockVersionProps._primary_term,
+ };
+
+ expectClientCallArgsAction([obj1WithSeq, obj2], { method: 'index' });
+ });
+
it(`should use the ES create method if ID is defined and overwrite=false`, async () => {
await bulkCreateSuccess([obj1, obj2]);
expectClientCallArgsAction([obj1, obj2], { method: 'create' });
@@ -1516,6 +1545,16 @@ describe('SavedObjectsRepository', () => {
expect(client.index).toHaveBeenCalled();
});
+ it(`should use the ES index with version if ID and version are defined and overwrite=true`, async () => {
+ await createSuccess(type, attributes, { id, overwrite: true, version: mockVersion });
+ expect(client.index).toHaveBeenCalled();
+
+ expect(client.index.mock.calls[0][0]).toMatchObject({
+ if_seq_no: mockVersionProps._seq_no,
+ if_primary_term: mockVersionProps._primary_term,
+ });
+ });
+
it(`should use the ES create action if ID is defined and overwrite=false`, async () => {
await createSuccess(type, attributes, { id });
expect(client.create).toHaveBeenCalled();
diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts
index d7e1ecba0370b..9f6db446ea195 100644
--- a/src/core/server/saved_objects/service/lib/repository.ts
+++ b/src/core/server/saved_objects/service/lib/repository.ts
@@ -220,6 +220,7 @@ export class SavedObjectsRepository {
overwrite = false,
references = [],
refresh = DEFAULT_REFRESH_SETTING,
+ version,
} = options;
if (!this._allowedTypes.includes(type)) {
@@ -259,6 +260,7 @@ export class SavedObjectsRepository {
index: this.getIndexForType(type),
refresh,
body: raw._source,
+ ...(overwrite && version ? decodeRequestVersion(version) : {}),
};
const { body } =
@@ -345,7 +347,12 @@ export class SavedObjectsRepository {
let savedObjectNamespace;
let savedObjectNamespaces;
- const { esRequestIndex, object, method } = expectedBulkGetResult.value;
+ let versionProperties;
+ const {
+ esRequestIndex,
+ object: { version, ...object },
+ method,
+ } = expectedBulkGetResult.value;
if (esRequestIndex !== undefined) {
const indexFound = bulkGetResponse?.statusCode !== 404;
const actualResult = indexFound ? bulkGetResponse?.body.docs[esRequestIndex] : undefined;
@@ -362,12 +369,14 @@ export class SavedObjectsRepository {
};
}
savedObjectNamespaces = getSavedObjectNamespaces(namespace, docFound && actualResult);
+ versionProperties = getExpectedVersionProperties(version, actualResult);
} else {
if (this._registry.isSingleNamespace(object.type)) {
savedObjectNamespace = namespace;
} else if (this._registry.isMultiNamespace(object.type)) {
savedObjectNamespaces = getSavedObjectNamespaces(namespace);
}
+ versionProperties = getExpectedVersionProperties(version);
}
const expectedResult = {
@@ -392,6 +401,7 @@ export class SavedObjectsRepository {
[method]: {
_id: expectedResult.rawMigratedDoc._id,
_index: this.getIndexForType(object.type),
+ ...(overwrite && versionProperties),
},
},
expectedResult.rawMigratedDoc._source
diff --git a/src/core/server/saved_objects/service/saved_objects_client.ts b/src/core/server/saved_objects/service/saved_objects_client.ts
index e15a92c92772f..6a9f4f5143e84 100644
--- a/src/core/server/saved_objects/service/saved_objects_client.ts
+++ b/src/core/server/saved_objects/service/saved_objects_client.ts
@@ -37,6 +37,11 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions {
id?: string;
/** Overwrite existing documents (defaults to false) */
overwrite?: boolean;
+ /**
+ * An opaque version number which changes on each successful write operation.
+ * Can be used in conjunction with `overwrite` for implementing optimistic concurrency control.
+ **/
+ version?: string;
/** {@inheritDoc SavedObjectsMigrationVersion} */
migrationVersion?: SavedObjectsMigrationVersion;
references?: SavedObjectReference[];
@@ -52,6 +57,7 @@ export interface SavedObjectsBulkCreateObject {
id?: string;
type: string;
attributes: T;
+ version?: string;
references?: SavedObjectReference[];
/** {@inheritDoc SavedObjectsMigrationVersion} */
migrationVersion?: SavedObjectsMigrationVersion;
diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md
index 03545284e14fb..01558c1460f1b 100644
--- a/src/core/server/server.api.md
+++ b/src/core/server/server.api.md
@@ -2044,6 +2044,8 @@ export interface SavedObjectsBulkCreateObject {
references?: SavedObjectReference[];
// (undocumented)
type: string;
+ // (undocumented)
+ version?: string;
}
// @public (undocumented)
@@ -2178,6 +2180,7 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions {
// (undocumented)
references?: SavedObjectReference[];
refresh?: MutatingOperationRefreshSetting;
+ version?: string;
}
// @public (undocumented)
diff --git a/src/plugins/home/server/services/sample_data/routes/install.ts b/src/plugins/home/server/services/sample_data/routes/install.ts
index 2d1a53fbb09dc..b94456682afcc 100644
--- a/src/plugins/home/server/services/sample_data/routes/install.ts
+++ b/src/plugins/home/server/services/sample_data/routes/install.ts
@@ -154,7 +154,7 @@ export function createInstallRoute(
let createResults;
try {
createResults = await context.core.savedObjects.client.bulkCreate(
- sampleDataset.savedObjects,
+ sampleDataset.savedObjects.map(({ version, ...savedObject }) => savedObject),
{ overwrite: true }
);
} catch (err) {
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/install.ts
index 5741764164b83..84892d2027847 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/install.ts
+++ b/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/install.ts
@@ -14,7 +14,9 @@ import * as Registry from '../../registry';
import { AssetType, KibanaAssetType, AssetReference } from '../../../../types';
import { savedObjectTypes } from '../../packages';
-type SavedObjectToBe = Required & { type: AssetType };
+type SavedObjectToBe = Required> & {
+ type: AssetType;
+};
export type ArchiveAsset = Pick<
SavedObject,
'id' | 'attributes' | 'migrationVersion' | 'references'
From a68f4beb8a7a04d83154a9eb10415eada07e926b Mon Sep 17 00:00:00 2001
From: spalger
Date: Wed, 19 Aug 2020 08:42:11 -0700
Subject: [PATCH 26/85] skip flaky suite (#75386)
---
.../tests/actions/builtin_action_types/pagerduty.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts
index c697cf69bb4d5..e81219152c248 100644
--- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts
@@ -20,7 +20,8 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) {
const kibanaServer = getService('kibanaServer');
const config = getService('config');
- describe('pagerduty action', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/75386
+ describe.skip('pagerduty action', () => {
let simulatedActionId = '';
let pagerdutySimulatorURL: string = '';
let proxyServer: any;
From f5e5abe50b3791a5dfd3cbc7ac6b22c2563ff64d Mon Sep 17 00:00:00 2001
From: spalger
Date: Wed, 19 Aug 2020 08:48:59 -0700
Subject: [PATCH 27/85] skip flaky suite (#75440)
---
test/plugin_functional/test_suites/core/route.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/test/plugin_functional/test_suites/core/route.ts b/test/plugin_functional/test_suites/core/route.ts
index becde49a69714..b8febf9fe5571 100644
--- a/test/plugin_functional/test_suites/core/route.ts
+++ b/test/plugin_functional/test_suites/core/route.ts
@@ -24,7 +24,8 @@ import { PluginFunctionalProviderContext } from '../../services';
export default function ({ getService }: PluginFunctionalProviderContext) {
const supertest = getService('supertest');
- describe('route', function () {
+ // FLAKY: https://github.com/elastic/kibana/issues/75440
+ describe.skip('route', function () {
describe('timeouts', function () {
const writeBodyCharAtATime = (request: Test, body: string, interval: number) => {
return new Promise((resolve, reject) => {
From 96118624de3d348f5e3d1c81057d2a1ca91a771f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?=
Date: Wed, 19 Aug 2020 18:07:33 +0200
Subject: [PATCH 28/85] [Logs UI] Add "View in machine learning" links in the
anomaly explorer (#74555)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Felix Stürmer
Co-authored-by: Elastic Machine
---
.../analyze_in_ml_button.tsx | 2 +-
.../setup_flyout/module_list.tsx | 12 ++++++--
.../setup_flyout/module_list_card.tsx | 30 +++++++++++++++++--
3 files changed, 38 insertions(+), 6 deletions(-)
diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx b/x-pack/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx
index 5383cb3b09d39..3e54920160c53 100644
--- a/x-pack/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx
+++ b/x-pack/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx
@@ -40,7 +40,7 @@ export const AnalyzeInMlButton: React.FunctionComponent<{
);
};
-const getOverallAnomalyExplorerLinkDescriptor = (
+export const getOverallAnomalyExplorerLinkDescriptor = (
jobId: string,
timeRange: TimeRange
): LinkDescriptor => {
diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/module_list.tsx b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/module_list.tsx
index 2c68aceccaa43..d7414950b35ad 100644
--- a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/module_list.tsx
+++ b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/module_list.tsx
@@ -22,8 +22,14 @@ export const LogAnalysisModuleList: React.FC<{
onViewModuleSetup: (module: ModuleId) => void;
}> = ({ onViewModuleSetup }) => {
const { hasLogAnalysisSetupCapabilities } = useLogAnalysisCapabilitiesContext();
- const { setupStatus: logEntryRateSetupStatus } = useLogEntryRateModuleContext();
- const { setupStatus: logEntryCategoriesSetupStatus } = useLogEntryCategoriesModuleContext();
+ const {
+ setupStatus: logEntryRateSetupStatus,
+ jobIds: logEntryRateJobIds,
+ } = useLogEntryRateModuleContext();
+ const {
+ setupStatus: logEntryCategoriesSetupStatus,
+ jobIds: logEntryCategoriesJobIds,
+ } = useLogEntryCategoriesModuleContext();
const viewLogEntryRateSetupFlyout = useCallback(() => {
onViewModuleSetup('logs_ui_analysis');
@@ -37,6 +43,7 @@ export const LogAnalysisModuleList: React.FC<{
void;
-}> = ({ hasSetupCapabilities, moduleDescription, moduleName, moduleStatus, onViewSetup }) => {
+}> = ({
+ jobId,
+ hasSetupCapabilities,
+ moduleDescription,
+ moduleName,
+ moduleStatus,
+ onViewSetup,
+}) => {
const moduleIcon =
moduleStatus.type === 'required' ? (
@@ -24,6 +33,12 @@ export const LogAnalysisModuleListCard: React.FC<{
);
+ const viewInMlLinkProps = useLinkProps({
+ app: 'ml',
+ pathname: '/jobs',
+ search: { mlManagement: `(jobId:${jobId})` },
+ });
+
const moduleSetupButton =
moduleStatus.type === 'required' ? (
@@ -33,7 +48,16 @@ export const LogAnalysisModuleListCard: React.FC<{
/>
) : (
-
+ <>
+
+
+
+
+
+ >
);
return (
From 0c6f682890b72823d5cfbf0c28ce99b1b03373f5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Loix?=
Date: Wed, 19 Aug 2020 18:08:15 +0200
Subject: [PATCH 29/85] =?UTF-8?q?[Form=20lib]=20Allow=20new=20"defaultValu?=
=?UTF-8?q?e"=20to=20be=20provided=20when=20resetting=20the=E2=80=A6=20(#7?=
=?UTF-8?q?5302)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../forms/form_wizard/form_wizard_context.tsx | 2 +-
.../components/use_field.test.tsx | 2 +-
.../hook_form_lib/components/use_field.tsx | 19 ++-
.../forms/hook_form_lib/hooks/use_field.ts | 36 ++--
.../hook_form_lib/hooks/use_form.test.tsx | 159 +++++++++++++++++-
.../forms/hook_form_lib/hooks/use_form.ts | 42 +++--
.../static/forms/hook_form_lib/types.ts | 5 +-
.../configuration_form/configuration_form.tsx | 21 +--
.../document_fields/document_fields.tsx | 45 ++++-
.../fields/create_field/create_field.tsx | 4 +
.../fields/edit_field/edit_field.tsx | 7 -
.../edit_field/edit_field_container.tsx | 91 ++++------
.../edit_field/edit_field_header_form.tsx | 4 +
.../fields/edit_field/index.ts | 6 +-
.../templates_form/templates_form.tsx | 21 +--
.../mappings_editor/use_state_listener.tsx | 5 +-
16 files changed, 325 insertions(+), 144 deletions(-)
diff --git a/src/plugins/es_ui_shared/public/forms/form_wizard/form_wizard_context.tsx b/src/plugins/es_ui_shared/public/forms/form_wizard/form_wizard_context.tsx
index 39b91a2e20b53..7719e7748829d 100644
--- a/src/plugins/es_ui_shared/public/forms/form_wizard/form_wizard_context.tsx
+++ b/src/plugins/es_ui_shared/public/forms/form_wizard/form_wizard_context.tsx
@@ -147,7 +147,7 @@ export const FormWizardProvider = WithMultiContent>(function FormWiza
return nextState;
});
},
- [getStepIndex, validate, onSave, getData]
+ [getStepIndex, validate, onSave, getData, lastStep]
);
const value: Context = {
diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx
index eead90d2f75b7..a55b2f0a8fa29 100644
--- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx
+++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx
@@ -98,7 +98,7 @@ describe(' ', () => {
useEffect(() => {
onForm(form);
- }, [form]);
+ }, [onForm, form]);
return (
);
});
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx
index ce5f2a60f5165..a8170b1d59fbb 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx
@@ -112,6 +112,10 @@ export const CreateField = React.memo(function CreateFieldComponent({
{/* Field subType (if any) */}
{({ type }) => {
+ if (type === undefined) {
+ return null;
+ }
+
const [fieldType] = type;
return (
, which wrapps the form with
// a . We can't have a div as first child of the Flyout as it breaks
// the height calculaction and does not render the footer position correctly.
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_container.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_container.tsx
index 4996f59105c04..1f11bac568859 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_container.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_container.tsx
@@ -3,31 +3,35 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { useEffect, useCallback, useMemo } from 'react';
+import React, { useEffect, useMemo } from 'react';
-import { useForm, GlobalFlyout } from '../../../../shared_imports';
+import { useForm } from '../../../../shared_imports';
import { useDispatch, useMappingsState } from '../../../../mappings_state_context';
import { Field } from '../../../../types';
import { fieldSerializer, fieldDeserializer } from '../../../../lib';
import { ModalConfirmationDeleteFields } from '../modal_confirmation_delete_fields';
-import { EditField, defaultFlyoutProps, Props as EditFieldProps } from './edit_field';
+import { EditField } from './edit_field';
import { useUpdateField } from './use_update_field';
-const { useGlobalFlyout } = GlobalFlyout;
+export const defaultFlyoutProps = {
+ 'data-test-subj': 'mappingsEditorFieldEdit',
+ 'aria-labelledby': 'mappingsEditorFieldEditTitle',
+ className: 'mappingsEditor__editField',
+ maxWidth: 720,
+};
-export const EditFieldContainer = React.memo(() => {
+export interface Props {
+ exitEdit: () => void;
+}
+
+export const EditFieldContainer = React.memo(({ exitEdit }: Props) => {
const { fields, documentFields } = useMappingsState();
const dispatch = useDispatch();
- const {
- addContent: addContentToGlobalFlyout,
- removeContent: removeContentFromGlobalFlyout,
- } = useGlobalFlyout();
const { updateField, modal } = useUpdateField();
const { status, fieldToEdit } = documentFields;
const isEditing = status === 'editingField';
-
- const field = isEditing ? fields.byId[fieldToEdit!] : undefined;
+ const field = fields.byId[fieldToEdit!];
const formDefaultValue = useMemo(() => {
return { ...field?.source };
@@ -38,6 +42,7 @@ export const EditFieldContainer = React.memo(() => {
serializer: fieldSerializer,
deserializer: fieldDeserializer,
options: { stripEmptyFields: false },
+ id: 'edit-field',
});
const { subscribe } = form;
@@ -50,52 +55,24 @@ export const EditFieldContainer = React.memo(() => {
return subscription.unsubscribe;
}, [subscribe, dispatch]);
- const exitEdit = useCallback(() => {
- dispatch({ type: 'documentField.changeStatus', value: 'idle' });
- }, [dispatch]);
-
- useEffect(() => {
- if (isEditing) {
- // Open the flyout with the
content
- addContentToGlobalFlyout
({
- id: 'mappingsEditField',
- Component: EditField,
- props: {
- form,
- field: field!,
- exitEdit,
- allFields: fields.byId,
- updateField,
- },
- flyoutProps: { ...defaultFlyoutProps, onClose: exitEdit },
- cleanUpFunc: exitEdit,
- });
- }
- }, [
- isEditing,
- field,
- form,
- addContentToGlobalFlyout,
- fields.byId,
- fieldToEdit,
- exitEdit,
- updateField,
- ]);
+ const renderModal = () => {
+ return modal.isOpen ? : null;
+ };
- useEffect(() => {
- if (!isEditing) {
- removeContentFromGlobalFlyout('mappingsEditField');
- }
- }, [isEditing, removeContentFromGlobalFlyout]);
-
- useEffect(() => {
- return () => {
- if (isEditing) {
- // When the component unmounts, exit edit mode.
- exitEdit();
- }
- };
- }, [isEditing, exitEdit]);
+ if (!isEditing) {
+ return null;
+ }
- return modal.isOpen ? : null;
+ return (
+ <>
+
+ {renderModal()}
+ >
+ );
});
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_header_form.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_header_form.tsx
index b4b5bce21f768..f2ad37cb45818 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_header_form.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_header_form.tsx
@@ -37,6 +37,10 @@ export const EditFieldHeaderForm = React.memo(
{/* Field subType (if any) */}
{({ type }) => {
+ if (type === undefined) {
+ return null;
+ }
+
const [fieldType] = type;
return (
{
};
export const TemplatesForm = React.memo(({ value }: Props) => {
- const isMounted = useRef(undefined);
+ const isMounted = useRef(false);
const { form } = useForm({
schema: templatesFormSchema,
@@ -75,23 +75,16 @@ export const TemplatesForm = React.memo(({ value }: Props) => {
}, [subscribe, dispatch, submitForm]);
useEffect(() => {
- if (isMounted.current === undefined) {
- // On mount: don't reset the form
- isMounted.current = true;
- return;
- } else if (isMounted.current === false) {
- // When we save the snapshot on unMount we update the "defaultValue" in our state
- // wich updates the "value" prop here on the component.
- // To avoid resetting the form at this stage, we exit early.
- return;
+ if (isMounted.current) {
+ // If the value has changed (it probably means that we have loaded a new JSON)
+ // we need to reset the form to update the fields values.
+ reset({ resetValues: true, defaultValue: value });
}
-
- // If the value has changed (it probably means that we have loaded a new JSON)
- // we need to reset the form to update the fields values.
- reset({ resetValues: true });
}, [value, reset]);
useEffect(() => {
+ isMounted.current = true;
+
return () => {
isMounted.current = false;
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/use_state_listener.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/use_state_listener.tsx
index f1ffd5356c977..feba79ce85e85 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/use_state_listener.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/use_state_listener.tsx
@@ -29,15 +29,14 @@ export const useMappingsStateListener = ({ onChange, value }: Args) => {
const dispatch = useDispatch();
const parsedFieldsDefaultValue = useMemo(() => normalize(value?.fields), [value?.fields]);
-
useEffect(() => {
// If we are creating a new field, but haven't entered any name
// it is valid and we can byPass its form validation (that requires a "name" to be defined)
const isFieldFormVisible = state.fieldForm !== undefined;
const emptyNameValue =
isFieldFormVisible &&
- state.fieldForm!.data.raw.name !== undefined &&
- state.fieldForm!.data.raw.name.trim() === '';
+ (state.fieldForm!.data.raw.name === undefined ||
+ state.fieldForm!.data.raw.name.trim() === '');
const bypassFieldFormValidation =
state.documentFields.status === 'creatingField' && emptyNameValue;
From f983e120b67ded1033c0f543c2e8b0acdca9b96e Mon Sep 17 00:00:00 2001
From: Dmitry Lemeshko
Date: Wed, 19 Aug 2020 18:13:29 +0200
Subject: [PATCH 30/85] [code coverage] always download node before team
assignment (#75424)
---
vars/kibanaTeamAssign.groovy | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/vars/kibanaTeamAssign.groovy b/vars/kibanaTeamAssign.groovy
index e2298ed43d408..a3d9c16ef506e 100644
--- a/vars/kibanaTeamAssign.groovy
+++ b/vars/kibanaTeamAssign.groovy
@@ -1,6 +1,6 @@
def loadIngestionPipeline(ingestionPipelineName, title) {
kibanaPipeline.bash("""
- source src/dev/ci_setup/setup_env.sh
+ source src/dev/ci_setup/setup_env.sh true
yarn kbn bootstrap --prefer-offline
. src/dev/code_coverage/shell_scripts/assign_teams.sh '${ingestionPipelineName}'
From 32c23eacc9f8c4a3eb5ba12f7c18aff33a27fc30 Mon Sep 17 00:00:00 2001
From: Ryland Herrick
Date: Wed, 19 Aug 2020 11:25:26 -0500
Subject: [PATCH 31/85] [Security Solution][Detections] Loosen lists
permissions (#75378)
* Allow read-only lists users to use Detections if lists indexes exist
We were previously showing them a configuration page, which was
incorrect. The only place we require the write permission is the value
lists modal, which is now hidden if that permission is absent.
* Disable the Value Lists modal when the user cannot write lists
If the user does not have permission to write to the lists index, they
should not be able to CRUD value lists.
* style: Remove unnecessary useCallbacks
* Replaces useCallback functions with inline anonymous functions
* Renames the modal state to be more similar to existing analogous
modal state
---
.../detection_engine/lists/use_lists_config.tsx | 2 +-
.../pages/detection_engine/rules/index.tsx | 14 ++++++++------
2 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_config.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_config.tsx
index 71847e7b7d8cb..d12941f35b523 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_config.tsx
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_config.tsx
@@ -29,7 +29,7 @@ export const useListsConfig = (): UseListsConfigReturn => {
const hasIndexError = indexError != null;
const needsIndexConfiguration =
needsIndex && (canManageIndex === false || (canManageIndex === true && hasIndexError));
- const needsConfiguration = !enabled || canWriteIndex === false || needsIndexConfiguration;
+ const needsConfiguration = !enabled || needsIndexConfiguration;
useEffect(() => {
if (needsIndex && canManageIndex) {
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx
index b6f58ef7045f8..92ec0bb5a72cd 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx
@@ -40,9 +40,7 @@ type Func = (refreshPrePackagedRule?: boolean) => void;
const RulesPageComponent: React.FC = () => {
const history = useHistory();
const [showImportModal, setShowImportModal] = useState(false);
- const [isValueListsModalShown, setIsValueListsModalShown] = useState(false);
- const showValueListsModal = useCallback(() => setIsValueListsModalShown(true), []);
- const hideValueListsModal = useCallback(() => setIsValueListsModalShown(false), []);
+ const [showValueListsModal, setShowValueListsModal] = useState(false);
const refreshRulesData = useRef(null);
const {
loading: userInfoLoading,
@@ -54,6 +52,7 @@ const RulesPageComponent: React.FC = () => {
} = useUserInfo();
const {
loading: listsConfigLoading,
+ canWriteIndex: canWriteListsIndex,
needsConfiguration: needsListsConfiguration,
} = useListsConfig();
const loading = userInfoLoading || listsConfigLoading;
@@ -147,7 +146,10 @@ const RulesPageComponent: React.FC = () => {
return (
<>
{userHasNoPermissions(canUserCRUD) && }
-
+ setShowValueListsModal(false)}
+ />
setShowImportModal(false)}
@@ -208,8 +210,8 @@ const RulesPageComponent: React.FC = () => {
setShowValueListsModal(true)}
>
{i18n.UPLOAD_VALUE_LISTS}
From 32bd45bfbbea46203e7287f4ef046c7338789dc9 Mon Sep 17 00:00:00 2001
From: Ben Skelker <54019610+benskelker@users.noreply.github.com>
Date: Wed, 19 Aug 2020 19:28:48 +0300
Subject: [PATCH 32/85] updates link to security docs (#75421)
---
.../pages/detection_engine/detection_engine_no_index.tsx | 2 +-
.../detection_engine/detection_engine_user_unauthenticated.tsx | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine_no_index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine_no_index.tsx
index 648a9405606ef..9bddd9e6f0fe7 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine_no_index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine_no_index.tsx
@@ -32,7 +32,7 @@ const DetectionEngineNoIndexComponent: React.FC<{
detections: {
icon: 'documents',
label: i18n.GO_TO_DOCUMENTATION,
- url: `${docLinks.ELASTIC_WEBSITE_URL}guide/en/security/${docLinks.DOC_LINK_VERSION}/detection-engine-overview.html#detections-permissions`,
+ url: `${docLinks.ELASTIC_WEBSITE_URL}guide/en/security/${docLinks.DOC_LINK_VERSION}/detections-permissions-section.html`,
target: '_blank',
},
}),
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine_user_unauthenticated.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine_user_unauthenticated.tsx
index 355898cff0120..37abd1f9adc11 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine_user_unauthenticated.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine_user_unauthenticated.tsx
@@ -17,7 +17,7 @@ export const DetectionEngineUserUnauthenticated = React.memo(() => {
detectionUnauthenticated: {
icon: 'documents',
label: i18n.GO_TO_DOCUMENTATION,
- url: `${docLinks.ELASTIC_WEBSITE_URL}guide/en/security/${docLinks.DOC_LINK_VERSION}/detection-engine-overview.html#detections-permissions`,
+ url: `${docLinks.ELASTIC_WEBSITE_URL}guide/en/security/${docLinks.DOC_LINK_VERSION}/detections-permissions-section.html`,
target: '_blank',
},
}),
From 2c865f5649117330591795375cf5b3d3e586dece Mon Sep 17 00:00:00 2001
From: Shahzad
Date: Wed, 19 Aug 2020 19:05:53 +0200
Subject: [PATCH 33/85] [RUM Dashboard] Create new path for client side
monitoring (#74740)
---
.../plugins/apm/public/application/csmApp.tsx | 130 ++++++++++++++++++
.../plugins/apm/public/application/index.tsx | 20 ++-
.../app/Main/route_config/index.tsx | 14 +-
.../app/Main/route_config/route_names.tsx | 2 +-
.../components/app/RumDashboard/RumHome.tsx | 7 +-
x-pack/plugins/apm/public/plugin.ts | 71 ++++++----
.../services/rest/apm_overview_fetchers.ts | 2 +
x-pack/plugins/apm/server/feature.ts | 6 +-
.../translations/translations/ja-JP.json | 1 -
.../translations/translations/zh-CN.json | 1 -
.../apps/apm/feature_controls/apm_security.ts | 9 +-
11 files changed, 215 insertions(+), 48 deletions(-)
create mode 100644 x-pack/plugins/apm/public/application/csmApp.tsx
diff --git a/x-pack/plugins/apm/public/application/csmApp.tsx b/x-pack/plugins/apm/public/application/csmApp.tsx
new file mode 100644
index 0000000000000..cf3fe2decfa44
--- /dev/null
+++ b/x-pack/plugins/apm/public/application/csmApp.tsx
@@ -0,0 +1,130 @@
+/*
+ * 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 ReactDOM from 'react-dom';
+import { Route, Router } from 'react-router-dom';
+import styled, { ThemeProvider, DefaultTheme } from 'styled-components';
+import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
+import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
+import { CoreStart, AppMountParameters } from 'kibana/public';
+import { ApmPluginSetupDeps } from '../plugin';
+
+import {
+ KibanaContextProvider,
+ useUiSetting$,
+} from '../../../../../src/plugins/kibana_react/public';
+import { px, units } from '../style/variables';
+import { UpdateBreadcrumbs } from '../components/app/Main/UpdateBreadcrumbs';
+import { ScrollToTopOnPathChange } from '../components/app/Main/ScrollToTopOnPathChange';
+import { history, resetHistory } from '../utils/history';
+import 'react-vis/dist/style.css';
+import { RumHome } from '../components/app/RumDashboard/RumHome';
+import { ConfigSchema } from '../index';
+import { BreadcrumbRoute } from '../components/app/Main/ProvideBreadcrumbs';
+import { RouteName } from '../components/app/Main/route_config/route_names';
+import { renderAsRedirectTo } from '../components/app/Main/route_config';
+import { ApmPluginContext } from '../context/ApmPluginContext';
+import { UrlParamsProvider } from '../context/UrlParamsContext';
+import { LoadingIndicatorProvider } from '../context/LoadingIndicatorContext';
+import { createCallApmApi } from '../services/rest/createCallApmApi';
+
+const CsmMainContainer = styled.div`
+ padding: ${px(units.plus)};
+ height: 100%;
+`;
+
+export const rumRoutes: BreadcrumbRoute[] = [
+ {
+ exact: true,
+ path: '/',
+ render: renderAsRedirectTo('/csm'),
+ breadcrumb: 'Client Side Monitoring',
+ name: RouteName.CSM,
+ },
+];
+
+function CsmApp() {
+ const [darkMode] = useUiSetting$('theme:darkMode');
+
+ return (
+ ({
+ ...outerTheme,
+ eui: darkMode ? euiDarkVars : euiLightVars,
+ darkMode,
+ })}
+ >
+
+
+
+
+
+
+ );
+}
+
+export function CsmAppRoot({
+ core,
+ deps,
+ routerHistory,
+ config,
+}: {
+ core: CoreStart;
+ deps: ApmPluginSetupDeps;
+ routerHistory: typeof history;
+ config: ConfigSchema;
+}) {
+ const i18nCore = core.i18n;
+ const plugins = deps;
+ const apmPluginContextValue = {
+ config,
+ core,
+ plugins,
+ };
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+/**
+ * This module is rendered asynchronously in the Kibana platform.
+ */
+
+export const renderApp = (
+ core: CoreStart,
+ deps: ApmPluginSetupDeps,
+ { element }: AppMountParameters,
+ config: ConfigSchema
+) => {
+ createCallApmApi(core.http);
+
+ resetHistory();
+ ReactDOM.render(
+ ,
+ element
+ );
+ return () => {
+ ReactDOM.unmountComponentAtNode(element);
+ };
+};
diff --git a/x-pack/plugins/apm/public/application/index.tsx b/x-pack/plugins/apm/public/application/index.tsx
index 0c9c6eb86225b..5e502f58e5f56 100644
--- a/x-pack/plugins/apm/public/application/index.tsx
+++ b/x-pack/plugins/apm/public/application/index.tsx
@@ -20,6 +20,7 @@ import { LocationProvider } from '../context/LocationContext';
import { MatchedRouteProvider } from '../context/MatchedRouteContext';
import { UrlParamsProvider } from '../context/UrlParamsContext';
import { AlertsContextProvider } from '../../../triggers_actions_ui/public';
+import { createStaticIndexPattern } from '../services/rest/index_pattern';
import {
KibanaContextProvider,
useUiSetting$,
@@ -29,6 +30,9 @@ import { UpdateBreadcrumbs } from '../components/app/Main/UpdateBreadcrumbs';
import { ScrollToTopOnPathChange } from '../components/app/Main/ScrollToTopOnPathChange';
import { routes } from '../components/app/Main/route_config';
import { history, resetHistory } from '../utils/history';
+import { setHelpExtension } from '../setHelpExtension';
+import { setReadonlyBadge } from '../updateBadge';
+import { createCallApmApi } from '../services/rest/createCallApmApi';
import { ConfigSchema } from '..';
import 'react-vis/dist/style.css';
@@ -61,7 +65,7 @@ function App() {
);
}
-function ApmAppRoot({
+export function ApmAppRoot({
core,
deps,
routerHistory,
@@ -116,13 +120,27 @@ function ApmAppRoot({
/**
* This module is rendered asynchronously in the Kibana platform.
*/
+
export const renderApp = (
core: CoreStart,
deps: ApmPluginSetupDeps,
{ element }: AppMountParameters,
config: ConfigSchema
) => {
+ // render APM feedback link in global help menu
+ setHelpExtension(core);
+ setReadonlyBadge(core);
+
+ createCallApmApi(core.http);
+
resetHistory();
+
+ // Automatically creates static index pattern and stores as saved object
+ createStaticIndexPattern().catch((e) => {
+ // eslint-disable-next-line no-console
+ console.log('Error creating static index pattern', e);
+ });
+
ReactDOM.render(
{
+export const renderAsRedirectTo = (to: string) => {
return ({ location }: RouteComponentProps) => (
,
- breadcrumb: i18n.translate('xpack.apm.home.rumOverview.title', {
- defaultMessage: 'Real User Monitoring',
- }),
- name: RouteName.RUM_OVERVIEW,
- },
{
exact: true,
path: '/settings/anomaly-detection',
diff --git a/x-pack/plugins/apm/public/components/app/Main/route_config/route_names.tsx b/x-pack/plugins/apm/public/components/app/Main/route_config/route_names.tsx
index 37d96e74d8ee6..1bf798e3b26d7 100644
--- a/x-pack/plugins/apm/public/components/app/Main/route_config/route_names.tsx
+++ b/x-pack/plugins/apm/public/components/app/Main/route_config/route_names.tsx
@@ -26,6 +26,6 @@ export enum RouteName {
SERVICE_NODES = 'nodes',
LINK_TO_TRACE = 'link_to_trace',
CUSTOMIZE_UI = 'customize_ui',
- RUM_OVERVIEW = 'rum_overview',
ANOMALY_DETECTION = 'anomaly_detection',
+ CSM = 'csm',
}
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx
index a1b07640b5c17..24da5e9ef3897 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx
@@ -6,6 +6,7 @@
import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
import React from 'react';
+import { i18n } from '@kbn/i18n';
import { RumOverview } from '../RumDashboard';
import { RumHeader } from './RumHeader';
@@ -16,7 +17,11 @@ export function RumHome() {
- End User Experience
+
+ {i18n.translate('xpack.apm.csm.title', {
+ defaultMessage: 'Client Side Monitoring',
+ })}
+
diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts
index f264ae6cd9852..cf729fe0ff301 100644
--- a/x-pack/plugins/apm/public/plugin.ts
+++ b/x-pack/plugins/apm/public/plugin.ts
@@ -7,7 +7,10 @@
import { i18n } from '@kbn/i18n';
import { lazy } from 'react';
import { ConfigSchema } from '.';
-import { ObservabilityPluginSetup } from '../../observability/public';
+import {
+ FetchDataParams,
+ ObservabilityPluginSetup,
+} from '../../observability/public';
import {
AppMountParameters,
CoreSetup,
@@ -33,15 +36,7 @@ import {
} from '../../triggers_actions_ui/public';
import { AlertType } from '../common/alert_types';
import { featureCatalogueEntry } from './featureCatalogueEntry';
-import { createCallApmApi } from './services/rest/createCallApmApi';
-import { setHelpExtension } from './setHelpExtension';
import { toggleAppLinkInNav } from './toggleAppLinkInNav';
-import { setReadonlyBadge } from './updateBadge';
-import { createStaticIndexPattern } from './services/rest/index_pattern';
-import {
- fetchOverviewPageData,
- hasData,
-} from './services/rest/apm_overview_fetchers';
export type ApmPluginSetup = void;
export type ApmPluginStart = void;
@@ -71,7 +66,6 @@ export class ApmPlugin implements Plugin {
this.initializerContext = initializerContext;
}
public setup(core: CoreSetup, plugins: ApmPluginSetupDeps) {
- createCallApmApi(core.http);
const config = this.initializerContext.config.get();
const pluginSetupDeps = plugins;
@@ -79,10 +73,27 @@ export class ApmPlugin implements Plugin {
pluginSetupDeps.home.featureCatalogue.register(featureCatalogueEntry);
if (plugins.observability) {
+ const getApmDataHelper = async () => {
+ const {
+ fetchOverviewPageData,
+ hasData,
+ createCallApmApi,
+ } = await import('./services/rest/apm_overview_fetchers');
+ // have to do this here as well in case app isn't mounted yet
+ createCallApmApi(core.http);
+
+ return { fetchOverviewPageData, hasData };
+ };
plugins.observability.dashboard.register({
appName: 'apm',
- fetchData: fetchOverviewPageData,
- hasData,
+ hasData: async () => {
+ const dataHelper = await getApmDataHelper();
+ return await dataHelper.hasData();
+ },
+ fetchData: async (params: FetchDataParams) => {
+ const dataHelper = await getApmDataHelper();
+ return await dataHelper.fetchOverviewPageData(params);
+ },
});
}
@@ -96,20 +107,28 @@ export class ApmPlugin implements Plugin {
category: DEFAULT_APP_CATEGORIES.observability,
async mount(params: AppMountParameters) {
- // Load application bundle
- const { renderApp } = await import('./application');
- // Get start services
- const [coreStart] = await core.getStartServices();
-
- // render APM feedback link in global help menu
- setHelpExtension(coreStart);
- setReadonlyBadge(coreStart);
-
- // Automatically creates static index pattern and stores as saved object
- createStaticIndexPattern().catch((e) => {
- // eslint-disable-next-line no-console
- console.log('Error creating static index pattern', e);
- });
+ // Load application bundle and Get start services
+ const [{ renderApp }, [coreStart]] = await Promise.all([
+ import('./application'),
+ core.getStartServices(),
+ ]);
+
+ return renderApp(coreStart, pluginSetupDeps, params, config);
+ },
+ });
+
+ core.application.register({
+ id: 'csm',
+ title: 'Client Side Monitoring',
+ order: 8500,
+ category: DEFAULT_APP_CATEGORIES.observability,
+
+ async mount(params: AppMountParameters) {
+ // Load application bundle and Get start service
+ const [{ renderApp }, [coreStart]] = await Promise.all([
+ import('./application/csmApp'),
+ core.getStartServices(),
+ ]);
return renderApp(coreStart, pluginSetupDeps, params, config);
},
diff --git a/x-pack/plugins/apm/public/services/rest/apm_overview_fetchers.ts b/x-pack/plugins/apm/public/services/rest/apm_overview_fetchers.ts
index 78f3a0a0aaa80..a20f89fac2d60 100644
--- a/x-pack/plugins/apm/public/services/rest/apm_overview_fetchers.ts
+++ b/x-pack/plugins/apm/public/services/rest/apm_overview_fetchers.ts
@@ -11,6 +11,8 @@ import {
} from '../../../../observability/public';
import { callApmApi } from './createCallApmApi';
+export { createCallApmApi } from './createCallApmApi';
+
export const fetchOverviewPageData = async ({
absoluteTime,
relativeTime,
diff --git a/x-pack/plugins/apm/server/feature.ts b/x-pack/plugins/apm/server/feature.ts
index 971bc96234376..0f6061653f352 100644
--- a/x-pack/plugins/apm/server/feature.ts
+++ b/x-pack/plugins/apm/server/feature.ts
@@ -15,7 +15,7 @@ export const APM_FEATURE = {
order: 900,
icon: 'apmApp',
navLinkId: 'apm',
- app: ['apm', 'kibana'],
+ app: ['apm', 'csm', 'kibana'],
catalogue: ['apm'],
management: {
insightsAndAlerting: ['triggersActions'],
@@ -24,7 +24,7 @@ export const APM_FEATURE = {
// see x-pack/plugins/features/common/feature_kibana_privileges.ts
privileges: {
all: {
- app: ['apm', 'kibana'],
+ app: ['apm', 'csm', 'kibana'],
api: ['apm', 'apm_write'],
catalogue: ['apm'],
savedObject: {
@@ -40,7 +40,7 @@ export const APM_FEATURE = {
ui: ['show', 'save', 'alerting:show', 'alerting:save'],
},
read: {
- app: ['apm', 'kibana'],
+ app: ['apm', 'csm', 'kibana'],
api: ['apm'],
catalogue: ['apm'],
savedObject: {
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 6a9909c4291ff..e8fe926ab2a0a 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -4833,7 +4833,6 @@
"xpack.apm.header.badge.readOnly.tooltip": "を保存できませんでした",
"xpack.apm.helpMenu.upgradeAssistantLink": "アップグレードアシスタント",
"xpack.apm.histogram.plot.noDataLabel": "この時間範囲のデータがありません。",
- "xpack.apm.home.rumOverview.title": "リアルユーザー監視",
"xpack.apm.home.serviceMapTabLabel": "サービスマップ",
"xpack.apm.home.servicesTabLabel": "サービス",
"xpack.apm.home.tracesTabLabel": "トレース",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index a2565d8af3ec9..d11ffde56f37f 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -4834,7 +4834,6 @@
"xpack.apm.header.badge.readOnly.tooltip": "无法保存",
"xpack.apm.helpMenu.upgradeAssistantLink": "升级助手",
"xpack.apm.histogram.plot.noDataLabel": "此时间范围内没有数据。",
- "xpack.apm.home.rumOverview.title": "真实用户监测",
"xpack.apm.home.serviceMapTabLabel": "服务地图",
"xpack.apm.home.servicesTabLabel": "服务",
"xpack.apm.home.tracesTabLabel": "追溯",
diff --git a/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts b/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts
index 98083f947c9ef..b93039c8fb0e4 100644
--- a/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts
+++ b/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts
@@ -60,7 +60,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
it('shows apm navlink', async () => {
const navLinks = await appsMenu.readLinks();
- expect(navLinks.map((link) => link.text)).to.eql(['Overview', 'APM', 'Stack Management']);
+ expect(navLinks.map((link) => link.text)).to.eql([
+ 'Overview',
+ 'APM',
+ 'Client Side Monitoring',
+ 'Stack Management',
+ ]);
});
it('can navigate to APM app', async () => {
@@ -109,7 +114,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
it('shows apm navlink', async () => {
const navLinks = (await appsMenu.readLinks()).map((link) => link.text);
- expect(navLinks).to.eql(['Overview', 'APM', 'Stack Management']);
+ expect(navLinks).to.eql(['Overview', 'APM', 'Client Side Monitoring', 'Stack Management']);
});
it('can navigate to APM app', async () => {
From b944dd3c96213958f41bea66181ac1b06f80a604 Mon Sep 17 00:00:00 2001
From: Liza Katz
Date: Wed, 19 Aug 2020 20:29:35 +0300
Subject: [PATCH 34/85] [Search] Use new es client (#74529)
* improve test stability
* Use new client
* docs
* Use asyncSearch endpoints
* Clean up types
* Use transport request for now
* fixes
* Fix functional test
* encode
* remove eslint
Co-authored-by: Elastic Machine
---
.../search_examples/public/components/app.tsx | 8 +-
.../data/common/search/es_search/types.ts | 5 +-
src/plugins/data/public/public.api.md | 2 +-
.../es_search/es_search_strategy.test.ts | 20 ++--
.../search/es_search/es_search_strategy.ts | 10 +-
src/plugins/data/server/server.api.md | 1 +
.../server/search/es_search_strategy.test.ts | 49 ++++-----
.../server/search/es_search_strategy.ts | 99 +++++++++----------
8 files changed, 96 insertions(+), 98 deletions(-)
diff --git a/examples/search_examples/public/components/app.tsx b/examples/search_examples/public/components/app.tsx
index 31cc5e420da11..704d31d42e640 100644
--- a/examples/search_examples/public/components/app.tsx
+++ b/examples/search_examples/public/components/app.tsx
@@ -47,7 +47,6 @@ import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/
import {
PLUGIN_ID,
PLUGIN_NAME,
- IMyStrategyRequest,
IMyStrategyResponse,
SERVER_SEARCH_ROUTE_PATH,
} from '../../common';
@@ -134,12 +133,9 @@ export const SearchExamplesApp = ({
query,
},
},
- };
-
- if (strategy) {
// Add a custom request parameter to be consumed by `MyStrategy`.
- (request as IMyStrategyRequest).get_cool = getCool;
- }
+ ...(strategy ? { get_cool: getCool } : {}),
+ };
// Submit the search request using the `data.search` service.
const searchSubscription$ = data.search
diff --git a/src/plugins/data/common/search/es_search/types.ts b/src/plugins/data/common/search/es_search/types.ts
index 6fc0923768703..8853e40dd0ad2 100644
--- a/src/plugins/data/common/search/es_search/types.ts
+++ b/src/plugins/data/common/search/es_search/types.ts
@@ -16,14 +16,15 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { SearchParams, SearchResponse } from 'elasticsearch';
+import { SearchResponse } from 'elasticsearch';
+import { Search } from '@elastic/elasticsearch/api/requestParams';
import { IKibanaSearchRequest, IKibanaSearchResponse } from '../types';
export const ES_SEARCH_STRATEGY = 'es';
export type ISearchRequestParams = {
trackTotalHits?: boolean;
-} & SearchParams;
+} & Search;
export interface IEsSearchRequest extends IKibanaSearchRequest {
params?: ISearchRequestParams;
diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md
index 58c2bd9957ab8..7041bcdfa4221 100644
--- a/src/plugins/data/public/public.api.md
+++ b/src/plugins/data/public/public.api.md
@@ -61,7 +61,7 @@ import * as Rx from 'rxjs';
import { SavedObject } from 'src/core/server';
import { SavedObject as SavedObject_3 } from 'src/core/public';
import { SavedObjectsClientContract } from 'src/core/public';
-import { SearchParams } from 'elasticsearch';
+import { Search } from '@elastic/elasticsearch/api/requestParams';
import { SearchResponse as SearchResponse_2 } from 'elasticsearch';
import { SerializedFieldFormat as SerializedFieldFormat_2 } from 'src/plugins/expressions/common';
import { Subscription } from 'rxjs';
diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts
index 2888f9d9d20b5..11564bb336b08 100644
--- a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts
+++ b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts
@@ -26,15 +26,17 @@ describe('ES search strategy', () => {
info: () => {},
};
const mockApiCaller = jest.fn().mockResolvedValue({
- _shards: {
- total: 10,
- failed: 1,
- skipped: 2,
- successful: 7,
+ body: {
+ _shards: {
+ total: 10,
+ failed: 1,
+ skipped: 2,
+ successful: 7,
+ },
},
});
const mockContext = {
- core: { elasticsearch: { legacy: { client: { callAsCurrentUser: mockApiCaller } } } },
+ core: { elasticsearch: { client: { asCurrentUser: { search: mockApiCaller } } } },
};
const mockConfig$ = pluginInitializerContextConfigMock({}).legacy.globalConfig$;
@@ -55,8 +57,7 @@ describe('ES search strategy', () => {
await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params });
expect(mockApiCaller).toBeCalled();
- expect(mockApiCaller.mock.calls[0][0]).toBe('search');
- expect(mockApiCaller.mock.calls[0][1]).toEqual({
+ expect(mockApiCaller.mock.calls[0][0]).toEqual({
...params,
timeout: '0ms',
ignoreUnavailable: true,
@@ -71,8 +72,7 @@ describe('ES search strategy', () => {
await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params });
expect(mockApiCaller).toBeCalled();
- expect(mockApiCaller.mock.calls[0][0]).toBe('search');
- expect(mockApiCaller.mock.calls[0][1]).toEqual({
+ expect(mockApiCaller.mock.calls[0][0]).toEqual({
...params,
restTotalHitsAsInt: true,
});
diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.ts b/src/plugins/data/server/search/es_search/es_search_strategy.ts
index 234c30376d6db..7f9cb665b96b5 100644
--- a/src/plugins/data/server/search/es_search/es_search_strategy.ts
+++ b/src/plugins/data/server/search/es_search/es_search_strategy.ts
@@ -20,6 +20,7 @@ import { first } from 'rxjs/operators';
import { SharedGlobalConfig, Logger } from 'kibana/server';
import { SearchResponse } from 'elasticsearch';
import { Observable } from 'rxjs';
+import { ApiResponse } from '@elastic/elasticsearch';
import { SearchUsage } from '../collectors/usage';
import { ISearchStrategy, getDefaultSearchParams, getTotalLoaded } from '..';
@@ -46,11 +47,10 @@ export const esSearchStrategyProvider = (
};
try {
- const rawResponse = (await context.core.elasticsearch.legacy.client.callAsCurrentUser(
- 'search',
- params,
- options
- )) as SearchResponse;
+ const esResponse = (await context.core.elasticsearch.client.asCurrentUser.search(
+ params
+ )) as ApiResponse>;
+ const rawResponse = esResponse.body;
if (usage) usage.trackSuccess(rawResponse.took);
diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md
index 9c8a79f27a9db..e259a7878398d 100644
--- a/src/plugins/data/server/server.api.md
+++ b/src/plugins/data/server/server.api.md
@@ -131,6 +131,7 @@ import { RequestStatistics } from 'src/plugins/inspector/common';
import { SavedObject } from 'src/core/server';
import { SavedObjectsClientContract as SavedObjectsClientContract_2 } from 'src/core/server';
import { ScrollParams } from 'elasticsearch';
+import { Search } from '@elastic/elasticsearch/api/requestParams';
import { SearchParams } from 'elasticsearch';
import { SearchResponse } from 'elasticsearch';
import { SearchShardsParams } from 'elasticsearch';
diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts
index 4fd1e889ba1a5..5cff7fea601d3 100644
--- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts
+++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts
@@ -9,8 +9,21 @@ import { pluginInitializerContextConfigMock } from '../../../../../src/core/serv
import { enhancedEsSearchStrategyProvider } from './es_search_strategy';
const mockAsyncResponse = {
- id: 'foo',
- response: {
+ body: {
+ id: 'foo',
+ response: {
+ _shards: {
+ total: 10,
+ failed: 1,
+ skipped: 2,
+ successful: 7,
+ },
+ },
+ },
+};
+
+const mockRollupResponse = {
+ body: {
_shards: {
total: 10,
failed: 1,
@@ -20,22 +33,15 @@ const mockAsyncResponse = {
},
};
-const mockRollupResponse = {
- _shards: {
- total: 10,
- failed: 1,
- skipped: 2,
- successful: 7,
- },
-};
-
describe('ES search strategy', () => {
const mockApiCaller = jest.fn();
const mockLogger: any = {
info: () => {},
};
const mockContext = {
- core: { elasticsearch: { legacy: { client: { callAsCurrentUser: mockApiCaller } } } },
+ core: {
+ elasticsearch: { client: { asCurrentUser: { transport: { request: mockApiCaller } } } },
+ },
};
const mockConfig$ = pluginInitializerContextConfigMock({}).legacy.globalConfig$;
@@ -58,8 +64,7 @@ describe('ES search strategy', () => {
await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params });
expect(mockApiCaller).toBeCalled();
- expect(mockApiCaller.mock.calls[0][0]).toBe('transport.request');
- const { method, path, body } = mockApiCaller.mock.calls[0][1];
+ const { method, path, body } = mockApiCaller.mock.calls[0][0];
expect(method).toBe('POST');
expect(path).toBe('/logstash-*/_async_search');
expect(body).toEqual({ query: {} });
@@ -74,8 +79,7 @@ describe('ES search strategy', () => {
await esSearch.search((mockContext as unknown) as RequestHandlerContext, { id: 'foo', params });
expect(mockApiCaller).toBeCalled();
- expect(mockApiCaller.mock.calls[0][0]).toBe('transport.request');
- const { method, path, body } = mockApiCaller.mock.calls[0][1];
+ const { method, path, body } = mockApiCaller.mock.calls[0][0];
expect(method).toBe('GET');
expect(path).toBe('/_async_search/foo');
expect(body).toEqual(undefined);
@@ -90,8 +94,7 @@ describe('ES search strategy', () => {
await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params });
expect(mockApiCaller).toBeCalled();
- expect(mockApiCaller.mock.calls[0][0]).toBe('transport.request');
- const { method, path } = mockApiCaller.mock.calls[0][1];
+ const { method, path } = mockApiCaller.mock.calls[0][0];
expect(method).toBe('POST');
expect(path).toBe('/foo-%E7%A8%8B/_async_search');
});
@@ -108,8 +111,7 @@ describe('ES search strategy', () => {
});
expect(mockApiCaller).toBeCalled();
- expect(mockApiCaller.mock.calls[0][0]).toBe('transport.request');
- const { method, path } = mockApiCaller.mock.calls[0][1];
+ const { method, path } = mockApiCaller.mock.calls[0][0];
expect(method).toBe('POST');
expect(path).toBe('/foo-%E7%A8%8B/_rollup_search');
});
@@ -123,9 +125,8 @@ describe('ES search strategy', () => {
await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params });
expect(mockApiCaller).toBeCalled();
- expect(mockApiCaller.mock.calls[0][0]).toBe('transport.request');
- const { query } = mockApiCaller.mock.calls[0][1];
- expect(query).toHaveProperty('wait_for_completion_timeout');
- expect(query).toHaveProperty('keep_alive');
+ const { querystring } = mockApiCaller.mock.calls[0][0];
+ expect(querystring).toHaveProperty('wait_for_completion_timeout');
+ expect(querystring).toHaveProperty('keep_alive');
});
});
diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts
index 95f1285363f49..f6425a63cef67 100644
--- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts
+++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts
@@ -6,41 +6,27 @@
import { first } from 'rxjs/operators';
import { mapKeys, snakeCase } from 'lodash';
-import { SearchResponse } from 'elasticsearch';
import { Observable } from 'rxjs';
+import { SearchResponse } from 'elasticsearch';
import {
- LegacyAPICaller,
SharedGlobalConfig,
RequestHandlerContext,
+ ElasticsearchClient,
Logger,
} from '../../../../../src/core/server';
import {
- ISearchOptions,
getDefaultSearchParams,
getTotalLoaded,
ISearchStrategy,
SearchUsage,
+ ISearchOptions,
} from '../../../../../src/plugins/data/server';
import { IEnhancedEsSearchRequest } from '../../common';
import { shimHitsTotal } from './shim_hits_total';
import { IEsSearchResponse } from '../../../../../src/plugins/data/common/search/es_search';
-interface AsyncSearchResponse {
- id: string;
- is_partial: boolean;
- is_running: boolean;
- response: SearchResponse;
-}
-
-interface EnhancedEsSearchResponse extends IEsSearchResponse {
- is_partial: boolean;
- is_running: boolean;
-}
-
-function isEnhancedEsSearchResponse(
- response: IEsSearchResponse
-): response is EnhancedEsSearchResponse {
- return response.hasOwnProperty('is_partial') && response.hasOwnProperty('is_running');
+function isEnhancedEsSearchResponse(response: any): response is IEsSearchResponse {
+ return response.hasOwnProperty('isPartial') && response.hasOwnProperty('isRunning');
}
export const enhancedEsSearchStrategyProvider = (
@@ -55,19 +41,23 @@ export const enhancedEsSearchStrategyProvider = (
) => {
logger.info(`search ${JSON.stringify(request.params) || request.id}`);
const config = await config$.pipe(first()).toPromise();
- const caller = context.core.elasticsearch.legacy.client.callAsCurrentUser;
+ const client = context.core.elasticsearch.client.asCurrentUser;
const defaultParams = getDefaultSearchParams(config);
const params = { ...defaultParams, ...request.params };
+ const isAsync = request.indexType !== 'rollup';
+
try {
- const response =
- request.indexType === 'rollup'
- ? await rollupSearch(caller, { ...request, params }, options)
- : await asyncSearch(caller, { ...request, params }, options);
+ const response = isAsync
+ ? await asyncSearch(client, { ...request, params }, options)
+ : await rollupSearch(client, { ...request, params }, options);
if (
usage &&
- (!isEnhancedEsSearchResponse(response) || (!response.is_partial && !response.is_running))
+ isAsync &&
+ isEnhancedEsSearchResponse(response) &&
+ !response.isRunning &&
+ !response.isPartial
) {
usage.trackSuccess(response.rawResponse.took);
}
@@ -81,11 +71,9 @@ export const enhancedEsSearchStrategyProvider = (
const cancel = async (context: RequestHandlerContext, id: string) => {
logger.info(`cancel ${id}`);
- const method = 'DELETE';
- const path = encodeURI(`/_async_search/${id}`);
- await context.core.elasticsearch.legacy.client.callAsCurrentUser('transport.request', {
- method,
- path,
+ await context.core.elasticsearch.client.asCurrentUser.transport.request({
+ method: 'DELETE',
+ path: encodeURI(`/_async_search/${id}`),
});
};
@@ -93,10 +81,10 @@ export const enhancedEsSearchStrategyProvider = (
};
async function asyncSearch(
- caller: LegacyAPICaller,
+ client: ElasticsearchClient,
request: IEnhancedEsSearchRequest,
options?: ISearchOptions
-) {
+): Promise {
const { timeout = undefined, restTotalHitsAsInt = undefined, ...params } = {
...request.params,
};
@@ -112,46 +100,57 @@ async function asyncSearch(
// Only report partial results every 64 shards; this should be reduced when we actually display partial results
const batchedReduceSize = request.id ? undefined : 64;
- const query = toSnakeCase({
+ const asyncOptions = {
waitForCompletionTimeout: '100ms', // Wait up to 100ms for the response to return
keepAlive: '1m', // Extend the TTL for this search request by one minute
+ };
+
+ const querystring = toSnakeCase({
+ ...asyncOptions,
...(batchedReduceSize && { batchedReduceSize }),
...queryParams,
});
- // eslint-disable-next-line @typescript-eslint/naming-convention
- const { id, response, is_partial, is_running } = (await caller(
- 'transport.request',
- { method, path, body, query },
- options
- )) as AsyncSearchResponse;
+ // TODO: replace with async endpoints once https://github.com/elastic/elasticsearch-js/issues/1280 is resolved
+ const esResponse = await client.transport.request({
+ method,
+ path,
+ body,
+ querystring,
+ });
+ const { id, response, is_partial: isPartial, is_running: isRunning } = esResponse.body;
return {
id,
- isPartial: is_partial,
- isRunning: is_running,
+ isPartial,
+ isRunning,
rawResponse: shimHitsTotal(response),
...getTotalLoaded(response._shards),
};
}
async function rollupSearch(
- caller: LegacyAPICaller,
+ client: ElasticsearchClient,
request: IEnhancedEsSearchRequest,
options?: ISearchOptions
-) {
+): Promise {
const { body, index, ...params } = request.params!;
const method = 'POST';
const path = encodeURI(`/${index}/_rollup_search`);
- const query = toSnakeCase(params);
+ const querystring = toSnakeCase(params);
- const rawResponse = await ((caller(
- 'transport.request',
- { method, path, body, query },
- options
- ) as unknown) as SearchResponse);
+ const esResponse = await client.transport.request({
+ method,
+ path,
+ body,
+ querystring,
+ });
- return { rawResponse, ...getTotalLoaded(rawResponse._shards) };
+ const response = esResponse.body as SearchResponse;
+ return {
+ rawResponse: shimHitsTotal(response),
+ ...getTotalLoaded(response._shards),
+ };
}
function toSnakeCase(obj: Record) {
From 3469e164f4e259a97844700cd40b854eab89a25e Mon Sep 17 00:00:00 2001
From: Quynh Nguyen <43350163+qn895@users.noreply.github.com>
Date: Wed, 19 Aug 2020 13:03:00 -0500
Subject: [PATCH 35/85] [ML] Add option for per-partition categorization to
categorization job wizard (#75061)
---
.../plugins/ml/common/constants/anomalies.ts | 2 +
.../plugins/ml/common/constants/messages.ts | 28 +++
x-pack/plugins/ml/common/types/anomalies.ts | 17 ++
.../types/anomaly_detection_jobs/job.ts | 2 +-
x-pack/plugins/ml/common/types/results.ts | 9 +
x-pack/plugins/ml/common/util/job_utils.ts | 56 +++++-
.../public/application/explorer/explorer.js | 19 +-
.../job_creator/categorization_job_creator.ts | 31 +++
.../new_job/common/job_creator/job_creator.ts | 30 +++
.../common/job_validator/job_validator.ts | 14 ++
.../jobs/new_job/common/job_validator/util.ts | 23 +++
.../advanced_view/detector_list.tsx | 14 +-
.../components/advanced_view/extra.tsx | 9 +-
.../categorization_field_select.tsx | 35 ++--
.../categorization_per_partition.tsx | 65 +++++++
.../categorization_per_partition_dropdown.tsx | 67 +++++++
.../categorization_per_partition_input.tsx | 55 ++++++
.../categorization_per_partition_switch.tsx | 54 +++++
.../categorization_stop_on_warn_switch.tsx | 44 +++++
.../description.tsx | 32 +++
.../categorization_partition_field/index.ts | 6 +
.../categorization_view/metric_selection.tsx | 4 +
.../application/routing/routes/explorer.tsx | 28 ++-
.../services/ml_api_service/results.ts | 19 +-
.../models/job_validation/job_validation.ts | 1 -
.../models/results_service/results_service.ts | 160 ++++++++++++++-
.../ml/server/routes/anomaly_detectors.ts | 1 -
x-pack/plugins/ml/server/routes/apidoc.json | 2 +
.../ml/server/routes/results_service.ts | 80 ++++++++
.../routes/schemas/results_service_schema.ts | 23 +++
.../apis/ml/results/get_categorizer_stats.ts | 148 ++++++++++++++
.../apis/ml/results/get_stopped_partitions.ts | 184 ++++++++++++++++++
.../api_integration/apis/ml/results/index.ts | 2 +
33 files changed, 1230 insertions(+), 34 deletions(-)
create mode 100644 x-pack/plugins/ml/common/types/results.ts
create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/categorization_per_partition.tsx
create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/categorization_per_partition_dropdown.tsx
create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/categorization_per_partition_input.tsx
create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/categorization_per_partition_switch.tsx
create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/categorization_stop_on_warn_switch.tsx
create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/description.tsx
create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/index.ts
create mode 100644 x-pack/test/api_integration/apis/ml/results/get_categorizer_stats.ts
create mode 100644 x-pack/test/api_integration/apis/ml/results/get_stopped_partitions.ts
diff --git a/x-pack/plugins/ml/common/constants/anomalies.ts b/x-pack/plugins/ml/common/constants/anomalies.ts
index d15033b738b0f..73a24bc11fe66 100644
--- a/x-pack/plugins/ml/common/constants/anomalies.ts
+++ b/x-pack/plugins/ml/common/constants/anomalies.ts
@@ -22,3 +22,5 @@ export enum ANOMALY_THRESHOLD {
}
export const PARTITION_FIELDS = ['partition_field', 'over_field', 'by_field'] as const;
+export const JOB_ID = 'job_id';
+export const PARTITION_FIELD_VALUE = 'partition_field_value';
diff --git a/x-pack/plugins/ml/common/constants/messages.ts b/x-pack/plugins/ml/common/constants/messages.ts
index b42eacbf7df5c..a9e4cdc4a0434 100644
--- a/x-pack/plugins/ml/common/constants/messages.ts
+++ b/x-pack/plugins/ml/common/constants/messages.ts
@@ -43,6 +43,34 @@ export const getMessages = once(() => {
const createJobsDocsUrl = `https://www.elastic.co/guide/en/machine-learning/{{version}}/create-jobs.html`;
return {
+ categorizer_detector_missing_per_partition_field: {
+ status: VALIDATION_STATUS.ERROR,
+ text: i18n.translate(
+ 'xpack.ml.models.jobValidation.messages.categorizerMissingPerPartitionFieldMessage',
+ {
+ defaultMessage:
+ 'Partition field must be set for detectors that reference "mlcategory" when per-partition categorization is enabled.',
+ }
+ ),
+ url:
+ 'https://www.elastic.co/guide/en/machine-learning/{{version}}/ml-configuring-categories.html',
+ },
+ categorizer_varying_per_partition_fields: {
+ status: VALIDATION_STATUS.ERROR,
+ text: i18n.translate(
+ 'xpack.ml.models.jobValidation.messages.categorizerVaryingPerPartitionFieldNamesMessage',
+ {
+ defaultMessage:
+ 'Detectors with keyword "mlcategory" cannot have different partition_field_name when per-partition categorization is enabled. Found [{fields}].',
+
+ values: {
+ fields: '"{{fields}}"',
+ },
+ }
+ ),
+ url:
+ 'https://www.elastic.co/guide/en/machine-learning/{{version}}/ml-configuring-categories.html',
+ },
field_not_aggregatable: {
status: VALIDATION_STATUS.ERROR,
text: i18n.translate('xpack.ml.models.jobValidation.messages.fieldNotAggregatableMessage', {
diff --git a/x-pack/plugins/ml/common/types/anomalies.ts b/x-pack/plugins/ml/common/types/anomalies.ts
index a23886e8fcdc6..703d74d1bd5e5 100644
--- a/x-pack/plugins/ml/common/types/anomalies.ts
+++ b/x-pack/plugins/ml/common/types/anomalies.ts
@@ -57,3 +57,20 @@ export interface AnomaliesTableRecord {
}
export type PartitionFieldsType = typeof PARTITION_FIELDS[number];
+
+export interface AnomalyCategorizerStatsDoc {
+ [key: string]: any;
+ job_id: string;
+ result_type: 'categorizer_stats';
+ partition_field_name?: string;
+ partition_field_value?: string;
+ categorized_doc_count: number;
+ total_category_count: number;
+ frequent_category_count: number;
+ rare_category_count: number;
+ dead_category_count: number;
+ failed_category_count: number;
+ categorization_status: 'ok' | 'warn';
+ log_time: number;
+ timestamp: number;
+}
diff --git a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts
index 744f9c4d759dd..48fd4ec914d07 100644
--- a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts
+++ b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts
@@ -93,6 +93,6 @@ export interface CustomRule {
}
export interface PerPartitionCategorization {
- enabled: boolean;
+ enabled?: boolean;
stop_on_warn?: boolean;
}
diff --git a/x-pack/plugins/ml/common/types/results.ts b/x-pack/plugins/ml/common/types/results.ts
new file mode 100644
index 0000000000000..c761620589080
--- /dev/null
+++ b/x-pack/plugins/ml/common/types/results.ts
@@ -0,0 +1,9 @@
+/*
+ * 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 interface GetStoppedPartitionResult {
+ jobs: string[] | Record;
+}
diff --git a/x-pack/plugins/ml/common/util/job_utils.ts b/x-pack/plugins/ml/common/util/job_utils.ts
index 8e6933ed5924f..9729240567c24 100644
--- a/x-pack/plugins/ml/common/util/job_utils.ts
+++ b/x-pack/plugins/ml/common/util/job_utils.ts
@@ -23,6 +23,7 @@ import { EntityField } from './anomaly_utils';
import { MlServerLimits } from '../types/ml_server_info';
import { JobValidationMessage, JobValidationMessageId } from '../constants/messages';
import { ES_AGGREGATION, ML_JOB_AGGREGATION } from '../constants/aggregation_types';
+import { MLCATEGORY } from '../constants/field_types';
export interface ValidationResults {
valid: boolean;
@@ -86,9 +87,9 @@ export function isSourceDataChartableForDetector(job: CombinedJob, detectorIndex
// whereas the 'function_description' field holds an ML-built display hint for function e.g. 'count'.
isSourceDataChartable =
mlFunctionToESAggregation(functionName) !== null &&
- dtr.by_field_name !== 'mlcategory' &&
- dtr.partition_field_name !== 'mlcategory' &&
- dtr.over_field_name !== 'mlcategory';
+ dtr.by_field_name !== MLCATEGORY &&
+ dtr.partition_field_name !== MLCATEGORY &&
+ dtr.over_field_name !== MLCATEGORY;
// If the datafeed uses script fields, we can only plot the time series if
// model plot is enabled. Without model plot it will be very difficult or impossible
@@ -380,16 +381,25 @@ export function basicJobValidation(
valid = false;
}
}
-
+ let categorizerDetectorMissingPartitionField = false;
if (job.analysis_config.detectors.length === 0) {
messages.push({ id: 'detectors_empty' });
valid = false;
} else {
let v = true;
+
each(job.analysis_config.detectors, (d) => {
if (isEmpty(d.function)) {
v = false;
}
+ // if detector has an ml category, check if the partition_field is missing
+ const needToHavePartitionFieldName =
+ job.analysis_config.per_partition_categorization?.enabled === true &&
+ (d.by_field_name === MLCATEGORY || d.over_field_name === MLCATEGORY);
+
+ if (needToHavePartitionFieldName && d.partition_field_name === undefined) {
+ categorizerDetectorMissingPartitionField = true;
+ }
});
if (v) {
messages.push({ id: 'detectors_function_not_empty' });
@@ -397,10 +407,46 @@ export function basicJobValidation(
messages.push({ id: 'detectors_function_empty' });
valid = false;
}
+ if (categorizerDetectorMissingPartitionField) {
+ messages.push({ id: 'categorizer_detector_missing_per_partition_field' });
+ valid = false;
+ }
}
- // check for duplicate detectors
if (job.analysis_config.detectors.length >= 2) {
+ // check if the detectors with mlcategory might have different per_partition_field values
+ // if per_partition_categorization is enabled
+ if (job.analysis_config.per_partition_categorization !== undefined) {
+ if (
+ job.analysis_config.per_partition_categorization.enabled ||
+ (job.analysis_config.per_partition_categorization.stop_on_warn &&
+ Array.isArray(job.analysis_config.detectors) &&
+ job.analysis_config.detectors.length >= 2)
+ ) {
+ const categorizationDetectors = job.analysis_config.detectors.filter(
+ (d) =>
+ d.by_field_name === MLCATEGORY ||
+ d.over_field_name === MLCATEGORY ||
+ d.partition_field_name === MLCATEGORY
+ );
+ const uniqPartitions = [
+ ...new Set(
+ categorizationDetectors
+ .map((d) => d.partition_field_name)
+ .filter((name) => name !== undefined)
+ ),
+ ];
+ if (uniqPartitions.length > 1) {
+ valid = false;
+ messages.push({
+ id: 'categorizer_varying_per_partition_fields',
+ fields: uniqPartitions.join(', '),
+ });
+ }
+ }
+ }
+
+ // check for duplicate detectors
// create an array of objects with a subset of the attributes
// where we want to make sure they are not be the same across detectors
const compareSubSet = job.analysis_config.detectors.map((d) =>
diff --git a/x-pack/plugins/ml/public/application/explorer/explorer.js b/x-pack/plugins/ml/public/application/explorer/explorer.js
index 06cec14578f2a..d78df80fad94e 100644
--- a/x-pack/plugins/ml/public/application/explorer/explorer.js
+++ b/x-pack/plugins/ml/public/application/explorer/explorer.js
@@ -205,7 +205,7 @@ export class Explorer extends React.Component {
updateLanguage = (language) => this.setState({ language });
render() {
- const { showCharts, severity } = this.props;
+ const { showCharts, severity, stoppedPartitions } = this.props;
const {
annotations,
@@ -298,6 +298,23 @@ export class Explorer extends React.Component {
+
+ {stoppedPartitions && (
+
+ }
+ />
+ )}
+
{
+ delete detector.partition_field_name;
+ });
+ if (this._partitionFieldName !== null) this.removeInfluencer(this._partitionFieldName);
+ this._partitionFieldName = null;
+ } else {
+ if (this._partitionFieldName !== fieldName) {
+ // remove the previous field from list of influencers
+ // and add the new one
+ if (this._partitionFieldName !== null) this.removeInfluencer(this._partitionFieldName);
+ this.addInfluencer(fieldName);
+ this._partitionFieldName = fieldName;
+ this._detectors.forEach((detector) => {
+ detector.partition_field_name = fieldName;
+ });
+ }
+ }
+ }
}
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts
index 29e8aafffef7e..4c030a22f54f7 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts
@@ -622,6 +622,36 @@ export class JobCreator {
return JSON.stringify(this._datafeed_config, null, 2);
}
+ private _initPerPartitionCategorization() {
+ if (this._job_config.analysis_config.per_partition_categorization === undefined) {
+ this._job_config.analysis_config.per_partition_categorization = {};
+ }
+ if (this._job_config.analysis_config.per_partition_categorization?.enabled === undefined) {
+ this._job_config.analysis_config.per_partition_categorization!.enabled = false;
+ }
+ if (this._job_config.analysis_config.per_partition_categorization?.stop_on_warn === undefined) {
+ this._job_config.analysis_config.per_partition_categorization!.stop_on_warn = false;
+ }
+ }
+
+ public get perPartitionCategorization() {
+ return this._job_config.analysis_config.per_partition_categorization?.enabled === true;
+ }
+
+ public set perPartitionCategorization(enabled: boolean) {
+ this._initPerPartitionCategorization();
+ this._job_config.analysis_config.per_partition_categorization!.enabled = enabled;
+ }
+
+ public get perPartitionStopOnWarn() {
+ return this._job_config.analysis_config.per_partition_categorization?.stop_on_warn === true;
+ }
+
+ public set perPartitionStopOnWarn(enabled: boolean) {
+ this._initPerPartitionCategorization();
+ this._job_config.analysis_config.per_partition_categorization!.stop_on_warn = enabled;
+ }
+
protected _overrideConfigs(job: Job, datafeed: Datafeed) {
this._job_config = job;
this._datafeed_config = datafeed;
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/job_validator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/job_validator.ts
index 242a643ddd3ce..635322a6c4469 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/job_validator.ts
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/job_validator.ts
@@ -51,6 +51,8 @@ export interface BasicValidations {
queryDelay: Validation;
frequency: Validation;
scrollSize: Validation;
+ categorizerMissingPerPartition: Validation;
+ categorizerVaryingPerPartitionField: Validation;
}
export interface AdvancedValidations {
@@ -76,6 +78,8 @@ export class JobValidator {
queryDelay: { valid: true },
frequency: { valid: true },
scrollSize: { valid: true },
+ categorizerMissingPerPartition: { valid: true },
+ categorizerVaryingPerPartitionField: { valid: true },
};
private _advancedValidations: AdvancedValidations = {
categorizationFieldValid: { valid: true },
@@ -273,6 +277,14 @@ export class JobValidator {
this._advancedValidations.categorizationFieldValid.valid = valid;
}
+ public get categorizerMissingPerPartition() {
+ return this._basicValidations.categorizerMissingPerPartition;
+ }
+
+ public get categorizerVaryingPerPartitionField() {
+ return this._basicValidations.categorizerVaryingPerPartitionField;
+ }
+
/**
* Indicates if the Pick Fields step has a valid input
*/
@@ -283,6 +295,8 @@ export class JobValidator {
(this._jobCreator.type === JOB_TYPE.ADVANCED && this.modelMemoryLimit.valid)) &&
this.bucketSpan.valid &&
this.duplicateDetectors.valid &&
+ this.categorizerMissingPerPartition.valid &&
+ this.categorizerVaryingPerPartitionField.valid &&
!this.validating &&
(this._jobCreator.type !== JOB_TYPE.CATEGORIZATION ||
(this._jobCreator.type === JOB_TYPE.CATEGORIZATION && this.categorizationField))
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/util.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/util.ts
index b97841542f76a..1ce81bf0dcdf0 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/util.ts
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/util.ts
@@ -130,6 +130,29 @@ export function populateValidationMessages(
basicValidations.duplicateDetectors.message = msg;
}
+ if (validationResults.contains('categorizer_detector_missing_per_partition_field')) {
+ basicValidations.categorizerMissingPerPartition.valid = false;
+ const msg = i18n.translate(
+ 'xpack.ml.newJob.wizard.validateJob.categorizerMissingPerPartitionFieldMessage',
+ {
+ defaultMessage:
+ 'Partition field must be set for detectors that reference "mlcategory" when per-partition categorization is enabled.',
+ }
+ );
+ basicValidations.categorizerMissingPerPartition.message = msg;
+ }
+ if (validationResults.contains('categorizer_varying_per_partition_fields')) {
+ basicValidations.categorizerVaryingPerPartitionField.valid = false;
+ const msg = i18n.translate(
+ 'xpack.ml.newJob.wizard.validateJob.categorizerVaryingPerPartitionFieldNamesMessage',
+ {
+ defaultMessage:
+ 'Detectors with keyword "mlcategory" cannot have different partition_field_name when per-partition categorization is enabled.',
+ }
+ );
+ basicValidations.categorizerVaryingPerPartitionField.message = msg;
+ }
+
if (validationResults.contains('bucket_span_empty')) {
basicValidations.bucketSpan.valid = false;
const msg = i18n.translate(
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx
index 38903dd4845a6..ee4bd73755ebe 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx
@@ -46,7 +46,15 @@ export const DetectorList: FC = ({ isActive, onEditJob, onDeleteJob }) =>
}, [jobCreatorUpdated]);
useEffect(() => {
- setValidation(jobValidator.duplicateDetectors);
+ if (!jobValidator.duplicateDetectors.valid) {
+ setValidation(jobValidator.duplicateDetectors);
+ }
+ if (!jobValidator.categorizerVaryingPerPartitionField.valid) {
+ setValidation(jobValidator.categorizerVaryingPerPartitionField);
+ }
+ if (!jobValidator.categorizerMissingPerPartition.valid) {
+ setValidation(jobValidator.categorizerMissingPerPartition);
+ }
}, [jobValidatorUpdated]);
const Buttons: FC<{ index: number }> = ({ index }) => {
@@ -129,7 +137,7 @@ export const DetectorList: FC = ({ isActive, onEditJob, onDeleteJob }) =>
))}
-
+
);
};
@@ -159,7 +167,7 @@ const NoDetectorsWarning: FC<{ show: boolean }> = ({ show }) => {
);
};
-const DuplicateDetectorsWarning: FC<{ validation: Validation }> = ({ validation }) => {
+const DetectorsValidationWarning: FC<{ validation: Validation }> = ({ validation }) => {
if (validation.valid === true) {
return null;
}
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_view/extra.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_view/extra.tsx
index c29ebce41593c..c619f8e4e02dd 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_view/extra.tsx
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_view/extra.tsx
@@ -4,13 +4,19 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { Fragment, FC } from 'react';
+import React, { Fragment, FC, useContext } from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { SummaryCountField } from '../summary_count_field';
import { CategorizationField } from '../categorization_field';
+import { CategorizationPerPartitionField } from '../categorization_partition_field';
+import { JobCreatorContext } from '../../../job_creator_context';
+import { isAdvancedJobCreator } from '../../../../../common/job_creator';
export const ExtraSettings: FC = () => {
+ const { jobCreator } = useContext(JobCreatorContext);
+ const showCategorizationPerPartitionField =
+ isAdvancedJobCreator(jobCreator) && jobCreator.categorizationFieldName !== null;
return (
@@ -21,6 +27,7 @@ export const ExtraSettings: FC = () => {
+ {showCategorizationPerPartitionField && }
);
};
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_field/categorization_field_select.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_field/categorization_field_select.tsx
index 2f3e8d43bc169..d4c96025f9e36 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_field/categorization_field_select.tsx
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_field/categorization_field_select.tsx
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { FC, useContext } from 'react';
+import React, { FC, useCallback, useContext, useMemo } from 'react';
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
import { JobCreatorContext } from '../../../job_creator_context';
@@ -18,24 +18,25 @@ interface Props {
}
export const CategorizationFieldSelect: FC = ({ fields, changeHandler, selectedField }) => {
- const { jobCreator } = useContext(JobCreatorContext);
- const options: EuiComboBoxOptionOption[] = [
- ...createFieldOptions(fields, jobCreator.additionalFields),
- ];
-
- const selection: EuiComboBoxOptionOption[] = [];
- if (selectedField !== null) {
- selection.push({ label: selectedField });
- }
+ const { jobCreator, jobCreatorUpdated } = useContext(JobCreatorContext);
+ const options: EuiComboBoxOptionOption[] = useMemo(
+ () => [...createFieldOptions(fields, jobCreator.additionalFields)],
+ [fields, jobCreatorUpdated]
+ );
- function onChange(selectedOptions: EuiComboBoxOptionOption[]) {
- const option = selectedOptions[0];
- if (typeof option !== 'undefined') {
- changeHandler(option.label);
- } else {
- changeHandler(null);
+ const selection: EuiComboBoxOptionOption[] = useMemo(() => {
+ const selectedOptions: EuiComboBoxOptionOption[] = [];
+ if (selectedField !== null) {
+ selectedOptions.push({ label: selectedField });
}
- }
+ return selectedOptions;
+ }, [selectedField]);
+
+ const onChange = useCallback(
+ (selectedOptions: EuiComboBoxOptionOption[]) =>
+ changeHandler((selectedOptions[0] && selectedOptions[0].label) ?? null),
+ [changeHandler]
+ );
return (
{
+ const { jobCreator: jc, jobCreatorUpdated } = useContext(JobCreatorContext);
+ const jobCreator = jc as AdvancedJobCreator | CategorizationJobCreator;
+ const [enablePerPartitionCategorization, setEnablePerPartitionCategorization] = useState(false);
+ useEffect(() => {
+ setEnablePerPartitionCategorization(jobCreator.perPartitionCategorization);
+ }, [jobCreatorUpdated]);
+
+ return (
+
+
+ }
+ >
+
+
+
+ {enablePerPartitionCategorization && (
+ <>
+
+ }
+ >
+
+
+ >
+ )}
+ {isCategorizationJobCreator(jobCreator) && enablePerPartitionCategorization && (
+
+ )}
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/categorization_per_partition_dropdown.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/categorization_per_partition_dropdown.tsx
new file mode 100644
index 0000000000000..a0eccf914af6e
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/categorization_per_partition_dropdown.tsx
@@ -0,0 +1,67 @@
+/*
+ * 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, { Dispatch, SetStateAction, useContext, useEffect, useState, useMemo } from 'react';
+import { EuiFormRow } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { JobCreatorContext } from '../../../job_creator_context';
+import { CategorizationJobCreator } from '../../../../../common/job_creator';
+import { newJobCapsService } from '../../../../../../../services/new_job_capabilities_service';
+import { CategorizationPerPartitionFieldSelect } from './categorization_per_partition_input';
+
+export const CategorizationPerPartitionFieldDropdown = ({
+ setEnablePerPartitionCategorization,
+}: {
+ setEnablePerPartitionCategorization: Dispatch>;
+}) => {
+ const { jobCreator: jc, jobCreatorUpdate, jobCreatorUpdated } = useContext(JobCreatorContext);
+ const jobCreator = jc as CategorizationJobCreator;
+
+ const [categorizationPartitionFieldName, setCategorizationPartitionFieldName] = useState<
+ string | null
+ >(jobCreator.categorizationPerPartitionField);
+ const { categoryFields } = newJobCapsService;
+
+ const filteredCategories = useMemo(
+ () => categoryFields.filter((c) => c.id !== jobCreator.categorizationFieldName),
+ [categoryFields, jobCreatorUpdated]
+ );
+ useEffect(() => {
+ jobCreator.categorizationPerPartitionField = categorizationPartitionFieldName;
+ jobCreatorUpdate();
+ }, [categorizationPartitionFieldName]);
+
+ useEffect(() => {
+ // set the first item in category as partition field by default
+ // because API requires partition_field to be defined in each detector with mlcategory
+ // if per-partition categorization is enabled
+ if (
+ jobCreator.perPartitionCategorization &&
+ jobCreator.categorizationPerPartitionField === null &&
+ filteredCategories.length > 0
+ ) {
+ jobCreator.categorizationPerPartitionField = filteredCategories[0].id;
+ }
+ setCategorizationPartitionFieldName(jobCreator.categorizationPerPartitionField);
+ setEnablePerPartitionCategorization(jobCreator.perPartitionCategorization);
+ }, [jobCreatorUpdated]);
+ return (
+
+ }
+ >
+
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/categorization_per_partition_input.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/categorization_per_partition_input.tsx
new file mode 100644
index 0000000000000..ec398a810a848
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/categorization_per_partition_input.tsx
@@ -0,0 +1,55 @@
+/*
+ * 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, { FC, useCallback, useContext, useMemo } from 'react';
+import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
+
+import { JobCreatorContext } from '../../../job_creator_context';
+import { Field } from '../../../../../../../../../common/types/fields';
+import { createFieldOptions } from '../../../../../common/job_creator/util/general';
+
+interface Props {
+ fields: Field[];
+ changeHandler(i: string | null): void;
+ selectedField: string | null;
+}
+
+export const CategorizationPerPartitionFieldSelect: FC = ({
+ fields,
+ changeHandler,
+ selectedField,
+}) => {
+ const { jobCreator, jobCreatorUpdated } = useContext(JobCreatorContext);
+ const options: EuiComboBoxOptionOption[] = useMemo(
+ () => [...createFieldOptions(fields, jobCreator.additionalFields)],
+ [fields, jobCreatorUpdated]
+ );
+
+ const selection: EuiComboBoxOptionOption[] = useMemo(() => {
+ const selectedOptions: EuiComboBoxOptionOption[] = [];
+ if (selectedField !== null) {
+ selectedOptions.push({ label: selectedField });
+ }
+ return selectedOptions;
+ }, [selectedField]);
+
+ const onChange = useCallback(
+ (selectedOptions: EuiComboBoxOptionOption[]) =>
+ changeHandler((selectedOptions[0] && selectedOptions[0].label) ?? null),
+ [changeHandler]
+ );
+
+ return (
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/categorization_per_partition_switch.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/categorization_per_partition_switch.tsx
new file mode 100644
index 0000000000000..4e2c1300b7937
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/categorization_per_partition_switch.tsx
@@ -0,0 +1,54 @@
+/*
+ * 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, { FC, useContext, useEffect, useCallback, useState } from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiSwitch } from '@elastic/eui';
+import { JobCreatorContext } from '../../../job_creator_context';
+import { AdvancedJobCreator, CategorizationJobCreator } from '../../../../../common/job_creator';
+
+export const CategorizationPerPartitionSwitch: FC = () => {
+ const { jobCreator: jc, jobCreatorUpdate, jobCreatorUpdated } = useContext(JobCreatorContext);
+ const jobCreator = jc as AdvancedJobCreator | CategorizationJobCreator;
+ const [enablePerPartitionCategorization, setEnablePerPartitionCategorization] = useState(
+ jobCreator.perPartitionCategorization
+ );
+
+ const toggleEnablePerPartitionCategorization = useCallback(
+ () => setEnablePerPartitionCategorization(!enablePerPartitionCategorization),
+ [enablePerPartitionCategorization]
+ );
+
+ useEffect(() => {
+ setEnablePerPartitionCategorization(jobCreator.perPartitionCategorization);
+ }, [jobCreatorUpdated]);
+
+ useEffect(() => {
+ // also turn off stop on warn if per_partition_categorization is turned off
+ if (enablePerPartitionCategorization === false) {
+ jobCreator.perPartitionStopOnWarn = false;
+ }
+
+ jobCreator.perPartitionCategorization = enablePerPartitionCategorization;
+ jobCreatorUpdate();
+ }, [enablePerPartitionCategorization]);
+
+ return (
+
+ }
+ />
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/categorization_stop_on_warn_switch.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/categorization_stop_on_warn_switch.tsx
new file mode 100644
index 0000000000000..8bbf03a999f88
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/categorization_stop_on_warn_switch.tsx
@@ -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.
+ */
+
+import React, { FC, useCallback, useContext, useEffect, useState } from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiSwitch } from '@elastic/eui';
+import { JobCreatorContext } from '../../../job_creator_context';
+import { AdvancedJobCreator, CategorizationJobCreator } from '../../../../../common/job_creator';
+
+export const CategorizationPerPartitionStopOnWarnSwitch: FC = () => {
+ const { jobCreator: jc, jobCreatorUpdate, jobCreatorUpdated } = useContext(JobCreatorContext);
+ const jobCreator = jc as AdvancedJobCreator | CategorizationJobCreator;
+ const [stopOnWarn, setStopOnWarn] = useState(jobCreator.perPartitionStopOnWarn);
+
+ const toggleStopOnWarn = useCallback(() => setStopOnWarn(!stopOnWarn), [stopOnWarn]);
+
+ useEffect(() => {
+ jobCreator.perPartitionStopOnWarn = stopOnWarn;
+ jobCreatorUpdate();
+ }, [stopOnWarn]);
+
+ useEffect(() => {
+ setStopOnWarn(jobCreator.perPartitionStopOnWarn);
+ }, [jobCreatorUpdated]);
+
+ return (
+
+ }
+ />
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/description.tsx
new file mode 100644
index 0000000000000..01369ca425391
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/description.tsx
@@ -0,0 +1,32 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { memo, FC } from 'react';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiDescribedFormGroup } from '@elastic/eui';
+
+interface Props {
+ children: React.ReactNode;
+}
+export const Description: FC = memo(({ children }) => {
+ const title = i18n.translate('xpack.ml.newJob.wizard.perPartitionCategorization.enable.title', {
+ defaultMessage: 'Enable per-partition categorization',
+ });
+ return (
+ {title}}
+ description={
+
+ }
+ >
+ <>{children}>
+
+ );
+});
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/index.ts b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/index.ts
new file mode 100644
index 0000000000000..f3b50a19c021e
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/index.ts
@@ -0,0 +1,6 @@
+/*
+ * 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 { CategorizationPerPartitionField } from './categorization_per_partition';
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection.tsx
index f5c3e90d63418..cbbddb5bbc5b8 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection.tsx
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection.tsx
@@ -12,6 +12,8 @@ import { JobCreatorContext } from '../../../job_creator_context';
import { CategorizationJobCreator } from '../../../../../common/job_creator';
import { CategorizationField } from '../categorization_field';
import { CategorizationDetector } from '../categorization_detector';
+import { CategorizationPerPartitionField } from '../categorization_partition_field';
+
import { FieldExamples } from './field_examples';
import { ExamplesValidCallout } from './examples_valid_callout';
import {
@@ -126,6 +128,8 @@ export const CategorizationDetectors: FC = ({ setIsValid }) => {
>
)}
+
+
>
);
};
diff --git a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx
index a2030776773a9..62d7e82a214b5 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { FC, useEffect, useState } from 'react';
+import React, { FC, useEffect, useState, useCallback } from 'react';
import useObservable from 'react-use/lib/useObservable';
import { i18n } from '@kbn/i18n';
@@ -32,6 +32,7 @@ import { useUrlState } from '../../util/url_state';
import { getBreadcrumbWithUrlForApp } from '../breadcrumbs';
import { useTimefilter } from '../../contexts/kibana';
import { isViewBySwimLaneData } from '../../explorer/swimlane_container';
+import { JOB_ID } from '../../../../common/constants/anomalies';
export const explorerRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
path: '/explorer',
@@ -70,6 +71,8 @@ const ExplorerUrlStateManager: FC = ({ jobsWithTim
const [appState, setAppState] = useUrlState('_a');
const [globalState, setGlobalState] = useUrlState('_g');
const [lastRefresh, setLastRefresh] = useState(0);
+ const [stoppedPartitions, setStoppedPartitions] = useState();
+
const timefilter = useTimefilter({ timeRangeSelector: true, autoRefreshSelector: true });
const { jobIds } = useJobSelection(jobsWithTimeRange);
@@ -109,9 +112,31 @@ const ExplorerUrlStateManager: FC = ({ jobsWithTim
}
}, [globalState?.time?.from, globalState?.time?.to]);
+ const getJobsWithStoppedPartitions = useCallback(async (selectedJobIds: string[]) => {
+ try {
+ const fetchedStoppedPartitions = await ml.results.getCategoryStoppedPartitions(
+ selectedJobIds,
+ JOB_ID
+ );
+ if (
+ fetchedStoppedPartitions &&
+ Array.isArray(fetchedStoppedPartitions.jobs) &&
+ fetchedStoppedPartitions.jobs.length > 0
+ ) {
+ setStoppedPartitions(fetchedStoppedPartitions.jobs);
+ } else {
+ setStoppedPartitions(undefined);
+ }
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error(error);
+ }
+ }, []);
+
useEffect(() => {
if (jobIds.length > 0) {
explorerService.updateJobSelection(jobIds);
+ getJobsWithStoppedPartitions(jobIds);
} else {
explorerService.clearJobs();
}
@@ -209,6 +234,7 @@ const ExplorerUrlStateManager: FC = ({ jobsWithTim
setSelectedCells,
showCharts,
severity: tableSeverity.val,
+ stoppedPartitions,
}}
/>
diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/results.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/results.ts
index 08c3853ace6f8..65bd4fb1eccc2 100644
--- a/x-pack/plugins/ml/public/application/services/ml_api_service/results.ts
+++ b/x-pack/plugins/ml/public/application/services/ml_api_service/results.ts
@@ -5,11 +5,11 @@
*/
// Service for obtaining data for the ML Results dashboards.
+import { GetStoppedPartitionResult } from '../../../../common/types/results';
import { HttpService } from '../http_service';
-
import { basePath } from './index';
-
import { JobId } from '../../../../common/types/anomaly_detection_jobs';
+import { JOB_ID, PARTITION_FIELD_VALUE } from '../../../../common/constants/anomalies';
import { PartitionFieldsDefinition } from '../results_service/result_service_rx';
export const resultsApiProvider = (httpService: HttpService) => ({
@@ -114,4 +114,19 @@ export const resultsApiProvider = (httpService: HttpService) => ({
body,
});
},
+
+ getCategoryStoppedPartitions(
+ jobIds: string[],
+ fieldToBucket?: typeof JOB_ID | typeof PARTITION_FIELD_VALUE
+ ) {
+ const body = JSON.stringify({
+ jobIds,
+ fieldToBucket,
+ });
+ return httpService.http({
+ path: `${basePath()}/results/category_stopped_partitions`,
+ method: 'POST',
+ body,
+ });
+ },
});
diff --git a/x-pack/plugins/ml/server/models/job_validation/job_validation.ts b/x-pack/plugins/ml/server/models/job_validation/job_validation.ts
index 118e923283b3f..6692ecb22bd9e 100644
--- a/x-pack/plugins/ml/server/models/job_validation/job_validation.ts
+++ b/x-pack/plugins/ml/server/models/job_validation/job_validation.ts
@@ -7,7 +7,6 @@
import { i18n } from '@kbn/i18n';
import Boom from 'boom';
import { ILegacyScopedClusterClient } from 'kibana/server';
-
import { TypeOf } from '@kbn/config-schema';
import { fieldsServiceProvider } from '../fields_service';
import { renderTemplate } from '../../../common/util/string_utils';
diff --git a/x-pack/plugins/ml/server/models/results_service/results_service.ts b/x-pack/plugins/ml/server/models/results_service/results_service.ts
index 8e71384942b57..7be8bac61e69d 100644
--- a/x-pack/plugins/ml/server/models/results_service/results_service.ts
+++ b/x-pack/plugins/ml/server/models/results_service/results_service.ts
@@ -10,11 +10,19 @@ import get from 'lodash/get';
import moment from 'moment';
import { SearchResponse } from 'elasticsearch';
import { ILegacyScopedClusterClient } from 'kibana/server';
+import Boom from 'boom';
import { buildAnomalyTableItems } from './build_anomaly_table_items';
import { ML_RESULTS_INDEX_PATTERN } from '../../../common/constants/index_patterns';
import { ANOMALIES_TABLE_DEFAULT_QUERY_SIZE } from '../../../common/constants/search';
import { getPartitionFieldsValuesFactory } from './get_partition_fields_values';
-import { AnomaliesTableRecord, AnomalyRecordDoc } from '../../../common/types/anomalies';
+import {
+ AnomaliesTableRecord,
+ AnomalyCategorizerStatsDoc,
+ AnomalyRecordDoc,
+} from '../../../common/types/anomalies';
+import { JOB_ID, PARTITION_FIELD_VALUE } from '../../../common/constants/anomalies';
+import { GetStoppedPartitionResult } from '../../../common/types/results';
+import { MlJobsResponse } from '../job_service/jobs';
// Service for carrying out Elasticsearch queries to obtain data for the
// ML Results dashboards.
@@ -432,6 +440,154 @@ export function resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClie
return definition;
}
+ async function getCategorizerStats(jobId: string, partitionByValue?: string) {
+ const mustMatchClauses: Array>> = [
+ {
+ match: {
+ result_type: 'categorizer_stats',
+ },
+ },
+ ];
+
+ if (typeof partitionByValue === 'string') {
+ mustMatchClauses.push({
+ match: {
+ partition_by_value: partitionByValue,
+ },
+ });
+ }
+ const results: SearchResponse = await callAsInternalUser('search', {
+ index: ML_RESULTS_INDEX_PATTERN,
+ body: {
+ query: {
+ bool: {
+ must: mustMatchClauses,
+ filter: [
+ {
+ term: {
+ job_id: jobId,
+ },
+ },
+ ],
+ },
+ },
+ },
+ });
+ return results ? results.hits.hits.map((r) => r._source) : [];
+ }
+
+ async function getCategoryStoppedPartitions(
+ jobIds: string[],
+ fieldToBucket: typeof JOB_ID | typeof PARTITION_FIELD_VALUE = PARTITION_FIELD_VALUE
+ ): Promise {
+ let finalResults: GetStoppedPartitionResult = {
+ jobs: {},
+ };
+ // first determine from job config if stop_on_warn is true
+ // if false return []
+ const jobConfigResponse: MlJobsResponse = await callAsInternalUser('ml.jobs', {
+ jobId: jobIds,
+ });
+
+ if (!jobConfigResponse || jobConfigResponse.jobs.length < 1) {
+ throw Boom.notFound(`Unable to find anomaly detector jobs ${jobIds.join(', ')}`);
+ }
+
+ const jobIdsWithStopOnWarnSet = jobConfigResponse.jobs
+ .filter(
+ (jobConfig) =>
+ jobConfig.analysis_config?.per_partition_categorization?.stop_on_warn === true
+ )
+ .map((j) => j.job_id);
+
+ let aggs: any;
+ if (fieldToBucket === JOB_ID) {
+ // if bucketing by job_id, then return list of job_ids with at least one stopped_partitions
+ aggs = {
+ unique_terms: {
+ terms: {
+ field: JOB_ID,
+ },
+ },
+ };
+ } else {
+ // if bucketing by partition field value, then return list of unique stopped_partitions for each job
+ aggs = {
+ jobs: {
+ terms: {
+ field: JOB_ID,
+ },
+ aggs: {
+ unique_stopped_partitions: {
+ terms: {
+ field: PARTITION_FIELD_VALUE,
+ },
+ },
+ },
+ },
+ };
+ }
+
+ if (jobIdsWithStopOnWarnSet.length > 0) {
+ // search for categorizer_stats documents for the current job where the categorization_status is warn
+ // Return all the partition_field_value values from the documents found
+ const mustMatchClauses: Array>> = [
+ {
+ match: {
+ result_type: 'categorizer_stats',
+ },
+ },
+ {
+ match: {
+ categorization_status: 'warn',
+ },
+ },
+ ];
+ const results: SearchResponse = await callAsInternalUser('search', {
+ index: ML_RESULTS_INDEX_PATTERN,
+ size: 0,
+ body: {
+ query: {
+ bool: {
+ must: mustMatchClauses,
+ filter: [
+ {
+ terms: {
+ job_id: jobIdsWithStopOnWarnSet,
+ },
+ },
+ ],
+ },
+ },
+ aggs,
+ },
+ });
+ if (fieldToBucket === JOB_ID) {
+ finalResults = {
+ jobs: results.aggregations?.unique_terms?.buckets.map(
+ (b: { key: string; doc_count: number }) => b.key
+ ),
+ };
+ } else if (fieldToBucket === PARTITION_FIELD_VALUE) {
+ const jobs: Record = jobIdsWithStopOnWarnSet.reduce(
+ (obj: Record, jobId: string) => {
+ obj[jobId] = [];
+ return obj;
+ },
+ {}
+ );
+ results.aggregations.jobs.buckets.forEach(
+ (bucket: { key: string | number; unique_stopped_partitions: { buckets: any[] } }) => {
+ jobs[bucket.key] = bucket.unique_stopped_partitions.buckets.map((b) => b.key);
+ }
+ );
+ finalResults.jobs = jobs;
+ }
+ }
+
+ return finalResults;
+ }
+
return {
getAnomaliesTableData,
getCategoryDefinition,
@@ -439,5 +595,7 @@ export function resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClie
getLatestBucketTimestampByJob,
getMaxAnomalyScore,
getPartitionFieldsValues: getPartitionFieldsValuesFactory(mlClusterClient),
+ getCategorizerStats,
+ getCategoryStoppedPartitions,
};
}
diff --git a/x-pack/plugins/ml/server/routes/anomaly_detectors.ts b/x-pack/plugins/ml/server/routes/anomaly_detectors.ts
index c6bdb32b262e4..0027bec910134 100644
--- a/x-pack/plugins/ml/server/routes/anomaly_detectors.ts
+++ b/x-pack/plugins/ml/server/routes/anomaly_detectors.ts
@@ -20,7 +20,6 @@ import {
getModelSnapshotsSchema,
updateModelSnapshotSchema,
} from './schemas/anomaly_detectors_schema';
-
/**
* Routes for the anomaly detectors
*/
diff --git a/x-pack/plugins/ml/server/routes/apidoc.json b/x-pack/plugins/ml/server/routes/apidoc.json
index f360da5df5392..86a62b28abb5e 100644
--- a/x-pack/plugins/ml/server/routes/apidoc.json
+++ b/x-pack/plugins/ml/server/routes/apidoc.json
@@ -49,6 +49,8 @@
"GetCategoryExamples",
"GetPartitionFieldsValues",
"AnomalySearch",
+ "GetCategorizerStats",
+ "GetCategoryStoppedPartitions",
"Modules",
"DataRecognizer",
diff --git a/x-pack/plugins/ml/server/routes/results_service.ts b/x-pack/plugins/ml/server/routes/results_service.ts
index 0d619bf63b8e7..2af37c17f714a 100644
--- a/x-pack/plugins/ml/server/routes/results_service.ts
+++ b/x-pack/plugins/ml/server/routes/results_service.ts
@@ -17,6 +17,11 @@ import {
} from './schemas/results_service_schema';
import { resultsServiceProvider } from '../models/results_service';
import { ML_RESULTS_INDEX_PATTERN } from '../../common/constants/index_patterns';
+import { jobIdSchema } from './schemas/anomaly_detectors_schema';
+import {
+ getCategorizerStatsSchema,
+ getCategorizerStoppedPartitionsSchema,
+} from './schemas/results_service_schema';
function getAnomaliesTableData(legacyClient: ILegacyScopedClusterClient, payload: any) {
const rs = resultsServiceProvider(legacyClient);
@@ -71,6 +76,19 @@ function getPartitionFieldsValues(legacyClient: ILegacyScopedClusterClient, payl
return rs.getPartitionFieldsValues(jobId, searchTerm, criteriaFields, earliestMs, latestMs);
}
+function getCategorizerStats(legacyClient: ILegacyScopedClusterClient, params: any, query: any) {
+ const { jobId } = params;
+ const { partitionByValue } = query;
+ const rs = resultsServiceProvider(legacyClient);
+ return rs.getCategorizerStats(jobId, partitionByValue);
+}
+
+function getCategoryStoppedPartitions(legacyClient: ILegacyScopedClusterClient, payload: any) {
+ const { jobIds, fieldToBucket } = payload;
+ const rs = resultsServiceProvider(legacyClient);
+ return rs.getCategoryStoppedPartitions(jobIds, fieldToBucket);
+}
+
/**
* Routes for results service
*/
@@ -265,4 +283,66 @@ export function resultsServiceRoutes({ router, mlLicense }: RouteInitialization)
}
})
);
+
+ /**
+ * @apiGroup ResultsService
+ *
+ * @api {get} /api/ml/results/:jobId/categorizer_stats
+ * @apiName GetCategorizerStats
+ * @apiDescription Returns the categorizer stats for the specified job ID
+ * @apiSchema (params) jobIdSchema
+ * @apiSchema (query) getCategorizerStatsSchema
+ */
+ router.get(
+ {
+ path: '/api/ml/results/{jobId}/categorizer_stats',
+ validate: {
+ params: jobIdSchema,
+ query: getCategorizerStatsSchema,
+ },
+ options: {
+ tags: ['access:ml:canGetJobs'],
+ },
+ },
+ mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => {
+ try {
+ const resp = await getCategorizerStats(legacyClient, request.params, request.query);
+ return response.ok({
+ body: resp,
+ });
+ } catch (e) {
+ return response.customError(wrapError(e));
+ }
+ })
+ );
+
+ /**
+ * @apiGroup ResultsService
+ *
+ * @api {get} /api/ml/results/category_stopped_partitions
+ * @apiName GetCategoryStoppedPartitions
+ * @apiDescription Returns information on the partitions that have stopped being categorized due to the categorization status changing from ok to warn. Can return either the list of stopped partitions for each job, or just the list of job IDs.
+ * @apiSchema (body) getCategorizerStoppedPartitionsSchema
+ */
+ router.post(
+ {
+ path: '/api/ml/results/category_stopped_partitions',
+ validate: {
+ body: getCategorizerStoppedPartitionsSchema,
+ },
+ options: {
+ tags: ['access:ml:canGetJobs'],
+ },
+ },
+ mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => {
+ try {
+ const resp = await getCategoryStoppedPartitions(legacyClient, request.body);
+ return response.ok({
+ body: resp,
+ });
+ } catch (e) {
+ return response.customError(wrapError(e));
+ }
+ })
+ );
}
diff --git a/x-pack/plugins/ml/server/routes/schemas/results_service_schema.ts b/x-pack/plugins/ml/server/routes/schemas/results_service_schema.ts
index f7317e534b33b..0bf37826b6146 100644
--- a/x-pack/plugins/ml/server/routes/schemas/results_service_schema.ts
+++ b/x-pack/plugins/ml/server/routes/schemas/results_service_schema.ts
@@ -52,3 +52,26 @@ export const partitionFieldValuesSchema = schema.object({
earliestMs: schema.number(),
latestMs: schema.number(),
});
+
+export const getCategorizerStatsSchema = schema.nullable(
+ schema.object({
+ /**
+ * Optional value to fetch the categorizer stats
+ * where results are filtered by partition_by_value = value
+ */
+ partitionByValue: schema.maybe(schema.string()),
+ })
+);
+
+export const getCategorizerStoppedPartitionsSchema = schema.object({
+ /**
+ * List of jobIds to fetch the categorizer partitions for
+ */
+ jobIds: schema.arrayOf(schema.string()),
+ /**
+ * Field to aggregate results by: 'job_id' or 'partition_field_value'
+ * If by job_id, will return list of jobIds with at least one partition that have stopped
+ * If by partition_field_value, it will return a list of categorizer stopped partitions for each job_id
+ */
+ fieldToBucket: schema.maybe(schema.string()),
+});
diff --git a/x-pack/test/api_integration/apis/ml/results/get_categorizer_stats.ts b/x-pack/test/api_integration/apis/ml/results/get_categorizer_stats.ts
new file mode 100644
index 0000000000000..a9d863b7526f9
--- /dev/null
+++ b/x-pack/test/api_integration/apis/ml/results/get_categorizer_stats.ts
@@ -0,0 +1,148 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import expect from '@kbn/expect';
+import { FtrProviderContext } from '../../../ftr_provider_context';
+import { USER } from '../../../../functional/services/ml/security_common';
+import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common';
+import { Datafeed } from '../../../../../plugins/ml/common/types/anomaly_detection_jobs';
+import { AnomalyCategorizerStatsDoc } from '../../../../../plugins/ml/common/types/anomalies';
+
+export default ({ getService }: FtrProviderContext) => {
+ const esArchiver = getService('esArchiver');
+ const supertest = getService('supertestWithoutAuth');
+ const ml = getService('ml');
+
+ const jobId = `sample_logs_${Date.now()}`;
+ const PARTITION_FIELD_NAME = 'event.dataset';
+ const testJobConfig = {
+ job_id: jobId,
+ groups: ['sample_logs', 'bootstrap', 'categorization'],
+ description: "count by mlcategory (message) on 'sample logs' dataset with 15m bucket span",
+ analysis_config: {
+ bucket_span: '15m',
+ categorization_field_name: 'message',
+ per_partition_categorization: { enabled: true, stop_on_warn: true },
+ detectors: [
+ {
+ function: 'count',
+ by_field_name: 'mlcategory',
+ partition_field_name: PARTITION_FIELD_NAME,
+ },
+ ],
+ influencers: ['mlcategory'],
+ },
+ analysis_limits: { model_memory_limit: '26MB' },
+ data_description: { time_field: '@timestamp', time_format: 'epoch_ms' },
+ model_plot_config: { enabled: false, annotations_enabled: true },
+ model_snapshot_retention_days: 10,
+ daily_model_snapshot_retention_after_days: 1,
+ allow_lazy_open: false,
+ };
+ const testDatafeedConfig: Datafeed = {
+ datafeed_id: `datafeed-${jobId}`,
+ indices: ['ft_module_sample_logs'],
+ job_id: jobId,
+ query: { bool: { must: [{ match_all: {} }] } },
+ };
+
+ describe('get categorizer_stats', function () {
+ before(async () => {
+ await esArchiver.loadIfNeeded('ml/module_sample_logs');
+ await ml.testResources.setKibanaTimeZoneToUTC();
+ await ml.api.createAndRunAnomalyDetectionLookbackJob(testJobConfig, testDatafeedConfig);
+ });
+
+ after(async () => {
+ await ml.testResources.deleteIndexPatternByTitle('ft_module_sample_logs');
+ await ml.api.cleanMlIndices();
+ });
+
+ it('should fetch all the categorizer stats for job id', async () => {
+ const { body } = await supertest
+ .get(`/api/ml/results/${jobId}/categorizer_stats`)
+ .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
+ .set(COMMON_REQUEST_HEADERS)
+ .expect(200);
+
+ body.forEach((doc: AnomalyCategorizerStatsDoc) => {
+ expect(doc.job_id).to.eql(jobId);
+ expect(doc.result_type).to.eql('categorizer_stats');
+ expect(doc.partition_field_name).to.be(PARTITION_FIELD_NAME);
+ expect(doc.partition_field_value).to.not.be(undefined);
+ });
+ });
+
+ it('should fetch categorizer stats for job id for user with view permission', async () => {
+ const { body } = await supertest
+ .get(`/api/ml/results/${jobId}/categorizer_stats`)
+ .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER))
+ .set(COMMON_REQUEST_HEADERS)
+ .expect(200);
+
+ body.forEach((doc: AnomalyCategorizerStatsDoc) => {
+ expect(doc.job_id).to.eql(jobId);
+ expect(doc.result_type).to.eql('categorizer_stats');
+ expect(doc.partition_field_name).to.be(PARTITION_FIELD_NAME);
+ expect(doc.partition_field_value).to.not.be(undefined);
+ });
+ });
+
+ it('should not fetch categorizer stats for job id for unauthorized user', async () => {
+ const { body } = await supertest
+ .get(`/api/ml/results/${jobId}/categorizer_stats`)
+ .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
+ .set(COMMON_REQUEST_HEADERS)
+ .expect(404);
+
+ expect(body.error).to.be('Not Found');
+ expect(body.message).to.be('Not Found');
+ });
+
+ it('should fetch all the categorizer stats with per-partition value for job id', async () => {
+ const { body } = await supertest
+ .get(`/api/ml/results/${jobId}/categorizer_stats`)
+ .query({ partitionByValue: 'sample_web_logs' })
+ .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
+ .set(COMMON_REQUEST_HEADERS)
+ .expect(200);
+ body.forEach((doc: AnomalyCategorizerStatsDoc) => {
+ expect(doc.job_id).to.eql(jobId);
+ expect(doc.result_type).to.eql('categorizer_stats');
+ expect(doc.partition_field_name).to.be(PARTITION_FIELD_NAME);
+ expect(doc.partition_field_value).to.be('sample_web_logs');
+ });
+ });
+
+ it('should fetch categorizer stats with per-partition value for user with view permission', async () => {
+ const { body } = await supertest
+ .get(`/api/ml/results/${jobId}/categorizer_stats`)
+ .query({ partitionByValue: 'sample_web_logs' })
+ .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER))
+ .set(COMMON_REQUEST_HEADERS)
+ .expect(200);
+
+ body.forEach((doc: AnomalyCategorizerStatsDoc) => {
+ expect(doc.job_id).to.eql(jobId);
+ expect(doc.result_type).to.eql('categorizer_stats');
+ expect(doc.partition_field_name).to.be(PARTITION_FIELD_NAME);
+ expect(doc.partition_field_value).to.be('sample_web_logs');
+ });
+ });
+
+ it('should not fetch categorizer stats with per-partition value for unauthorized user', async () => {
+ const { body } = await supertest
+ .get(`/api/ml/results/${jobId}/categorizer_stats`)
+ .query({ partitionByValue: 'sample_web_logs' })
+ .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
+ .set(COMMON_REQUEST_HEADERS)
+ .expect(404);
+
+ expect(body.error).to.be('Not Found');
+ expect(body.message).to.be('Not Found');
+ });
+ });
+};
diff --git a/x-pack/test/api_integration/apis/ml/results/get_stopped_partitions.ts b/x-pack/test/api_integration/apis/ml/results/get_stopped_partitions.ts
new file mode 100644
index 0000000000000..424bc8c333aab
--- /dev/null
+++ b/x-pack/test/api_integration/apis/ml/results/get_stopped_partitions.ts
@@ -0,0 +1,184 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import expect from '@kbn/expect';
+import { Datafeed, Job } from '../../../../../plugins/ml/common/types/anomaly_detection_jobs';
+import { FtrProviderContext } from '../../../ftr_provider_context';
+import { USER } from '../../../../functional/services/ml/security_common';
+import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common';
+
+export default ({ getService }: FtrProviderContext) => {
+ const esArchiver = getService('esArchiver');
+ const supertest = getService('supertestWithoutAuth');
+ const ml = getService('ml');
+
+ const testJobId = `sample_logs_${Date.now()}`;
+ // non-aggregatable field to cause some partitions to change status to warn
+ const PARTITION_FIELD_NAME = 'agent';
+
+ interface TestConfig {
+ testDescription: string;
+ jobId: string;
+ jobConfig: Job;
+ datafeedConfig: Datafeed;
+ }
+ const setupTestConfigs = (
+ jobId: string,
+ stopOnWarn: boolean,
+ enabledPerPartitionCat: boolean = true
+ ): TestConfig => {
+ const commonJobConfig = {
+ groups: ['sample_logs', 'bootstrap', 'categorization'],
+ description: "count by mlcategory (message) on 'sample logs' dataset with 15m bucket span",
+ analysis_limits: { model_memory_limit: '26MB' },
+ data_description: { time_field: '@timestamp', time_format: 'epoch_ms' },
+ model_snapshot_retention_days: 10,
+ daily_model_snapshot_retention_after_days: 1,
+ allow_lazy_open: false,
+ };
+ const datafeedConfig: Datafeed = {
+ datafeed_id: `datafeed-${jobId}`,
+ indices: ['ft_module_sample_logs'],
+ job_id: jobId,
+ query: { bool: { must: [{ match_all: {} }] } },
+ };
+
+ return {
+ testDescription: `stop_on_warn is ${stopOnWarn}`,
+ jobId,
+ jobConfig: {
+ job_id: jobId,
+ ...commonJobConfig,
+ analysis_config: {
+ bucket_span: '1m',
+ categorization_field_name: 'message',
+ per_partition_categorization: {
+ enabled: enabledPerPartitionCat,
+ stop_on_warn: stopOnWarn,
+ },
+ detectors: [
+ {
+ function: 'count',
+ by_field_name: 'mlcategory',
+ partition_field_name: PARTITION_FIELD_NAME,
+ },
+ ],
+ influencers: ['mlcategory'],
+ },
+ },
+ datafeedConfig,
+ };
+ };
+
+ const testSetUps: TestConfig[] = [
+ setupTestConfigs(`${testJobId}_t`, true),
+ setupTestConfigs(`${testJobId}_f`, false),
+ setupTestConfigs(`${testJobId}_viewer`, true),
+ setupTestConfigs(`${testJobId}_unauthorized`, true),
+ ];
+
+ const testJobIds = testSetUps.map((t) => t.jobId);
+
+ describe('get stopped_partitions', function () {
+ before(async () => {
+ await esArchiver.loadIfNeeded('ml/module_sample_logs');
+ await ml.testResources.setKibanaTimeZoneToUTC();
+ for (const testData of testSetUps) {
+ const { jobConfig, datafeedConfig } = testData;
+ await ml.api.createAndRunAnomalyDetectionLookbackJob(jobConfig, datafeedConfig);
+ }
+ });
+
+ after(async () => {
+ await ml.testResources.deleteIndexPatternByTitle('ft_module_sample_logs');
+ await ml.api.cleanMlIndices();
+ });
+
+ it('should fetch all the stopped partitions correctly', async () => {
+ const { jobId } = testSetUps[0];
+ const { body } = await supertest
+ .post(`/api/ml/results/category_stopped_partitions`)
+ .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
+ .send({ jobIds: [jobId] })
+ .set(COMMON_REQUEST_HEADERS)
+ .expect(200);
+ expect(body.jobs).to.not.be(undefined);
+ expect(body.jobs[jobId]).to.be.an('array');
+ expect(body.jobs[jobId].length).to.be.greaterThan(0);
+ });
+
+ it('should not return jobId in response if stopped_on_warn is false', async () => {
+ const { jobId } = testSetUps[1];
+ const { body } = await supertest
+ .post(`/api/ml/results/category_stopped_partitions`)
+ .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
+ .send({ jobIds: [jobId] })
+ .set(COMMON_REQUEST_HEADERS)
+ .expect(200);
+ expect(body.jobs).to.not.be(undefined);
+ expect(body.jobs).to.not.have.property(jobId);
+ });
+
+ it('should fetch stopped partitions for user with view permission', async () => {
+ const { jobId } = testSetUps[2];
+ const { body } = await supertest
+ .post(`/api/ml/results/category_stopped_partitions`)
+ .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER))
+ .send({ jobIds: [jobId] })
+ .set(COMMON_REQUEST_HEADERS)
+ .expect(200);
+
+ expect(body.jobs).to.not.be(undefined);
+ expect(body.jobs[jobId]).to.be.an('array');
+ expect(body.jobs[jobId].length).to.be.greaterThan(0);
+ });
+
+ it('should not fetch stopped partitions for unauthorized user', async () => {
+ const { jobId } = testSetUps[3];
+
+ const { body } = await supertest
+ .post(`/api/ml/results/category_stopped_partitions`)
+ .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
+ .send({ jobIds: [jobId] })
+ .set(COMMON_REQUEST_HEADERS)
+ .expect(404);
+
+ expect(body.error).to.be('Not Found');
+ expect(body.message).to.be('Not Found');
+ });
+
+ it('should fetch stopped partitions for multiple job ids', async () => {
+ const { body } = await supertest
+ .post(`/api/ml/results/category_stopped_partitions`)
+ .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
+ .send({ jobIds: testJobIds })
+ .set(COMMON_REQUEST_HEADERS)
+ .expect(200);
+ expect(body.jobs).to.not.be(undefined);
+ expect(body.jobs).to.not.have.property(testSetUps[1].jobId);
+
+ Object.keys(body.jobs).forEach((currentJobId: string) => {
+ expect(testJobIds).to.contain(currentJobId);
+ expect(body.jobs[currentJobId]).to.be.an('array');
+ expect(body.jobs[currentJobId].length).to.be.greaterThan(0);
+ });
+ });
+
+ it('should return array of jobIds with stopped_partitions for multiple job ids when bucketed by job_id', async () => {
+ const { body } = await supertest
+ .post(`/api/ml/results/category_stopped_partitions`)
+ .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
+ .send({ jobIds: testJobIds, fieldToBucket: 'job_id' })
+ .set(COMMON_REQUEST_HEADERS)
+ .expect(200);
+
+ expect(body.jobs).to.not.be(undefined);
+ body.jobs.forEach((currentJobId: string) => {
+ expect(testJobIds).to.contain(currentJobId);
+ });
+ });
+ });
+};
diff --git a/x-pack/test/api_integration/apis/ml/results/index.ts b/x-pack/test/api_integration/apis/ml/results/index.ts
index 7f44ebefc7b2b..f6a7c6ed81843 100644
--- a/x-pack/test/api_integration/apis/ml/results/index.ts
+++ b/x-pack/test/api_integration/apis/ml/results/index.ts
@@ -8,5 +8,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('ResultsService', () => {
loadTestFile(require.resolve('./get_anomalies_table_data'));
+ loadTestFile(require.resolve('./get_categorizer_stats'));
+ loadTestFile(require.resolve('./get_stopped_partitions'));
});
}
From fb2cac9a8a704e34257718d783fc2945e9be10f9 Mon Sep 17 00:00:00 2001
From: James Gowdy
Date: Wed, 19 Aug 2020 19:13:12 +0100
Subject: [PATCH 36/85] [ML] Fixing file import button on basic license
(#75458)
---
x-pack/plugins/ml/server/routes/system.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/x-pack/plugins/ml/server/routes/system.ts b/x-pack/plugins/ml/server/routes/system.ts
index 99ba9519b6a34..273b86163245f 100644
--- a/x-pack/plugins/ml/server/routes/system.ts
+++ b/x-pack/plugins/ml/server/routes/system.ts
@@ -240,10 +240,10 @@ export function systemRoutes(
body: schema.object({ index: schema.string() }),
},
options: {
- tags: ['access:ml:canGetJobs'],
+ tags: ['access:ml:canAccessML'],
},
},
- mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => {
+ mlLicense.basicLicenseAPIGuard(async ({ legacyClient, request, response }) => {
try {
const { index } = request.body;
From 7ac929b4cb97e0657208e02b201d41afce8429b5 Mon Sep 17 00:00:00 2001
From: Brent Kimmel
Date: Wed, 19 Aug 2020 14:27:48 -0400
Subject: [PATCH 37/85] [Security Solution][Resolver] enzyme semantics (#75311)
* remove old edgeline method on simulator
* [Security Solution][Resolver] Enzyme test treeitem/tree roles
---
.../test_utilities/simulator/index.tsx | 7 -----
.../resolver/view/clickthrough.test.tsx | 30 +++++++++++++++++--
2 files changed, 28 insertions(+), 9 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/resolver/test_utilities/simulator/index.tsx b/x-pack/plugins/security_solution/public/resolver/test_utilities/simulator/index.tsx
index 47e92320c4c20..b79b7df48a6de 100644
--- a/x-pack/plugins/security_solution/public/resolver/test_utilities/simulator/index.tsx
+++ b/x-pack/plugins/security_solution/public/resolver/test_utilities/simulator/index.tsx
@@ -220,13 +220,6 @@ export class Simulator {
return this.wrapper.debug();
}
- /**
- * Lines that connect the nodes in the graph
- */
- public edgeLines(): ReactWrapper {
- return this.domNodes('[data-test-subj="resolver:graph:edgeline"]');
- }
-
/**
* This manually runs the animation frames tied to a configurable timestamp in the future.
*/
diff --git a/x-pack/plugins/security_solution/public/resolver/view/clickthrough.test.tsx b/x-pack/plugins/security_solution/public/resolver/view/clickthrough.test.tsx
index 7a2301b7bb515..358fcd17b998a 100644
--- a/x-pack/plugins/security_solution/public/resolver/view/clickthrough.test.tsx
+++ b/x-pack/plugins/security_solution/public/resolver/view/clickthrough.test.tsx
@@ -73,6 +73,32 @@ describe('Resolver, when analyzing a tree that has no ancestors and 2 children',
});
});
+ it('should render 3 elements with "treeitem" roles, each owned by an element with a "tree" role', async () => {
+ await expect(
+ simulator.map(() => ({
+ nodesOwnedByTrees: simulator.testSubject('resolver:node').filterWhere((domNode) => {
+ /**
+ * This test verifies corectness w.r.t. the tree/treeitem roles
+ * From W3C: `Authors MUST ensure elements with role treeitem are contained in, or owned by, an element with the role group or tree.`
+ *
+ * https://www.w3.org/TR/wai-aria-1.1/#tree
+ * https://www.w3.org/TR/wai-aria-1.1/#treeitem
+ *
+ * w3c defines two ways for an element to be an "owned element"
+ * 1. Any DOM descendant
+ * 2. Any element specified as a child via aria-owns
+ * (see: https://www.w3.org/TR/wai-aria-1.1/#dfn-owned-element)
+ *
+ * In the context of Resolver (as of this writing) nodes/treeitems are children of the tree,
+ * but they could be moved out of the tree, provided that the tree is given an `aria-owns`
+ * attribute referring to them (method 2 above).
+ */
+ return domNode.closest('[role="tree"]').length === 1;
+ }).length,
+ }))
+ ).toYieldEqualTo({ nodesOwnedByTrees: 3 });
+ });
+
it(`should show links to the 3 nodes (with icons) in the node list.`, async () => {
await expect(
simulator.map(() => simulator.testSubject('resolver:node-list:node-link:title').length)
@@ -252,7 +278,7 @@ function computedNodeBoundaries(entityID: string): AABB {
* Coordinates for where the edgelines "start"
*/
function computedEdgeStartingCoordinates(): Vector2[] {
- return simulator.edgeLines().map((edge) => {
+ return simulator.testSubject('resolver:graph:edgeline').map((edge) => {
const { left, top } = getComputedStyle(edge.getDOMNode());
return [pxNum(left), pxNum(top)];
});
@@ -262,7 +288,7 @@ function computedEdgeStartingCoordinates(): Vector2[] {
* Coordinates for where edgelines "end" (after application of transform)
*/
function computedEdgeTerminalCoordinates(): Vector2[] {
- return simulator.edgeLines().map((edge) => {
+ return simulator.testSubject('resolver:graph:edgeline').map((edge) => {
const { left, top, width, transform } = getComputedStyle(edge.getDOMNode());
/**
* Without the transform in the rotation, edgelines lay flat across the x-axis.
From 02fcbaa794ada3464a5588913f3b31ce898edcf3 Mon Sep 17 00:00:00 2001
From: Frank Hassanabad
Date: Wed, 19 Aug 2020 12:30:11 -0600
Subject: [PATCH 38/85] Fixed bug where list index privileges was returned
twice instead of list item index (#75256)
## Summary
Fixes a bug where the list privileges was returning the `.list` privileges twice instead of returning it once and returning the `.items` privileges second with the call. No UI has to change as the way it was written was dynamic to grab the first key found.
This also adds the functional tests to `x-pack/scripts/functional_tests.js` which was not there originally so the end to tend tests should actually run on the CI machine where it was not running on CI before.
Adds the functional tests to the code owners file as well.
Ensure that you go to the test results page from the Jenkins build:
And ensure you see the tests under:
```
X-Pack Lists Integration Tests
```
Then click through it and ensure they are shown as running and passing
### Checklist
- [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
---
.github/CODEOWNERS | 1 +
.../routes/read_privileges_route.mock.ts | 181 ++++++++++++++++++
.../server/routes/read_privileges_route.ts | 2 +-
x-pack/scripts/functional_tests.js | 1 +
.../security_and_spaces/tests/index.ts | 1 +
.../tests/read_list_privileges.ts | 85 ++++++++
6 files changed, 270 insertions(+), 1 deletion(-)
create mode 100644 x-pack/plugins/lists/server/routes/read_privileges_route.mock.ts
create mode 100644 x-pack/test/lists_api_integration/security_and_spaces/tests/read_list_privileges.ts
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 7e34c931c5feb..52df586b8bda7 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -267,6 +267,7 @@ x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @elastic/kib
/x-pack/plugins/security_solution/ @elastic/siem @elastic/endpoint-app-team
/x-pack/plugins/security_solution/**/*.scss @elastic/security-design
/x-pack/test/detection_engine_api_integration @elastic/siem @elastic/endpoint-app-team
+/x-pack/test/lists_api_integration @elastic/siem @elastic/endpoint-app-team
/x-pack/test/api_integration/apis/security_solution @elastic/siem @elastic/endpoint-app-team
/x-pack/plugins/case @elastic/siem @elastic/endpoint-app-team
/x-pack/plugins/lists @elastic/siem @elastic/endpoint-app-team
diff --git a/x-pack/plugins/lists/server/routes/read_privileges_route.mock.ts b/x-pack/plugins/lists/server/routes/read_privileges_route.mock.ts
new file mode 100644
index 0000000000000..cef6233440db6
--- /dev/null
+++ b/x-pack/plugins/lists/server/routes/read_privileges_route.mock.ts
@@ -0,0 +1,181 @@
+/*
+ * 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.
+ */
+
+interface Cluster {
+ monitor_ml: boolean;
+ manage_ccr: boolean;
+ manage_index_templates: boolean;
+ monitor_watcher: boolean;
+ monitor_transform: boolean;
+ read_ilm: boolean;
+ manage_security: boolean;
+ manage_own_api_key: boolean;
+ manage_saml: boolean;
+ all: boolean;
+ manage_ilm: boolean;
+ manage_ingest_pipelines: boolean;
+ read_ccr: boolean;
+ manage_rollup: boolean;
+ monitor: boolean;
+ manage_watcher: boolean;
+ manage: boolean;
+ manage_transform: boolean;
+ manage_api_key: boolean;
+ manage_token: boolean;
+ manage_ml: boolean;
+ manage_pipeline: boolean;
+ monitor_rollup: boolean;
+ transport_client: boolean;
+ create_snapshot: boolean;
+}
+
+interface Index {
+ [indexName: string]: {
+ all: boolean;
+ manage_ilm: boolean;
+ read: boolean;
+ create_index: boolean;
+ read_cross_cluster: boolean;
+ index: boolean;
+ monitor: boolean;
+ delete: boolean;
+ manage: boolean;
+ delete_index: boolean;
+ create_doc: boolean;
+ view_index_metadata: boolean;
+ create: boolean;
+ manage_follow_index: boolean;
+ manage_leader_index: boolean;
+ write: boolean;
+ };
+}
+
+interface IndexPrivilege {
+ application: {};
+ cluster: Cluster;
+ has_all_requested: boolean;
+ index: Index;
+ username: string;
+}
+
+export interface Privilege {
+ listItems: IndexPrivilege;
+ lists: IndexPrivilege;
+ is_authenticated: boolean;
+}
+
+export const getReadPrivilegeMock = (
+ listIndex: string = '.lists-default',
+ listItemsIndex: string = '.items-default',
+ username = 'elastic',
+ booleanValues: boolean = true
+): Privilege => ({
+ is_authenticated: true,
+ listItems: {
+ application: {},
+ cluster: {
+ all: booleanValues,
+ create_snapshot: booleanValues,
+ manage: booleanValues,
+ manage_api_key: booleanValues,
+ manage_ccr: booleanValues,
+ manage_ilm: booleanValues,
+ manage_index_templates: booleanValues,
+ manage_ingest_pipelines: booleanValues,
+ manage_ml: booleanValues,
+ manage_own_api_key: false,
+ manage_pipeline: booleanValues,
+ manage_rollup: booleanValues,
+ manage_saml: booleanValues,
+ manage_security: booleanValues,
+ manage_token: booleanValues,
+ manage_transform: booleanValues,
+ manage_watcher: booleanValues,
+ monitor: booleanValues,
+ monitor_ml: booleanValues,
+ monitor_rollup: booleanValues,
+ monitor_transform: booleanValues,
+ monitor_watcher: booleanValues,
+ read_ccr: booleanValues,
+ read_ilm: booleanValues,
+ transport_client: booleanValues,
+ },
+ has_all_requested: false,
+ index: {
+ [listItemsIndex]: {
+ all: booleanValues,
+ create: booleanValues,
+ create_doc: booleanValues,
+ create_index: booleanValues,
+ delete: booleanValues,
+ delete_index: booleanValues,
+ index: booleanValues,
+ manage: booleanValues,
+ manage_follow_index: booleanValues,
+ manage_ilm: booleanValues,
+ manage_leader_index: booleanValues,
+ monitor: booleanValues,
+ read: booleanValues,
+ read_cross_cluster: booleanValues,
+ view_index_metadata: booleanValues,
+ write: booleanValues,
+ },
+ },
+ username,
+ },
+ lists: {
+ application: {},
+ cluster: {
+ all: booleanValues,
+ create_snapshot: booleanValues,
+ manage: booleanValues,
+ manage_api_key: booleanValues,
+ manage_ccr: booleanValues,
+ manage_ilm: booleanValues,
+ manage_index_templates: booleanValues,
+ manage_ingest_pipelines: booleanValues,
+ manage_ml: booleanValues,
+ manage_own_api_key: false,
+ manage_pipeline: booleanValues,
+ manage_rollup: booleanValues,
+ manage_saml: booleanValues,
+ manage_security: booleanValues,
+ manage_token: booleanValues,
+ manage_transform: booleanValues,
+ manage_watcher: booleanValues,
+ monitor: booleanValues,
+ monitor_ml: booleanValues,
+ monitor_rollup: booleanValues,
+ monitor_transform: booleanValues,
+ monitor_watcher: booleanValues,
+ read_ccr: booleanValues,
+ read_ilm: booleanValues,
+ transport_client: booleanValues,
+ },
+ has_all_requested: false,
+ index: {
+ [listIndex]: {
+ all: booleanValues,
+ create: booleanValues,
+ create_doc: booleanValues,
+ create_index: booleanValues,
+ delete: booleanValues,
+ delete_index: booleanValues,
+ index: booleanValues,
+ manage: booleanValues,
+ manage_follow_index: booleanValues,
+ manage_ilm: booleanValues,
+ manage_leader_index: booleanValues,
+ monitor: booleanValues,
+ read: booleanValues,
+ read_cross_cluster: booleanValues,
+ view_index_metadata: booleanValues,
+ write: booleanValues,
+ },
+ },
+ username,
+ },
+});
diff --git a/x-pack/plugins/lists/server/routes/read_privileges_route.ts b/x-pack/plugins/lists/server/routes/read_privileges_route.ts
index a4ec878613608..9d695b348b422 100644
--- a/x-pack/plugins/lists/server/routes/read_privileges_route.ts
+++ b/x-pack/plugins/lists/server/routes/read_privileges_route.ts
@@ -36,7 +36,7 @@ export const readPrivilegesRoute = (
);
const clusterPrivilegesListItems = await readPrivileges(
clusterClient.callAsCurrentUser,
- lists.getListIndex()
+ lists.getListItemIndex()
);
const privileges = merge(
{
diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js
index 205ff500a36ec..ef646343871d3 100644
--- a/x-pack/scripts/functional_tests.js
+++ b/x-pack/scripts/functional_tests.js
@@ -26,6 +26,7 @@ const onlyNotInCoverageTests = [
require.resolve('../test/apm_api_integration/trial/config.ts'),
require.resolve('../test/detection_engine_api_integration/security_and_spaces/config.ts'),
require.resolve('../test/detection_engine_api_integration/basic/config.ts'),
+ require.resolve('../test/lists_api_integration/security_and_spaces/config.ts'),
require.resolve('../test/plugin_api_integration/config.ts'),
require.resolve('../test/kerberos_api_integration/config.ts'),
require.resolve('../test/kerberos_api_integration/anonymous_access.config.ts'),
diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/index.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/index.ts
index 5458b4a9a7db2..9e619811d67ee 100644
--- a/x-pack/test/lists_api_integration/security_and_spaces/tests/index.ts
+++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/index.ts
@@ -33,5 +33,6 @@ export default ({ loadTestFile }: FtrProviderContext): void => {
loadTestFile(require.resolve('./delete_exception_list_items'));
loadTestFile(require.resolve('./find_exception_lists'));
loadTestFile(require.resolve('./find_exception_list_items'));
+ loadTestFile(require.resolve('./read_list_privileges'));
});
};
diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/read_list_privileges.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/read_list_privileges.ts
new file mode 100644
index 0000000000000..c1696d3205294
--- /dev/null
+++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/read_list_privileges.ts
@@ -0,0 +1,85 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import expect from '@kbn/expect';
+
+import { getReadPrivilegeMock } from '../../../../plugins/lists/server/routes/read_privileges_route.mock';
+import { FtrProviderContext } from '../../common/ftr_provider_context';
+import { LIST_PRIVILEGES_URL } from '../../../../plugins/lists/common/constants';
+
+// eslint-disable-next-line import/no-default-export
+export default ({ getService }: FtrProviderContext) => {
+ const supertest = getService('supertest');
+ const security = getService('security');
+ const spacesService = getService('spaces');
+ const supertestWithoutAuth = getService('supertestWithoutAuth');
+
+ describe('read_list_privileges', () => {
+ const space1Id = 'space_1';
+
+ const user1 = {
+ username: 'user_1',
+ roleName: 'user_1',
+ password: 'user_1-password',
+ };
+
+ beforeEach(async () => {
+ await spacesService.create({
+ id: space1Id,
+ name: space1Id,
+ disabledFeatures: [],
+ });
+
+ await security.role.create(user1.roleName, {
+ kibana: [
+ {
+ feature: {
+ dashboard: ['all'],
+ siem: ['all', 'read'],
+ },
+ spaces: [space1Id],
+ },
+ ],
+ });
+
+ await security.user.create(user1.username, {
+ password: user1.password,
+ roles: [user1.roleName],
+ });
+ });
+
+ afterEach(async () => {
+ await spacesService.delete(space1Id);
+ });
+
+ it('should return true for all privileges when its the system user of "elastic" in space of "default"', async () => {
+ const { body } = await supertest.get(LIST_PRIVILEGES_URL).set('kbn-xsrf', 'true').expect(200);
+ expect(body).to.eql(getReadPrivilegeMock());
+ });
+
+ it('should return true for all privileges when its the system user of "elastic" in space of "space_1"', async () => {
+ const { body } = await supertest.get(LIST_PRIVILEGES_URL).set('kbn-xsrf', 'true').expect(200);
+ expect(body).to.eql(getReadPrivilegeMock());
+ });
+
+ it('should return false for all privileges when its the system user of "user_1" in a space of "space_1"', async () => {
+ const { body } = await supertestWithoutAuth
+ .get(`/s/${space1Id}${LIST_PRIVILEGES_URL}`)
+ .auth(user1.username, user1.password)
+ .send()
+ .expect(200);
+
+ const privilege = getReadPrivilegeMock(
+ `.lists-${space1Id}`,
+ `.items-${space1Id}`,
+ user1.username,
+ false
+ );
+
+ expect(body).to.eql(privilege);
+ });
+ });
+};
From d46227421e361fd5ea29cbb6e512c9df66448d5e Mon Sep 17 00:00:00 2001
From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com>
Date: Wed, 19 Aug 2020 14:32:43 -0400
Subject: [PATCH 39/85] [SECURITY_SOLUTION][ENDPOINT] Add creation of Trusted
Apps Agnostic List (#74868)
* Add method to ExceptionsListClient for creating trusted apps list
---
x-pack/plugins/lists/common/constants.ts | 9 +++
.../create_endpoint_trusted_apps_list.ts | 77 +++++++++++++++++++
.../exception_lists/exception_list_client.ts | 13 ++++
3 files changed, 99 insertions(+)
create mode 100644 x-pack/plugins/lists/server/services/exception_lists/create_endpoint_trusted_apps_list.ts
diff --git a/x-pack/plugins/lists/common/constants.ts b/x-pack/plugins/lists/common/constants.ts
index 6c73dc1656302..1851487b824a2 100644
--- a/x-pack/plugins/lists/common/constants.ts
+++ b/x-pack/plugins/lists/common/constants.ts
@@ -50,3 +50,12 @@ export const ENDPOINT_LIST_NAME = 'Elastic Endpoint Security Exception List';
export const ENDPOINT_LIST_DESCRIPTION = 'Elastic Endpoint Security Exception List';
export const MAX_EXCEPTION_LIST_SIZE = 10000;
+
+/** ID of trusted apps agnostic list */
+export const ENDPOINT_TRUSTED_APPS_LIST_ID = 'endpoint_trusted_apps';
+
+/** Name of trusted apps agnostic list */
+export const ENDPOINT_TRUSTED_APPS_LIST_NAME = 'Elastic Endpoint Security Trusted Apps List';
+
+/** Description of trusted apps agnostic list */
+export const ENDPOINT_TRUSTED_APPS_LIST_DESCRIPTION = 'Elastic Endpoint Security Trusted Apps List';
diff --git a/x-pack/plugins/lists/server/services/exception_lists/create_endpoint_trusted_apps_list.ts b/x-pack/plugins/lists/server/services/exception_lists/create_endpoint_trusted_apps_list.ts
new file mode 100644
index 0000000000000..c782cdd302666
--- /dev/null
+++ b/x-pack/plugins/lists/server/services/exception_lists/create_endpoint_trusted_apps_list.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 { SavedObjectsClientContract } from 'kibana/server';
+import uuid from 'uuid';
+
+import {
+ ENDPOINT_TRUSTED_APPS_LIST_DESCRIPTION,
+ ENDPOINT_TRUSTED_APPS_LIST_ID,
+ ENDPOINT_TRUSTED_APPS_LIST_NAME,
+} from '../../../common/constants';
+import { ExceptionListSchema, ExceptionListSoSchema, Version } from '../../../common/schemas';
+
+import { getSavedObjectType, transformSavedObjectToExceptionList } from './utils';
+
+interface CreateEndpointListOptions {
+ savedObjectsClient: SavedObjectsClientContract;
+ user: string;
+ tieBreaker?: string;
+ version: Version;
+}
+
+/**
+ * Creates the Endpoint Trusted Apps agnostic list if it does not yet exist
+ *
+ * @param savedObjectsClient
+ * @param user
+ * @param tieBreaker
+ * @param version
+ */
+export const createEndpointTrustedAppsList = async ({
+ savedObjectsClient,
+ user,
+ tieBreaker,
+ version,
+}: CreateEndpointListOptions): Promise => {
+ const savedObjectType = getSavedObjectType({ namespaceType: 'agnostic' });
+ const dateNow = new Date().toISOString();
+ try {
+ const savedObject = await savedObjectsClient.create(
+ savedObjectType,
+ {
+ _tags: [],
+ comments: undefined,
+ created_at: dateNow,
+ created_by: user,
+ description: ENDPOINT_TRUSTED_APPS_LIST_DESCRIPTION,
+ entries: undefined,
+ immutable: false,
+ item_id: undefined,
+ list_id: ENDPOINT_TRUSTED_APPS_LIST_ID,
+ list_type: 'list',
+ meta: undefined,
+ name: ENDPOINT_TRUSTED_APPS_LIST_NAME,
+ tags: [],
+ tie_breaker_id: tieBreaker ?? uuid.v4(),
+ type: 'endpoint',
+ updated_by: user,
+ version,
+ },
+ {
+ // We intentionally hard coding the id so that there can only be one Trusted apps list within the space
+ id: ENDPOINT_TRUSTED_APPS_LIST_ID,
+ }
+ );
+ return transformSavedObjectToExceptionList({ savedObject });
+ } catch (err) {
+ if (savedObjectsClient.errors.isConflictError(err)) {
+ return null;
+ } else {
+ throw err;
+ }
+ }
+};
diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts
index 83b44ababf9de..747458175e3b8 100644
--- a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts
+++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts
@@ -46,6 +46,7 @@ import { findExceptionListItem } from './find_exception_list_item';
import { findExceptionList } from './find_exception_list';
import { findExceptionListsItem } from './find_exception_list_items';
import { createEndpointList } from './create_endpoint_list';
+import { createEndpointTrustedAppsList } from './create_endpoint_trusted_apps_list';
export class ExceptionListClient {
private readonly user: string;
@@ -90,6 +91,18 @@ export class ExceptionListClient {
});
};
+ /**
+ * Create the Trusted Apps Agnostic list if it does not yet exist (`null` is returned if it does exist)
+ */
+ public createTrustedAppsList = async (): Promise => {
+ const { savedObjectsClient, user } = this;
+ return createEndpointTrustedAppsList({
+ savedObjectsClient,
+ user,
+ version: 1,
+ });
+ };
+
/**
* This is the same as "createListItem" except it applies specifically to the agnostic endpoint list and will
* auto-call the "createEndpointList" for you so that you have the best chance of the agnostic endpoint
From eb3cb8258fb6ddd61fa0cecaf03b43a33ab54c65 Mon Sep 17 00:00:00 2001
From: Chris Roberson
Date: Wed, 19 Aug 2020 14:57:53 -0400
Subject: [PATCH 40/85] Add loading page (#75362)
---
.../public/directives/main/index.js | 5 ++
x-pack/plugins/monitoring/public/views/all.js | 1 +
.../public/views/cluster/listing/index.js | 2 +-
.../public/views/cluster/overview/index.html | 2 +-
.../public/views/cluster/overview/index.js | 3 +
.../public/views/loading/index.html | 5 ++
.../monitoring/public/views/loading/index.js | 67 +++++++++++++++++++
7 files changed, 83 insertions(+), 2 deletions(-)
create mode 100644 x-pack/plugins/monitoring/public/views/loading/index.html
create mode 100644 x-pack/plugins/monitoring/public/views/loading/index.js
diff --git a/x-pack/plugins/monitoring/public/directives/main/index.js b/x-pack/plugins/monitoring/public/directives/main/index.js
index d682e87b7ca95..cee18004c5777 100644
--- a/x-pack/plugins/monitoring/public/directives/main/index.js
+++ b/x-pack/plugins/monitoring/public/directives/main/index.js
@@ -205,6 +205,7 @@ export class MonitoringMainController {
export function monitoringMainProvider(breadcrumbs, license, $injector) {
const $executor = $injector.get('$executor');
+ const $parse = $injector.get('$parse');
return {
restrict: 'E',
@@ -221,6 +222,10 @@ export function monitoringMainProvider(breadcrumbs, license, $injector) {
Object.keys(setupObj.attributes).forEach((key) => {
attributes.$observe(key, () => controller.setup(getSetupObj()));
});
+ if (attributes.onLoaded) {
+ const onLoaded = $parse(attributes.onLoaded)(scope);
+ onLoaded();
+ }
});
initSetupModeState(scope, $injector, () => {
diff --git a/x-pack/plugins/monitoring/public/views/all.js b/x-pack/plugins/monitoring/public/views/all.js
index d192b366fec33..e4903ea974a18 100644
--- a/x-pack/plugins/monitoring/public/views/all.js
+++ b/x-pack/plugins/monitoring/public/views/all.js
@@ -35,3 +35,4 @@ import './beats/beat';
import './apm/overview';
import './apm/instances';
import './apm/instance';
+import './loading';
diff --git a/x-pack/plugins/monitoring/public/views/cluster/listing/index.js b/x-pack/plugins/monitoring/public/views/cluster/listing/index.js
index b00945ca37e19..db1408e3905f0 100644
--- a/x-pack/plugins/monitoring/public/views/cluster/listing/index.js
+++ b/x-pack/plugins/monitoring/public/views/cluster/listing/index.js
@@ -79,4 +79,4 @@ uiRoutes
}
},
})
- .otherwise({ redirectTo: '/no-data' });
+ .otherwise({ redirectTo: '/loading' });
diff --git a/x-pack/plugins/monitoring/public/views/cluster/overview/index.html b/x-pack/plugins/monitoring/public/views/cluster/overview/index.html
index 7ea786510bf19..1762ee1c2a282 100644
--- a/x-pack/plugins/monitoring/public/views/cluster/overview/index.html
+++ b/x-pack/plugins/monitoring/public/views/cluster/overview/index.html
@@ -1,3 +1,3 @@
-
+
diff --git a/x-pack/plugins/monitoring/public/views/cluster/overview/index.js b/x-pack/plugins/monitoring/public/views/cluster/overview/index.js
index f3e6d5def9b6f..09aef1d431071 100644
--- a/x-pack/plugins/monitoring/public/views/cluster/overview/index.js
+++ b/x-pack/plugins/monitoring/public/views/cluster/overview/index.js
@@ -25,6 +25,7 @@ uiRoutes.when('/overview', {
return routeInit({ codePaths: CODE_PATHS });
},
},
+ controllerAs: 'monitoringClusterOverview',
controller: class extends MonitoringViewBaseController {
constructor($injector, $scope) {
const monitoringClusters = $injector.get('monitoringClusters');
@@ -52,6 +53,8 @@ uiRoutes.when('/overview', {
},
});
+ this.init = () => this.renderReact(null);
+
$scope.$watch(
() => this.data,
async (data) => {
diff --git a/x-pack/plugins/monitoring/public/views/loading/index.html b/x-pack/plugins/monitoring/public/views/loading/index.html
new file mode 100644
index 0000000000000..9a5971a65bc39
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/views/loading/index.html
@@ -0,0 +1,5 @@
+
+
+
diff --git a/x-pack/plugins/monitoring/public/views/loading/index.js b/x-pack/plugins/monitoring/public/views/loading/index.js
new file mode 100644
index 0000000000000..04e0325aa70e2
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/views/loading/index.js
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+/**
+ * Controller for single index detail
+ */
+import React from 'react';
+import { render } from 'react-dom';
+import { i18n } from '@kbn/i18n';
+import { uiRoutes } from '../../angular/helpers/routes';
+import { routeInitProvider } from '../../lib/route_init';
+import template from './index.html';
+import { Legacy } from '../../legacy_shims';
+import { CODE_PATH_ELASTICSEARCH } from '../../../common/constants';
+import { PageLoading } from '../../components';
+
+const CODE_PATHS = [CODE_PATH_ELASTICSEARCH];
+uiRoutes.when('/loading', {
+ template,
+ controllerAs: 'monitoringLoading',
+ controller: class {
+ constructor($injector, $scope) {
+ const Private = $injector.get('Private');
+ const titleService = $injector.get('title');
+ titleService(
+ $scope.cluster,
+ i18n.translate('xpack.monitoring.loading.pageTitle', {
+ defaultMessage: 'Loading',
+ })
+ );
+
+ this.init = () => {
+ const reactNodeId = 'monitoringLoadingReact';
+ const renderElement = document.getElementById(reactNodeId);
+ if (!renderElement) {
+ console.warn(`"#${reactNodeId}" element has not been added to the DOM yet`);
+ return;
+ }
+ const I18nContext = Legacy.shims.I18nContext;
+ render(
+
+
+ ,
+ renderElement
+ );
+ };
+
+ const routeInit = Private(routeInitProvider);
+ routeInit({ codePaths: CODE_PATHS, fetchAllClusters: true }).then((clusters) => {
+ if (!clusters || !clusters.length) {
+ window.location.hash = '#/no-data';
+ return;
+ }
+ if (clusters.length === 1) {
+ // Bypass the cluster listing if there is just 1 cluster
+ window.history.replaceState(null, null, '#/overview');
+ return;
+ }
+
+ window.history.replaceState(null, null, '#/home');
+ });
+ }
+ },
+});
From 9111d5096566b0d3c6659895b40f01f66450520e Mon Sep 17 00:00:00 2001
From: Steph Milovic
Date: Wed, 19 Aug 2020 13:15:55 -0600
Subject: [PATCH 41/85] fix tests and enable in CI (#75313)
---
x-pack/scripts/functional_tests.js | 1 +
.../basic/tests/configure/get_connectors.ts | 18 ++++++++--
.../case_api_integration/common/config.ts | 3 +-
.../case_api_integration/common/lib/utils.ts | 35 ++++++++++++++++++-
4 files changed, 53 insertions(+), 4 deletions(-)
diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js
index ef646343871d3..a6550bef46d4a 100644
--- a/x-pack/scripts/functional_tests.js
+++ b/x-pack/scripts/functional_tests.js
@@ -22,6 +22,7 @@ const onlyNotInCoverageTests = [
require.resolve('../test/alerting_api_integration/basic/config.ts'),
require.resolve('../test/alerting_api_integration/spaces_only/config.ts'),
require.resolve('../test/alerting_api_integration/security_and_spaces/config.ts'),
+ require.resolve('../test/case_api_integration/basic/config.ts'),
require.resolve('../test/apm_api_integration/basic/config.ts'),
require.resolve('../test/apm_api_integration/trial/config.ts'),
require.resolve('../test/detection_engine_api_integration/security_and_spaces/config.ts'),
diff --git a/x-pack/test/case_api_integration/basic/tests/configure/get_connectors.ts b/x-pack/test/case_api_integration/basic/tests/configure/get_connectors.ts
index f5f290476dd0b..5ba1aac4c8f92 100644
--- a/x-pack/test/case_api_integration/basic/tests/configure/get_connectors.ts
+++ b/x-pack/test/case_api_integration/basic/tests/configure/get_connectors.ts
@@ -9,7 +9,11 @@ import { FtrProviderContext } from '../../../common/ftr_provider_context';
import { CASE_CONFIGURE_CONNECTORS_URL } from '../../../../../plugins/case/common/constants';
import { ObjectRemover as ActionsRemover } from '../../../../alerting_api_integration/common/lib';
-import { getServiceNowConnector, getJiraConnector } from '../../../common/lib/utils';
+import {
+ getServiceNowConnector,
+ getJiraConnector,
+ getResilientConnector,
+} from '../../../common/lib/utils';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
@@ -61,9 +65,16 @@ export default ({ getService }: FtrProviderContext): void => {
.send(getJiraConnector())
.expect(200);
+ const { body: connectorFour } = await supertest
+ .post('/api/actions/action')
+ .set('kbn-xsrf', 'true')
+ .send(getResilientConnector())
+ .expect(200);
+
actionsRemover.add('default', connectorOne.id, 'action', 'actions');
actionsRemover.add('default', connectorTwo.id, 'action', 'actions');
actionsRemover.add('default', connectorThree.id, 'action', 'actions');
+ actionsRemover.add('default', connectorFour.id, 'action', 'actions');
const { body: connectors } = await supertest
.get(`${CASE_CONFIGURE_CONNECTORS_URL}/_find`)
@@ -71,13 +82,16 @@ export default ({ getService }: FtrProviderContext): void => {
.send()
.expect(200);
- expect(connectors.length).to.equal(2);
+ expect(connectors.length).to.equal(3);
expect(
connectors.some((c: { actionTypeId: string }) => c.actionTypeId === '.servicenow')
).to.equal(true);
expect(connectors.some((c: { actionTypeId: string }) => c.actionTypeId === '.jira')).to.equal(
true
);
+ expect(
+ connectors.some((c: { actionTypeId: string }) => c.actionTypeId === '.resilient')
+ ).to.equal(true);
});
});
};
diff --git a/x-pack/test/case_api_integration/common/config.ts b/x-pack/test/case_api_integration/common/config.ts
index 098d94f35d9ca..9b048813b479a 100644
--- a/x-pack/test/case_api_integration/common/config.ts
+++ b/x-pack/test/case_api_integration/common/config.ts
@@ -21,10 +21,11 @@ interface CreateTestConfigOptions {
const enabledActionTypes = [
'.email',
'.index',
+ '.jira',
'.pagerduty',
+ '.resilient',
'.server-log',
'.servicenow',
- '.jira',
'.slack',
'.webhook',
'test.authorization',
diff --git a/x-pack/test/case_api_integration/common/lib/utils.ts b/x-pack/test/case_api_integration/common/lib/utils.ts
index a24e17f9e5efb..fb6f4fce3c29a 100644
--- a/x-pack/test/case_api_integration/common/lib/utils.ts
+++ b/x-pack/test/case_api_integration/common/lib/utils.ts
@@ -33,7 +33,7 @@ export const getServiceNowConnector = () => ({
},
config: {
apiUrl: 'http://some.non.existent.com',
- casesConfiguration: {
+ incidentConfiguration: {
mapping: [
{
source: 'title',
@@ -52,6 +52,7 @@ export const getServiceNowConnector = () => ({
},
],
},
+ isCaseOwned: true,
},
});
@@ -87,6 +88,38 @@ export const getJiraConnector = () => ({
},
});
+export const getResilientConnector = () => ({
+ name: 'Resilient Connector',
+ actionTypeId: '.resilient',
+ secrets: {
+ apiKeyId: 'id',
+ apiKeySecret: 'secret',
+ },
+ config: {
+ apiUrl: 'http://some.non.existent.com',
+ orgId: 'pkey',
+ casesConfiguration: {
+ mapping: [
+ {
+ source: 'title',
+ target: 'summary',
+ actionType: 'overwrite',
+ },
+ {
+ source: 'description',
+ target: 'description',
+ actionType: 'overwrite',
+ },
+ {
+ source: 'comments',
+ target: 'comments',
+ actionType: 'append',
+ },
+ ],
+ },
+ },
+});
+
export const removeServerGeneratedPropertiesFromConfigure = (
config: Partial
): Partial => {
From ad5c0f58fe2eac946a21a23f56f51cea74d1ab6b Mon Sep 17 00:00:00 2001
From: Jen Huang
Date: Wed, 19 Aug 2020 13:52:06 -0700
Subject: [PATCH 42/85] [Ingest Manager] Rename agent/package config(s) to
agent/package policy(ies) (#74914)
* Initial pass at updating client routes, variables names, code comments, and UI copy
* Adjust server routes and param names, more var names and i18n fixes
* Fix test
* More var renaming
* Rest of server-side var renaming
* Rest of client side var renaming
* Rename agent SO attributes and add migrations
* Remove agent prefix from policy fields
* Rename agent policy SO attributes and add migrations
* Rename enrollment api key SO attributes and add migrations
* Rename package policy SO attributes and add migrations
* Rename agent event SO attributes and add migrations
* Rename subtype CONFIG to POLICY (I don't think this string is ever sent by agent, though)
* Update snapshot
* Remove unnecessary cloning in migrations
* Fix migration typos
* Update naming in tests and es archiver data
* Rename file names in /common
* Rename /server files
* Rename /public files
* Rename test file names
* Rename missed files
* Revert "Rename subtype CONFIG to POLICY (I don't think this string is ever sent by agent, though)"
This reverts commit 3c91e01ed98d34de18a06ae8cb06dadf3b2c77be.
* Add migration version to updated es archiver data to fix tests
---
x-pack/plugins/ingest_manager/README.md | 2 +-
.../ingest_manager/common/constants/agent.ts | 4 +-
.../common/constants/agent_config.ts | 20 -
.../common/constants/agent_policy.ts | 20 +
.../ingest_manager/common/constants/index.ts | 4 +-
.../{package_config.ts => package_policy.ts} | 2 +-
.../ingest_manager/common/constants/routes.ts | 38 +-
x-pack/plugins/ingest_manager/common/mocks.ts | 12 +-
.../common/openapi/spec_oas3.json | 176 +++----
...o_yaml.ts => full_agent_policy_to_yaml.ts} | 12 +-
.../ingest_manager/common/services/index.ts | 8 +-
.../common/services/limited_package.ts | 14 +-
... package_policies_to_agent_inputs.test.ts} | 38 +-
...ts => package_policies_to_agent_inputs.ts} | 32 +-
...t.ts => package_to_package_policy.test.ts} | 56 +--
...config.ts => package_to_package_policy.ts} | 64 +--
.../ingest_manager/common/services/routes.ts | 52 +--
.../ingest_manager/common/types/index.ts | 4 +-
.../common/types/models/agent.ts | 6 +-
.../{agent_config.ts => agent_policy.ts} | 26 +-
.../common/types/models/enrollment_api_key.ts | 12 +-
.../common/types/models/index.ts | 4 +-
.../common/types/models/package_config.ts | 73 ---
.../common/types/models/package_policy.ts | 73 +++
.../common/types/rest_spec/agent.ts | 4 +-
.../common/types/rest_spec/agent_config.ts | 83 ----
.../common/types/rest_spec/agent_policy.ts | 83 ++++
.../types/rest_spec/enrollment_api_key.ts | 2 +-
.../common/types/rest_spec/index.ts | 4 +-
.../common/types/rest_spec/package_config.ts | 59 ---
.../common/types/rest_spec/package_policy.ts | 59 +++
.../dev_docs/actions_and_events.md | 2 +-
.../dev_docs/api/agents_enroll.md | 2 +-
.../ingest_manager/dev_docs/definitions.md | 21 +-
x-pack/plugins/ingest_manager/dev_docs/epm.md | 22 +-
.../fleet_agents_interactions_detailed.md | 14 +-
.../dev_docs/schema/agent_checkin.mml | 6 +-
.../dev_docs/schema/saved_objects.mml | 26 +-
.../tutorial_directory_notice.tsx | 2 +-
.../tutorial_module_notice.tsx | 2 +-
.../components/settings_flyout.tsx | 4 +-
.../ingest_manager/constants/index.ts | 4 +-
.../ingest_manager/constants/page_paths.ts | 38 +-
.../ingest_manager/hooks/use_breadcrumbs.tsx | 54 +--
.../hooks/use_request/agent_config.ts | 109 -----
.../hooks/use_request/agent_policy.ts | 109 +++++
.../ingest_manager/hooks/use_request/index.ts | 4 +-
.../hooks/use_request/package_config.ts | 62 ---
.../hooks/use_request/package_policy.ts | 62 +++
.../applications/ingest_manager/index.tsx | 8 +-
.../ingest_manager/layouts/default.tsx | 9 +-
.../sections/agent_config/components/index.ts | 12 -
.../components/custom_package_config.tsx | 61 ---
.../details_page/components/index.ts | 8 -
.../components/package_configs/index.tsx | 23 -
.../sections/agent_config/index.tsx | 36 --
.../components/actions_menu.tsx | 44 +-
.../agent_policy_copy_provider.tsx} | 94 ++--
.../agent_policy_delete_provider.tsx} | 82 ++--
.../components/agent_policy_form.tsx} | 126 +++--
.../components/agent_policy_yaml_flyout.tsx} | 38 +-
.../components/confirm_deploy_modal.tsx | 26 +-
.../danger_eui_context_menu_item.tsx | 0
.../sections/agent_policy/components/index.ts | 14 +
.../components/linked_agent_count.tsx | 8 +-
.../package_policy_delete_provider.tsx} | 82 ++--
.../components/custom_package_policy.tsx | 61 +++
.../components/index.ts | 8 +-
.../components/layout.tsx | 44 +-
.../package_policy_input_config.tsx} | 42 +-
.../package_policy_input_panel.tsx} | 82 ++--
.../package_policy_input_stream.tsx} | 42 +-
.../package_policy_input_var_field.tsx} | 4 +-
.../create_package_policy_page}/index.tsx | 244 +++++-----
.../has_invalid_but_required_var.test.ts | 0
.../services/has_invalid_but_required_var.ts | 14 +-
.../services/index.ts | 12 +-
.../services/is_advanced_var.test.ts | 0
.../services/is_advanced_var.ts | 0
.../validate_package_policy.test..ts} | 56 +--
.../services/validate_package_policy.ts} | 74 +--
.../step_configure_package.tsx | 52 +--
.../step_define_package_policy.tsx} | 94 ++--
.../step_select_agent_policy.tsx} | 202 ++++----
.../step_select_package.tsx | 58 +--
.../create_package_policy_page}/types.ts | 4 +-
.../details_page/components/index.ts | 8 +
.../components/package_policies/index.tsx | 23 +
.../package_policies/no_package_policies.tsx} | 12 +-
.../package_policies_table.tsx} | 116 ++---
.../components/settings/index.tsx | 80 ++--
.../details_page/hooks/index.ts | 2 +-
.../details_page/hooks/use_agent_status.tsx | 4 +-
.../details_page/hooks/use_config.tsx | 6 +-
.../details_page/index.tsx | 134 +++---
.../edit_package_policy_page}/index.tsx | 220 ++++-----
.../sections/agent_policy/index.tsx | 36 ++
.../components/create_agent_policy.tsx} | 64 +--
.../list_page/components/index.ts | 2 +-
.../list_page/index.tsx | 151 +++---
.../sections/data_stream/list_page/index.tsx | 2 +-
.../ingest_manager/sections/epm/index.tsx | 6 +-
.../detail/confirm_package_uninstall.tsx | 2 +-
.../sections/epm/screens/detail/content.tsx | 4 +-
.../sections/epm/screens/detail/index.tsx | 4 +-
...s_panel.tsx => package_policies_panel.tsx} | 4 +-
.../epm/screens/detail/settings_panel.tsx | 14 +-
.../epm/screens/detail/side_nav_links.tsx | 2 +-
.../components/actions_menu.tsx | 8 +-
.../components/agent_details.tsx | 18 +-
.../components/type_labels.tsx | 4 +-
.../fleet/agent_details_page/index.tsx | 40 +-
.../sections/fleet/agent_list_page/index.tsx | 126 ++---
...lection.tsx => agent_policy_selection.tsx} | 82 ++--
.../agent_enrollment_flyout/index.tsx | 10 +-
.../managed_instructions.tsx | 10 +-
.../standalone_instructions.tsx | 48 +-
.../agent_enrollment_flyout/steps.tsx | 24 +-
...es.tsx => agent_policy_package_badges.tsx} | 32 +-
.../index.tsx | 63 +--
.../sections/fleet/components/index.tsx | 2 +-
.../sections/fleet/components/list_layout.tsx | 12 +-
.../components/new_enrollment_key_flyout.tsx | 33 +-
.../enrollment_token_list_page/index.tsx | 20 +-
.../sections/fleet/setup_page/index.tsx | 4 +-
.../ingest_manager/sections/index.tsx | 4 +-
...n_section.tsx => agent_policy_section.tsx} | 36 +-
.../overview/components/agent_section.tsx | 2 +-
.../components/integration_section.tsx | 2 +-
.../sections/overview/index.tsx | 16 +-
.../ingest_manager/services/index.ts | 12 +-
.../ingest_manager/types/index.ts | 56 +--
.../types/intra_app_route_state.ts | 24 +-
x-pack/plugins/ingest_manager/public/index.ts | 10 +-
.../plugins/ingest_manager/public/plugin.ts | 6 +-
.../server/collectors/agent_collectors.ts | 4 +-
.../server/collectors/package_collectors.ts | 23 +-
.../ingest_manager/server/constants/index.ts | 14 +-
x-pack/plugins/ingest_manager/server/index.ts | 14 +-
.../server/integration_tests/router.test.ts | 42 +-
x-pack/plugins/ingest_manager/server/mocks.ts | 8 +-
.../plugins/ingest_manager/server/plugin.ts | 30 +-
.../server/routes/agent/handlers.ts | 11 +-
.../server/routes/agent/index.ts | 6 +-
.../server/routes/agent_config/index.ts | 108 -----
.../handlers.ts | 174 +++----
.../server/routes/agent_policy/index.ts | 108 +++++
.../routes/enrollment_api_key/handler.ts | 2 +-
.../ingest_manager/server/routes/index.ts | 4 +-
.../server/routes/package_config/index.ts | 73 ---
.../handlers.test.ts | 56 +--
.../handlers.ts | 90 ++--
.../server/routes/package_policy/index.ts | 73 +++
.../server/saved_objects/index.ts | 53 ++-
.../saved_objects/migrations/to_v7_10_0.ts | 72 +++
.../server/services/agent_config.ts | 442 ------------------
...nt_config.test.ts => agent_policy.test.ts} | 34 +-
.../server/services/agent_policy.ts | 442 ++++++++++++++++++
...onfig_update.ts => agent_policy_update.ts} | 14 +-
.../server/services/agents/acks.test.ts | 16 +-
.../server/services/agents/acks.ts | 20 +-
.../server/services/agents/checkin/index.ts | 2 +-
.../agents/checkin/state_new_actions.ts | 78 ++--
.../server/services/agents/enroll.ts | 6 +-
.../server/services/agents/reassign.ts | 14 +-
.../server/services/agents/status.ts | 18 +-
.../server/services/agents/update.ts | 7 +-
.../services/api_keys/enrollment_api_key.ts | 25 +-
.../server/services/api_keys/index.ts | 6 +-
.../ingest_manager/server/services/config.ts | 2 +-
.../server/services/epm/agent/agent.ts | 6 +-
.../server/services/epm/packages/get.ts | 2 +-
.../server/services/epm/packages/remove.ts | 10 +-
.../ingest_manager/server/services/index.ts | 4 +-
..._config.test.ts => package_policy.test.ts} | 22 +-
.../{package_config.ts => package_policy.ts} | 218 ++++-----
.../ingest_manager/server/services/setup.ts | 62 +--
.../ingest_manager/server/types/index.tsx | 28 +-
.../server/types/models/agent.ts | 2 +-
.../{agent_config.ts => agent_policy.ts} | 22 +-
.../server/types/models/enrollment_api_key.ts | 12 +-
.../server/types/models/index.ts | 4 +-
.../{package_config.ts => package_policy.ts} | 16 +-
.../server/types/rest_spec/agent.ts | 4 +-
.../{agent_config.ts => agent_policy.ts} | 30 +-
.../types/rest_spec/enrollment_api_key.ts | 2 +-
.../server/types/rest_spec/index.ts | 4 +-
.../server/types/rest_spec/package_config.ts | 33 --
.../server/types/rest_spec/package_policy.ts | 33 ++
.../common/endpoint/generate_data.ts | 20 +-
.../common/endpoint/types.ts | 8 +-
.../use_navigate_to_app_event_handler.ts | 4 +-
.../mock/endpoint/dependencies_start_mock.ts | 4 +-
.../pages/endpoint_hosts/store/middleware.ts | 24 +-
.../store/mock_endpoint_result_list.ts | 30 +-
.../view/details/endpoint_details.tsx | 4 +-
.../pages/endpoint_hosts/view/index.test.tsx | 12 +-
.../pages/endpoint_hosts/view/index.tsx | 14 +-
.../policy/store/policy_details/index.test.ts | 4 +-
.../policy/store/policy_details/middleware.ts | 22 +-
.../policy/store/policy_details/selectors.ts | 2 +-
.../pages/policy/store/policy_list/action.ts | 2 +-
.../policy/store/policy_list/index.test.ts | 28 +-
.../policy/store/policy_list/middleware.ts | 22 +-
.../store/policy_list/services/ingest.test.ts | 30 +-
.../store/policy_list/services/ingest.ts | 82 ++--
.../store/policy_list/test_mock_utils.ts | 6 +-
.../public/management/pages/policy/types.ts | 16 +-
...onfig.tsx => configure_package_policy.tsx} | 36 +-
.../pages/policy/view/policy_details.test.tsx | 24 +-
.../pages/policy/view/policy_details.tsx | 2 +-
.../pages/policy/view/policy_list.test.tsx | 2 +-
.../pages/policy/view/policy_list.tsx | 26 +-
.../security_solution/public/plugin.tsx | 6 +-
.../endpoint/endpoint_app_context_services.ts | 6 +-
.../endpoint/ingest_integration.test.ts | 20 +-
.../server/endpoint/ingest_integration.ts | 30 +-
.../server/endpoint/lib/artifacts/mocks.ts | 20 +-
.../server/endpoint/lib/artifacts/task.ts | 2 +-
.../server/endpoint/mocks.ts | 4 +-
.../manifest_manager/manifest_manager.mock.ts | 26 +-
.../manifest_manager/manifest_manager.test.ts | 20 +-
.../manifest_manager/manifest_manager.ts | 30 +-
.../security_solution/server/plugin.ts | 2 +-
.../server/usage/endpoints/endpoint.mocks.ts | 10 +-
.../translations/translations/ja-JP.json | 410 ++++++++--------
.../translations/translations/zh-CN.json | 410 ++++++++--------
.../endpoint/artifacts/api_feature/data.json | 14 +-
.../es_archives/fleet/agents/data.json | 35 +-
.../es_archives/fleet/agents/mappings.json | 15 +-
.../es_archives/lists/mappings.json | 12 +-
.../canvas_disallowed_url/mappings.json | 12 +-
.../agent_policy.ts} | 34 +-
.../{agent_config => agent_policy}/index.js | 2 +-
.../apis/fleet/agents/complete_flow.ts | 6 +-
.../apis/fleet/agents/enroll.ts | 2 +-
.../apis/fleet/enrollment_api_keys/crud.ts | 14 +-
.../apis/index.js | 13 +-
.../create.ts | 40 +-
.../{package_config => package_policy}/get.ts | 34 +-
.../update.ts | 42 +-
.../es_archives/export_rule/mappings.json | 10 +-
.../apps/endpoint/endpoint_list.ts | 2 +-
.../apps/endpoint/policy_details.ts | 48 +-
.../apps/endpoint/policy_list.ts | 42 +-
.../page_objects/index.ts | 4 +-
...est_manager_create_package_policy_page.ts} | 50 +-
.../page_objects/policy_page.ts | 6 +-
.../services/endpoint_policy.ts | 150 +++---
249 files changed, 4845 insertions(+), 4720 deletions(-)
delete mode 100644 x-pack/plugins/ingest_manager/common/constants/agent_config.ts
create mode 100644 x-pack/plugins/ingest_manager/common/constants/agent_policy.ts
rename x-pack/plugins/ingest_manager/common/constants/{package_config.ts => package_policy.ts} (79%)
rename x-pack/plugins/ingest_manager/common/services/{config_to_yaml.ts => full_agent_policy_to_yaml.ts} (70%)
rename x-pack/plugins/ingest_manager/common/services/{package_configs_to_agent_inputs.test.ts => package_policies_to_agent_inputs.test.ts} (80%)
rename x-pack/plugins/ingest_manager/common/services/{package_configs_to_agent_inputs.ts => package_policies_to_agent_inputs.ts} (62%)
rename x-pack/plugins/ingest_manager/common/services/{package_to_config.test.ts => package_to_package_policy.test.ts} (88%)
rename x-pack/plugins/ingest_manager/common/services/{package_to_config.ts => package_to_package_policy.ts} (65%)
rename x-pack/plugins/ingest_manager/common/types/models/{agent_config.ts => agent_policy.ts} (65%)
delete mode 100644 x-pack/plugins/ingest_manager/common/types/models/package_config.ts
create mode 100644 x-pack/plugins/ingest_manager/common/types/models/package_policy.ts
delete mode 100644 x-pack/plugins/ingest_manager/common/types/rest_spec/agent_config.ts
create mode 100644 x-pack/plugins/ingest_manager/common/types/rest_spec/agent_policy.ts
delete mode 100644 x-pack/plugins/ingest_manager/common/types/rest_spec/package_config.ts
create mode 100644 x-pack/plugins/ingest_manager/common/types/rest_spec/package_policy.ts
delete mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts
create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_policy.ts
delete mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/package_config.ts
create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/package_policy.ts
delete mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/index.ts
delete mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_package_config_page/components/custom_package_config.tsx
delete mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/components/index.ts
delete mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/components/package_configs/index.tsx
delete mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/index.tsx
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config => agent_policy}/components/actions_menu.tsx (74%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/components/config_copy_provider.tsx => agent_policy/components/agent_policy_copy_provider.tsx} (54%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/components/config_delete_provider.tsx => agent_policy/components/agent_policy_delete_provider.tsx} (62%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/components/config_form.tsx => agent_policy/components/agent_policy_form.tsx} (70%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/components/config_yaml_flyout.tsx => agent_policy/components/agent_policy_yaml_flyout.tsx} (68%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config => agent_policy}/components/confirm_deploy_modal.tsx (72%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config => agent_policy}/components/danger_eui_context_menu_item.tsx (100%)
create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/index.ts
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config => agent_policy}/components/linked_agent_count.tsx (77%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/components/package_config_delete_provider.tsx => agent_policy/components/package_policy_delete_provider.tsx} (70%)
create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/custom_package_policy.tsx
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/create_package_config_page => agent_policy/create_package_policy_page}/components/index.ts (51%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/create_package_config_page => agent_policy/create_package_policy_page}/components/layout.tsx (79%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/create_package_config_page/components/package_config_input_config.tsx => agent_policy/create_package_policy_page/components/package_policy_input_config.tsx} (83%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/create_package_config_page/components/package_config_input_panel.tsx => agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx} (74%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/create_package_config_page/components/package_config_input_stream.tsx => agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx} (83%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/create_package_config_page/components/package_config_input_var_field.tsx => agent_policy/create_package_policy_page/components/package_policy_input_var_field.tsx} (95%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/create_package_config_page => agent_policy/create_package_policy_page}/index.tsx (60%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/create_package_config_page => agent_policy/create_package_policy_page}/services/has_invalid_but_required_var.test.ts (100%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/create_package_config_page => agent_policy/create_package_policy_page}/services/has_invalid_but_required_var.ts (60%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/create_package_config_page => agent_policy/create_package_policy_page}/services/index.ts (67%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/create_package_config_page => agent_policy/create_package_policy_page}/services/is_advanced_var.test.ts (100%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/create_package_config_page => agent_policy/create_package_policy_page}/services/is_advanced_var.ts (100%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/create_package_config_page/services/validate_package_config.ts.test.ts => agent_policy/create_package_policy_page/services/validate_package_policy.test..ts} (91%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/create_package_config_page/services/validate_package_config.ts => agent_policy/create_package_policy_page/services/validate_package_policy.ts} (76%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/create_package_config_page => agent_policy/create_package_policy_page}/step_configure_package.tsx (68%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/create_package_config_page/step_define_package_config.tsx => agent_policy/create_package_policy_page/step_define_package_policy.tsx} (64%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/create_package_config_page/step_select_config.tsx => agent_policy/create_package_policy_page/step_select_agent_policy.tsx} (51%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/create_package_config_page => agent_policy/create_package_policy_page}/step_select_package.tsx (80%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/create_package_config_page => agent_policy/create_package_policy_page}/types.ts (68%)
create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/index.ts
create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/package_policies/index.tsx
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/details_page/components/package_configs/no_package_configs.tsx => agent_policy/details_page/components/package_policies/no_package_policies.tsx} (68%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/details_page/components/package_configs/package_configs_table.tsx => agent_policy/details_page/components/package_policies/package_policies_table.tsx} (62%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config => agent_policy}/details_page/components/settings/index.tsx (73%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config => agent_policy}/details_page/hooks/index.ts (80%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config => agent_policy}/details_page/hooks/use_agent_status.tsx (93%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config => agent_policy}/details_page/hooks/use_config.tsx (59%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config => agent_policy}/details_page/index.tsx (64%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/edit_package_config_page => agent_policy/edit_package_policy_page}/index.tsx (59%)
create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/index.tsx
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config/list_page/components/create_config.tsx => agent_policy/list_page/components/create_agent_policy.tsx} (65%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config => agent_policy}/list_page/components/index.ts (78%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/{agent_config => agent_policy}/list_page/index.tsx (61%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/{package_configs_panel.tsx => package_policies_panel.tsx} (88%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/{config_selection.tsx => agent_policy_selection.tsx} (67%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/{agent_config_package_badges.tsx => agent_policy_package_badges.tsx} (52%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/{agent_reassign_config_flyout => agent_reassign_policy_flyout}/index.tsx (63%)
rename x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/{configuration_section.tsx => agent_policy_section.tsx} (61%)
delete mode 100644 x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts
rename x-pack/plugins/ingest_manager/server/routes/{agent_config => agent_policy}/handlers.ts (54%)
create mode 100644 x-pack/plugins/ingest_manager/server/routes/agent_policy/index.ts
delete mode 100644 x-pack/plugins/ingest_manager/server/routes/package_config/index.ts
rename x-pack/plugins/ingest_manager/server/routes/{package_config => package_policy}/handlers.test.ts (83%)
rename x-pack/plugins/ingest_manager/server/routes/{package_config => package_policy}/handlers.ts (61%)
create mode 100644 x-pack/plugins/ingest_manager/server/routes/package_policy/index.ts
create mode 100644 x-pack/plugins/ingest_manager/server/saved_objects/migrations/to_v7_10_0.ts
delete mode 100644 x-pack/plugins/ingest_manager/server/services/agent_config.ts
rename x-pack/plugins/ingest_manager/server/services/{agent_config.test.ts => agent_policy.test.ts} (74%)
create mode 100644 x-pack/plugins/ingest_manager/server/services/agent_policy.ts
rename x-pack/plugins/ingest_manager/server/services/{agent_config_update.ts => agent_policy_update.ts} (64%)
rename x-pack/plugins/ingest_manager/server/services/{package_config.test.ts => package_policy.test.ts} (88%)
rename x-pack/plugins/ingest_manager/server/services/{package_config.ts => package_policy.ts} (58%)
rename x-pack/plugins/ingest_manager/server/types/models/{agent_config.ts => agent_policy.ts} (60%)
rename x-pack/plugins/ingest_manager/server/types/models/{package_config.ts => package_policy.ts} (85%)
rename x-pack/plugins/ingest_manager/server/types/rest_spec/{agent_config.ts => agent_policy.ts} (58%)
delete mode 100644 x-pack/plugins/ingest_manager/server/types/rest_spec/package_config.ts
create mode 100644 x-pack/plugins/ingest_manager/server/types/rest_spec/package_policy.ts
rename x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/{configure_package_config.tsx => configure_package_policy.tsx} (73%)
rename x-pack/test/ingest_manager_api_integration/apis/{agent_config/agent_config.ts => agent_policy/agent_policy.ts} (71%)
rename x-pack/test/ingest_manager_api_integration/apis/{agent_config => agent_policy}/index.js (86%)
rename x-pack/test/ingest_manager_api_integration/apis/{package_config => package_policy}/create.ts (81%)
rename x-pack/test/ingest_manager_api_integration/apis/{package_config => package_policy}/get.ts (68%)
rename x-pack/test/ingest_manager_api_integration/apis/{package_config => package_policy}/update.ts (72%)
rename x-pack/test/security_solution_endpoint/page_objects/{ingest_manager_create_package_config_page.ts => ingest_manager_create_package_policy_page.ts} (56%)
diff --git a/x-pack/plugins/ingest_manager/README.md b/x-pack/plugins/ingest_manager/README.md
index 9fd23e3d41dde..a95a2582f1f60 100644
--- a/x-pack/plugins/ingest_manager/README.md
+++ b/x-pack/plugins/ingest_manager/README.md
@@ -3,7 +3,7 @@
## Plugin
- The plugin is enabled by default. See the TypeScript type for the [the available plugin configuration options](https://github.com/elastic/kibana/blob/master/x-pack/plugins/ingest_manager/common/types/index.ts#L9-L27)
-- Adding `xpack.ingestManager.enabled=false` will disable the plugin including the EPM and Fleet features. It will also remove the `PACKAGE_CONFIG_API_ROUTES` and `AGENT_CONFIG_API_ROUTES` values in [`common/constants/routes.ts`](./common/constants/routes.ts)
+- Adding `xpack.ingestManager.enabled=false` will disable the plugin including the EPM and Fleet features. It will also remove the `PACKAGE_POLICY_API_ROUTES` and `AGENT_POLICY_API_ROUTES` values in [`common/constants/routes.ts`](./common/constants/routes.ts)
- Adding `--xpack.ingestManager.fleet.enabled=false` will disable the Fleet API & UI
- [code for adding the routes](https://github.com/elastic/kibana/blob/1f27d349533b1c2865c10c45b2cf705d7416fb36/x-pack/plugins/ingest_manager/server/plugin.ts#L115-L133)
- [Integration tests](server/integration_tests/router.test.ts)
diff --git a/x-pack/plugins/ingest_manager/common/constants/agent.ts b/x-pack/plugins/ingest_manager/common/constants/agent.ts
index d94f536b5659a..6d0d9ee801a94 100644
--- a/x-pack/plugins/ingest_manager/common/constants/agent.ts
+++ b/x-pack/plugins/ingest_manager/common/constants/agent.ts
@@ -17,5 +17,5 @@ export const AGENT_POLLING_INTERVAL = 1000;
export const AGENT_UPDATE_LAST_CHECKIN_INTERVAL_MS = 30000;
export const AGENT_UPDATE_ACTIONS_INTERVAL_MS = 5000;
-export const AGENT_CONFIG_ROLLOUT_RATE_LIMIT_INTERVAL_MS = 5000;
-export const AGENT_CONFIG_ROLLOUT_RATE_LIMIT_REQUEST_PER_INTERVAL = 25;
+export const AGENT_POLICY_ROLLOUT_RATE_LIMIT_INTERVAL_MS = 5000;
+export const AGENT_POLICY_ROLLOUT_RATE_LIMIT_REQUEST_PER_INTERVAL = 25;
diff --git a/x-pack/plugins/ingest_manager/common/constants/agent_config.ts b/x-pack/plugins/ingest_manager/common/constants/agent_config.ts
deleted file mode 100644
index aa6399b73f14e..0000000000000
--- a/x-pack/plugins/ingest_manager/common/constants/agent_config.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-import { AgentConfigStatus, DefaultPackages } from '../types';
-
-export const AGENT_CONFIG_SAVED_OBJECT_TYPE = 'ingest-agent-policies';
-
-export const DEFAULT_AGENT_CONFIG = {
- name: 'Default config',
- namespace: 'default',
- description: 'Default agent configuration created by Kibana',
- status: AgentConfigStatus.Active,
- package_configs: [],
- is_default: true,
- monitoring_enabled: ['logs', 'metrics'] as Array<'logs' | 'metrics'>,
-};
-
-export const DEFAULT_AGENT_CONFIGS_PACKAGES = [DefaultPackages.system];
diff --git a/x-pack/plugins/ingest_manager/common/constants/agent_policy.ts b/x-pack/plugins/ingest_manager/common/constants/agent_policy.ts
new file mode 100644
index 0000000000000..f6261790f6673
--- /dev/null
+++ b/x-pack/plugins/ingest_manager/common/constants/agent_policy.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { AgentPolicyStatus, DefaultPackages } from '../types';
+
+export const AGENT_POLICY_SAVED_OBJECT_TYPE = 'ingest-agent-policies';
+
+export const DEFAULT_AGENT_POLICY = {
+ name: 'Default policy',
+ namespace: 'default',
+ description: 'Default agent policy created by Kibana',
+ status: AgentPolicyStatus.Active,
+ package_policies: [],
+ is_default: true,
+ monitoring_enabled: ['logs', 'metrics'] as Array<'logs' | 'metrics'>,
+};
+
+export const DEFAULT_AGENT_POLICIES_PACKAGES = [DefaultPackages.system];
diff --git a/x-pack/plugins/ingest_manager/common/constants/index.ts b/x-pack/plugins/ingest_manager/common/constants/index.ts
index ed01fcdd316a3..519e2861cdc1d 100644
--- a/x-pack/plugins/ingest_manager/common/constants/index.ts
+++ b/x-pack/plugins/ingest_manager/common/constants/index.ts
@@ -7,8 +7,8 @@ export * from './plugin';
export * from './routes';
export * from './agent';
-export * from './agent_config';
-export * from './package_config';
+export * from './agent_policy';
+export * from './package_policy';
export * from './epm';
export * from './output';
export * from './enrollment_api_key';
diff --git a/x-pack/plugins/ingest_manager/common/constants/package_config.ts b/x-pack/plugins/ingest_manager/common/constants/package_policy.ts
similarity index 79%
rename from x-pack/plugins/ingest_manager/common/constants/package_config.ts
rename to x-pack/plugins/ingest_manager/common/constants/package_policy.ts
index 48fee967a3d3d..27814e6baafb1 100644
--- a/x-pack/plugins/ingest_manager/common/constants/package_config.ts
+++ b/x-pack/plugins/ingest_manager/common/constants/package_policy.ts
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export const PACKAGE_CONFIG_SAVED_OBJECT_TYPE = 'ingest-package-policies';
+export const PACKAGE_POLICY_SAVED_OBJECT_TYPE = 'ingest-package-policies';
diff --git a/x-pack/plugins/ingest_manager/common/constants/routes.ts b/x-pack/plugins/ingest_manager/common/constants/routes.ts
index 94265c3920922..551a0f7d60164 100644
--- a/x-pack/plugins/ingest_manager/common/constants/routes.ts
+++ b/x-pack/plugins/ingest_manager/common/constants/routes.ts
@@ -7,8 +7,8 @@
export const API_ROOT = `/api/ingest_manager`;
export const EPM_API_ROOT = `${API_ROOT}/epm`;
export const DATA_STREAM_API_ROOT = `${API_ROOT}/data_streams`;
-export const PACKAGE_CONFIG_API_ROOT = `${API_ROOT}/package_configs`;
-export const AGENT_CONFIG_API_ROOT = `${API_ROOT}/agent_configs`;
+export const PACKAGE_POLICY_API_ROOT = `${API_ROOT}/package_policies`;
+export const AGENT_POLICY_API_ROOT = `${API_ROOT}/agent_policies`;
export const FLEET_API_ROOT = `${API_ROOT}/fleet`;
export const LIMITED_CONCURRENCY_ROUTE_TAG = 'ingest:limited-concurrency';
@@ -32,25 +32,25 @@ export const DATA_STREAM_API_ROUTES = {
LIST_PATTERN: `${DATA_STREAM_API_ROOT}`,
};
-// Package config API routes
-export const PACKAGE_CONFIG_API_ROUTES = {
- LIST_PATTERN: `${PACKAGE_CONFIG_API_ROOT}`,
- INFO_PATTERN: `${PACKAGE_CONFIG_API_ROOT}/{packageConfigId}`,
- CREATE_PATTERN: `${PACKAGE_CONFIG_API_ROOT}`,
- UPDATE_PATTERN: `${PACKAGE_CONFIG_API_ROOT}/{packageConfigId}`,
- DELETE_PATTERN: `${PACKAGE_CONFIG_API_ROOT}/delete`,
+// Package policy API routes
+export const PACKAGE_POLICY_API_ROUTES = {
+ LIST_PATTERN: `${PACKAGE_POLICY_API_ROOT}`,
+ INFO_PATTERN: `${PACKAGE_POLICY_API_ROOT}/{packagePolicyId}`,
+ CREATE_PATTERN: `${PACKAGE_POLICY_API_ROOT}`,
+ UPDATE_PATTERN: `${PACKAGE_POLICY_API_ROOT}/{packagePolicyId}`,
+ DELETE_PATTERN: `${PACKAGE_POLICY_API_ROOT}/delete`,
};
-// Agent config API routes
-export const AGENT_CONFIG_API_ROUTES = {
- LIST_PATTERN: `${AGENT_CONFIG_API_ROOT}`,
- INFO_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}`,
- CREATE_PATTERN: `${AGENT_CONFIG_API_ROOT}`,
- UPDATE_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}`,
- COPY_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}/copy`,
- DELETE_PATTERN: `${AGENT_CONFIG_API_ROOT}/delete`,
- FULL_INFO_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}/full`,
- FULL_INFO_DOWNLOAD_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}/download`,
+// Agent policy API routes
+export const AGENT_POLICY_API_ROUTES = {
+ LIST_PATTERN: `${AGENT_POLICY_API_ROOT}`,
+ INFO_PATTERN: `${AGENT_POLICY_API_ROOT}/{agentPolicyId}`,
+ CREATE_PATTERN: `${AGENT_POLICY_API_ROOT}`,
+ UPDATE_PATTERN: `${AGENT_POLICY_API_ROOT}/{agentPolicyId}`,
+ COPY_PATTERN: `${AGENT_POLICY_API_ROOT}/{agentPolicyId}/copy`,
+ DELETE_PATTERN: `${AGENT_POLICY_API_ROOT}/delete`,
+ FULL_INFO_PATTERN: `${AGENT_POLICY_API_ROOT}/{agentPolicyId}/full`,
+ FULL_INFO_DOWNLOAD_PATTERN: `${AGENT_POLICY_API_ROOT}/{agentPolicyId}/download`,
};
// Output API routes
diff --git a/x-pack/plugins/ingest_manager/common/mocks.ts b/x-pack/plugins/ingest_manager/common/mocks.ts
index e85364f2bb672..1e14245e0f3ef 100644
--- a/x-pack/plugins/ingest_manager/common/mocks.ts
+++ b/x-pack/plugins/ingest_manager/common/mocks.ts
@@ -4,15 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { NewPackageConfig, PackageConfig } from './types/models/package_config';
+import { NewPackagePolicy, PackagePolicy } from './types';
-export const createNewPackageConfigMock = (): NewPackageConfig => {
+export const createNewPackagePolicyMock = (): NewPackagePolicy => {
return {
name: 'endpoint-1',
description: '',
namespace: 'default',
enabled: true,
- config_id: '93c46720-c217-11ea-9906-b5b8a21b268e',
+ policy_id: '93c46720-c217-11ea-9906-b5b8a21b268e',
output_id: '',
package: {
name: 'endpoint',
@@ -23,10 +23,10 @@ export const createNewPackageConfigMock = (): NewPackageConfig => {
};
};
-export const createPackageConfigMock = (): PackageConfig => {
- const newPackageConfig = createNewPackageConfigMock();
+export const createPackagePolicyMock = (): PackagePolicy => {
+ const newPackagePolicy = createNewPackagePolicyMock();
return {
- ...newPackageConfig,
+ ...newPackagePolicy,
id: 'c6d16e42-c32d-4dce-8a88-113cfe276ad1',
version: 'abcd',
revision: 1,
diff --git a/x-pack/plugins/ingest_manager/common/openapi/spec_oas3.json b/x-pack/plugins/ingest_manager/common/openapi/spec_oas3.json
index cfae2c450c824..8c9ede1fde9ba 100644
--- a/x-pack/plugins/ingest_manager/common/openapi/spec_oas3.json
+++ b/x-pack/plugins/ingest_manager/common/openapi/spec_oas3.json
@@ -17,9 +17,9 @@
}
],
"paths": {
- "/agent_configs": {
+ "/agent_policies": {
"get": {
- "summary": "Agent Config - List",
+ "summary": "Agent policy - List",
"tags": [],
"responses": {
"200": {
@@ -32,7 +32,7 @@
"items": {
"type": "array",
"items": {
- "$ref": "#/components/schemas/AgentConfig"
+ "$ref": "#/components/schemas/AgentPolicy"
}
},
"total": {
@@ -56,11 +56,11 @@
"items": [
{
"id": "82da1fc0-8fbf-11ea-b2ce-01c4a6127154",
- "name": "Default config",
+ "name": "Default policy",
"namespace": "default",
- "description": "Default agent configuration created by Kibana",
+ "description": "Default agent policy created by Kibana",
"status": "active",
- "packageConfigs": ["8a5679b0-8fbf-11ea-b2ce-01c4a6127154"],
+ "packagePolicies": ["8a5679b0-8fbf-11ea-b2ce-01c4a6127154"],
"is_default": true,
"monitoring_enabled": ["logs", "metrics"],
"revision": 2,
@@ -80,7 +80,7 @@
}
}
},
- "operationId": "agent-config-list",
+ "operationId": "agent-policy-list",
"parameters": [
{
"$ref": "#/components/parameters/pageSizeParam"
@@ -95,7 +95,7 @@
"description": ""
},
"post": {
- "summary": "Agent Config - Create",
+ "summary": "Agent policy - Create",
"tags": [],
"responses": {
"200": {
@@ -106,7 +106,7 @@
"type": "object",
"properties": {
"item": {
- "$ref": "#/components/schemas/AgentConfig"
+ "$ref": "#/components/schemas/AgentPolicy"
},
"success": {
"type": "boolean"
@@ -117,12 +117,12 @@
}
}
},
- "operationId": "post-agent_configs",
+ "operationId": "post-agent-policy",
"requestBody": {
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/NewAgentConfig"
+ "$ref": "#/components/schemas/NewAgentPolicy"
}
}
}
@@ -135,19 +135,19 @@
]
}
},
- "/agent_configs/{agentConfigId}": {
+ "/agent_policies/{agentPolicyId}": {
"parameters": [
{
"schema": {
"type": "string"
},
- "name": "agentConfigId",
+ "name": "agentPolicyId",
"in": "path",
"required": true
}
],
"get": {
- "summary": "Agent Config - Info",
+ "summary": "Agent policy - Info",
"tags": [],
"responses": {
"200": {
@@ -158,7 +158,7 @@
"type": "object",
"properties": {
"item": {
- "$ref": "#/components/schemas/AgentConfig"
+ "$ref": "#/components/schemas/AgentPolicy"
},
"success": {
"type": "boolean"
@@ -171,11 +171,11 @@
"value": {
"item": {
"id": "82da1fc0-8fbf-11ea-b2ce-01c4a6127154",
- "name": "Default config",
+ "name": "Default policy",
"namespace": "default",
- "description": "Default agent configuration created by Kibana",
+ "description": "Default agent policy created by Kibana",
"status": "active",
- "packageConfigs": [
+ "packagePolicies": [
{
"id": "8a5679b0-8fbf-11ea-b2ce-01c4a6127154",
"name": "system-1",
@@ -186,7 +186,7 @@
"version": "0.0.3"
},
"enabled": true,
- "config_id": "82da1fc0-8fbf-11ea-b2ce-01c4a6127154",
+ "policy_id": "82da1fc0-8fbf-11ea-b2ce-01c4a6127154",
"output_id": "08adc51c-69f3-4294-80e2-24527c6ff73d",
"inputs": [
{
@@ -716,12 +716,12 @@
}
}
},
- "operationId": "agent-config-info",
- "description": "Get one agent config",
+ "operationId": "agent-policy-info",
+ "description": "Get one agent policy",
"parameters": []
},
"put": {
- "summary": "Agent Config - Update",
+ "summary": "Agent policy - Update",
"tags": [],
"responses": {
"200": {
@@ -732,7 +732,7 @@
"type": "object",
"properties": {
"item": {
- "$ref": "#/components/schemas/AgentConfig"
+ "$ref": "#/components/schemas/AgentPolicy"
},
"success": {
"type": "boolean"
@@ -750,7 +750,7 @@
"namespace": "UPDATED namespace",
"updated_on": "Fri Feb 28 2020 16:22:31 GMT-0500 (Eastern Standard Time)",
"updated_by": "elastic",
- "packageConfigs": []
+ "packagePolicies": []
},
"success": true
}
@@ -760,12 +760,12 @@
}
}
},
- "operationId": "put-agent_configs-agentConfigId",
+ "operationId": "put-agent-policy-agentPolicyId",
"requestBody": {
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/NewAgentConfig"
+ "$ref": "#/components/schemas/NewAgentPolicy"
},
"examples": {
"example-1": {
@@ -786,20 +786,20 @@
]
}
},
- "/agent_configs/{agentConfigId}/copy": {
+ "/agent_policies/{agentPolicyId}/copy": {
"parameters": [
{
"schema": {
"type": "string"
},
- "name": "agentConfigId",
+ "name": "agentPolicyId",
"in": "path",
"required": true
}
],
"post": {
- "summary": "Agent config - copy one config",
- "operationId": "agent-config-copy",
+ "summary": "Agent policy - copy one policy",
+ "operationId": "agent-policy-copy",
"responses": {
"200": {
"description": "OK",
@@ -809,7 +809,7 @@
"type": "object",
"properties": {
"item": {
- "$ref": "#/components/schemas/AgentConfig"
+ "$ref": "#/components/schemas/AgentPolicy"
},
"success": {
"type": "boolean"
@@ -841,13 +841,13 @@
},
"description": ""
},
- "description": "Copies one agent config"
+ "description": "Copies one agent policy"
}
},
- "/agent_configs/delete": {
+ "/agent_policies/delete": {
"post": {
- "summary": "Agent Config - Delete",
- "operationId": "post-agent_config-delete",
+ "summary": "Agent policy - Delete",
+ "operationId": "post-agent-policy-delete",
"responses": {
"200": {
"description": "OK",
@@ -896,7 +896,7 @@
"schema": {
"type": "object",
"properties": {
- "agentConfigIds": {
+ "agentPolicyIds": {
"type": "array",
"items": {
"type": "string"
@@ -907,7 +907,7 @@
"examples": {
"example-1": {
"value": {
- "agentConfigIds": ["df7d2540-5a47-11ea-80da-89b5a66da347"]
+ "agentPolicyIds": ["df7d2540-5a47-11ea-80da-89b5a66da347"]
}
}
}
@@ -922,9 +922,9 @@
},
"parameters": []
},
- "/package_configs": {
+ "/package_policies": {
"get": {
- "summary": "PackageConfigs - List",
+ "summary": "PackagePolicies - List",
"tags": [],
"responses": {
"200": {
@@ -937,7 +937,7 @@
"items": {
"type": "array",
"items": {
- "$ref": "#/components/schemas/PackageConfig"
+ "$ref": "#/components/schemas/PackagePolicy"
}
},
"total": {
@@ -1166,14 +1166,14 @@
}
}
},
- "operationId": "get-packageConfigs",
+ "operationId": "get-packagePolicies",
"security": [],
"parameters": []
},
"parameters": [],
"post": {
- "summary": "PackageConfigs - Create",
- "operationId": "post-packageConfigs",
+ "summary": "PackagePolicies - Create",
+ "operationId": "post-packagePolicies",
"responses": {
"200": {
"description": "OK"
@@ -1183,7 +1183,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/NewPackageConfig"
+ "$ref": "#/components/schemas/NewPackagePolicy"
},
"examples": {
"example-1": {
@@ -1237,9 +1237,9 @@
]
}
},
- "/package_configs/{packageConfigId}": {
+ "/package_policies/{packagePolicyId}": {
"get": {
- "summary": "PackageConfigs - Info",
+ "summary": "PackagePolicies - Info",
"tags": [],
"responses": {
"200": {
@@ -1250,7 +1250,7 @@
"type": "object",
"properties": {
"item": {
- "$ref": "#/components/schemas/PackageConfig"
+ "$ref": "#/components/schemas/PackagePolicy"
},
"success": {
"type": "boolean"
@@ -1262,21 +1262,21 @@
}
}
},
- "operationId": "get-packageConfigs-packageConfigId"
+ "operationId": "get-packagePolicies-packagePolicyId"
},
"parameters": [
{
"schema": {
"type": "string"
},
- "name": "packageConfigId",
+ "name": "packagePolicyId",
"in": "path",
"required": true
}
],
"put": {
- "summary": "PackageConfigs - Update",
- "operationId": "put-packageConfigs-packageConfigId",
+ "summary": "PackagePolicies - Update",
+ "operationId": "put-packagePolicies-packagePolicyId",
"responses": {
"200": {
"description": "OK",
@@ -1286,7 +1286,7 @@
"type": "object",
"properties": {
"item": {
- "$ref": "#/components/schemas/PackageConfig"
+ "$ref": "#/components/schemas/PackagePolicy"
},
"sucess": {
"type": "boolean"
@@ -1824,10 +1824,10 @@
"path": "telemetry"
}
],
- "packageConfigs": [
+ "packagePolicies": [
{
"name": "endpoint",
- "title": "Endpoint package config",
+ "title": "Endpoint package policy",
"description": "Interact with the endpoint.",
"inputs": null,
"multiple": false
@@ -2119,7 +2119,7 @@
}
},
{
- "description": "The log package should be used to create package configs for all type of logs for which an package doesn't exist yet.\n",
+ "description": "The log package should be used to create package policies for all type of logs for which an package doesn't exist yet.\n",
"download": "/epr/log/log-0.9.0.tar.gz",
"icons": [
{
@@ -2765,7 +2765,7 @@
{
"id": "205661d0-5e53-11ea-ad31-4f31c06bd9a4",
"active": true,
- "config_id": "ae556400-5e39-11ea-8b49-f9747e466f7b",
+ "policy_id": "ae556400-5e39-11ea-8b49-f9747e466f7b",
"type": "PERMANENT",
"enrolled_at": "2020-03-04T20:02:50.605Z",
"user_provided_metadata": {
@@ -2780,7 +2780,7 @@
},
"actions": [
{
- "data": "{\"config\":{\"id\":\"ae556400-5e39-11ea-8b49-f9747e466f7b\",\"outputs\":{\"default\":{\"type\":\"elasticsearch\",\"hosts\":[\"http://localhost:9200\"],\"api_key\":\"\",\"api_token\":\"6ckkp3ABz7e_XRqr3LM8:gQuDfUNSRgmY0iziYqP9Hw\"}},\"packageConfigs\":[]}}",
+ "data": "{\"config\":{\"id\":\"ae556400-5e39-11ea-8b49-f9747e466f7b\",\"outputs\":{\"default\":{\"type\":\"elasticsearch\",\"hosts\":[\"http://localhost:9200\"],\"api_key\":\"\",\"api_token\":\"6ckkp3ABz7e_XRqr3LM8:gQuDfUNSRgmY0iziYqP9Hw\"}},\"packagePolicies\":[]}}",
"created_at": "2020-03-04T20:02:56.149Z",
"id": "6a95c00a-d76d-4931-97c3-0bf935272d7d",
"type": "CONFIG_CHANGE"
@@ -2965,7 +2965,7 @@
"api_key": "Z-XkgHIBvwtjzIKtSCTh:AejRqdKpQx6z-6dqSI1LHg"
}
},
- "packageConfigs": [
+ "packagePolicies": [
{
"id": "33d6bd70-a5e0-11ea-a587-5f886c8a849f",
"name": "system-1",
@@ -3433,7 +3433,7 @@
"item": {
"id": "8086fb1a-72ca-4a67-8533-09300c1639fa",
"active": true,
- "config_id": "2fe89350-a5e0-11ea-a587-5f886c8a849f",
+ "policy_id": "2fe89350-a5e0-11ea-a587-5f886c8a849f",
"type": "PERMANENT",
"enrolled_at": "2020-06-04T13:03:57.856Z",
"user_provided_metadata": {
@@ -3562,22 +3562,22 @@
}
}
},
- "/fleet/config/{configId}/agent-status": {
- "parameters": [
- {
- "schema": {
- "type": "string"
- },
- "name": "configId",
- "in": "path",
- "required": true
- }
- ],
+ "/fleet/agent-status": {
"get": {
- "summary": "Fleet - Agent - Status for config",
+ "summary": "Fleet - Agent - Status for policy",
"tags": [],
"responses": {},
- "operationId": "get-fleet-config-configId-agent-status"
+ "operationId": "get-fleet-agent-status",
+ "parameters": [
+ {
+ "schema": {
+ "type": "string"
+ },
+ "name": "policyId",
+ "in": "query",
+ "required": false
+ }
+ ]
}
},
"/fleet/enrollment-api-keys": {
@@ -3702,10 +3702,10 @@
},
"components": {
"schemas": {
- "AgentConfig": {
+ "AgentPolicy": {
"allOf": [
{
- "$ref": "#/components/schemas/NewAgentConfig"
+ "$ref": "#/components/schemas/NewAgentPolicy"
},
{
"type": "object",
@@ -3717,7 +3717,7 @@
"type": "string",
"enum": ["active", "inactive"]
},
- "packageConfigs": {
+ "packagePolicies": {
"oneOf": [
{
"items": {
@@ -3726,7 +3726,7 @@
},
{
"items": {
- "$ref": "#/components/schemas/PackageConfig"
+ "$ref": "#/components/schemas/PackagePolicy"
}
}
],
@@ -3750,8 +3750,8 @@
}
]
},
- "PackageConfig": {
- "title": "PackageConfig",
+ "PackagePolicy": {
+ "title": "PackagePolicy",
"allOf": [
{
"type": "object",
@@ -3770,15 +3770,15 @@
"required": ["id", "revision"]
},
{
- "$ref": "#/components/schemas/NewPackageConfig"
+ "$ref": "#/components/schemas/NewPackagePolicy"
}
],
"x-examples": {
"example-1": {}
}
},
- "NewAgentConfig": {
- "title": "NewAgentConfig",
+ "NewAgentPolicy": {
+ "title": "NewAgentPolicy",
"type": "object",
"properties": {
"name": {
@@ -3792,8 +3792,8 @@
}
}
},
- "NewPackageConfig": {
- "title": "NewPackageConfig",
+ "NewPackagePolicy": {
+ "title": "NewPackagePolicy",
"type": "object",
"x-examples": {
"example-1": {
@@ -3892,7 +3892,7 @@
"required": ["type", "enabled", "streams"]
}
},
- "config_id": {
+ "policy_id": {
"type": "string"
},
"name": {
@@ -3902,7 +3902,7 @@
"type": "string"
}
},
- "required": ["output_id", "inputs", "config_id", "name"]
+ "required": ["output_id", "inputs", "policy_id", "name"]
},
"PackageInfo": {
"title": "PackageInfo",
@@ -4140,10 +4140,10 @@
"default_api_key_id": {
"type": "string"
},
- "config_id": {
+ "policy_id": {
"type": "string"
},
- "config_revision": {
+ "policy_revision": {
"type": ["number", "null"]
},
"last_checkin": {
@@ -4221,7 +4221,7 @@
"agent_id": {
"type": "string"
},
- "config_id": {
+ "policy_id": {
"type": "string"
},
"stream_id": {
diff --git a/x-pack/plugins/ingest_manager/common/services/config_to_yaml.ts b/x-pack/plugins/ingest_manager/common/services/full_agent_policy_to_yaml.ts
similarity index 70%
rename from x-pack/plugins/ingest_manager/common/services/config_to_yaml.ts
rename to x-pack/plugins/ingest_manager/common/services/full_agent_policy_to_yaml.ts
index e2e6393738d1f..5b4a3b2dacf7b 100644
--- a/x-pack/plugins/ingest_manager/common/services/config_to_yaml.ts
+++ b/x-pack/plugins/ingest_manager/common/services/full_agent_policy_to_yaml.ts
@@ -4,9 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { safeDump } from 'js-yaml';
-import { FullAgentConfig } from '../types';
+import { FullAgentPolicy } from '../types';
-const CONFIG_KEYS_ORDER = [
+const POLICY_KEYS_ORDER = [
'id',
'name',
'revision',
@@ -21,12 +21,12 @@ const CONFIG_KEYS_ORDER = [
'input',
];
-export const configToYaml = (config: FullAgentConfig): string => {
- return safeDump(config, {
+export const fullAgentPolicyToYaml = (policy: FullAgentPolicy): string => {
+ return safeDump(policy, {
skipInvalid: true,
sortKeys: (keyA: string, keyB: string) => {
- const indexA = CONFIG_KEYS_ORDER.indexOf(keyA);
- const indexB = CONFIG_KEYS_ORDER.indexOf(keyB);
+ const indexA = POLICY_KEYS_ORDER.indexOf(keyA);
+ const indexB = POLICY_KEYS_ORDER.indexOf(keyB);
if (indexA >= 0 && indexB < 0) {
return -1;
}
diff --git a/x-pack/plugins/ingest_manager/common/services/index.ts b/x-pack/plugins/ingest_manager/common/services/index.ts
index 0c91dbbe10354..28fd15b5ea700 100644
--- a/x-pack/plugins/ingest_manager/common/services/index.ts
+++ b/x-pack/plugins/ingest_manager/common/services/index.ts
@@ -5,8 +5,8 @@
*/
export * from './routes';
export * as AgentStatusKueryHelper from './agent_status';
-export { packageToPackageConfigInputs, packageToPackageConfig } from './package_to_config';
-export { storedPackageConfigsToAgentInputs } from './package_configs_to_agent_inputs';
-export { configToYaml } from './config_to_yaml';
-export { isPackageLimited, doesAgentConfigAlreadyIncludePackage } from './limited_package';
+export { packageToPackagePolicyInputs, packageToPackagePolicy } from './package_to_package_policy';
+export { storedPackagePoliciesToAgentInputs } from './package_policies_to_agent_inputs';
+export { fullAgentPolicyToYaml } from './full_agent_policy_to_yaml';
+export { isPackageLimited, doesAgentPolicyAlreadyIncludePackage } from './limited_package';
export { decodeCloudId } from './decode_cloud_id';
diff --git a/x-pack/plugins/ingest_manager/common/services/limited_package.ts b/x-pack/plugins/ingest_manager/common/services/limited_package.ts
index 7ef445d55063c..21d1dbd1556b7 100644
--- a/x-pack/plugins/ingest_manager/common/services/limited_package.ts
+++ b/x-pack/plugins/ingest_manager/common/services/limited_package.ts
@@ -3,21 +3,21 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { PackageInfo, AgentConfig, PackageConfig } from '../types';
+import { PackageInfo, AgentPolicy, PackagePolicy } from '../types';
// Assume packages only ever include 1 config template for now
export const isPackageLimited = (packageInfo: PackageInfo): boolean => {
return packageInfo.config_templates?.[0]?.multiple === false;
};
-export const doesAgentConfigAlreadyIncludePackage = (
- agentConfig: AgentConfig,
+export const doesAgentPolicyAlreadyIncludePackage = (
+ agentPolicy: AgentPolicy,
packageName: string
): boolean => {
- if (agentConfig.package_configs.length && typeof agentConfig.package_configs[0] === 'string') {
- throw new Error('Unable to read full package config information');
+ if (agentPolicy.package_policies.length && typeof agentPolicy.package_policies[0] === 'string') {
+ throw new Error('Unable to read full package policy information');
}
- return (agentConfig.package_configs as PackageConfig[])
- .map((packageConfig) => packageConfig.package?.name || '')
+ return (agentPolicy.package_policies as PackagePolicy[])
+ .map((packagePolicy) => packagePolicy.package?.name || '')
.includes(packageName);
};
diff --git a/x-pack/plugins/ingest_manager/common/services/package_configs_to_agent_inputs.test.ts b/x-pack/plugins/ingest_manager/common/services/package_policies_to_agent_inputs.test.ts
similarity index 80%
rename from x-pack/plugins/ingest_manager/common/services/package_configs_to_agent_inputs.test.ts
rename to x-pack/plugins/ingest_manager/common/services/package_policies_to_agent_inputs.test.ts
index d6c09f058ab85..956423a497373 100644
--- a/x-pack/plugins/ingest_manager/common/services/package_configs_to_agent_inputs.test.ts
+++ b/x-pack/plugins/ingest_manager/common/services/package_policies_to_agent_inputs.test.ts
@@ -3,19 +3,19 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { PackageConfig, PackageConfigInput } from '../types';
-import { storedPackageConfigsToAgentInputs } from './package_configs_to_agent_inputs';
+import { PackagePolicy, PackagePolicyInput } from '../types';
+import { storedPackagePoliciesToAgentInputs } from './package_policies_to_agent_inputs';
-describe('Ingest Manager - storedPackageConfigsToAgentInputs', () => {
- const mockPackageConfig: PackageConfig = {
+describe('Ingest Manager - storedPackagePoliciesToAgentInputs', () => {
+ const mockPackagePolicy: PackagePolicy = {
id: 'some-uuid',
- name: 'mock-package-config',
+ name: 'mock-package-policy',
description: '',
created_at: '',
created_by: '',
updated_at: '',
updated_by: '',
- config_id: '',
+ policy_id: '',
enabled: true,
output_id: '',
namespace: 'default',
@@ -23,7 +23,7 @@ describe('Ingest Manager - storedPackageConfigsToAgentInputs', () => {
revision: 1,
};
- const mockInput: PackageConfigInput = {
+ const mockInput: PackagePolicyInput = {
type: 'test-logs',
enabled: true,
vars: {
@@ -74,13 +74,13 @@ describe('Ingest Manager - storedPackageConfigsToAgentInputs', () => {
],
};
- it('returns no inputs for package config with no inputs, or only disabled inputs', () => {
- expect(storedPackageConfigsToAgentInputs([mockPackageConfig])).toEqual([]);
+ it('returns no inputs for package policy with no inputs, or only disabled inputs', () => {
+ expect(storedPackagePoliciesToAgentInputs([mockPackagePolicy])).toEqual([]);
expect(
- storedPackageConfigsToAgentInputs([
+ storedPackagePoliciesToAgentInputs([
{
- ...mockPackageConfig,
+ ...mockPackagePolicy,
package: {
name: 'mock-package',
title: 'Mock package',
@@ -91,9 +91,9 @@ describe('Ingest Manager - storedPackageConfigsToAgentInputs', () => {
).toEqual([]);
expect(
- storedPackageConfigsToAgentInputs([
+ storedPackagePoliciesToAgentInputs([
{
- ...mockPackageConfig,
+ ...mockPackagePolicy,
inputs: [{ ...mockInput, enabled: false }],
},
])
@@ -102,9 +102,9 @@ describe('Ingest Manager - storedPackageConfigsToAgentInputs', () => {
it('returns agent inputs', () => {
expect(
- storedPackageConfigsToAgentInputs([
+ storedPackagePoliciesToAgentInputs([
{
- ...mockPackageConfig,
+ ...mockPackagePolicy,
package: {
name: 'mock-package',
title: 'Mock package',
@@ -116,7 +116,7 @@ describe('Ingest Manager - storedPackageConfigsToAgentInputs', () => {
).toEqual([
{
id: 'some-uuid',
- name: 'mock-package-config',
+ name: 'mock-package-policy',
type: 'test-logs',
data_stream: { namespace: 'default' },
use_output: 'default',
@@ -144,9 +144,9 @@ describe('Ingest Manager - storedPackageConfigsToAgentInputs', () => {
it('returns agent inputs without disabled streams', () => {
expect(
- storedPackageConfigsToAgentInputs([
+ storedPackagePoliciesToAgentInputs([
{
- ...mockPackageConfig,
+ ...mockPackagePolicy,
inputs: [
{
...mockInput,
@@ -158,7 +158,7 @@ describe('Ingest Manager - storedPackageConfigsToAgentInputs', () => {
).toEqual([
{
id: 'some-uuid',
- name: 'mock-package-config',
+ name: 'mock-package-policy',
type: 'test-logs',
data_stream: { namespace: 'default' },
use_output: 'default',
diff --git a/x-pack/plugins/ingest_manager/common/services/package_configs_to_agent_inputs.ts b/x-pack/plugins/ingest_manager/common/services/package_policies_to_agent_inputs.ts
similarity index 62%
rename from x-pack/plugins/ingest_manager/common/services/package_configs_to_agent_inputs.ts
rename to x-pack/plugins/ingest_manager/common/services/package_policies_to_agent_inputs.ts
index b94fc39e0567c..639f20eb08828 100644
--- a/x-pack/plugins/ingest_manager/common/services/package_configs_to_agent_inputs.ts
+++ b/x-pack/plugins/ingest_manager/common/services/package_policies_to_agent_inputs.ts
@@ -3,29 +3,29 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { PackageConfig, FullAgentConfigInput, FullAgentConfigInputStream } from '../types';
+import { PackagePolicy, FullAgentPolicyInput, FullAgentPolicyInputStream } from '../types';
import { DEFAULT_OUTPUT } from '../constants';
-export const storedPackageConfigsToAgentInputs = (
- packageConfigs: PackageConfig[]
-): FullAgentConfigInput[] => {
- const fullInputs: FullAgentConfigInput[] = [];
+export const storedPackagePoliciesToAgentInputs = (
+ packagePolicies: PackagePolicy[]
+): FullAgentPolicyInput[] => {
+ const fullInputs: FullAgentPolicyInput[] = [];
- packageConfigs.forEach((packageConfig) => {
- if (!packageConfig.enabled || !packageConfig.inputs || !packageConfig.inputs.length) {
+ packagePolicies.forEach((packagePolicy) => {
+ if (!packagePolicy.enabled || !packagePolicy.inputs || !packagePolicy.inputs.length) {
return;
}
- packageConfig.inputs.forEach((input) => {
+ packagePolicy.inputs.forEach((input) => {
if (!input.enabled) {
return;
}
- const fullInput: FullAgentConfigInput = {
- id: packageConfig.id || packageConfig.name,
- name: packageConfig.name,
+ const fullInput: FullAgentPolicyInput = {
+ id: packagePolicy.id || packagePolicy.name,
+ name: packagePolicy.name,
type: input.type,
data_stream: {
- namespace: packageConfig.namespace || 'default',
+ namespace: packagePolicy.namespace || 'default',
},
use_output: DEFAULT_OUTPUT.name,
...Object.entries(input.config || {}).reduce((acc, [key, { value }]) => {
@@ -35,7 +35,7 @@ export const storedPackageConfigsToAgentInputs = (
streams: input.streams
.filter((stream) => stream.enabled)
.map((stream) => {
- const fullStream: FullAgentConfigInputStream = {
+ const fullStream: FullAgentPolicyInputStream = {
id: stream.id,
data_stream: stream.data_stream,
...stream.compiled_stream,
@@ -48,11 +48,11 @@ export const storedPackageConfigsToAgentInputs = (
}),
};
- if (packageConfig.package) {
+ if (packagePolicy.package) {
fullInput.meta = {
package: {
- name: packageConfig.package.name,
- version: packageConfig.package.version,
+ name: packagePolicy.package.name,
+ version: packagePolicy.package.version,
},
};
}
diff --git a/x-pack/plugins/ingest_manager/common/services/package_to_config.test.ts b/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.test.ts
similarity index 88%
rename from x-pack/plugins/ingest_manager/common/services/package_to_config.test.ts
rename to x-pack/plugins/ingest_manager/common/services/package_to_package_policy.test.ts
index 1f4cd43247be1..6c3559d7cc5a0 100644
--- a/x-pack/plugins/ingest_manager/common/services/package_to_config.test.ts
+++ b/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.test.ts
@@ -4,9 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { PackageInfo, InstallationStatus } from '../types';
-import { packageToPackageConfig, packageToPackageConfigInputs } from './package_to_config';
+import { packageToPackagePolicy, packageToPackagePolicyInputs } from './package_to_package_policy';
-describe('Ingest Manager - packageToConfig', () => {
+describe('Ingest Manager - packageToPackagePolicy', () => {
const mockPackage: PackageInfo = {
name: 'mock-package',
title: 'Mock package',
@@ -31,15 +31,15 @@ describe('Ingest Manager - packageToConfig', () => {
status: InstallationStatus.notInstalled,
};
- describe('packageToPackageConfigInputs', () => {
+ describe('packageToPackagePolicyInputs', () => {
it('returns empty array for packages with no config templates', () => {
- expect(packageToPackageConfigInputs(mockPackage)).toEqual([]);
- expect(packageToPackageConfigInputs({ ...mockPackage, config_templates: [] })).toEqual([]);
+ expect(packageToPackagePolicyInputs(mockPackage)).toEqual([]);
+ expect(packageToPackagePolicyInputs({ ...mockPackage, config_templates: [] })).toEqual([]);
});
it('returns empty array for packages with a config template but no inputs', () => {
expect(
- packageToPackageConfigInputs(({
+ packageToPackagePolicyInputs(({
...mockPackage,
config_templates: [{ inputs: [] }],
} as unknown) as PackageInfo)
@@ -48,13 +48,13 @@ describe('Ingest Manager - packageToConfig', () => {
it('returns inputs with no streams for packages with no streams', () => {
expect(
- packageToPackageConfigInputs(({
+ packageToPackagePolicyInputs(({
...mockPackage,
config_templates: [{ inputs: [{ type: 'foo' }] }],
} as unknown) as PackageInfo)
).toEqual([{ type: 'foo', enabled: true, streams: [] }]);
expect(
- packageToPackageConfigInputs(({
+ packageToPackagePolicyInputs(({
...mockPackage,
config_templates: [{ inputs: [{ type: 'foo' }, { type: 'bar' }] }],
} as unknown) as PackageInfo)
@@ -66,7 +66,7 @@ describe('Ingest Manager - packageToConfig', () => {
it('returns inputs with streams for packages with streams', () => {
expect(
- packageToPackageConfigInputs(({
+ packageToPackagePolicyInputs(({
...mockPackage,
datasets: [
{ type: 'logs', name: 'foo', streams: [{ input: 'foo' }] },
@@ -100,7 +100,7 @@ describe('Ingest Manager - packageToConfig', () => {
it('returns inputs with streams configurations for packages with stream vars', () => {
expect(
- packageToPackageConfigInputs(({
+ packageToPackagePolicyInputs(({
...mockPackage,
datasets: [
{
@@ -171,7 +171,7 @@ describe('Ingest Manager - packageToConfig', () => {
it('returns inputs with streams configurations for packages with stream and input vars', () => {
expect(
- packageToPackageConfigInputs(({
+ packageToPackagePolicyInputs(({
...mockPackage,
datasets: [
{
@@ -315,10 +315,10 @@ describe('Ingest Manager - packageToConfig', () => {
});
});
- describe('packageToPackageConfig', () => {
- it('returns package config with default name', () => {
- expect(packageToPackageConfig(mockPackage, '1', '2')).toEqual({
- config_id: '1',
+ describe('packageToPackagePolicy', () => {
+ it('returns package policy with default name', () => {
+ expect(packageToPackagePolicy(mockPackage, '1', '2')).toEqual({
+ policy_id: '1',
namespace: '',
enabled: true,
inputs: [],
@@ -331,13 +331,13 @@ describe('Ingest Manager - packageToConfig', () => {
},
});
});
- it('returns package config with custom name', () => {
- expect(packageToPackageConfig(mockPackage, '1', '2', 'default', 'pkgConfig-1')).toEqual({
- config_id: '1',
+ it('returns package policy with custom name', () => {
+ expect(packageToPackagePolicy(mockPackage, '1', '2', 'default', 'pkgPolicy-1')).toEqual({
+ policy_id: '1',
namespace: 'default',
enabled: true,
inputs: [],
- name: 'pkgConfig-1',
+ name: 'pkgPolicy-1',
output_id: '2',
package: {
name: 'mock-package',
@@ -346,21 +346,21 @@ describe('Ingest Manager - packageToConfig', () => {
},
});
});
- it('returns package config with namespace and description', () => {
+ it('returns package policy with namespace and description', () => {
expect(
- packageToPackageConfig(
+ packageToPackagePolicy(
mockPackage,
'1',
'2',
'mock-namespace',
- 'pkgConfig-1',
+ 'pkgPolicy-1',
'Test description'
)
).toEqual({
- config_id: '1',
+ policy_id: '1',
enabled: true,
inputs: [],
- name: 'pkgConfig-1',
+ name: 'pkgPolicy-1',
namespace: 'mock-namespace',
description: 'Test description',
output_id: '2',
@@ -371,20 +371,20 @@ describe('Ingest Manager - packageToConfig', () => {
},
});
});
- it('returns package config with inputs', () => {
+ it('returns package policy with inputs', () => {
const mockPackageWithConfigTemplates = ({
...mockPackage,
config_templates: [{ inputs: [{ type: 'foo' }] }],
} as unknown) as PackageInfo;
expect(
- packageToPackageConfig(mockPackageWithConfigTemplates, '1', '2', 'default', 'pkgConfig-1')
+ packageToPackagePolicy(mockPackageWithConfigTemplates, '1', '2', 'default', 'pkgPolicy-1')
).toEqual({
- config_id: '1',
+ policy_id: '1',
namespace: 'default',
enabled: true,
inputs: [{ type: 'foo', enabled: true, streams: [] }],
- name: 'pkgConfig-1',
+ name: 'pkgPolicy-1',
output_id: '2',
package: {
name: 'mock-package',
diff --git a/x-pack/plugins/ingest_manager/common/services/package_to_config.ts b/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.ts
similarity index 65%
rename from x-pack/plugins/ingest_manager/common/services/package_to_config.ts
rename to x-pack/plugins/ingest_manager/common/services/package_to_package_policy.ts
index 184b44cb9e530..eab2e8ac2d745 100644
--- a/x-pack/plugins/ingest_manager/common/services/package_to_config.ts
+++ b/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.ts
@@ -8,12 +8,12 @@ import {
RegistryConfigTemplate,
RegistryVarsEntry,
RegistryStream,
- PackageConfig,
- PackageConfigConfigRecord,
- PackageConfigConfigRecordEntry,
- PackageConfigInput,
- PackageConfigInputStream,
- NewPackageConfig,
+ PackagePolicy,
+ PackagePolicyConfigRecord,
+ PackagePolicyConfigRecordEntry,
+ PackagePolicyInput,
+ PackagePolicyInputStream,
+ NewPackagePolicy,
} from '../types';
const getStreamsForInputType = (
@@ -40,27 +40,27 @@ const getStreamsForInputType = (
};
/*
- * This service creates a package config inputs definition from defaults provided in package info
+ * This service creates a package policy inputs definition from defaults provided in package info
*/
-export const packageToPackageConfigInputs = (packageInfo: PackageInfo): PackageConfig['inputs'] => {
- const inputs: PackageConfig['inputs'] = [];
+export const packageToPackagePolicyInputs = (packageInfo: PackageInfo): PackagePolicy['inputs'] => {
+ const inputs: PackagePolicy['inputs'] = [];
- // Assume package will only ever ship one package config template for now
- const packageConfigTemplate: RegistryConfigTemplate | null =
+ // Assume package will only ever ship one package policy template for now
+ const packagePolicyTemplate: RegistryConfigTemplate | null =
packageInfo.config_templates && packageInfo.config_templates[0]
? packageInfo.config_templates[0]
: null;
- // Create package config input property
- if (packageConfigTemplate?.inputs?.length) {
- // Map each package package config input to agent config package config input
- packageConfigTemplate.inputs.forEach((packageInput) => {
+ // Create package policy input property
+ if (packagePolicyTemplate?.inputs?.length) {
+ // Map each package package policy input to agent policy package policy input
+ packagePolicyTemplate.inputs.forEach((packageInput) => {
// Reduces registry var def into config object entry
const varsReducer = (
- configObject: PackageConfigConfigRecord,
+ configObject: PackagePolicyConfigRecord,
registryVar: RegistryVarsEntry
- ): PackageConfigConfigRecord => {
- const configEntry: PackageConfigConfigRecordEntry = {
+ ): PackagePolicyConfigRecord => {
+ const configEntry: PackagePolicyConfigRecordEntry = {
value: !registryVar.default && registryVar.multi ? [] : registryVar.default,
};
if (registryVar.type) {
@@ -70,12 +70,12 @@ export const packageToPackageConfigInputs = (packageInfo: PackageInfo): PackageC
return configObject;
};
- // Map each package input stream into package config input stream
- const streams: PackageConfigInputStream[] = getStreamsForInputType(
+ // Map each package input stream into package policy input stream
+ const streams: PackagePolicyInputStream[] = getStreamsForInputType(
packageInput.type,
packageInfo
).map((packageStream) => {
- const stream: PackageConfigInputStream = {
+ const stream: PackagePolicyInputStream = {
id: `${packageInput.type}-${packageStream.data_stream.dataset}`,
enabled: packageStream.enabled === false ? false : true,
data_stream: packageStream.data_stream,
@@ -86,7 +86,7 @@ export const packageToPackageConfigInputs = (packageInfo: PackageInfo): PackageC
return stream;
});
- const input: PackageConfigInput = {
+ const input: PackagePolicyInput = {
type: packageInput.type,
enabled: streams.length ? !!streams.find((stream) => stream.enabled) : true,
streams,
@@ -104,23 +104,23 @@ export const packageToPackageConfigInputs = (packageInfo: PackageInfo): PackageC
};
/**
- * Builds a `NewPackageConfig` structure based on a package
+ * Builds a `NewPackagePolicy` structure based on a package
*
* @param packageInfo
- * @param configId
+ * @param agentPolicyId
* @param outputId
- * @param packageConfigName
+ * @param packagePolicyName
*/
-export const packageToPackageConfig = (
+export const packageToPackagePolicy = (
packageInfo: PackageInfo,
- configId: string,
+ agentPolicyId: string,
outputId: string,
namespace: string = '',
- packageConfigName?: string,
+ packagePolicyName?: string,
description?: string
-): NewPackageConfig => {
+): NewPackagePolicy => {
return {
- name: packageConfigName || `${packageInfo.name}-1`,
+ name: packagePolicyName || `${packageInfo.name}-1`,
namespace,
description,
package: {
@@ -129,8 +129,8 @@ export const packageToPackageConfig = (
version: packageInfo.version,
},
enabled: true,
- config_id: configId,
+ policy_id: agentPolicyId,
output_id: outputId,
- inputs: packageToPackageConfigInputs(packageInfo),
+ inputs: packageToPackagePolicyInputs(packageInfo),
};
};
diff --git a/x-pack/plugins/ingest_manager/common/services/routes.ts b/x-pack/plugins/ingest_manager/common/services/routes.ts
index 49de9a4d8fd85..1d802739a1b86 100644
--- a/x-pack/plugins/ingest_manager/common/services/routes.ts
+++ b/x-pack/plugins/ingest_manager/common/services/routes.ts
@@ -6,8 +6,8 @@
import {
EPM_API_ROOT,
EPM_API_ROUTES,
- PACKAGE_CONFIG_API_ROUTES,
- AGENT_CONFIG_API_ROUTES,
+ PACKAGE_POLICY_API_ROUTES,
+ AGENT_POLICY_API_ROUTES,
DATA_STREAM_API_ROUTES,
FLEET_SETUP_API_ROUTES,
AGENT_API_ROUTES,
@@ -48,61 +48,61 @@ export const epmRouteService = {
},
};
-export const packageConfigRouteService = {
+export const packagePolicyRouteService = {
getListPath: () => {
- return PACKAGE_CONFIG_API_ROUTES.LIST_PATTERN;
+ return PACKAGE_POLICY_API_ROUTES.LIST_PATTERN;
},
- getInfoPath: (packageConfigId: string) => {
- return PACKAGE_CONFIG_API_ROUTES.INFO_PATTERN.replace('{packageConfigId}', packageConfigId);
+ getInfoPath: (packagePolicyId: string) => {
+ return PACKAGE_POLICY_API_ROUTES.INFO_PATTERN.replace('{packagePolicyId}', packagePolicyId);
},
getCreatePath: () => {
- return PACKAGE_CONFIG_API_ROUTES.CREATE_PATTERN;
+ return PACKAGE_POLICY_API_ROUTES.CREATE_PATTERN;
},
- getUpdatePath: (packageConfigId: string) => {
- return PACKAGE_CONFIG_API_ROUTES.UPDATE_PATTERN.replace('{packageConfigId}', packageConfigId);
+ getUpdatePath: (packagePolicyId: string) => {
+ return PACKAGE_POLICY_API_ROUTES.UPDATE_PATTERN.replace('{packagePolicyId}', packagePolicyId);
},
getDeletePath: () => {
- return PACKAGE_CONFIG_API_ROUTES.DELETE_PATTERN;
+ return PACKAGE_POLICY_API_ROUTES.DELETE_PATTERN;
},
};
-export const agentConfigRouteService = {
+export const agentPolicyRouteService = {
getListPath: () => {
- return AGENT_CONFIG_API_ROUTES.LIST_PATTERN;
+ return AGENT_POLICY_API_ROUTES.LIST_PATTERN;
},
- getInfoPath: (agentConfigId: string) => {
- return AGENT_CONFIG_API_ROUTES.INFO_PATTERN.replace('{agentConfigId}', agentConfigId);
+ getInfoPath: (agentPolicyId: string) => {
+ return AGENT_POLICY_API_ROUTES.INFO_PATTERN.replace('{agentPolicyId}', agentPolicyId);
},
getCreatePath: () => {
- return AGENT_CONFIG_API_ROUTES.CREATE_PATTERN;
+ return AGENT_POLICY_API_ROUTES.CREATE_PATTERN;
},
- getUpdatePath: (agentConfigId: string) => {
- return AGENT_CONFIG_API_ROUTES.UPDATE_PATTERN.replace('{agentConfigId}', agentConfigId);
+ getUpdatePath: (agentPolicyId: string) => {
+ return AGENT_POLICY_API_ROUTES.UPDATE_PATTERN.replace('{agentPolicyId}', agentPolicyId);
},
- getCopyPath: (agentConfigId: string) => {
- return AGENT_CONFIG_API_ROUTES.COPY_PATTERN.replace('{agentConfigId}', agentConfigId);
+ getCopyPath: (agentPolicyId: string) => {
+ return AGENT_POLICY_API_ROUTES.COPY_PATTERN.replace('{agentPolicyId}', agentPolicyId);
},
getDeletePath: () => {
- return AGENT_CONFIG_API_ROUTES.DELETE_PATTERN;
+ return AGENT_POLICY_API_ROUTES.DELETE_PATTERN;
},
- getInfoFullPath: (agentConfigId: string) => {
- return AGENT_CONFIG_API_ROUTES.FULL_INFO_PATTERN.replace('{agentConfigId}', agentConfigId);
+ getInfoFullPath: (agentPolicyId: string) => {
+ return AGENT_POLICY_API_ROUTES.FULL_INFO_PATTERN.replace('{agentPolicyId}', agentPolicyId);
},
- getInfoFullDownloadPath: (agentConfigId: string) => {
- return AGENT_CONFIG_API_ROUTES.FULL_INFO_DOWNLOAD_PATTERN.replace(
- '{agentConfigId}',
- agentConfigId
+ getInfoFullDownloadPath: (agentPolicyId: string) => {
+ return AGENT_POLICY_API_ROUTES.FULL_INFO_DOWNLOAD_PATTERN.replace(
+ '{agentPolicyId}',
+ agentPolicyId
);
},
};
diff --git a/x-pack/plugins/ingest_manager/common/types/index.ts b/x-pack/plugins/ingest_manager/common/types/index.ts
index 69bcc498c18be..cafd0f03f66e2 100644
--- a/x-pack/plugins/ingest_manager/common/types/index.ts
+++ b/x-pack/plugins/ingest_manager/common/types/index.ts
@@ -22,8 +22,8 @@ export interface IngestManagerConfigType {
host?: string;
ca_sha256?: string;
};
- agentConfigRolloutRateLimitIntervalMs: number;
- agentConfigRolloutRateLimitRequestPerInterval: number;
+ agentPolicyRolloutRateLimitIntervalMs: number;
+ agentPolicyRolloutRateLimitRequestPerInterval: number;
};
}
diff --git a/x-pack/plugins/ingest_manager/common/types/models/agent.ts b/x-pack/plugins/ingest_manager/common/types/models/agent.ts
index f31d33e73c76f..2b8a306577e7d 100644
--- a/x-pack/plugins/ingest_manager/common/types/models/agent.ts
+++ b/x-pack/plugins/ingest_manager/common/types/models/agent.ts
@@ -64,7 +64,7 @@ export interface NewAgentEvent {
payload?: any;
agent_id: string;
action_id?: string;
- config_id?: string;
+ policy_id?: string;
stream_id?: string;
}
@@ -89,8 +89,8 @@ interface AgentBase {
access_api_key_id?: string;
default_api_key?: string;
default_api_key_id?: string;
- config_id?: string;
- config_revision?: number | null;
+ policy_id?: string;
+ policy_revision?: number | null;
last_checkin?: string;
last_checkin_status?: 'error' | 'online' | 'degraded';
user_provided_metadata: AgentMetadata;
diff --git a/x-pack/plugins/ingest_manager/common/types/models/agent_config.ts b/x-pack/plugins/ingest_manager/common/types/models/agent_policy.ts
similarity index 65%
rename from x-pack/plugins/ingest_manager/common/types/models/agent_config.ts
rename to x-pack/plugins/ingest_manager/common/types/models/agent_policy.ts
index cdaea1cc5f9a4..c626c85d3fb24 100644
--- a/x-pack/plugins/ingest_manager/common/types/models/agent_config.ts
+++ b/x-pack/plugins/ingest_manager/common/types/models/agent_policy.ts
@@ -3,15 +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 { PackageConfig, PackageConfigPackage } from './package_config';
+import { PackagePolicy, PackagePolicyPackage } from './package_policy';
import { Output } from './output';
-export enum AgentConfigStatus {
+export enum AgentPolicyStatus {
Active = 'active',
Inactive = 'inactive',
}
-export interface NewAgentConfig {
+export interface NewAgentPolicy {
name: string;
namespace: string;
description?: string;
@@ -19,18 +19,18 @@ export interface NewAgentConfig {
monitoring_enabled?: Array<'logs' | 'metrics'>;
}
-export interface AgentConfig extends NewAgentConfig {
+export interface AgentPolicy extends NewAgentPolicy {
id: string;
- status: AgentConfigStatus;
- package_configs: string[] | PackageConfig[];
+ status: AgentPolicyStatus;
+ package_policies: string[] | PackagePolicy[];
updated_at: string;
updated_by: string;
revision: number;
}
-export type AgentConfigSOAttributes = Omit;
+export type AgentPolicySOAttributes = Omit;
-export interface FullAgentConfigInputStream {
+export interface FullAgentPolicyInputStream {
id: string;
data_stream: {
dataset: string;
@@ -39,28 +39,28 @@ export interface FullAgentConfigInputStream {
[key: string]: any;
}
-export interface FullAgentConfigInput {
+export interface FullAgentPolicyInput {
id: string;
name: string;
type: string;
data_stream: { namespace: string };
use_output: string;
meta?: {
- package?: Pick;
+ package?: Pick;
[key: string]: unknown;
};
- streams: FullAgentConfigInputStream[];
+ streams: FullAgentPolicyInputStream[];
[key: string]: any;
}
-export interface FullAgentConfig {
+export interface FullAgentPolicy {
id: string;
outputs: {
[key: string]: Pick & {
[key: string]: any;
};
};
- inputs: FullAgentConfigInput[];
+ inputs: FullAgentPolicyInput[];
revision?: number;
agent?: {
monitoring: {
diff --git a/x-pack/plugins/ingest_manager/common/types/models/enrollment_api_key.ts b/x-pack/plugins/ingest_manager/common/types/models/enrollment_api_key.ts
index 204ce4b15ea5b..f39076ce1027b 100644
--- a/x-pack/plugins/ingest_manager/common/types/models/enrollment_api_key.ts
+++ b/x-pack/plugins/ingest_manager/common/types/models/enrollment_api_key.ts
@@ -3,7 +3,6 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { SavedObjectAttributes } from 'src/core/public';
export interface EnrollmentAPIKey {
id: string;
@@ -11,13 +10,8 @@ export interface EnrollmentAPIKey {
api_key: string;
name?: string;
active: boolean;
- config_id?: string;
+ policy_id?: string;
+ created_at: string;
}
-export interface EnrollmentAPIKeySOAttributes extends SavedObjectAttributes {
- api_key_id: string;
- api_key: string;
- name?: string;
- active: boolean;
- config_id?: string;
-}
+export type EnrollmentAPIKeySOAttributes = Omit;
diff --git a/x-pack/plugins/ingest_manager/common/types/models/index.ts b/x-pack/plugins/ingest_manager/common/types/models/index.ts
index 8ad716a4ba768..ad4c6ad02639e 100644
--- a/x-pack/plugins/ingest_manager/common/types/models/index.ts
+++ b/x-pack/plugins/ingest_manager/common/types/models/index.ts
@@ -5,8 +5,8 @@
*/
export * from './agent';
-export * from './agent_config';
-export * from './package_config';
+export * from './agent_policy';
+export * from './package_policy';
export * from './data_stream';
export * from './output';
export * from './epm';
diff --git a/x-pack/plugins/ingest_manager/common/types/models/package_config.ts b/x-pack/plugins/ingest_manager/common/types/models/package_config.ts
deleted file mode 100644
index 635afbd47850e..0000000000000
--- a/x-pack/plugins/ingest_manager/common/types/models/package_config.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-export interface PackageConfigPackage {
- name: string;
- title: string;
- version: string;
-}
-
-export interface PackageConfigConfigRecordEntry {
- type?: string;
- value?: any;
-}
-
-export type PackageConfigConfigRecord = Record;
-
-export interface NewPackageConfigInputStream {
- id: string;
- enabled: boolean;
- data_stream: {
- dataset: string;
- type: string;
- };
- vars?: PackageConfigConfigRecord;
- config?: PackageConfigConfigRecord;
-}
-
-export interface PackageConfigInputStream extends NewPackageConfigInputStream {
- compiled_stream?: any;
-}
-
-export interface NewPackageConfigInput {
- type: string;
- enabled: boolean;
- vars?: PackageConfigConfigRecord;
- config?: PackageConfigConfigRecord;
- streams: NewPackageConfigInputStream[];
-}
-
-export interface PackageConfigInput extends Omit {
- streams: PackageConfigInputStream[];
-}
-
-export interface NewPackageConfig {
- name: string;
- description?: string;
- namespace: string;
- enabled: boolean;
- config_id: string;
- output_id: string;
- package?: PackageConfigPackage;
- inputs: NewPackageConfigInput[];
-}
-
-export interface UpdatePackageConfig extends NewPackageConfig {
- version?: string;
-}
-
-export interface PackageConfig extends Omit {
- id: string;
- inputs: PackageConfigInput[];
- version?: string;
- revision: number;
- updated_at: string;
- updated_by: string;
- created_at: string;
- created_by: string;
-}
-
-export type PackageConfigSOAttributes = Omit;
diff --git a/x-pack/plugins/ingest_manager/common/types/models/package_policy.ts b/x-pack/plugins/ingest_manager/common/types/models/package_policy.ts
new file mode 100644
index 0000000000000..724dbae5dac85
--- /dev/null
+++ b/x-pack/plugins/ingest_manager/common/types/models/package_policy.ts
@@ -0,0 +1,73 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export interface PackagePolicyPackage {
+ name: string;
+ title: string;
+ version: string;
+}
+
+export interface PackagePolicyConfigRecordEntry {
+ type?: string;
+ value?: any;
+}
+
+export type PackagePolicyConfigRecord = Record;
+
+export interface NewPackagePolicyInputStream {
+ id: string;
+ enabled: boolean;
+ data_stream: {
+ dataset: string;
+ type: string;
+ };
+ vars?: PackagePolicyConfigRecord;
+ config?: PackagePolicyConfigRecord;
+}
+
+export interface PackagePolicyInputStream extends NewPackagePolicyInputStream {
+ compiled_stream?: any;
+}
+
+export interface NewPackagePolicyInput {
+ type: string;
+ enabled: boolean;
+ vars?: PackagePolicyConfigRecord;
+ config?: PackagePolicyConfigRecord;
+ streams: NewPackagePolicyInputStream[];
+}
+
+export interface PackagePolicyInput extends Omit {
+ streams: PackagePolicyInputStream[];
+}
+
+export interface NewPackagePolicy {
+ name: string;
+ description?: string;
+ namespace: string;
+ enabled: boolean;
+ policy_id: string;
+ output_id: string;
+ package?: PackagePolicyPackage;
+ inputs: NewPackagePolicyInput[];
+}
+
+export interface UpdatePackagePolicy extends NewPackagePolicy {
+ version?: string;
+}
+
+export interface PackagePolicy extends Omit {
+ id: string;
+ inputs: PackagePolicyInput[];
+ version?: string;
+ revision: number;
+ updated_at: string;
+ updated_by: string;
+ created_at: string;
+ created_by: string;
+}
+
+export type PackagePolicySOAttributes = Omit;
diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts
index 7ec5a8d68311f..4a50938049a7b 100644
--- a/x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts
+++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts
@@ -118,7 +118,7 @@ export interface PutAgentReassignRequest {
params: {
agentId: string;
};
- body: { config_id: string };
+ body: { policy_id: string };
}
export interface PutAgentReassignResponse {
@@ -161,7 +161,7 @@ export interface UpdateAgentRequest {
export interface GetAgentStatusRequest {
query: {
- configId?: string;
+ policyId?: string;
};
}
diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/agent_config.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/agent_config.ts
deleted file mode 100644
index 4e1612d144ede..0000000000000
--- a/x-pack/plugins/ingest_manager/common/types/rest_spec/agent_config.ts
+++ /dev/null
@@ -1,83 +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 { AgentConfig, NewAgentConfig, FullAgentConfig } from '../models';
-import { ListWithKuery } from './common';
-
-export interface GetAgentConfigsRequest {
- query: ListWithKuery & {
- full?: boolean;
- };
-}
-
-export type GetAgentConfigsResponseItem = AgentConfig & { agents?: number };
-
-export interface GetAgentConfigsResponse {
- items: GetAgentConfigsResponseItem[];
- total: number;
- page: number;
- perPage: number;
- success: boolean;
-}
-
-export interface GetOneAgentConfigRequest {
- params: {
- agentConfigId: string;
- };
-}
-
-export interface GetOneAgentConfigResponse {
- item: AgentConfig;
- success: boolean;
-}
-
-export interface CreateAgentConfigRequest {
- body: NewAgentConfig;
-}
-
-export interface CreateAgentConfigResponse {
- item: AgentConfig;
- success: boolean;
-}
-
-export type UpdateAgentConfigRequest = GetOneAgentConfigRequest & {
- body: NewAgentConfig;
-};
-
-export interface UpdateAgentConfigResponse {
- item: AgentConfig;
- success: boolean;
-}
-
-export interface CopyAgentConfigRequest {
- body: Pick;
-}
-
-export interface CopyAgentConfigResponse {
- item: AgentConfig;
- success: boolean;
-}
-
-export interface DeleteAgentConfigRequest {
- body: {
- agentConfigId: string;
- };
-}
-
-export interface DeleteAgentConfigResponse {
- id: string;
- success: boolean;
-}
-
-export interface GetFullAgentConfigRequest {
- params: {
- agentConfigId: string;
- };
-}
-
-export interface GetFullAgentConfigResponse {
- item: FullAgentConfig;
- success: boolean;
-}
diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/agent_policy.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/agent_policy.ts
new file mode 100644
index 0000000000000..4683727ed51ac
--- /dev/null
+++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/agent_policy.ts
@@ -0,0 +1,83 @@
+/*
+ * 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 { AgentPolicy, NewAgentPolicy, FullAgentPolicy } from '../models';
+import { ListWithKuery } from './common';
+
+export interface GetAgentPoliciesRequest {
+ query: ListWithKuery & {
+ full?: boolean;
+ };
+}
+
+export type GetAgentPoliciesResponseItem = AgentPolicy & { agents?: number };
+
+export interface GetAgentPoliciesResponse {
+ items: GetAgentPoliciesResponseItem[];
+ total: number;
+ page: number;
+ perPage: number;
+ success: boolean;
+}
+
+export interface GetOneAgentPolicyRequest {
+ params: {
+ agentPolicyId: string;
+ };
+}
+
+export interface GetOneAgentPolicyResponse {
+ item: AgentPolicy;
+ success: boolean;
+}
+
+export interface CreateAgentPolicyRequest {
+ body: NewAgentPolicy;
+}
+
+export interface CreateAgentPolicyResponse {
+ item: AgentPolicy;
+ success: boolean;
+}
+
+export type UpdateAgentPolicyRequest = GetOneAgentPolicyRequest & {
+ body: NewAgentPolicy;
+};
+
+export interface UpdateAgentPolicyResponse {
+ item: AgentPolicy;
+ success: boolean;
+}
+
+export interface CopyAgentPolicyRequest {
+ body: Pick;
+}
+
+export interface CopyAgentPolicyResponse {
+ item: AgentPolicy;
+ success: boolean;
+}
+
+export interface DeleteAgentPolicyRequest {
+ body: {
+ agentPolicyId: string;
+ };
+}
+
+export interface DeleteAgentPolicyResponse {
+ id: string;
+ success: boolean;
+}
+
+export interface GetFullAgentPolicyRequest {
+ params: {
+ agentPolicyId: string;
+ };
+}
+
+export interface GetFullAgentPolicyResponse {
+ item: FullAgentPolicy;
+ success: boolean;
+}
diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/enrollment_api_key.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/enrollment_api_key.ts
index 851e6571c0dd2..1929955cac84c 100644
--- a/x-pack/plugins/ingest_manager/common/types/rest_spec/enrollment_api_key.ts
+++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/enrollment_api_key.ts
@@ -47,7 +47,7 @@ export interface DeleteEnrollmentAPIKeyResponse {
export interface PostEnrollmentAPIKeyRequest {
body: {
name?: string;
- config_id: string;
+ policy_id: string;
expiration?: string;
};
}
diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/index.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/index.ts
index c40940fdbb623..2916347693b98 100644
--- a/x-pack/plugins/ingest_manager/common/types/rest_spec/index.ts
+++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/index.ts
@@ -4,10 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
export * from './common';
-export * from './package_config';
+export * from './package_policy';
export * from './data_stream';
export * from './agent';
-export * from './agent_config';
+export * from './agent_policy';
export * from './fleet_setup';
export * from './epm';
export * from './enrollment_api_key';
diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/package_config.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/package_config.ts
deleted file mode 100644
index e62645debb748..0000000000000
--- a/x-pack/plugins/ingest_manager/common/types/rest_spec/package_config.ts
+++ /dev/null
@@ -1,59 +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 { PackageConfig, NewPackageConfig, UpdatePackageConfig } from '../models';
-
-export interface GetPackageConfigsRequest {
- query: {
- page: number;
- perPage: number;
- kuery?: string;
- };
-}
-
-export interface GetPackageConfigsResponse {
- items: PackageConfig[];
- total: number;
- page: number;
- perPage: number;
- success: boolean;
-}
-
-export interface GetOnePackageConfigRequest {
- params: {
- packageConfigId: string;
- };
-}
-
-export interface GetOnePackageConfigResponse {
- item: PackageConfig;
- success: boolean;
-}
-
-export interface CreatePackageConfigRequest {
- body: NewPackageConfig;
-}
-
-export interface CreatePackageConfigResponse {
- item: PackageConfig;
- success: boolean;
-}
-
-export type UpdatePackageConfigRequest = GetOnePackageConfigRequest & {
- body: UpdatePackageConfig;
-};
-
-export type UpdatePackageConfigResponse = CreatePackageConfigResponse;
-
-export interface DeletePackageConfigsRequest {
- body: {
- packageConfigIds: string[];
- };
-}
-
-export type DeletePackageConfigsResponse = Array<{
- id: string;
- success: boolean;
-}>;
diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/package_policy.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/package_policy.ts
new file mode 100644
index 0000000000000..e5bba3d6deab3
--- /dev/null
+++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/package_policy.ts
@@ -0,0 +1,59 @@
+/*
+ * 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 { PackagePolicy, NewPackagePolicy, UpdatePackagePolicy } from '../models';
+
+export interface GetPackagePoliciesRequest {
+ query: {
+ page: number;
+ perPage: number;
+ kuery?: string;
+ };
+}
+
+export interface GetPackagePoliciesResponse {
+ items: PackagePolicy[];
+ total: number;
+ page: number;
+ perPage: number;
+ success: boolean;
+}
+
+export interface GetOnePackagePolicyRequest {
+ params: {
+ packagePolicyId: string;
+ };
+}
+
+export interface GetOnePackagePolicyResponse {
+ item: PackagePolicy;
+ success: boolean;
+}
+
+export interface CreatePackagePolicyRequest {
+ body: NewPackagePolicy;
+}
+
+export interface CreatePackagePolicyResponse {
+ item: PackagePolicy;
+ success: boolean;
+}
+
+export type UpdatePackagePolicyRequest = GetOnePackagePolicyRequest & {
+ body: UpdatePackagePolicy;
+};
+
+export type UpdatePackagePolicyResponse = CreatePackagePolicyResponse;
+
+export interface DeletePackagePoliciesRequest {
+ body: {
+ packagePolicyIds: string[];
+ };
+}
+
+export type DeletePackagePoliciesResponse = Array<{
+ id: string;
+ success: boolean;
+}>;
diff --git a/x-pack/plugins/ingest_manager/dev_docs/actions_and_events.md b/x-pack/plugins/ingest_manager/dev_docs/actions_and_events.md
index b41cdc221c51a..f98a1c281e3ee 100644
--- a/x-pack/plugins/ingest_manager/dev_docs/actions_and_events.md
+++ b/x-pack/plugins/ingest_manager/dev_docs/actions_and_events.md
@@ -17,7 +17,7 @@ This action is send when a new policy is available, the policy is available unde
"id": "action_id_1",
"data": {
"policy": {
- "id": "config_id",
+ "id": "policy_id",
"outputs": {
"default": {
"api_key": "slfhsdlfhjjkshfkjh:sdfsdfsdfsdf",
diff --git a/x-pack/plugins/ingest_manager/dev_docs/api/agents_enroll.md b/x-pack/plugins/ingest_manager/dev_docs/api/agents_enroll.md
index 304ce733b7dcd..b32e6873c60e5 100644
--- a/x-pack/plugins/ingest_manager/dev_docs/api/agents_enroll.md
+++ b/x-pack/plugins/ingest_manager/dev_docs/api/agents_enroll.md
@@ -45,7 +45,7 @@ The API returns the following:
"item": {
"id": "a4937110-e53e-11e9-934f-47a8e38a522c",
"active": true,
- "config_id": "default",
+ "policy_id": "default",
"type": "PERMANENT",
"enrolled_at": "2019-10-02T18:01:22.337Z",
"user_provided_metadata": {},
diff --git a/x-pack/plugins/ingest_manager/dev_docs/definitions.md b/x-pack/plugins/ingest_manager/dev_docs/definitions.md
index bd20780611055..be5aeb923e903 100644
--- a/x-pack/plugins/ingest_manager/dev_docs/definitions.md
+++ b/x-pack/plugins/ingest_manager/dev_docs/definitions.md
@@ -5,12 +5,12 @@ Overall documentation of Ingest Management is now maintained in the `elastic/sta
This section is to define terms used across ingest management.
-## Package Config
+## Package policy
-A package config is a definition on how to collect data from a service, for example `nginx`. A package config contains
+A package policy is a definition on how to collect data from a service, for example `nginx`. A package policy contains
definitions for one or multiple inputs and each input can contain one or multiple streams.
-With the example of the nginx Package Config, it contains two inputs: `logs` and `nginx/metrics`. Logs and metrics are collected
+With the example of the nginx Package policy, it contains two inputs: `logs` and `nginx/metrics`. Logs and metrics are collected
differently. The `logs` input contains two streams, `access` and `error`, the `nginx/metrics` input contains the stubstatus stream.
## Data Stream
@@ -20,7 +20,7 @@ ingesting data and the setup of Elasticsearch.
## Elastic Agent
-A single, unified agent that users can deploy to hosts or containers. It controls which data is collected from the host or containers and where the data is sent. It will run Beats, Endpoint or other monitoring programs as needed. It can operate standalone or pull a configuration policy from Fleet.
+A single, unified agent that users can deploy to hosts or containers. It controls which data is collected from the host or containers and where the data is sent. It will run Beats, Endpoint or other monitoring programs as needed. It can operate standalone or pull an agent policy from Fleet.
## Elastic Package Registry
@@ -29,8 +29,7 @@ More details about the registry can be found [here](https://github.com/elastic/p
## Fleet
-Fleet is the part of the Ingest Manager UI in Kibana that handles the part of enrolling Elastic Agents,
-managing agents and sending configurations to the Elastic Agent.
+Fleet is the part of the Ingest Manager UI in Kibana that handles the part of enrolling Elastic Agents, managing agents and sending policies to the Elastic Agent.
## Indexing Strategy
@@ -40,15 +39,15 @@ the index strategy is sent to Data Streams.
## Input
-An input is the configuration unit in an Agent Config that defines the options on how to collect data from
+An input is the configuration unit in an Agent policy that defines the options on how to collect data from
an endpoint. This could be username / password which are need to authenticate with a service or a host url
as an example.
-An input is part of a Package Config and contains streams.
+An input is part of a Package policy and contains streams.
## Integration
-An integration is a package with the type integration. An integration package has at least 1 package config
+An integration is a package with the type integration. An integration package has at least 1 package policy
and usually collects data from / about a service.
## Namespace
@@ -60,9 +59,9 @@ A user-specified string that will be used to part of the index name in Elasticse
A package contains all the assets for the Elastic Stack. A more detailed definition of a
package can be found under https://github.com/elastic/package-registry.
-Besides the assets, a package contains the package config definitions with its inputs and streams.
+Besides the assets, a package contains the package policy definitions with its inputs and streams.
## Stream
-A stream is a configuration unit in the Elastic Agent config. A stream is part of an input and defines how the data
+A stream is a configuration unit in the Elastic Agent policy. A stream is part of an input and defines how the data
fetched by this input should be processed and which Data Stream to send it to.
diff --git a/x-pack/plugins/ingest_manager/dev_docs/epm.md b/x-pack/plugins/ingest_manager/dev_docs/epm.md
index abcab718f7ab9..20209d09e6cc2 100644
--- a/x-pack/plugins/ingest_manager/dev_docs/epm.md
+++ b/x-pack/plugins/ingest_manager/dev_docs/epm.md
@@ -5,26 +5,26 @@ Overall documentation of Ingest Management is now maintained in the `elastic/sta
When upgrading a package between a bugfix or a minor version, no breaking changes should happen. Upgrading a package has the following effect:
-* Removal of existing dashboards
-* Installation of new dashboards
-* Write new ingest pipelines with the version
-* Write new Elasticsearch alias templates
-* Trigger a rollover for all the affected indices
+- Removal of existing dashboards
+- Installation of new dashboards
+- Write new ingest pipelines with the version
+- Write new Elasticsearch alias templates
+- Trigger a rollover for all the affected indices
-The new ingest pipeline is expected to still work with the data coming from older configurations. In most cases this means some of the fields can be missing. For this to work, each event must contain the version of config / package it is coming from to make such a decision.
+The new ingest pipeline is expected to still work with the data coming from older policies. In most cases this means some of the fields can be missing. For this to work, each event must contain the version of policy / package it is coming from to make such a decision.
In case of a breaking change in the data structure, the new ingest pipeline is also expected to deal with this change. In case there are breaking changes which cannot be dealt with in an ingest pipeline, a new package has to be created.
-Each package lists its minimal required agent version. In case there are agents enrolled with an older version, the user is notified to upgrade these agents as otherwise the new configs cannot be rolled out.
+Each package lists its minimal required agent version. In case there are agents enrolled with an older version, the user is notified to upgrade these agents as otherwise the new policies cannot be rolled out.
# Generated assets
-When a package is installed or upgraded, certain Kibana and Elasticsearch assets are generated from . These follow the naming conventions explained above (see "indexing strategy") and contain configuration for the elastic stack that makes ingesting and displaying data work with as little user interaction as possible.
+When a package is installed or upgraded, certain Kibana and Elasticsearch assets are generated from . These follow the naming conventions explained above (see "indexing strategy") and contain configuration for the Elastic stack that makes ingesting and displaying data work with as little user interaction as possible.
## Elasticsearch Index Templates
### Generation
-* Index templates are generated from `YAML` files contained in the package.
-* There is one index template per dataset.
-* For the generation of an index template, all `yml` files contained in the package subdirectory `dataset/DATASET_NAME/fields/` are used.
\ No newline at end of file
+- Index templates are generated from `YAML` files contained in the package.
+- There is one index template per dataset.
+- For the generation of an index template, all `yml` files contained in the package subdirectory `dataset/DATASET_NAME/fields/` are used.
diff --git a/x-pack/plugins/ingest_manager/dev_docs/fleet_agents_interactions_detailed.md b/x-pack/plugins/ingest_manager/dev_docs/fleet_agents_interactions_detailed.md
index d563712fdf0a6..52a1dde37cd07 100644
--- a/x-pack/plugins/ingest_manager/dev_docs/fleet_agents_interactions_detailed.md
+++ b/x-pack/plugins/ingest_manager/dev_docs/fleet_agents_interactions_detailed.md
@@ -5,7 +5,7 @@
Fleet workflow:
- an agent enroll to fleet using an enrollment token.
-- Every n seconds agent is polling the checkin API to send events and check for new configuration
+- Every n seconds agent is polling the checkin API to send events and check for new policy
### Agent enrollment
@@ -13,7 +13,7 @@ An agent must enroll using the REST Api provided by fleet.
When an agent enroll Fleet:
- verify the Enrollment token is a valid ES API key
-- retrieve the Saved Object (SO) associated to this api key id (this SO contains the configuration|policy id)
+- retrieve the Saved Object (SO) associated to this api key id (this SO contains the agent policy id)
- create an ES ApiKey unique to the agent for accessing kibana during checkin
- create an ES ApiKey per output to send logs and metrics to the output
- Save the new agent in a SO with keys encrypted inside the agent SO object
@@ -22,15 +22,15 @@ When an agent enroll Fleet:
### Agent checkin
-Agent are going to poll the checkin API to send events and check for new configration. To checkin agent are going to use the REST Api provided by fleet.
+Agent are going to poll the checkin API to send events and check for new policy. To checkin agent are going to use the REST Api provided by fleet.
When an agent checkin fleet:
- verify the access token is a valid ES API key
- retrieve the agent (SO associated to this api key id)
- Insert events SO
-- If the Agent configuration has been updated since last checkin
- - generate the agent config
+- If the Agent policy has been updated since last checkin
+ - generate the agent policy
- Create the missing API key for agent -> ES communication
- Save the new agent (with last checkin date) in a SavedObject with keys encrypted inside the agent
@@ -44,6 +44,6 @@ An agent can acknowledge one or multiple actions by calling `POST /api/ingest_ma
## Other interactions
-### Agent Configuration update
+### Agent policy update
-When a configuration is updated, every SO agent running this configuration is updated with a timestamp of the latest config.
+When a policy is updated, every SO agent running this policy is updated with a timestamp of the latest policy.
diff --git a/x-pack/plugins/ingest_manager/dev_docs/schema/agent_checkin.mml b/x-pack/plugins/ingest_manager/dev_docs/schema/agent_checkin.mml
index a5332c50ab947..377c6a30fa230 100644
--- a/x-pack/plugins/ingest_manager/dev_docs/schema/agent_checkin.mml
+++ b/x-pack/plugins/ingest_manager/dev_docs/schema/agent_checkin.mml
@@ -12,8 +12,8 @@ sequenceDiagram
Note right of SavedObjects: encrypted SO here
end
- alt If configuration updated since last checkin
- Fleet API->>SavedObjects: getAgentConfiguration(configId)
+ alt If policy updated since last checkin
+ Fleet API->>SavedObjects: getAgentPolicy(agentPolicyId)
opt If there is not API Key for default output
Fleet API->>Elasticsearch: createAgentESApiKey()
@@ -34,4 +34,4 @@ sequenceDiagram
end
- Fleet API->>Agent: actions|agent config
+ Fleet API->>Agent: actions|agent policy
diff --git a/x-pack/plugins/ingest_manager/dev_docs/schema/saved_objects.mml b/x-pack/plugins/ingest_manager/dev_docs/schema/saved_objects.mml
index d157bf32fa66b..9f73a15bed2eb 100644
--- a/x-pack/plugins/ingest_manager/dev_docs/schema/saved_objects.mml
+++ b/x-pack/plugins/ingest_manager/dev_docs/schema/saved_objects.mml
@@ -1,11 +1,11 @@
classDiagram
- agent_configs "1" -- "*" package_configs
- agent_configs "1" -- "*" enrollment_api_keys
- agent_configs "1" -- "*" agents : is used
- agent_configs "*" -- "*" outputs
+ agent_policies "1" -- "*" package_policies
+ agent_policies "1" -- "*" enrollment_api_keys
+ agent_policies "1" -- "*" agents : is used
+ agent_policies "*" -- "*" outputs
agents "1" -- "*" agent_events
agents "1" -- "*" agent_events
- package "1" -- "*" package_configs
+ package "1" -- "*" package_policies
class package {
installed
@@ -14,11 +14,11 @@ classDiagram
class agents {
status
access_api_key_id
- config_id
+ policy_id
last_checkin
local_metadata
user_provided_metadata
- actions // Encrypted contains new agent config
+ actions // Encrypted contains new agent policy
default_api_key // Encrypted
}
@@ -28,7 +28,7 @@ classDiagram
subtype
agent_id
action_id
- config_id
+ policy_id
stream_id
timestamp
message
@@ -36,18 +36,18 @@ classDiagram
data
}
- class agent_configs {
- package_configs // package_config ids
+ class agent_policies {
+ package_policies // package_policy ids
name
namespace
description
status
}
- class package_configs {
+ class package_policies {
name
namespace
- config_id
+ policy_id
enabled
package
output_id
@@ -64,7 +64,7 @@ classDiagram
}
class enrollment_api_keys {
- config_id
+ policy_id
api_key_id
api_key // Encrypted
}
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/home_integration/tutorial_directory_notice.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/home_integration/tutorial_directory_notice.tsx
index 553623380dcc0..bc5435369f44a 100644
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/home_integration/tutorial_directory_notice.tsx
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/home_integration/tutorial_directory_notice.tsx
@@ -82,7 +82,7 @@ export const TutorialDirectoryNotice: TutorialDirectoryNoticeComponent = memo(()
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/settings_flyout.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/settings_flyout.tsx
index e8081e097fd42..be1b3df8a0c3e 100644
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/settings_flyout.tsx
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/settings_flyout.tsx
@@ -167,7 +167,7 @@ export const SettingFlyout: React.FunctionComponent = ({ onClose }) => {
'xpack.ingestManager.settings.integrationUpgradeEnabledFieldLabel',
{
defaultMessage:
- 'Automatically update integrations to the latest version to receive the latest assets. Agent configurations may need to be updated in order to use new features.',
+ 'Automatically update integrations to the latest version to receive the latest assets. Agent policies may need to be updated in order to use new features.',
}
),
},
@@ -210,7 +210,7 @@ export const SettingFlyout: React.FunctionComponent = ({ onClose }) => {
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/index.ts
index d31d66d889c96..185e1fa5eb0ce 100644
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/index.ts
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/index.ts
@@ -7,11 +7,11 @@ export {
PLUGIN_ID,
EPM_API_ROUTES,
AGENT_API_ROUTES,
- AGENT_CONFIG_SAVED_OBJECT_TYPE,
+ AGENT_POLICY_SAVED_OBJECT_TYPE,
AGENT_EVENT_SAVED_OBJECT_TYPE,
AGENT_SAVED_OBJECT_TYPE,
ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
- PACKAGE_CONFIG_SAVED_OBJECT_TYPE,
+ PACKAGE_POLICY_SAVED_OBJECT_TYPE,
} from '../../../../common';
export * from './page_paths';
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts
index 9f1088a94aa94..4a8dcfedc0936 100644
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts
@@ -9,17 +9,17 @@ export type StaticPage =
| 'integrations'
| 'integrations_all'
| 'integrations_installed'
- | 'configurations'
- | 'configurations_list'
+ | 'policies'
+ | 'policies_list'
| 'fleet'
| 'fleet_enrollment_tokens'
| 'data_streams';
export type DynamicPage =
| 'integration_details'
- | 'configuration_details'
- | 'add_integration_from_configuration'
- | 'add_integration_to_configuration'
+ | 'policy_details'
+ | 'add_integration_from_policy'
+ | 'add_integration_to_policy'
| 'edit_integration'
| 'fleet_agent_list'
| 'fleet_agent_details';
@@ -40,13 +40,13 @@ export const PAGE_ROUTING_PATHS = {
integrations_all: '/integrations',
integrations_installed: '/integrations/installed',
integration_details: '/integrations/detail/:pkgkey/:panel?',
- configurations: '/configs',
- configurations_list: '/configs',
- configuration_details: '/configs/:configId/:tabId?',
- configuration_details_settings: '/configs/:configId/settings',
- add_integration_from_configuration: '/configs/:configId/add-integration',
- add_integration_to_configuration: '/integrations/:pkgkey/add-integration',
- edit_integration: '/configs/:configId/edit-integration/:packageConfigId',
+ policies: '/policies',
+ policies_list: '/policies',
+ policy_details: '/policies/:policyId/:tabId?',
+ policy_details_settings: '/policies/:policyId/settings',
+ add_integration_from_policy: '/policies/:policyId/add-integration',
+ add_integration_to_policy: '/integrations/:pkgkey/add-integration',
+ edit_integration: '/policies/:policyId/edit-integration/:packagePolicyId',
fleet: '/fleet',
fleet_agent_list: '/fleet/agents',
fleet_agent_details: '/fleet/agents/:agentId/:tabId?',
@@ -68,13 +68,13 @@ export const pagePathGetters: {
integrations_installed: () => '/integrations/installed',
integration_details: ({ pkgkey, panel }) =>
`/integrations/detail/${pkgkey}${panel ? `/${panel}` : ''}`,
- configurations: () => '/configs',
- configurations_list: () => '/configs',
- configuration_details: ({ configId, tabId }) => `/configs/${configId}${tabId ? `/${tabId}` : ''}`,
- add_integration_from_configuration: ({ configId }) => `/configs/${configId}/add-integration`,
- add_integration_to_configuration: ({ pkgkey }) => `/integrations/${pkgkey}/add-integration`,
- edit_integration: ({ configId, packageConfigId }) =>
- `/configs/${configId}/edit-integration/${packageConfigId}`,
+ policies: () => '/policies',
+ policies_list: () => '/policies',
+ policy_details: ({ policyId, tabId }) => `/policies/${policyId}${tabId ? `/${tabId}` : ''}`,
+ add_integration_from_policy: ({ policyId }) => `/policies/${policyId}/add-integration`,
+ add_integration_to_policy: ({ pkgkey }) => `/integrations/${pkgkey}/add-integration`,
+ edit_integration: ({ policyId, packagePolicyId }) =>
+ `/policies/${policyId}/edit-integration/${packagePolicyId}`,
fleet: () => '/fleet',
fleet_agent_list: ({ kuery }) => `/fleet/agents${kuery ? `?kuery=${kuery}` : ''}`,
fleet_agent_details: ({ agentId, tabId }) =>
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_breadcrumbs.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_breadcrumbs.tsx
index 293638cff50bf..6ef1351dc5b60 100644
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_breadcrumbs.tsx
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_breadcrumbs.tsx
@@ -72,51 +72,51 @@ const breadcrumbGetters: {
},
{ text: pkgTitle },
],
- configurations: () => [
+ policies: () => [
BASE_BREADCRUMB,
{
- text: i18n.translate('xpack.ingestManager.breadcrumbs.configurationsPageTitle', {
- defaultMessage: 'Configurations',
+ text: i18n.translate('xpack.ingestManager.breadcrumbs.policiesPageTitle', {
+ defaultMessage: 'Policies',
}),
},
],
- configurations_list: () => [
+ policies_list: () => [
BASE_BREADCRUMB,
{
- text: i18n.translate('xpack.ingestManager.breadcrumbs.configurationsPageTitle', {
- defaultMessage: 'Configurations',
+ text: i18n.translate('xpack.ingestManager.breadcrumbs.policiesPageTitle', {
+ defaultMessage: 'Policies',
}),
},
],
- configuration_details: ({ configName }) => [
+ policy_details: ({ policyName }) => [
BASE_BREADCRUMB,
{
- href: pagePathGetters.configurations(),
- text: i18n.translate('xpack.ingestManager.breadcrumbs.configurationsPageTitle', {
- defaultMessage: 'Configurations',
+ href: pagePathGetters.policies(),
+ text: i18n.translate('xpack.ingestManager.breadcrumbs.policiesPageTitle', {
+ defaultMessage: 'Policies',
}),
},
- { text: configName },
+ { text: policyName },
],
- add_integration_from_configuration: ({ configName, configId }) => [
+ add_integration_from_policy: ({ policyName, policyId }) => [
BASE_BREADCRUMB,
{
- href: pagePathGetters.configurations(),
- text: i18n.translate('xpack.ingestManager.breadcrumbs.configurationsPageTitle', {
- defaultMessage: 'Configurations',
+ href: pagePathGetters.policies(),
+ text: i18n.translate('xpack.ingestManager.breadcrumbs.policiesPageTitle', {
+ defaultMessage: 'Policies',
}),
},
{
- href: pagePathGetters.configuration_details({ configId }),
- text: configName,
+ href: pagePathGetters.policy_details({ policyId }),
+ text: policyName,
},
{
- text: i18n.translate('xpack.ingestManager.breadcrumbs.addPackageConfigPageTitle', {
+ text: i18n.translate('xpack.ingestManager.breadcrumbs.addPackagePolicyPageTitle', {
defaultMessage: 'Add integration',
}),
},
],
- add_integration_to_configuration: ({ pkgTitle, pkgkey }) => [
+ add_integration_to_policy: ({ pkgTitle, pkgkey }) => [
BASE_BREADCRUMB,
{
href: pagePathGetters.integrations(),
@@ -129,25 +129,25 @@ const breadcrumbGetters: {
text: pkgTitle,
},
{
- text: i18n.translate('xpack.ingestManager.breadcrumbs.addPackageConfigPageTitle', {
+ text: i18n.translate('xpack.ingestManager.breadcrumbs.addPackagePolicyPageTitle', {
defaultMessage: 'Add integration',
}),
},
],
- edit_integration: ({ configName, configId }) => [
+ edit_integration: ({ policyName, policyId }) => [
BASE_BREADCRUMB,
{
- href: pagePathGetters.configurations(),
- text: i18n.translate('xpack.ingestManager.breadcrumbs.configurationsPageTitle', {
- defaultMessage: 'Configurations',
+ href: pagePathGetters.policies(),
+ text: i18n.translate('xpack.ingestManager.breadcrumbs.policiesPageTitle', {
+ defaultMessage: 'Policies',
}),
},
{
- href: pagePathGetters.configuration_details({ configId }),
- text: configName,
+ href: pagePathGetters.policy_details({ policyId }),
+ text: policyName,
},
{
- text: i18n.translate('xpack.ingestManager.breadcrumbs.editPackageConfigPageTitle', {
+ text: i18n.translate('xpack.ingestManager.breadcrumbs.editPackagePolicyPageTitle', {
defaultMessage: 'Edit integration',
}),
},
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts
deleted file mode 100644
index 0bb09c2731032..0000000000000
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts
+++ /dev/null
@@ -1,109 +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 {
- useRequest,
- sendRequest,
- useConditionalRequest,
- SendConditionalRequestConfig,
-} from './use_request';
-import { agentConfigRouteService } from '../../services';
-import {
- GetAgentConfigsRequest,
- GetAgentConfigsResponse,
- GetOneAgentConfigResponse,
- GetFullAgentConfigResponse,
- CreateAgentConfigRequest,
- CreateAgentConfigResponse,
- UpdateAgentConfigRequest,
- UpdateAgentConfigResponse,
- CopyAgentConfigRequest,
- CopyAgentConfigResponse,
- DeleteAgentConfigRequest,
- DeleteAgentConfigResponse,
-} from '../../types';
-
-export const useGetAgentConfigs = (query?: GetAgentConfigsRequest['query']) => {
- return useRequest({
- path: agentConfigRouteService.getListPath(),
- method: 'get',
- query,
- });
-};
-
-export const useGetOneAgentConfig = (agentConfigId: string | undefined) => {
- return useConditionalRequest({
- path: agentConfigId ? agentConfigRouteService.getInfoPath(agentConfigId) : undefined,
- method: 'get',
- shouldSendRequest: !!agentConfigId,
- } as SendConditionalRequestConfig);
-};
-
-export const useGetOneAgentConfigFull = (agentConfigId: string) => {
- return useRequest({
- path: agentConfigRouteService.getInfoFullPath(agentConfigId),
- method: 'get',
- });
-};
-
-export const sendGetOneAgentConfigFull = (
- agentConfigId: string,
- query: { standalone?: boolean } = {}
-) => {
- return sendRequest({
- path: agentConfigRouteService.getInfoFullPath(agentConfigId),
- method: 'get',
- query,
- });
-};
-
-export const sendGetOneAgentConfig = (agentConfigId: string) => {
- return sendRequest({
- path: agentConfigRouteService.getInfoPath(agentConfigId),
- method: 'get',
- });
-};
-
-export const sendCreateAgentConfig = (
- body: CreateAgentConfigRequest['body'],
- { withSysMonitoring }: { withSysMonitoring: boolean } = { withSysMonitoring: false }
-) => {
- return sendRequest({
- path: agentConfigRouteService.getCreatePath(),
- method: 'post',
- body: JSON.stringify(body),
- query: withSysMonitoring ? { sys_monitoring: true } : {},
- });
-};
-
-export const sendUpdateAgentConfig = (
- agentConfigId: string,
- body: UpdateAgentConfigRequest['body']
-) => {
- return sendRequest({
- path: agentConfigRouteService.getUpdatePath(agentConfigId),
- method: 'put',
- body: JSON.stringify(body),
- });
-};
-
-export const sendCopyAgentConfig = (
- agentConfigId: string,
- body: CopyAgentConfigRequest['body']
-) => {
- return sendRequest({
- path: agentConfigRouteService.getCopyPath(agentConfigId),
- method: 'post',
- body: JSON.stringify(body),
- });
-};
-
-export const sendDeleteAgentConfig = (body: DeleteAgentConfigRequest['body']) => {
- return sendRequest({
- path: agentConfigRouteService.getDeletePath(),
- method: 'post',
- body: JSON.stringify(body),
- });
-};
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_policy.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_policy.ts
new file mode 100644
index 0000000000000..8707dcab9e584
--- /dev/null
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_policy.ts
@@ -0,0 +1,109 @@
+/*
+ * 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 {
+ useRequest,
+ sendRequest,
+ useConditionalRequest,
+ SendConditionalRequestConfig,
+} from './use_request';
+import { agentPolicyRouteService } from '../../services';
+import {
+ GetAgentPoliciesRequest,
+ GetAgentPoliciesResponse,
+ GetOneAgentPolicyResponse,
+ GetFullAgentPolicyResponse,
+ CreateAgentPolicyRequest,
+ CreateAgentPolicyResponse,
+ UpdateAgentPolicyRequest,
+ UpdateAgentPolicyResponse,
+ CopyAgentPolicyRequest,
+ CopyAgentPolicyResponse,
+ DeleteAgentPolicyRequest,
+ DeleteAgentPolicyResponse,
+} from '../../types';
+
+export const useGetAgentPolicies = (query?: GetAgentPoliciesRequest['query']) => {
+ return useRequest({
+ path: agentPolicyRouteService.getListPath(),
+ method: 'get',
+ query,
+ });
+};
+
+export const useGetOneAgentPolicy = (agentPolicyId: string | undefined) => {
+ return useConditionalRequest