diff --git a/packages/kbn-optimizer/package.json b/packages/kbn-optimizer/package.json
index d32c7489641a0..b648004760d7c 100644
--- a/packages/kbn-optimizer/package.json
+++ b/packages/kbn-optimizer/package.json
@@ -14,9 +14,12 @@
"@kbn/babel-preset": "1.0.0",
"@kbn/dev-utils": "1.0.0",
"@kbn/ui-shared-deps": "1.0.0",
+ "@types/estree": "^0.0.44",
"@types/loader-utils": "^1.1.3",
"@types/watchpack": "^1.1.5",
"@types/webpack": "^4.41.3",
+ "acorn": "^7.1.1",
+ "acorn-walk": "^7.1.1",
"autoprefixer": "^9.7.4",
"babel-loader": "^8.0.6",
"clean-webpack-plugin": "^3.0.0",
diff --git a/packages/kbn-optimizer/src/worker/__snapshots__/parse_path.test.ts.snap b/packages/kbn-optimizer/src/common/__snapshots__/parse_path.test.ts.snap
similarity index 64%
rename from packages/kbn-optimizer/src/worker/__snapshots__/parse_path.test.ts.snap
rename to packages/kbn-optimizer/src/common/__snapshots__/parse_path.test.ts.snap
index 2973ac116d6bd..f537674c3fff7 100644
--- a/packages/kbn-optimizer/src/worker/__snapshots__/parse_path.test.ts.snap
+++ b/packages/kbn-optimizer/src/common/__snapshots__/parse_path.test.ts.snap
@@ -4,6 +4,7 @@ exports[`parseDirPath() parses / 1`] = `
Object {
"dirs": Array [],
"filename": undefined,
+ "query": undefined,
"root": "/",
}
`;
@@ -14,6 +15,7 @@ Object {
"foo",
],
"filename": undefined,
+ "query": undefined,
"root": "/",
}
`;
@@ -26,6 +28,7 @@ Object {
"baz",
],
"filename": undefined,
+ "query": undefined,
"root": "/",
}
`;
@@ -38,6 +41,7 @@ Object {
"baz",
],
"filename": undefined,
+ "query": undefined,
"root": "/",
}
`;
@@ -46,6 +50,7 @@ exports[`parseDirPath() parses c:\\ 1`] = `
Object {
"dirs": Array [],
"filename": undefined,
+ "query": undefined,
"root": "c:",
}
`;
@@ -56,6 +61,7 @@ Object {
"foo",
],
"filename": undefined,
+ "query": undefined,
"root": "c:",
}
`;
@@ -68,6 +74,7 @@ Object {
"baz",
],
"filename": undefined,
+ "query": undefined,
"root": "c:",
}
`;
@@ -80,6 +87,7 @@ Object {
"baz",
],
"filename": undefined,
+ "query": undefined,
"root": "c:",
}
`;
@@ -88,6 +96,7 @@ exports[`parseFilePath() parses /foo 1`] = `
Object {
"dirs": Array [],
"filename": "foo",
+ "query": undefined,
"root": "/",
}
`;
@@ -99,6 +108,7 @@ Object {
"bar",
],
"filename": "baz",
+ "query": undefined,
"root": "/",
}
`;
@@ -110,6 +120,36 @@ Object {
"bar",
],
"filename": "baz.json",
+ "query": undefined,
+ "root": "/",
+}
+`;
+
+exports[`parseFilePath() parses /foo/bar/baz.json?light 1`] = `
+Object {
+ "dirs": Array [
+ "foo",
+ "bar",
+ ],
+ "filename": "baz.json",
+ "query": Object {
+ "light": "",
+ },
+ "root": "/",
+}
+`;
+
+exports[`parseFilePath() parses /foo/bar/baz.json?light=true&dark=false 1`] = `
+Object {
+ "dirs": Array [
+ "foo",
+ "bar",
+ ],
+ "filename": "baz.json",
+ "query": Object {
+ "dark": "false",
+ "light": "true",
+ },
"root": "/",
}
`;
@@ -121,6 +161,7 @@ Object {
"bar",
],
"filename": "baz.json",
+ "query": undefined,
"root": "c:",
}
`;
@@ -129,6 +170,7 @@ exports[`parseFilePath() parses c:\\foo 1`] = `
Object {
"dirs": Array [],
"filename": "foo",
+ "query": undefined,
"root": "c:",
}
`;
@@ -140,6 +182,7 @@ Object {
"bar",
],
"filename": "baz",
+ "query": undefined,
"root": "c:",
}
`;
@@ -151,6 +194,36 @@ Object {
"bar",
],
"filename": "baz.json",
+ "query": undefined,
+ "root": "c:",
+}
+`;
+
+exports[`parseFilePath() parses c:\\foo\\bar\\baz.json?dark 1`] = `
+Object {
+ "dirs": Array [
+ "foo",
+ "bar",
+ ],
+ "filename": "baz.json",
+ "query": Object {
+ "dark": "",
+ },
+ "root": "c:",
+}
+`;
+
+exports[`parseFilePath() parses c:\\foo\\bar\\baz.json?dark=true&light=false 1`] = `
+Object {
+ "dirs": Array [
+ "foo",
+ "bar",
+ ],
+ "filename": "baz.json",
+ "query": Object {
+ "dark": "true",
+ "light": "false",
+ },
"root": "c:",
}
`;
diff --git a/packages/kbn-optimizer/src/common/disallowed_syntax_plugin/disallowed_syntax.ts b/packages/kbn-optimizer/src/common/disallowed_syntax_plugin/disallowed_syntax.ts
new file mode 100644
index 0000000000000..ba19bdc9c3be7
--- /dev/null
+++ b/packages/kbn-optimizer/src/common/disallowed_syntax_plugin/disallowed_syntax.ts
@@ -0,0 +1,194 @@
+/*
+ * 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 estree from 'estree';
+
+export interface DisallowedSyntaxCheck {
+ name: string;
+ nodeType: estree.Node['type'] | Array;
+ test?: (n: any) => boolean | void;
+}
+
+export const checks: DisallowedSyntaxCheck[] = [
+ /**
+ * es2015
+ */
+ // https://github.com/estree/estree/blob/master/es2015.md#functions
+ {
+ name: '[es2015] generator function',
+ nodeType: ['FunctionDeclaration', 'FunctionExpression'],
+ test: (n: estree.FunctionDeclaration | estree.FunctionExpression) => !!n.generator,
+ },
+ // https://github.com/estree/estree/blob/master/es2015.md#forofstatement
+ {
+ name: '[es2015] for-of statement',
+ nodeType: 'ForOfStatement',
+ },
+ // https://github.com/estree/estree/blob/master/es2015.md#variabledeclaration
+ {
+ name: '[es2015] let/const variable declaration',
+ nodeType: 'VariableDeclaration',
+ test: (n: estree.VariableDeclaration) => n.kind === 'let' || n.kind === 'const',
+ },
+ // https://github.com/estree/estree/blob/master/es2015.md#expressions
+ {
+ name: '[es2015] `super`',
+ nodeType: 'Super',
+ },
+ // https://github.com/estree/estree/blob/master/es2015.md#expressions
+ {
+ name: '[es2015] ...spread',
+ nodeType: 'SpreadElement',
+ },
+ // https://github.com/estree/estree/blob/master/es2015.md#arrowfunctionexpression
+ {
+ name: '[es2015] arrow function expression',
+ nodeType: 'ArrowFunctionExpression',
+ },
+ // https://github.com/estree/estree/blob/master/es2015.md#yieldexpression
+ {
+ name: '[es2015] `yield` expression',
+ nodeType: 'YieldExpression',
+ },
+ // https://github.com/estree/estree/blob/master/es2015.md#templateliteral
+ {
+ name: '[es2015] template literal',
+ nodeType: 'TemplateLiteral',
+ },
+ // https://github.com/estree/estree/blob/master/es2015.md#patterns
+ {
+ name: '[es2015] destructuring',
+ nodeType: ['ObjectPattern', 'ArrayPattern', 'AssignmentPattern'],
+ },
+ // https://github.com/estree/estree/blob/master/es2015.md#classes
+ {
+ name: '[es2015] class',
+ nodeType: [
+ 'ClassDeclaration',
+ 'ClassExpression',
+ 'ClassBody',
+ 'MethodDefinition',
+ 'MetaProperty',
+ ],
+ },
+
+ /**
+ * es2016
+ */
+ {
+ name: '[es2016] exponent operator',
+ nodeType: 'BinaryExpression',
+ test: (n: estree.BinaryExpression) => n.operator === '**',
+ },
+ {
+ name: '[es2016] exponent assignment',
+ nodeType: 'AssignmentExpression',
+ test: (n: estree.AssignmentExpression) => n.operator === '**=',
+ },
+
+ /**
+ * es2017
+ */
+ // https://github.com/estree/estree/blob/master/es2017.md#function
+ {
+ name: '[es2017] async function',
+ nodeType: ['FunctionDeclaration', 'FunctionExpression'],
+ test: (n: estree.FunctionDeclaration | estree.FunctionExpression) => n.async,
+ },
+ // https://github.com/estree/estree/blob/master/es2017.md#awaitexpression
+ {
+ name: '[es2017] await expression',
+ nodeType: 'AwaitExpression',
+ },
+
+ /**
+ * es2018
+ */
+ // https://github.com/estree/estree/blob/master/es2018.md#statements
+ {
+ name: '[es2018] for-await-of statements',
+ nodeType: 'ForOfStatement',
+ test: (n: estree.ForOfStatement) => n.await,
+ },
+ // https://github.com/estree/estree/blob/master/es2018.md#expressions
+ {
+ name: '[es2018] object spread properties',
+ nodeType: 'ObjectExpression',
+ test: (n: estree.ObjectExpression) => n.properties.some(p => p.type === 'SpreadElement'),
+ },
+ // https://github.com/estree/estree/blob/master/es2018.md#template-literals
+ {
+ name: '[es2018] tagged template literal with invalid escape',
+ nodeType: 'TemplateElement',
+ test: (n: estree.TemplateElement) => n.value.cooked === null,
+ },
+ // https://github.com/estree/estree/blob/master/es2018.md#patterns
+ {
+ name: '[es2018] rest properties',
+ nodeType: 'ObjectPattern',
+ test: (n: estree.ObjectPattern) => n.properties.some(p => p.type === 'RestElement'),
+ },
+
+ /**
+ * es2019
+ */
+ // https://github.com/estree/estree/blob/master/es2019.md#catchclause
+ {
+ name: '[es2019] catch clause without a binding',
+ nodeType: 'CatchClause',
+ test: (n: estree.CatchClause) => !n.param,
+ },
+
+ /**
+ * es2020
+ */
+ // https://github.com/estree/estree/blob/master/es2020.md#bigintliteral
+ {
+ name: '[es2020] bigint literal',
+ nodeType: 'Literal',
+ test: (n: estree.Literal) => typeof n.value === 'bigint',
+ },
+
+ /**
+ * webpack transforms import/export in order to support tree shaking and async imports
+ *
+ * // https://github.com/estree/estree/blob/master/es2020.md#importexpression
+ * {
+ * name: '[es2020] import expression',
+ * nodeType: 'ImportExpression',
+ * },
+ * // https://github.com/estree/estree/blob/master/es2020.md#exportalldeclaration
+ * {
+ * name: '[es2020] export all declaration',
+ * nodeType: 'ExportAllDeclaration',
+ * },
+ *
+ */
+];
+
+export const checksByNodeType = new Map();
+for (const check of checks) {
+ const nodeTypes = Array.isArray(check.nodeType) ? check.nodeType : [check.nodeType];
+ for (const nodeType of nodeTypes) {
+ if (!checksByNodeType.has(nodeType)) {
+ checksByNodeType.set(nodeType, []);
+ }
+ checksByNodeType.get(nodeType)!.push(check);
+ }
+}
diff --git a/packages/kbn-optimizer/src/common/disallowed_syntax_plugin/disallowed_syntax_plugin.ts b/packages/kbn-optimizer/src/common/disallowed_syntax_plugin/disallowed_syntax_plugin.ts
new file mode 100644
index 0000000000000..7377462eb267b
--- /dev/null
+++ b/packages/kbn-optimizer/src/common/disallowed_syntax_plugin/disallowed_syntax_plugin.ts
@@ -0,0 +1,73 @@
+/*
+ * 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 webpack from 'webpack';
+import acorn from 'acorn';
+import * as AcornWalk from 'acorn-walk';
+
+import { checksByNodeType, DisallowedSyntaxCheck } from './disallowed_syntax';
+import { parseFilePath } from '../parse_path';
+
+export class DisallowedSyntaxPlugin {
+ apply(compiler: webpack.Compiler) {
+ compiler.hooks.normalModuleFactory.tap(DisallowedSyntaxPlugin.name, factory => {
+ factory.hooks.parser.for('javascript/auto').tap(DisallowedSyntaxPlugin.name, parser => {
+ parser.hooks.program.tap(DisallowedSyntaxPlugin.name, (program: acorn.Node) => {
+ const module = parser.state?.current;
+ if (!module || !module.resource) {
+ return;
+ }
+
+ const resource: string = module.resource;
+ const { dirs } = parseFilePath(resource);
+
+ if (!dirs.includes('node_modules')) {
+ return;
+ }
+
+ const failedChecks = new Set();
+
+ AcornWalk.full(program, node => {
+ const checks = checksByNodeType.get(node.type as any);
+ if (!checks) {
+ return;
+ }
+
+ for (const check of checks) {
+ if (!check.test || check.test(node)) {
+ failedChecks.add(check);
+ }
+ }
+ });
+
+ if (!failedChecks.size) {
+ return;
+ }
+
+ // throw an error to trigger a parse failure, causing this module to be reported as invalid
+ throw new Error(
+ `disallowed syntax found in file ${resource}:\n - ${Array.from(failedChecks)
+ .map(c => c.name)
+ .join('\n - ')}`
+ );
+ });
+ });
+ });
+ }
+}
diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/index.ts b/packages/kbn-optimizer/src/common/disallowed_syntax_plugin/index.ts
similarity index 92%
rename from src/legacy/core_plugins/embeddable_api/public/np_ready/public/index.ts
rename to packages/kbn-optimizer/src/common/disallowed_syntax_plugin/index.ts
index 4b69616a777e9..ca5ba1b90fe95 100644
--- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/index.ts
+++ b/packages/kbn-optimizer/src/common/disallowed_syntax_plugin/index.ts
@@ -17,4 +17,4 @@
* under the License.
*/
-export * from '../../../../../../plugins/embeddable/public';
+export * from './disallowed_syntax_plugin';
diff --git a/packages/kbn-optimizer/src/common/index.ts b/packages/kbn-optimizer/src/common/index.ts
index ea0560f132153..c51905be04565 100644
--- a/packages/kbn-optimizer/src/common/index.ts
+++ b/packages/kbn-optimizer/src/common/index.ts
@@ -26,3 +26,5 @@ export * from './ts_helpers';
export * from './rxjs_helpers';
export * from './array_helpers';
export * from './event_stream_helpers';
+export * from './disallowed_syntax_plugin';
+export * from './parse_path';
diff --git a/packages/kbn-optimizer/src/worker/parse_path.test.ts b/packages/kbn-optimizer/src/common/parse_path.test.ts
similarity index 83%
rename from packages/kbn-optimizer/src/worker/parse_path.test.ts
rename to packages/kbn-optimizer/src/common/parse_path.test.ts
index 72197e8c8fb07..61be44348cfae 100644
--- a/packages/kbn-optimizer/src/worker/parse_path.test.ts
+++ b/packages/kbn-optimizer/src/common/parse_path.test.ts
@@ -21,7 +21,15 @@ import { parseFilePath, parseDirPath } from './parse_path';
const DIRS = ['/', '/foo/bar/baz/', 'c:\\', 'c:\\foo\\bar\\baz\\'];
const AMBIGUOUS = ['/foo', '/foo/bar/baz', 'c:\\foo', 'c:\\foo\\bar\\baz'];
-const FILES = ['/foo/bar/baz.json', 'c:/foo/bar/baz.json', 'c:\\foo\\bar\\baz.json'];
+const FILES = [
+ '/foo/bar/baz.json',
+ 'c:/foo/bar/baz.json',
+ 'c:\\foo\\bar\\baz.json',
+ '/foo/bar/baz.json?light',
+ '/foo/bar/baz.json?light=true&dark=false',
+ 'c:\\foo\\bar\\baz.json?dark',
+ 'c:\\foo\\bar\\baz.json?dark=true&light=false',
+];
describe('parseFilePath()', () => {
it.each([...FILES, ...AMBIGUOUS])('parses %s', path => {
diff --git a/packages/kbn-optimizer/src/worker/parse_path.ts b/packages/kbn-optimizer/src/common/parse_path.ts
similarity index 83%
rename from packages/kbn-optimizer/src/worker/parse_path.ts
rename to packages/kbn-optimizer/src/common/parse_path.ts
index 88152df55b84f..4c96417800252 100644
--- a/packages/kbn-optimizer/src/worker/parse_path.ts
+++ b/packages/kbn-optimizer/src/common/parse_path.ts
@@ -18,6 +18,7 @@
*/
import normalizePath from 'normalize-path';
+import Qs from 'querystring';
/**
* Parse an absolute path, supporting normalized paths from webpack,
@@ -33,11 +34,19 @@ export function parseDirPath(path: string) {
}
export function parseFilePath(path: string) {
- const normalized = normalizePath(path);
+ let normalized = normalizePath(path);
+ let query;
+ const queryIndex = normalized.indexOf('?');
+ if (queryIndex !== -1) {
+ query = Qs.parse(normalized.slice(queryIndex + 1));
+ normalized = normalized.slice(0, queryIndex);
+ }
+
const [root, ...others] = normalized.split('/');
return {
root: root === '' ? '/' : root,
dirs: others.slice(0, -1),
+ query,
filename: others[others.length - 1] || undefined,
};
}
diff --git a/packages/kbn-optimizer/src/index.ts b/packages/kbn-optimizer/src/index.ts
index 48777f1d54aaf..8026cf39db73d 100644
--- a/packages/kbn-optimizer/src/index.ts
+++ b/packages/kbn-optimizer/src/index.ts
@@ -20,3 +20,4 @@
export { OptimizerConfig } from './optimizer';
export * from './run_optimizer';
export * from './log_optimizer_state';
+export * from './common/disallowed_syntax_plugin';
diff --git a/packages/kbn-optimizer/src/worker/run_compilers.ts b/packages/kbn-optimizer/src/worker/run_compilers.ts
index e87ddc7d0185c..0dfce4b5addba 100644
--- a/packages/kbn-optimizer/src/worker/run_compilers.ts
+++ b/packages/kbn-optimizer/src/worker/run_compilers.ts
@@ -27,10 +27,17 @@ import webpack, { Stats } from 'webpack';
import * as Rx from 'rxjs';
import { mergeMap, map, mapTo, takeUntil } from 'rxjs/operators';
-import { CompilerMsgs, CompilerMsg, maybeMap, Bundle, WorkerConfig, ascending } from '../common';
+import {
+ CompilerMsgs,
+ CompilerMsg,
+ maybeMap,
+ Bundle,
+ WorkerConfig,
+ ascending,
+ parseFilePath,
+} from '../common';
import { getWebpackConfig } from './webpack.config';
import { isFailureStats, failedStatsToErrorMessage } from './webpack_helpers';
-import { parseFilePath } from './parse_path';
import {
isExternalModule,
isNormalModule,
diff --git a/packages/kbn-optimizer/src/worker/webpack.config.ts b/packages/kbn-optimizer/src/worker/webpack.config.ts
index dabfed7f9725c..9337daf419bfa 100644
--- a/packages/kbn-optimizer/src/worker/webpack.config.ts
+++ b/packages/kbn-optimizer/src/worker/webpack.config.ts
@@ -29,8 +29,7 @@ import webpackMerge from 'webpack-merge';
import { CleanWebpackPlugin } from 'clean-webpack-plugin';
import * as SharedDeps from '@kbn/ui-shared-deps';
-import { Bundle, WorkerConfig } from '../common';
-import { parseDirPath } from './parse_path';
+import { Bundle, WorkerConfig, parseDirPath, DisallowedSyntaxPlugin } from '../common';
const IS_CODE_COVERAGE = !!process.env.CODE_COVERAGE;
const ISTANBUL_PRESET_PATH = require.resolve('@kbn/babel-preset/istanbul_preset');
@@ -77,7 +76,7 @@ export function getWebpackConfig(bundle: Bundle, worker: WorkerConfig) {
...SharedDeps.externals,
},
- plugins: [new CleanWebpackPlugin()],
+ plugins: [new CleanWebpackPlugin(), new DisallowedSyntaxPlugin()],
module: {
// no parse rules for a few known large packages which have no require() statements
diff --git a/packages/kbn-optimizer/src/worker/webpack_helpers.ts b/packages/kbn-optimizer/src/worker/webpack_helpers.ts
index a11c85c64198e..e30920b960144 100644
--- a/packages/kbn-optimizer/src/worker/webpack_helpers.ts
+++ b/packages/kbn-optimizer/src/worker/webpack_helpers.ts
@@ -18,7 +18,6 @@
*/
import webpack from 'webpack';
-import { defaults } from 'lodash';
// @ts-ignore
import Stats from 'webpack/lib/Stats';
@@ -55,12 +54,14 @@ const STATS_WARNINGS_FILTER = new RegExp(
);
export function failedStatsToErrorMessage(stats: webpack.Stats) {
- const details = stats.toString(
- defaults(
- { colors: true, warningsFilter: STATS_WARNINGS_FILTER },
- Stats.presetToOptions('minimal')
- )
- );
+ const details = stats.toString({
+ ...Stats.presetToOptions('minimal'),
+ colors: true,
+ warningsFilter: STATS_WARNINGS_FILTER,
+ errors: true,
+ errorDetails: true,
+ moduleTrace: true,
+ });
return `Optimizations failure.\n${details.split('\n').join('\n ')}`;
}
diff --git a/renovate.json5 b/renovate.json5
index 57f175d1afc8e..ffa006264873d 100644
--- a/renovate.json5
+++ b/renovate.json5
@@ -265,6 +265,14 @@
'(\\b|_)eslint(\\b|_)',
],
},
+ {
+ groupSlug: 'estree',
+ groupName: 'estree related packages',
+ packageNames: [
+ 'estree',
+ '@types/estree',
+ ],
+ },
{
groupSlug: 'fancy-log',
groupName: 'fancy-log related packages',
diff --git a/src/legacy/core_plugins/embeddable_api/README.md b/src/legacy/core_plugins/embeddable_api/README.md
deleted file mode 100644
index c2f67572df873..0000000000000
--- a/src/legacy/core_plugins/embeddable_api/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-- Embeddables have been moved to `/src/plugins/embeddable` NP plugin.
-- This legacy plugin is still there to make necessary CSS working, but soon will be completely deleted.
diff --git a/src/legacy/core_plugins/embeddable_api/package.json b/src/legacy/core_plugins/embeddable_api/package.json
deleted file mode 100644
index f625408fe4c6c..0000000000000
--- a/src/legacy/core_plugins/embeddable_api/package.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "embeddable_api",
- "version": "kibana"
-}
diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/mocks.ts b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/mocks.ts
deleted file mode 100644
index 10510bff0c97e..0000000000000
--- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/mocks.ts
+++ /dev/null
@@ -1,21 +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.
- */
-
-// eslint-disable-next-line
-export * from '../../../../../../plugins/embeddable/public/mocks';
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx
index e21033ffe10ec..cc7299b884890 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx
+++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx
@@ -21,7 +21,7 @@ import moment from 'moment';
import { Subscription } from 'rxjs';
import { History } from 'history';
-import { ViewMode } from '../../../../embeddable_api/public/np_ready/public';
+import { ViewMode } from '../../../../../../plugins/embeddable/public';
import { SavedObjectDashboard } from '../../../../../../plugins/dashboard/public';
import { DashboardAppState, SavedDashboardPanel } from './types';
import {
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx
index 0c6686c993371..a39266ecd8db3 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx
+++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx
@@ -58,7 +58,7 @@ import {
isErrorEmbeddable,
openAddPanelFlyout,
ViewMode,
-} from '../../../../embeddable_api/public/np_ready/public';
+} from '../../../../../../plugins/embeddable/public';
import { NavAction, SavedDashboardPanel } from './types';
import { showOptionsPopover } from './top_nav/show_options_popover';
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/embeddable_saved_object_converters.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/embeddable_saved_object_converters.test.ts
index b2a2f43b9152d..d3c3dc46c7057 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/embeddable_saved_object_converters.test.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/embeddable_saved_object_converters.test.ts
@@ -24,7 +24,7 @@ import {
} from './embeddable_saved_object_converters';
import { SavedDashboardPanel } from '../types';
import { DashboardPanelState } from 'src/plugins/dashboard/public';
-import { EmbeddableInput } from 'src/legacy/core_plugins/embeddable_api/public/np_ready/public';
+import { EmbeddableInput } from 'src/plugins/embeddable/public';
interface CustomInput extends EmbeddableInput {
something: string;
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable.ts
index d09b7612af49c..3cb8bce80fa41 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable.ts
@@ -34,7 +34,7 @@ import {
Query,
IFieldType,
} from '../../../../../../../plugins/data/public';
-import { Container, Embeddable } from '../../../../../embeddable_api/public/np_ready/public';
+import { Container, Embeddable } from '../../../../../../../plugins/embeddable/public';
import * as columnActions from '../angular/doc_table/actions/columns';
import searchTemplate from './search_template.html';
import { ISearchEmbeddable, SearchInput, SearchOutput } from './types';
diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js
index 4b7618712cdd8..a66d3b24732f0 100644
--- a/src/legacy/server/config/schema.js
+++ b/src/legacy/server/config/schema.js
@@ -195,6 +195,7 @@ export default () =>
}),
workers: Joi.number().min(1),
profile: Joi.boolean().default(false),
+ validateSyntaxOfNodeModules: Joi.boolean().default(true),
}).default(),
status: Joi.object({
allowAnonymous: Joi.boolean().default(false),
diff --git a/src/legacy/ui/public/chrome/api/sub_url_hooks.js b/src/legacy/ui/public/chrome/api/sub_url_hooks.js
index 27d147b1ffc72..f147aef7b4b7d 100644
--- a/src/legacy/ui/public/chrome/api/sub_url_hooks.js
+++ b/src/legacy/ui/public/chrome/api/sub_url_hooks.js
@@ -17,11 +17,10 @@
* under the License.
*/
-import url from 'url';
-
import { unhashUrl } from '../../../../../plugins/kibana_utils/public';
import { toastNotifications } from '../../notify/toasts';
import { npSetup } from '../../new_platform';
+import { areHashesDifferentButDecodedHashesEquals } from './sub_url_hooks_utils';
export function registerSubUrlHooks(angularModule, internals) {
angularModule.run(($rootScope, Private, $location) => {
@@ -49,17 +48,10 @@ export function registerSubUrlHooks(angularModule, internals) {
$rootScope.$on('$locationChangeStart', (e, newUrl) => {
// This handler fixes issue #31238 where browser back navigation
// fails due to angular 1.6 parsing url encoded params wrong.
- const parsedAbsUrl = url.parse($location.absUrl());
- const absUrlHash = parsedAbsUrl.hash ? parsedAbsUrl.hash.slice(1) : '';
- const decodedAbsUrlHash = decodeURIComponent(absUrlHash);
-
- const parsedNewUrl = url.parse(newUrl);
- const newHash = parsedNewUrl.hash ? parsedNewUrl.hash.slice(1) : '';
- const decodedHash = decodeURIComponent(newHash);
-
- if (absUrlHash !== newHash && decodedHash === decodedAbsUrlHash) {
+ if (areHashesDifferentButDecodedHashesEquals($location.absUrl(), newUrl)) {
// replace the urlencoded hash with the version that angular sees.
- $location.url(absUrlHash).replace();
+ const newHash = newUrl.split('#')[1] || '';
+ $location.url(newHash).replace();
}
});
diff --git a/src/legacy/ui/public/chrome/api/sub_url_hooks_utils.test.ts b/src/legacy/ui/public/chrome/api/sub_url_hooks_utils.test.ts
new file mode 100644
index 0000000000000..4dec526302344
--- /dev/null
+++ b/src/legacy/ui/public/chrome/api/sub_url_hooks_utils.test.ts
@@ -0,0 +1,58 @@
+/*
+ * 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 { areHashesDifferentButDecodedHashesEquals } from './sub_url_hooks_utils';
+
+test('false for different hashes', () => {
+ const url1 = `https://localhost/kibana/#/dashboard/id`;
+ const url2 = `https://localhost/kibana/#/dashboard/DIFFERENT`;
+ expect(areHashesDifferentButDecodedHashesEquals(url1, url2)).toBeFalsy();
+});
+
+test('false for same hashes', () => {
+ const hash = `/dashboard/id?_a=(filters:!(),query:(language:kuery,query:''))&_g=(filters:!(),time:(from:now-120m,to:now))`;
+ const url1 = `https://localhost/kibana/#/${hash}`;
+ expect(areHashesDifferentButDecodedHashesEquals(url1, url1)).toBeFalsy();
+});
+
+test('true for same hashes, but one is encoded', () => {
+ const hash = `/dashboard/id?_a=(filters:!(),query:(language:kuery,query:''))&_g=(filters:!(),time:(from:now-120m,to:now))`;
+ const url1 = `https://localhost/kibana/#/${hash}`;
+ const url2 = `https://localhost/kibana/#/${encodeURIComponent(hash)}`;
+ expect(areHashesDifferentButDecodedHashesEquals(url1, url2)).toBeTruthy();
+});
+
+/**
+ * This edge case occurs when trying to navigate within kibana app using core's `navigateToApp` api
+ * and there is reserved characters in hash (see: query:'' part)
+ * For example:
+ * ```ts
+ * navigateToApp('kibana', {
+ * path: '#/dashboard/f8bc19f0-6918-11ea-9258-a74c2ded064d?_a=(filters:!(),query:(language:kuery,query:''))&_g=(filters:!(),time:(from:now-120m,to:now))'
+ * })
+ * ```
+ * Core internally is using url.parse which parses ' -> %27 and performs the navigation
+ * Then angular decodes it back and causes redundant history record if not the fix which is covered by the test below
+ */
+test("true for same hashes, but one has reserved character (') encoded", () => {
+ const hash = `/dashboard/id?_a=(filters:!(),query:(language:kuery,query:''))&_g=(filters:!(),time:(from:now-120m,to:now))`;
+ const url1 = `https://localhost/kibana/#/${hash}`;
+ const url2 = `https://localhost/kibana/#/${hash.replace(/\'/g, '%27')}`;
+ expect(areHashesDifferentButDecodedHashesEquals(url1, url2)).toBeTruthy();
+});
diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/index.js b/src/legacy/ui/public/chrome/api/sub_url_hooks_utils.ts
similarity index 67%
rename from test/plugin_functional/plugins/kbn_tp_custom_visualizations/index.js
rename to src/legacy/ui/public/chrome/api/sub_url_hooks_utils.ts
index b2497a824ba2b..8517877acd387 100644
--- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/index.js
+++ b/src/legacy/ui/public/chrome/api/sub_url_hooks_utils.ts
@@ -17,10 +17,13 @@
* under the License.
*/
-export default function(kibana) {
- return new kibana.Plugin({
- uiExports: {
- hacks: ['plugins/kbn_tp_custom_visualizations/self_changing_vis/self_changing_vis'],
- },
- });
+export function areHashesDifferentButDecodedHashesEquals(urlA: string, urlB: string): boolean {
+ const getHash = (url: string) => url.split('#')[1] ?? '';
+ const hashA = getHash(urlA);
+ const decodedHashA = decodeURIComponent(hashA);
+
+ const hashB = getHash(urlB);
+ const decodedHashB = decodeURIComponent(hashB);
+
+ return hashA !== hashB && decodedHashA === decodedHashB;
}
diff --git a/src/legacy/ui/ui_bundles/ui_bundles_controller.js b/src/legacy/ui/ui_bundles/ui_bundles_controller.js
index 1a78569e874f2..7afa283af83e0 100644
--- a/src/legacy/ui/ui_bundles/ui_bundles_controller.js
+++ b/src/legacy/ui/ui_bundles/ui_bundles_controller.js
@@ -73,6 +73,7 @@ export class UiBundlesController {
this._workingDir = config.get('optimize.bundleDir');
this._env = config.get('env.name');
+ this._validateSyntaxOfNodeModules = config.get('optimize.validateSyntaxOfNodeModules');
this._context = {
env: config.get('env.name'),
sourceMaps: config.get('optimize.sourceMaps'),
@@ -135,6 +136,10 @@ export class UiBundlesController {
return this._env === 'development';
}
+ shouldValidateSyntaxOfNodeModules() {
+ return !!this._validateSyntaxOfNodeModules;
+ }
+
getWebpackPluginProviders() {
return this._webpackPluginProviders || [];
}
diff --git a/src/optimize/dynamic_dll_plugin/dll_config_model.js b/src/optimize/dynamic_dll_plugin/dll_config_model.js
index 9ca6071b8f515..eec369b194fef 100644
--- a/src/optimize/dynamic_dll_plugin/dll_config_model.js
+++ b/src/optimize/dynamic_dll_plugin/dll_config_model.js
@@ -28,6 +28,7 @@ import * as UiSharedDeps from '@kbn/ui-shared-deps';
function generateDLL(config) {
const {
dllAlias,
+ dllValidateSyntax,
dllNoParseRules,
dllContext,
dllEntry,
@@ -44,6 +45,22 @@ function generateDLL(config) {
const BABEL_PRESET_PATH = require.resolve('@kbn/babel-preset/webpack_preset');
const BABEL_EXCLUDE_RE = [/[\/\\](webpackShims|node_modules|bower_components)[\/\\]/];
+ /**
+ * Wrap plugin loading in a function so that we can require
+ * `@kbn/optimizer` only when absolutely necessary since we
+ * don't ship this package in the distributable but this code
+ * is still shipped, though it's not used.
+ */
+ const getValidateSyntaxPlugins = () => {
+ if (!dllValidateSyntax) {
+ return [];
+ }
+
+ // only require @kbn/optimizer
+ const { DisallowedSyntaxPlugin } = require('@kbn/optimizer');
+ return [new DisallowedSyntaxPlugin()];
+ };
+
return {
entry: dllEntry,
context: dllContext,
@@ -140,6 +157,7 @@ function generateDLL(config) {
new MiniCssExtractPlugin({
filename: dllStyleFilename,
}),
+ ...getValidateSyntaxPlugins(),
],
// Single runtime for the dll bundles which assures that common transient dependencies won't be evaluated twice.
// The module cache will be shared, even when module code may be duplicated across chunks.
@@ -163,6 +181,7 @@ function generateDLL(config) {
function extendRawConfig(rawConfig) {
// Build all extended configs from raw config
const dllAlias = rawConfig.uiBundles.getAliases();
+ const dllValidateSyntax = rawConfig.uiBundles.shouldValidateSyntaxOfNodeModules();
const dllNoParseRules = rawConfig.uiBundles.getWebpackNoParseRules();
const dllDevMode = rawConfig.uiBundles.isDevMode();
const dllContext = rawConfig.context;
@@ -195,6 +214,7 @@ function extendRawConfig(rawConfig) {
// Export dll config map
return {
dllAlias,
+ dllValidateSyntax,
dllNoParseRules,
dllDevMode,
dllContext,
diff --git a/src/plugins/dashboard/public/url_generator.test.ts b/src/plugins/dashboard/public/url_generator.test.ts
index 5dfc47b694f60..d48aacc1d8c1e 100644
--- a/src/plugins/dashboard/public/url_generator.test.ts
+++ b/src/plugins/dashboard/public/url_generator.test.ts
@@ -21,6 +21,7 @@ import { createDirectAccessDashboardLinkGenerator } from './url_generator';
import { hashedItemStore } from '../../kibana_utils/public';
// eslint-disable-next-line
import { mockStorage } from '../../kibana_utils/public/storage/hashed_item_store/mock';
+import { esFilters } from '../../data/public';
const APP_BASE_PATH: string = 'xyz/app/kibana';
@@ -50,12 +51,13 @@ describe('dashboard url generator', () => {
);
});
- test('creates a link with filters, time range and query to a saved object', async () => {
+ test('creates a link with filters, time range, refresh interval and query to a saved object', async () => {
const generator = createDirectAccessDashboardLinkGenerator(() =>
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 },
dashboardId: '123',
filters: [
{
@@ -66,11 +68,22 @@ describe('dashboard url generator', () => {
},
query: { query: 'hi' },
},
+ {
+ meta: {
+ alias: null,
+ disabled: false,
+ negate: false,
+ },
+ query: { query: 'hi' },
+ $state: {
+ store: esFilters.FilterStateStore.GLOBAL_STATE,
+ },
+ },
],
query: { query: 'bye', language: 'kuery' },
});
expect(url).toMatchInlineSnapshot(
- `"xyz/app/kibana#/dashboard/123?_a=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:hi))),query:(language:kuery,query:bye))&_g=(time:(from:now-15m,mode:relative,to:now))"`
+ `"xyz/app/kibana#/dashboard/123?_a=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:hi))),query:(language:kuery,query:bye))&_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!f,negate:!f),query:(query:hi))),refreshInterval:(pause:!f,value:300),time:(from:now-15m,mode:relative,to:now))"`
);
});
diff --git a/src/plugins/dashboard/public/url_generator.ts b/src/plugins/dashboard/public/url_generator.ts
index 5f1255bc9d45f..0fdf395e75bca 100644
--- a/src/plugins/dashboard/public/url_generator.ts
+++ b/src/plugins/dashboard/public/url_generator.ts
@@ -17,7 +17,14 @@
* under the License.
*/
-import { TimeRange, Filter, Query } from '../../data/public';
+import {
+ TimeRange,
+ Filter,
+ Query,
+ esFilters,
+ QueryState,
+ RefreshInterval,
+} from '../../data/public';
import { setStateToKbnUrl } from '../../kibana_utils/public';
import { UrlGeneratorsDefinition, UrlGeneratorState } from '../../share/public';
@@ -36,10 +43,15 @@ export type DashboardAppLinkGeneratorState = UrlGeneratorState<{
* Optionally set the time range in the time picker.
*/
timeRange?: TimeRange;
+
+ /**
+ * 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. This will set
- * app filters, not global filters.
+ * saved dashboard has filters saved with it, this will _replace_ those filters.
*/
filters?: Filter[];
/**
@@ -64,21 +76,32 @@ export const createDirectAccessDashboardLinkGenerator = (
const appBasePath = startServices.appBasePath;
const hash = state.dashboardId ? `dashboard/${state.dashboardId}` : `dashboard`;
+ const cleanEmptyKeys = (stateObj: Record) => {
+ Object.keys(stateObj).forEach(key => {
+ if (stateObj[key] === undefined) {
+ delete stateObj[key];
+ }
+ });
+ return stateObj;
+ };
+
const appStateUrl = setStateToKbnUrl(
STATE_STORAGE_KEY,
- {
+ cleanEmptyKeys({
query: state.query,
- filters: state.filters,
- },
+ filters: state.filters?.filter(f => !esFilters.isFilterPinned(f)),
+ }),
{ useHash },
`${appBasePath}#/${hash}`
);
- return setStateToKbnUrl(
+ return setStateToKbnUrl(
GLOBAL_STATE_STORAGE_KEY,
- {
+ cleanEmptyKeys({
time: state.timeRange,
- },
+ filters: state.filters?.filter(f => esFilters.isFilterPinned(f)),
+ refreshInterval: state.refreshInterval,
+ }),
{ useHash },
appStateUrl
);
diff --git a/src/plugins/data/common/query/filter_manager/compare_filters.test.ts b/src/plugins/data/common/query/filter_manager/compare_filters.test.ts
index b0bb2f754d6cf..0c3947ade8221 100644
--- a/src/plugins/data/common/query/filter_manager/compare_filters.test.ts
+++ b/src/plugins/data/common/query/filter_manager/compare_filters.test.ts
@@ -197,6 +197,22 @@ describe('filter manager utilities', () => {
expect(compareFilters([f1], [f2], COMPARE_ALL_OPTIONS)).toBeTruthy();
});
+ test('should compare alias with alias true', () => {
+ const f1 = {
+ $state: { store: FilterStateStore.GLOBAL_STATE },
+ ...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index', ''),
+ };
+ const f2 = {
+ $state: { store: FilterStateStore.GLOBAL_STATE },
+ ...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index', ''),
+ };
+
+ f2.meta.alias = 'wassup';
+ f2.meta.alias = 'dog';
+
+ expect(compareFilters([f1], [f2], { alias: true })).toBeFalsy();
+ });
+
test('should compare alias with COMPARE_ALL_OPTIONS', () => {
const f1 = {
$state: { store: FilterStateStore.GLOBAL_STATE },
diff --git a/src/plugins/data/common/query/filter_manager/compare_filters.ts b/src/plugins/data/common/query/filter_manager/compare_filters.ts
index e047d5e0665d5..3be52a9a60977 100644
--- a/src/plugins/data/common/query/filter_manager/compare_filters.ts
+++ b/src/plugins/data/common/query/filter_manager/compare_filters.ts
@@ -46,7 +46,7 @@ const mapFilter = (
if (comparators.negate) cleaned.negate = filter.meta && Boolean(filter.meta.negate);
if (comparators.disabled) cleaned.disabled = filter.meta && Boolean(filter.meta.disabled);
- if (comparators.disabled) cleaned.alias = filter.meta?.alias;
+ if (comparators.alias) cleaned.alias = filter.meta?.alias;
return cleaned;
};
diff --git a/src/plugins/data/public/query/state_sync/connect_to_query_state.test.ts b/src/plugins/data/public/query/state_sync/connect_to_query_state.test.ts
index 5da929c441cde..06e4c1c8be6d5 100644
--- a/src/plugins/data/public/query/state_sync/connect_to_query_state.test.ts
+++ b/src/plugins/data/public/query/state_sync/connect_to_query_state.test.ts
@@ -463,3 +463,97 @@ describe('connect_to_app_state', () => {
});
});
});
+
+describe('filters with different state', () => {
+ let queryServiceStart: QueryStart;
+ let filterManager: FilterManager;
+ let state: BaseStateContainer;
+ let stateSub: Subscription;
+ let stateChangeTriggered = jest.fn();
+ let filterManagerChangeSub: Subscription;
+ let filterManagerChangeTriggered = jest.fn();
+
+ let filter: Filter;
+
+ beforeEach(() => {
+ const queryService = new QueryService();
+ queryService.setup({
+ uiSettings: setupMock.uiSettings,
+ storage: new Storage(new StubBrowserStorage()),
+ });
+ queryServiceStart = queryService.start(startMock.savedObjects);
+ filterManager = queryServiceStart.filterManager;
+
+ state = createStateContainer({});
+ stateChangeTriggered = jest.fn();
+ stateSub = state.state$.subscribe(stateChangeTriggered);
+
+ filterManagerChangeTriggered = jest.fn();
+ filterManagerChangeSub = filterManager.getUpdates$().subscribe(filterManagerChangeTriggered);
+
+ filter = getFilter(FilterStateStore.GLOBAL_STATE, true, true, 'key1', 'value1');
+ });
+
+ // applies filter state changes, changes only internal $state.store value
+ function runChanges() {
+ filter = { ...filter, $state: { store: FilterStateStore.GLOBAL_STATE } };
+
+ state.set({
+ filters: [filter],
+ });
+
+ filter = { ...filter, $state: { store: FilterStateStore.APP_STATE } };
+
+ state.set({
+ filters: [filter],
+ });
+
+ filter = { ...filter };
+ delete filter.$state;
+
+ state.set({
+ filters: [filter],
+ });
+ }
+
+ test('when syncing all filters, changes to filter.state$ should be taken into account', () => {
+ const stop = connectToQueryState(queryServiceStart, state, {
+ filters: true,
+ });
+
+ runChanges();
+
+ expect(filterManagerChangeTriggered).toBeCalledTimes(3);
+
+ stop();
+ });
+
+ test('when syncing app state filters, changes to filter.state$ should be ignored', () => {
+ const stop = connectToQueryState(queryServiceStart, state, {
+ filters: FilterStateStore.APP_STATE,
+ });
+
+ runChanges();
+
+ expect(filterManagerChangeTriggered).toBeCalledTimes(1);
+
+ stop();
+ });
+
+ test('when syncing global state filters, changes to filter.state$ should be ignored', () => {
+ const stop = connectToQueryState(queryServiceStart, state, {
+ filters: FilterStateStore.GLOBAL_STATE,
+ });
+
+ runChanges();
+
+ expect(filterManagerChangeTriggered).toBeCalledTimes(1);
+
+ stop();
+ });
+
+ afterEach(() => {
+ stateSub.unsubscribe();
+ filterManagerChangeSub.unsubscribe();
+ });
+});
diff --git a/src/plugins/data/public/query/state_sync/connect_to_query_state.ts b/src/plugins/data/public/query/state_sync/connect_to_query_state.ts
index 331d8969f2483..3256c1cbd65a1 100644
--- a/src/plugins/data/public/query/state_sync/connect_to_query_state.ts
+++ b/src/plugins/data/public/query/state_sync/connect_to_query_state.ts
@@ -91,7 +91,10 @@ export const connectToQueryState = (
} else if (syncConfig.filters === FilterStateStore.GLOBAL_STATE) {
if (
!initialState.filters ||
- !compareFilters(initialState.filters, filterManager.getGlobalFilters(), COMPARE_ALL_OPTIONS)
+ !compareFilters(initialState.filters, filterManager.getGlobalFilters(), {
+ ...COMPARE_ALL_OPTIONS,
+ state: false,
+ })
) {
initialState.filters = filterManager.getGlobalFilters();
initialDirty = true;
@@ -99,7 +102,10 @@ export const connectToQueryState = (
} else if (syncConfig.filters === FilterStateStore.APP_STATE) {
if (
!initialState.filters ||
- !compareFilters(initialState.filters, filterManager.getAppFilters(), COMPARE_ALL_OPTIONS)
+ !compareFilters(initialState.filters, filterManager.getAppFilters(), {
+ ...COMPARE_ALL_OPTIONS,
+ state: false,
+ })
) {
initialState.filters = filterManager.getAppFilters();
initialDirty = true;
@@ -173,11 +179,21 @@ export const connectToQueryState = (
filterManager.setFilters(_.cloneDeep(filters));
}
} else if (syncConfig.filters === FilterStateStore.APP_STATE) {
- if (!compareFilters(filters, filterManager.getAppFilters(), COMPARE_ALL_OPTIONS)) {
+ if (
+ !compareFilters(filters, filterManager.getAppFilters(), {
+ ...COMPARE_ALL_OPTIONS,
+ state: false,
+ })
+ ) {
filterManager.setAppFilters(_.cloneDeep(filters));
}
} else if (syncConfig.filters === FilterStateStore.GLOBAL_STATE) {
- if (!compareFilters(filters, filterManager.getGlobalFilters(), COMPARE_ALL_OPTIONS)) {
+ if (
+ !compareFilters(filters, filterManager.getGlobalFilters(), {
+ ...COMPARE_ALL_OPTIONS,
+ state: false,
+ })
+ ) {
filterManager.setGlobalFilters(_.cloneDeep(filters));
}
}
diff --git a/tasks/config/run.js b/tasks/config/run.js
index 50417ebd8333d..dca0f69c35668 100644
--- a/tasks/config/run.js
+++ b/tasks/config/run.js
@@ -58,6 +58,7 @@ module.exports = function(grunt) {
'--env.name=development',
'--plugins.initialize=false',
'--optimize.bundleFilter=tests',
+ '--optimize.validateSyntaxOfNodeModules=false',
'--server.port=5610',
'--migrations.skip=true',
];
diff --git a/test/plugin_functional/plugins/kbn_top_nav/kibana.json b/test/plugin_functional/plugins/kbn_top_nav/kibana.json
new file mode 100644
index 0000000000000..b274e80b9ef65
--- /dev/null
+++ b/test/plugin_functional/plugins/kbn_top_nav/kibana.json
@@ -0,0 +1,9 @@
+{
+ "id": "kbn_top_nav",
+ "version": "0.0.1",
+ "kibanaVersion": "kibana",
+ "configPath": ["kbn_top_nav"],
+ "server": false,
+ "ui": true,
+ "requiredPlugins": ["navigation"]
+}
\ No newline at end of file
diff --git a/test/plugin_functional/plugins/kbn_top_nav/package.json b/test/plugin_functional/plugins/kbn_top_nav/package.json
new file mode 100644
index 0000000000000..510d681a4a75c
--- /dev/null
+++ b/test/plugin_functional/plugins/kbn_top_nav/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "kbn_top_nav",
+ "version": "1.0.0",
+ "main": "target/test/plugin_functional/plugins/kbn_top_nav",
+ "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.7.2"
+ }
+}
+
diff --git a/test/plugin_functional/plugins/kbn_tp_top_nav/public/top_nav.tsx b/test/plugin_functional/plugins/kbn_top_nav/public/application.tsx
similarity index 71%
rename from test/plugin_functional/plugins/kbn_tp_top_nav/public/top_nav.tsx
rename to test/plugin_functional/plugins/kbn_top_nav/public/application.tsx
index f77db4fe1654e..0f65e6159796b 100644
--- a/test/plugin_functional/plugins/kbn_tp_top_nav/public/top_nav.tsx
+++ b/test/plugin_functional/plugins/kbn_top_nav/public/application.tsx
@@ -18,11 +18,15 @@
*/
import React from 'react';
-import './initialize';
-import { npStart } from 'ui/new_platform';
+import { render, unmountComponentAtNode } from 'react-dom';
+import { AppMountParameters } from 'kibana/public';
+import { AppPluginDependencies } from './types';
-export const AppWithTopNav = () => {
- const { TopNavMenu } = npStart.plugins.navigation.ui;
+export const renderApp = (
+ depsStart: AppPluginDependencies,
+ { appBasePath, element }: AppMountParameters
+) => {
+ const { TopNavMenu } = depsStart.navigation.ui;
const config = [
{
id: 'new',
@@ -32,10 +36,12 @@ export const AppWithTopNav = () => {
testId: 'demoNewButton',
},
];
-
- return (
+ render(
Hey
-
+ ,
+ element
);
+
+ return () => unmountComponentAtNode(element);
};
diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/test_samples/index.ts b/test/plugin_functional/plugins/kbn_top_nav/public/index.ts
similarity index 75%
rename from src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/test_samples/index.ts
rename to test/plugin_functional/plugins/kbn_top_nav/public/index.ts
index 4f0537aff5dc2..bd478f1dd3bdb 100644
--- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/test_samples/index.ts
+++ b/test/plugin_functional/plugins/kbn_top_nav/public/index.ts
@@ -17,5 +17,8 @@
* under the License.
*/
-// eslint-disable-next-line
-export * from '../../../../../../../../plugins/embeddable/public/lib/test_samples';
+import { PluginInitializer } from 'kibana/public';
+import { TopNavTestPlugin, TopNavTestPluginSetup, TopNavTestPluginStart } from './plugin';
+
+export const plugin: PluginInitializer = () =>
+ new TopNavTestPlugin();
diff --git a/test/plugin_functional/plugins/kbn_top_nav/public/plugin.tsx b/test/plugin_functional/plugins/kbn_top_nav/public/plugin.tsx
new file mode 100644
index 0000000000000..a433de98357fb
--- /dev/null
+++ b/test/plugin_functional/plugins/kbn_top_nav/public/plugin.tsx
@@ -0,0 +1,65 @@
+/*
+ * 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 { CoreSetup, Plugin, AppMountParameters } from 'kibana/public';
+import { NavigationPublicPluginSetup } from '../../../../../src/plugins/navigation/public';
+import { AppPluginDependencies } from './types';
+
+export class TopNavTestPlugin implements Plugin {
+ public setup(core: CoreSetup, { navigation }: { navigation: NavigationPublicPluginSetup }) {
+ const customExtension = {
+ id: 'registered-prop',
+ label: 'Registered Button',
+ description: 'Registered Demo',
+ run() {},
+ testId: 'demoRegisteredNewButton',
+ };
+
+ navigation.registerMenuItem(customExtension);
+
+ const customDiscoverExtension = {
+ id: 'registered-discover-prop',
+ label: 'Registered Discover Button',
+ description: 'Registered Discover Demo',
+ run() {},
+ testId: 'demoDiscoverRegisteredNewButton',
+ appName: 'discover',
+ };
+
+ navigation.registerMenuItem(customDiscoverExtension);
+
+ core.application.register({
+ id: 'topNavMenu',
+ title: 'Top nav menu example',
+ async mount(params: AppMountParameters) {
+ const { renderApp } = await import('./application');
+ const services = await core.getStartServices();
+ return renderApp(services[1] as AppPluginDependencies, params);
+ },
+ });
+
+ return {};
+ }
+
+ public start() {}
+ public stop() {}
+}
+
+export type TopNavTestPluginSetup = ReturnType;
+export type TopNavTestPluginStart = ReturnType;
diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/legacy.ts b/test/plugin_functional/plugins/kbn_top_nav/public/types.ts
similarity index 81%
rename from src/legacy/core_plugins/embeddable_api/public/np_ready/public/legacy.ts
rename to test/plugin_functional/plugins/kbn_top_nav/public/types.ts
index 5357c2458e3b0..c70a78bedb54f 100644
--- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/legacy.ts
+++ b/test/plugin_functional/plugins/kbn_top_nav/public/types.ts
@@ -17,8 +17,8 @@
* under the License.
*/
-// eslint-disable-next-line
-import { npSetup, npStart } from 'ui/new_platform';
+import { NavigationPublicPluginStart } from '../../../../../src/plugins/navigation/public';
-export const setup = npSetup.plugins.embeddable;
-export const start = npStart.plugins.embeddable;
+export interface AppPluginDependencies {
+ navigation: NavigationPublicPluginStart;
+}
diff --git a/test/plugin_functional/plugins/kbn_tp_top_nav/tsconfig.json b/test/plugin_functional/plugins/kbn_top_nav/tsconfig.json
similarity index 100%
rename from test/plugin_functional/plugins/kbn_tp_top_nav/tsconfig.json
rename to test/plugin_functional/plugins/kbn_top_nav/tsconfig.json
diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/kibana.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/kibana.json
new file mode 100644
index 0000000000000..622cbd80090ba
--- /dev/null
+++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/kibana.json
@@ -0,0 +1,10 @@
+{
+ "id": "kbn_tp_custom_visualizations",
+ "version": "0.0.1",
+ "kibanaVersion": "kibana",
+ "requiredPlugins": [
+ "visualizations"
+ ],
+ "server": false,
+ "ui": true
+}
diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json
index 344aae30b5bbc..9ee7845816faa 100644
--- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json
+++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json
@@ -1,6 +1,7 @@
{
"name": "kbn_tp_custom_visualizations",
"version": "1.0.0",
+ "main": "target/test/plugin_functional/plugins/kbn_tp_custom_visualizations",
"kibana": {
"version": "kibana",
"templateVersion": "1.0.0"
@@ -9,5 +10,13 @@
"dependencies": {
"@elastic/eui": "21.0.1",
"react": "^16.12.0"
+ },
+ "scripts": {
+ "kbn": "node ../../../../scripts/kbn.js",
+ "build": "rm -rf './target' && tsc"
+ },
+ "devDependencies": {
+ "@kbn/plugin-helpers": "9.0.2",
+ "typescript": "3.7.2"
}
}
diff --git a/src/legacy/core_plugins/embeddable_api/index.ts b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/index.ts
similarity index 68%
rename from src/legacy/core_plugins/embeddable_api/index.ts
rename to test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/index.ts
index 52206e3d0f105..cb821a2698479 100644
--- a/src/legacy/core_plugins/embeddable_api/index.ts
+++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/index.ts
@@ -17,9 +17,14 @@
* under the License.
*/
-import { LegacyPluginApi, LegacyPluginSpec, ArrayOrItem } from 'src/legacy/plugin_discovery/types';
+import { PluginInitializer } from 'kibana/public';
+import {
+ CustomVisualizationsPublicPlugin,
+ CustomVisualizationsSetup,
+ CustomVisualizationsStart,
+} from './plugin';
-// eslint-disable-next-line import/no-default-export
-export default function(kibana: LegacyPluginApi): ArrayOrItem {
- return new kibana.Plugin({});
-}
+export { CustomVisualizationsPublicPlugin as Plugin };
+
+export const plugin: PluginInitializer = () =>
+ new CustomVisualizationsPublicPlugin();
diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/plugin.ts b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/plugin.ts
new file mode 100644
index 0000000000000..1be4aa9ee42ae
--- /dev/null
+++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/plugin.ts
@@ -0,0 +1,61 @@
+/*
+ * 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 { CoreSetup, Plugin } from 'kibana/public';
+import { VisualizationsSetup } from '../../../../../src/plugins/visualizations/public';
+import { SelfChangingEditor } from './self_changing_vis/self_changing_editor';
+import { SelfChangingComponent } from './self_changing_vis/self_changing_components';
+
+export interface SetupDependencies {
+ visualizations: VisualizationsSetup;
+}
+
+export class CustomVisualizationsPublicPlugin
+ implements Plugin {
+ public setup(core: CoreSetup, setupDeps: SetupDependencies) {
+ setupDeps.visualizations.createReactVisualization({
+ name: 'self_changing_vis',
+ title: 'Self Changing Vis',
+ icon: 'controlsHorizontal',
+ description:
+ 'This visualization is able to change its own settings, that you could also set in the editor.',
+ visConfig: {
+ component: SelfChangingComponent,
+ defaults: {
+ counter: 0,
+ },
+ },
+ editorConfig: {
+ optionTabs: [
+ {
+ name: 'options',
+ title: 'Options',
+ editor: SelfChangingEditor,
+ },
+ ],
+ },
+ requestHandler: 'none',
+ });
+ }
+
+ public start() {}
+ public stop() {}
+}
+
+export type CustomVisualizationsSetup = ReturnType;
+export type CustomVisualizationsStart = ReturnType;
diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_components.js b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_components.js
deleted file mode 100644
index c5b074db43a1b..0000000000000
--- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_components.js
+++ /dev/null
@@ -1,52 +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 React from 'react';
-
-import { EuiBadge } from '@elastic/eui';
-
-export class SelfChangingComponent extends React.Component {
- onClick = () => {
- this.props.vis.params.counter++;
- this.props.vis.updateState();
- };
-
- render() {
- return (
-
-
- {this.props.vis.params.counter}
-
-
- );
- }
-
- componentDidMount() {
- this.props.renderComplete();
- }
-
- componentDidUpdate() {
- this.props.renderComplete();
- }
-}
diff --git a/test/plugin_functional/plugins/kbn_tp_top_nav/index.js b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_components.tsx
similarity index 59%
rename from test/plugin_functional/plugins/kbn_tp_top_nav/index.js
rename to test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_components.tsx
index b4c3e05c28b66..2f01908122457 100644
--- a/test/plugin_functional/plugins/kbn_tp_top_nav/index.js
+++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_components.tsx
@@ -17,15 +17,32 @@
* under the License.
*/
-export default function(kibana) {
- return new kibana.Plugin({
- uiExports: {
- app: {
- title: 'Top Nav Menu test',
- description: 'This is a sample plugin for the functional tests.',
- main: 'plugins/kbn_tp_top_nav/app',
- },
- hacks: ['plugins/kbn_tp_top_nav/initialize'],
- },
+import React, { useEffect } from 'react';
+
+import { EuiBadge } from '@elastic/eui';
+
+interface SelfChangingComponentProps {
+ renderComplete: () => {};
+ visParams: {
+ counter: number;
+ };
+}
+
+export function SelfChangingComponent(props: SelfChangingComponentProps) {
+ useEffect(() => {
+ props.renderComplete();
});
+
+ return (
+
+ {}}
+ data-test-subj="counter"
+ onClickAriaLabel="Increase counter"
+ color="primary"
+ >
+ {props.visParams.counter}
+
+
+ );
}
diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_editor.js b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_editor.tsx
similarity index 76%
rename from test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_editor.js
rename to test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_editor.tsx
index fa3a0c8b9f6fe..d3f66d708603c 100644
--- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_editor.js
+++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_editor.tsx
@@ -20,10 +20,15 @@
import React from 'react';
import { EuiFieldNumber, EuiFormRow } from '@elastic/eui';
+import { VisOptionsProps } from '../../../../../../src/legacy/core_plugins/vis_default_editor/public/vis_options_props';
-export class SelfChangingEditor extends React.Component {
- onCounterChange = ev => {
- this.props.setValue('counter', parseInt(ev.target.value));
+interface CounterParams {
+ counter: number;
+}
+
+export class SelfChangingEditor extends React.Component> {
+ onCounterChange = (ev: any) => {
+ this.props.setValue('counter', parseInt(ev.target.value, 10));
};
render() {
diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/tsconfig.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/tsconfig.json
new file mode 100644
index 0000000000000..d8096d9aab27a
--- /dev/null
+++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./target",
+ "skipLibCheck": true,
+ "types": [
+ "node",
+ "jest",
+ "react"
+ ]
+ },
+ "include": [
+ "index.ts",
+ "public/**/*.ts",
+ "public/**/*.tsx",
+ "../../../../typings/**/*",
+ ],
+ "exclude": []
+}
\ No newline at end of file
diff --git a/test/plugin_functional/plugins/kbn_tp_top_nav/package.json b/test/plugin_functional/plugins/kbn_tp_top_nav/package.json
deleted file mode 100644
index 7102d24d3292d..0000000000000
--- a/test/plugin_functional/plugins/kbn_tp_top_nav/package.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "name": "kbn_tp_top_nav",
- "version": "1.0.0",
- "kibana": {
- "version": "kibana",
- "templateVersion": "1.0.0"
- },
- "license": "Apache-2.0"
-}
diff --git a/test/plugin_functional/plugins/kbn_tp_top_nav/public/app.js b/test/plugin_functional/plugins/kbn_tp_top_nav/public/app.js
deleted file mode 100644
index e7f97e68c086d..0000000000000
--- a/test/plugin_functional/plugins/kbn_tp_top_nav/public/app.js
+++ /dev/null
@@ -1,54 +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 React from 'react';
-import { render, unmountComponentAtNode } from 'react-dom';
-
-import { uiModules } from 'ui/modules';
-import chrome from 'ui/chrome';
-
-// This is required so some default styles and required scripts/Angular modules are loaded,
-// or the timezone setting is correctly applied.
-import 'ui/autoload/all';
-
-import { AppWithTopNav } from './top_nav';
-
-const app = uiModules.get('apps/topnavDemoPlugin', ['kibana']);
-
-app.config($locationProvider => {
- $locationProvider.html5Mode({
- enabled: false,
- requireBase: false,
- rewriteLinks: false,
- });
-});
-
-function RootController($scope, $element) {
- const domNode = $element[0];
-
- // render react to DOM
- render(, domNode);
-
- // unmount react on controller destroy
- $scope.$on('$destroy', () => {
- unmountComponentAtNode(domNode);
- });
-}
-
-chrome.setRootController('topnavDemoPlugin', RootController);
diff --git a/test/plugin_functional/plugins/kbn_tp_top_nav/public/initialize.js b/test/plugin_functional/plugins/kbn_tp_top_nav/public/initialize.js
deleted file mode 100644
index d46e47f6d248a..0000000000000
--- a/test/plugin_functional/plugins/kbn_tp_top_nav/public/initialize.js
+++ /dev/null
@@ -1,41 +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 { npSetup } from 'ui/new_platform';
-
-const customExtension = {
- id: 'registered-prop',
- label: 'Registered Button',
- description: 'Registered Demo',
- run() {},
- testId: 'demoRegisteredNewButton',
-};
-
-npSetup.plugins.navigation.registerMenuItem(customExtension);
-
-const customDiscoverExtension = {
- id: 'registered-discover-prop',
- label: 'Registered Discover Button',
- description: 'Registered Discover Demo',
- run() {},
- testId: 'demoDiscoverRegisteredNewButton',
- appName: 'discover',
-};
-
-npSetup.plugins.navigation.registerMenuItem(customDiscoverExtension);
diff --git a/test/plugin_functional/test_suites/custom_visualizations/self_changing_vis.js b/test/plugin_functional/test_suites/custom_visualizations/self_changing_vis.js
index ef6f0a626bd15..83258a1ca3bdc 100644
--- a/test/plugin_functional/test_suites/custom_visualizations/self_changing_vis.js
+++ b/test/plugin_functional/test_suites/custom_visualizations/self_changing_vis.js
@@ -28,11 +28,7 @@ export default function({ getService, getPageObjects }) {
return await testSubjects.getVisibleText('counter');
}
- async function getEditorValue() {
- return await testSubjects.getAttribute('counterEditor', 'value');
- }
-
- describe.skip('self changing vis', function describeIndexTests() {
+ describe('self changing vis', function describeIndexTests() {
before(async () => {
await PageObjects.visualize.navigateToNewVisualization();
await PageObjects.visualize.clickVisType('self_changing_vis');
@@ -45,16 +41,17 @@ export default function({ getService, getPageObjects }) {
const isApplyEnabled = await PageObjects.visEditor.isApplyEnabled();
expect(isApplyEnabled).to.be(true);
await PageObjects.visEditor.clickGo();
+ await renderable.waitForRender();
const counter = await getCounterValue();
expect(counter).to.be('10');
});
- it('should allow changing params from within the vis', async () => {
+ it.skip('should allow changing params from within the vis', async () => {
await testSubjects.click('counter');
await renderable.waitForRender();
const visValue = await getCounterValue();
expect(visValue).to.be('11');
- const editorValue = await getEditorValue();
+ const editorValue = await testSubjects.getAttribute('counterEditor', 'value');
expect(editorValue).to.be('11');
// If changing a param from within the vis it should immediately apply and not bring editor in an unchanged state
const isApplyEnabled = await PageObjects.visEditor.isApplyEnabled();
diff --git a/x-pack/legacy/plugins/apm/index.ts b/x-pack/legacy/plugins/apm/index.ts
index d1f7ce325d23e..d2383acd45eba 100644
--- a/x-pack/legacy/plugins/apm/index.ts
+++ b/x-pack/legacy/plugins/apm/index.ts
@@ -105,10 +105,17 @@ export const apm: LegacyPluginInitializer = kibana => {
privileges: {
all: {
app: ['apm', 'kibana'],
- api: ['apm', 'apm_write', 'actions-read', 'alerting-read'],
+ api: [
+ 'apm',
+ 'apm_write',
+ 'actions-read',
+ 'actions-all',
+ 'alerting-read',
+ 'alerting-all'
+ ],
catalogue: ['apm'],
savedObject: {
- all: ['action', 'action_task_params'],
+ all: ['alert', 'action', 'action_task_params'],
read: []
},
ui: [
@@ -124,13 +131,27 @@ export const apm: LegacyPluginInitializer = kibana => {
},
read: {
app: ['apm', 'kibana'],
- api: ['apm', 'actions-read', 'alerting-read'],
+ api: [
+ 'apm',
+ 'actions-read',
+ 'actions-all',
+ 'alerting-read',
+ 'alerting-all'
+ ],
catalogue: ['apm'],
savedObject: {
- all: ['action', 'action_task_params'],
+ all: ['alert', 'action', 'action_task_params'],
read: []
},
- ui: ['show', 'alerting:show', 'actions:show']
+ ui: [
+ 'show',
+ 'alerting:show',
+ 'actions:show',
+ 'alerting:save',
+ 'actions:save',
+ 'alerting:delete',
+ 'actions:delete'
+ ]
}
}
});
diff --git a/x-pack/legacy/plugins/apm/mappings.json b/x-pack/legacy/plugins/apm/mappings.json
index 5d14ae03f9a33..1e906dd2a5967 100644
--- a/x-pack/legacy/plugins/apm/mappings.json
+++ b/x-pack/legacy/plugins/apm/mappings.json
@@ -9,7 +9,7 @@
"properties": {
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
},
@@ -19,15 +19,15 @@
"properties": {
"composite": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"name": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
},
@@ -35,7 +35,7 @@
"properties": {
"name": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
},
@@ -43,15 +43,15 @@
"properties": {
"composite": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"name": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
}
@@ -65,7 +65,7 @@
"properties": {
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
},
@@ -75,15 +75,15 @@
"properties": {
"composite": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"name": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
},
@@ -91,15 +91,15 @@
"properties": {
"composite": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"name": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
},
@@ -107,15 +107,15 @@
"properties": {
"composite": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"name": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
}
@@ -129,7 +129,7 @@
"properties": {
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
},
@@ -139,15 +139,15 @@
"properties": {
"composite": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"name": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
},
@@ -155,15 +155,15 @@
"properties": {
"composite": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"name": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
},
@@ -171,15 +171,15 @@
"properties": {
"composite": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"name": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
}
@@ -193,7 +193,7 @@
"properties": {
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
},
@@ -203,15 +203,15 @@
"properties": {
"composite": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"name": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
},
@@ -219,15 +219,15 @@
"properties": {
"composite": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"name": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
},
@@ -235,15 +235,15 @@
"properties": {
"composite": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"name": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
}
@@ -257,7 +257,7 @@
"properties": {
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
},
@@ -267,15 +267,15 @@
"properties": {
"composite": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"name": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
},
@@ -283,15 +283,15 @@
"properties": {
"composite": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"name": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
},
@@ -299,15 +299,15 @@
"properties": {
"composite": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"name": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
}
@@ -321,7 +321,7 @@
"properties": {
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
},
@@ -331,15 +331,15 @@
"properties": {
"composite": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"name": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
},
@@ -347,15 +347,15 @@
"properties": {
"composite": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"name": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
},
@@ -363,15 +363,15 @@
"properties": {
"composite": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"name": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
}
@@ -385,7 +385,7 @@
"properties": {
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
},
@@ -395,15 +395,15 @@
"properties": {
"composite": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"name": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
},
@@ -411,15 +411,15 @@
"properties": {
"composite": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"name": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
},
@@ -427,15 +427,15 @@
"properties": {
"composite": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"name": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
}
@@ -449,7 +449,7 @@
"properties": {
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
},
@@ -459,15 +459,15 @@
"properties": {
"composite": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"name": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
},
@@ -475,15 +475,15 @@
"properties": {
"composite": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"name": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
},
@@ -491,15 +491,15 @@
"properties": {
"composite": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"name": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
},
"version": {
"type": "keyword",
- "ignore_above": 256
+ "ignore_above": 1024
}
}
}
diff --git a/x-pack/legacy/plugins/apm/public/components/app/Home/__snapshots__/Home.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/app/Home/__snapshots__/Home.test.tsx.snap
index 88d9d7864576f..2b1f835a14f4a 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/Home/__snapshots__/Home.test.tsx.snap
+++ b/x-pack/legacy/plugins/apm/public/components/app/Home/__snapshots__/Home.test.tsx.snap
@@ -15,6 +15,10 @@ exports[`Home component should render services 1`] = `
"chrome": Object {
"setBreadcrumbs": [Function],
},
+ "docLinks": Object {
+ "DOC_LINK_VERSION": "0",
+ "ELASTIC_WEBSITE_URL": "https://www.elastic.co/",
+ },
"http": Object {
"basePath": Object {
"prepend": [Function],
@@ -27,9 +31,6 @@ exports[`Home component should render services 1`] = `
},
},
},
- "packageInfo": Object {
- "version": "0",
- },
"plugins": Object {},
}
}
@@ -55,6 +56,10 @@ exports[`Home component should render traces 1`] = `
"chrome": Object {
"setBreadcrumbs": [Function],
},
+ "docLinks": Object {
+ "DOC_LINK_VERSION": "0",
+ "ELASTIC_WEBSITE_URL": "https://www.elastic.co/",
+ },
"http": Object {
"basePath": Object {
"prepend": [Function],
@@ -67,9 +72,6 @@ exports[`Home component should render traces 1`] = `
},
},
},
- "packageInfo": Object {
- "version": "0",
- },
"plugins": Object {},
}
}
diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx
new file mode 100644
index 0000000000000..938962cc9dd18
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx
@@ -0,0 +1,76 @@
+/*
+ * 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 { storiesOf } from '@storybook/react';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { TraceAPIResponse } from '../../../../../../../../../plugins/apm/server/lib/traces/get_trace';
+import { WaterfallContainer } from './index';
+import {
+ location,
+ urlParams,
+ simpleTrace,
+ traceWithErrors,
+ traceChildStartBeforeParent
+} from './waterfallContainer.stories.data';
+import { getWaterfall } from './Waterfall/waterfall_helpers/waterfall_helpers';
+
+storiesOf('app/TransactionDetails/Waterfall', module).add(
+ 'simple',
+ () => {
+ const waterfall = getWaterfall(
+ simpleTrace as TraceAPIResponse,
+ '975c8d5bfd1dd20b'
+ );
+ return (
+
+ );
+ },
+ { info: { source: false } }
+);
+
+storiesOf('app/TransactionDetails/Waterfall', module).add(
+ 'with errors',
+ () => {
+ const waterfall = getWaterfall(
+ (traceWithErrors as unknown) as TraceAPIResponse,
+ '975c8d5bfd1dd20b'
+ );
+ return (
+
+ );
+ },
+ { info: { source: false } }
+);
+
+storiesOf('app/TransactionDetails/Waterfall', module).add(
+ 'child starts before parent',
+ () => {
+ const waterfall = getWaterfall(
+ traceChildStartBeforeParent as TraceAPIResponse,
+ '975c8d5bfd1dd20b'
+ );
+ return (
+
+ );
+ },
+ { info: { source: false } }
+);
diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/waterfallContainer.stories.data.ts b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/waterfallContainer.stories.data.ts
new file mode 100644
index 0000000000000..835183e73b298
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/waterfallContainer.stories.data.ts
@@ -0,0 +1,1647 @@
+/*
+ * 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 { Location } from 'history';
+import { IUrlParams } from '../../../../../context/UrlParamsContext/types';
+
+export const location = {
+ pathname: '/services/opbeans-go/transactions/view',
+ search:
+ '?rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0&kuery=service.name%253A%2520%2522opbeans-java%2522%2520or%2520service.name%2520%253A%2520%2522opbeans-go%2522&traceId=513d33fafe99bbe6134749310c9b5322&transactionId=975c8d5bfd1dd20b&transactionName=GET%20%2Fapi%2Forders&transactionType=request',
+ hash: ''
+} as Location;
+
+export const urlParams = {
+ start: '2020-03-22T15:16:38.742Z',
+ end: '2020-03-23T15:16:38.742Z',
+ rangeFrom: 'now-24h',
+ rangeTo: 'now',
+ refreshPaused: true,
+ refreshInterval: 0,
+ page: 0,
+ transactionId: '975c8d5bfd1dd20b',
+ traceId: '513d33fafe99bbe6134749310c9b5322',
+ kuery: 'service.name: "opbeans-java" or service.name : "opbeans-go"',
+ transactionName: 'GET /api/orders',
+ transactionType: 'request',
+ processorEvent: 'transaction',
+ serviceName: 'opbeans-go'
+} as IUrlParams;
+
+export const simpleTrace = {
+ trace: {
+ items: [
+ {
+ container: {
+ id: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e'
+ },
+ process: {
+ pid: 6,
+ title: '/usr/lib/jvm/java-10-openjdk-amd64/bin/java',
+ ppid: 1
+ },
+ agent: {
+ name: 'java',
+ ephemeral_id: '99ce8403-5875-4945-b074-d37dc10563eb',
+ version: '1.14.1-SNAPSHOT'
+ },
+ internal: {
+ sampler: {
+ value: 46
+ }
+ },
+ source: {
+ ip: '172.19.0.13'
+ },
+ processor: {
+ name: 'transaction',
+ event: 'transaction'
+ },
+ url: {
+ path: '/api/orders',
+ scheme: 'http',
+ port: 3000,
+ domain: '172.19.0.9',
+ full: 'http://172.19.0.9:3000/api/orders'
+ },
+ observer: {
+ hostname: 'f37f48d8b60b',
+ id: 'd8522e1f-be8e-43c2-b290-ac6b6c0f171e',
+ ephemeral_id: '6ed88f14-170e-478d-a4f5-ea5e7f4b16b9',
+ type: 'apm-server',
+ version: '8.0.0',
+ version_major: 8
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.785Z',
+ ecs: {
+ version: '1.4.0'
+ },
+ service: {
+ node: {
+ name:
+ '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e'
+ },
+ environment: 'production',
+ name: 'opbeans-java',
+ runtime: {
+ name: 'Java',
+ version: '10.0.2'
+ },
+ language: {
+ name: 'Java',
+ version: '10.0.2'
+ },
+ version: 'None'
+ },
+ host: {
+ hostname: '4cf84d094553',
+ os: {
+ platform: 'Linux'
+ },
+ ip: '172.19.0.9',
+ name: '4cf84d094553',
+ architecture: 'amd64'
+ },
+ http: {
+ request: {
+ headers: {
+ Accept: ['*/*'],
+ 'User-Agent': ['Python/3.7 aiohttp/3.3.2'],
+ Host: ['172.19.0.9:3000'],
+ 'Accept-Encoding': ['gzip, deflate']
+ },
+ method: 'get',
+ socket: {
+ encrypted: false,
+ remote_address: '172.19.0.13'
+ },
+ body: {
+ original: '[REDACTED]'
+ }
+ },
+ response: {
+ headers: {
+ 'Transfer-Encoding': ['chunked'],
+ Date: ['Mon, 23 Mar 2020 15:04:28 GMT'],
+ 'Content-Type': ['application/json;charset=ISO-8859-1']
+ },
+ status_code: 200,
+ finished: true,
+ headers_sent: true
+ },
+ version: '1.1'
+ },
+ client: {
+ ip: '172.19.0.13'
+ },
+ transaction: {
+ duration: {
+ us: 18842
+ },
+ result: 'HTTP 2xx',
+ name: 'DispatcherServlet#doGet',
+ id: '49809ad3c26adf74',
+ span_count: {
+ dropped: 0,
+ started: 1
+ },
+ type: 'request',
+ sampled: true
+ },
+ user_agent: {
+ original: 'Python/3.7 aiohttp/3.3.2',
+ name: 'Other',
+ device: {
+ name: 'Other'
+ }
+ },
+ timestamp: {
+ us: 1584975868785000
+ }
+ },
+ {
+ parent: {
+ id: 'fc107f7b556eb49b'
+ },
+ agent: {
+ name: 'go',
+ version: '1.7.2'
+ },
+ processor: {
+ name: 'transaction',
+ event: 'transaction'
+ },
+ url: {
+ path: '/api/orders',
+ scheme: 'http',
+ port: 3000,
+ domain: 'opbeans-go',
+ full: 'http://opbeans-go:3000/api/orders'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.787Z',
+ service: {
+ node: {
+ name:
+ 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29'
+ },
+ environment: 'production',
+ framework: {
+ name: 'gin',
+ version: 'v1.4.0'
+ },
+ name: 'opbeans-go',
+ runtime: {
+ name: 'gc',
+ version: 'go1.14.1'
+ },
+ language: {
+ name: 'go',
+ version: 'go1.14.1'
+ },
+ version: 'None'
+ },
+ transaction: {
+ duration: {
+ us: 16597
+ },
+ result: 'HTTP 2xx',
+ name: 'GET /api/orders',
+ id: '975c8d5bfd1dd20b',
+ span_count: {
+ dropped: 0,
+ started: 1
+ },
+ type: 'request',
+ sampled: true
+ },
+ timestamp: {
+ us: 1584975868787052
+ }
+ },
+ {
+ parent: {
+ id: 'daae24d83c269918'
+ },
+ agent: {
+ name: 'python',
+ version: '5.5.2'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ timestamp: {
+ us: 1584975868788603
+ },
+ processor: {
+ name: 'transaction',
+ event: 'transaction'
+ },
+ url: {
+ path: '/api/orders',
+ scheme: 'http',
+ port: 3000,
+ domain: 'opbeans-go',
+ full: 'http://opbeans-go:3000/api/orders'
+ },
+ '@timestamp': '2020-03-23T15:04:28.788Z',
+ service: {
+ node: {
+ name:
+ 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51'
+ },
+ environment: 'production',
+ framework: {
+ name: 'django',
+ version: '2.1.13'
+ },
+ name: 'opbeans-python',
+ runtime: {
+ name: 'CPython',
+ version: '3.6.10'
+ },
+ language: {
+ name: 'python',
+ version: '3.6.10'
+ },
+ version: 'None'
+ },
+ transaction: {
+ result: 'HTTP 2xx',
+ duration: {
+ us: 14648
+ },
+ name: 'GET opbeans.views.orders',
+ span_count: {
+ dropped: 0,
+ started: 1
+ },
+ id: '6fb0ff7365b87298',
+ type: 'request',
+ sampled: true
+ }
+ },
+ {
+ container: {
+ id: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e'
+ },
+ parent: {
+ id: '49809ad3c26adf74'
+ },
+ process: {
+ pid: 6,
+ title: '/usr/lib/jvm/java-10-openjdk-amd64/bin/java',
+ ppid: 1
+ },
+ agent: {
+ name: 'java',
+ ephemeral_id: '99ce8403-5875-4945-b074-d37dc10563eb',
+ version: '1.14.1-SNAPSHOT'
+ },
+ internal: {
+ sampler: {
+ value: 44
+ }
+ },
+ destination: {
+ address: 'opbeans-go',
+ port: 3000
+ },
+ processor: {
+ name: 'transaction',
+ event: 'span'
+ },
+ observer: {
+ hostname: 'f37f48d8b60b',
+ id: 'd8522e1f-be8e-43c2-b290-ac6b6c0f171e',
+ type: 'apm-server',
+ ephemeral_id: '6ed88f14-170e-478d-a4f5-ea5e7f4b16b9',
+ version: '8.0.0',
+ version_major: 8
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.785Z',
+ ecs: {
+ version: '1.4.0'
+ },
+ service: {
+ node: {
+ name:
+ '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e'
+ },
+ environment: 'production',
+ name: 'opbeans-java',
+ runtime: {
+ name: 'Java',
+ version: '10.0.2'
+ },
+ language: {
+ name: 'Java',
+ version: '10.0.2'
+ },
+ version: 'None'
+ },
+ host: {
+ hostname: '4cf84d094553',
+ os: {
+ platform: 'Linux'
+ },
+ ip: '172.19.0.9',
+ name: '4cf84d094553',
+ architecture: 'amd64'
+ },
+ connection: {
+ hash:
+ "{service.environment:'production'}/{service.name:'opbeans-java'}/{span.subtype:'http'}/{destination.address:'opbeans-go'}/{span.type:'external'}"
+ },
+ transaction: {
+ id: '49809ad3c26adf74'
+ },
+ timestamp: {
+ us: 1584975868785273
+ },
+ span: {
+ duration: {
+ us: 17530
+ },
+ subtype: 'http',
+ name: 'GET opbeans-go',
+ destination: {
+ service: {
+ resource: 'opbeans-go:3000',
+ name: 'http://opbeans-go:3000',
+ type: 'external'
+ }
+ },
+ http: {
+ response: {
+ status_code: 200
+ },
+ url: {
+ original: 'http://opbeans-go:3000/api/orders'
+ }
+ },
+ id: 'fc107f7b556eb49b',
+ type: 'external'
+ }
+ },
+ {
+ parent: {
+ id: '975c8d5bfd1dd20b'
+ },
+ agent: {
+ name: 'go',
+ version: '1.7.2'
+ },
+ processor: {
+ name: 'transaction',
+ event: 'span'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.787Z',
+ service: {
+ node: {
+ name:
+ 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29'
+ },
+ environment: 'production',
+ name: 'opbeans-go',
+ runtime: {
+ name: 'gc',
+ version: 'go1.14.1'
+ },
+ language: {
+ name: 'go',
+ version: 'go1.14.1'
+ },
+ version: 'None'
+ },
+ transaction: {
+ id: '975c8d5bfd1dd20b'
+ },
+ timestamp: {
+ us: 1584975868787174
+ },
+ span: {
+ duration: {
+ us: 16250
+ },
+ subtype: 'http',
+ destination: {
+ service: {
+ resource: 'opbeans-python:3000',
+ name: 'http://opbeans-python:3000',
+ type: 'external'
+ }
+ },
+ name: 'GET opbeans-python:3000',
+ http: {
+ response: {
+ status_code: 200
+ },
+ url: {
+ original: 'http://opbeans-python:3000/api/orders'
+ }
+ },
+ id: 'daae24d83c269918',
+ type: 'external'
+ }
+ },
+ {
+ container: {
+ id: 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51'
+ },
+ parent: {
+ id: '6fb0ff7365b87298'
+ },
+ agent: {
+ name: 'python',
+ version: '5.5.2'
+ },
+ processor: {
+ name: 'transaction',
+ event: 'span'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.790Z',
+ service: {
+ node: {
+ name:
+ 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51'
+ },
+ environment: 'production',
+ framework: {
+ name: 'django',
+ version: '2.1.13'
+ },
+ name: 'opbeans-python',
+ runtime: {
+ name: 'CPython',
+ version: '3.6.10'
+ },
+ language: {
+ name: 'python',
+ version: '3.6.10'
+ },
+ version: 'None'
+ },
+ transaction: {
+ id: '6fb0ff7365b87298'
+ },
+ timestamp: {
+ us: 1584975868790080
+ },
+ span: {
+ duration: {
+ us: 2519
+ },
+ subtype: 'postgresql',
+ name: 'SELECT FROM opbeans_order',
+ destination: {
+ service: {
+ resource: 'postgresql',
+ name: 'postgresql',
+ type: 'db'
+ }
+ },
+ action: 'query',
+ id: 'c9407abb4d08ead1',
+ type: 'db',
+ sync: true,
+ db: {
+ statement:
+ 'SELECT "opbeans_order"."id", "opbeans_order"."customer_id", "opbeans_customer"."full_name", "opbeans_order"."created_at" FROM "opbeans_order" INNER JOIN "opbeans_customer" ON ("opbeans_order"."customer_id" = "opbeans_customer"."id") LIMIT 1000',
+ type: 'sql'
+ }
+ }
+ }
+ ],
+ exceedsMax: false,
+ errorDocs: []
+ },
+ errorsPerTransaction: {}
+};
+
+export const traceWithErrors = {
+ trace: {
+ items: [
+ {
+ container: {
+ id: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e'
+ },
+ process: {
+ pid: 6,
+ title: '/usr/lib/jvm/java-10-openjdk-amd64/bin/java',
+ ppid: 1
+ },
+ agent: {
+ name: 'java',
+ ephemeral_id: '99ce8403-5875-4945-b074-d37dc10563eb',
+ version: '1.14.1-SNAPSHOT'
+ },
+ internal: {
+ sampler: {
+ value: 46
+ }
+ },
+ source: {
+ ip: '172.19.0.13'
+ },
+ processor: {
+ name: 'transaction',
+ event: 'transaction'
+ },
+ url: {
+ path: '/api/orders',
+ scheme: 'http',
+ port: 3000,
+ domain: '172.19.0.9',
+ full: 'http://172.19.0.9:3000/api/orders'
+ },
+ observer: {
+ hostname: 'f37f48d8b60b',
+ id: 'd8522e1f-be8e-43c2-b290-ac6b6c0f171e',
+ ephemeral_id: '6ed88f14-170e-478d-a4f5-ea5e7f4b16b9',
+ type: 'apm-server',
+ version: '8.0.0',
+ version_major: 8
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.785Z',
+ ecs: {
+ version: '1.4.0'
+ },
+ service: {
+ node: {
+ name:
+ '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e'
+ },
+ environment: 'production',
+ name: 'opbeans-java',
+ runtime: {
+ name: 'Java',
+ version: '10.0.2'
+ },
+ language: {
+ name: 'Java',
+ version: '10.0.2'
+ },
+ version: 'None'
+ },
+ host: {
+ hostname: '4cf84d094553',
+ os: {
+ platform: 'Linux'
+ },
+ ip: '172.19.0.9',
+ name: '4cf84d094553',
+ architecture: 'amd64'
+ },
+ http: {
+ request: {
+ headers: {
+ Accept: ['*/*'],
+ 'User-Agent': ['Python/3.7 aiohttp/3.3.2'],
+ Host: ['172.19.0.9:3000'],
+ 'Accept-Encoding': ['gzip, deflate']
+ },
+ method: 'get',
+ socket: {
+ encrypted: false,
+ remote_address: '172.19.0.13'
+ },
+ body: {
+ original: '[REDACTED]'
+ }
+ },
+ response: {
+ headers: {
+ 'Transfer-Encoding': ['chunked'],
+ Date: ['Mon, 23 Mar 2020 15:04:28 GMT'],
+ 'Content-Type': ['application/json;charset=ISO-8859-1']
+ },
+ status_code: 200,
+ finished: true,
+ headers_sent: true
+ },
+ version: '1.1'
+ },
+ client: {
+ ip: '172.19.0.13'
+ },
+ transaction: {
+ duration: {
+ us: 18842
+ },
+ result: 'HTTP 2xx',
+ name: 'DispatcherServlet#doGet',
+ id: '49809ad3c26adf74',
+ span_count: {
+ dropped: 0,
+ started: 1
+ },
+ type: 'request',
+ sampled: true
+ },
+ user_agent: {
+ original: 'Python/3.7 aiohttp/3.3.2',
+ name: 'Other',
+ device: {
+ name: 'Other'
+ }
+ },
+ timestamp: {
+ us: 1584975868785000
+ }
+ },
+ {
+ parent: {
+ id: 'fc107f7b556eb49b'
+ },
+ agent: {
+ name: 'go',
+ version: '1.7.2'
+ },
+ processor: {
+ name: 'transaction',
+ event: 'transaction'
+ },
+ url: {
+ path: '/api/orders',
+ scheme: 'http',
+ port: 3000,
+ domain: 'opbeans-go',
+ full: 'http://opbeans-go:3000/api/orders'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.787Z',
+ service: {
+ node: {
+ name:
+ 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29'
+ },
+ environment: 'production',
+ framework: {
+ name: 'gin',
+ version: 'v1.4.0'
+ },
+ name: 'opbeans-go',
+ runtime: {
+ name: 'gc',
+ version: 'go1.14.1'
+ },
+ language: {
+ name: 'go',
+ version: 'go1.14.1'
+ },
+ version: 'None'
+ },
+ transaction: {
+ duration: {
+ us: 16597
+ },
+ result: 'HTTP 2xx',
+ name: 'GET /api/orders',
+ id: '975c8d5bfd1dd20b',
+ span_count: {
+ dropped: 0,
+ started: 1
+ },
+ type: 'request',
+ sampled: true
+ },
+ timestamp: {
+ us: 1584975868787052
+ }
+ },
+ {
+ parent: {
+ id: 'daae24d83c269918'
+ },
+ agent: {
+ name: 'python',
+ version: '5.5.2'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ timestamp: {
+ us: 1584975868788603
+ },
+ processor: {
+ name: 'transaction',
+ event: 'transaction'
+ },
+ url: {
+ path: '/api/orders',
+ scheme: 'http',
+ port: 3000,
+ domain: 'opbeans-go',
+ full: 'http://opbeans-go:3000/api/orders'
+ },
+ '@timestamp': '2020-03-23T15:04:28.788Z',
+ service: {
+ node: {
+ name:
+ 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51'
+ },
+ environment: 'production',
+ framework: {
+ name: 'django',
+ version: '2.1.13'
+ },
+ name: 'opbeans-python',
+ runtime: {
+ name: 'CPython',
+ version: '3.6.10'
+ },
+ language: {
+ name: 'python',
+ version: '3.6.10'
+ },
+ version: 'None'
+ },
+ transaction: {
+ result: 'HTTP 2xx',
+ duration: {
+ us: 14648
+ },
+ name: 'GET opbeans.views.orders',
+ span_count: {
+ dropped: 0,
+ started: 1
+ },
+ id: '6fb0ff7365b87298',
+ type: 'request',
+ sampled: true
+ }
+ },
+ {
+ container: {
+ id: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e'
+ },
+ parent: {
+ id: '49809ad3c26adf74'
+ },
+ process: {
+ pid: 6,
+ title: '/usr/lib/jvm/java-10-openjdk-amd64/bin/java',
+ ppid: 1
+ },
+ agent: {
+ name: 'java',
+ ephemeral_id: '99ce8403-5875-4945-b074-d37dc10563eb',
+ version: '1.14.1-SNAPSHOT'
+ },
+ internal: {
+ sampler: {
+ value: 44
+ }
+ },
+ destination: {
+ address: 'opbeans-go',
+ port: 3000
+ },
+ processor: {
+ name: 'transaction',
+ event: 'span'
+ },
+ observer: {
+ hostname: 'f37f48d8b60b',
+ id: 'd8522e1f-be8e-43c2-b290-ac6b6c0f171e',
+ type: 'apm-server',
+ ephemeral_id: '6ed88f14-170e-478d-a4f5-ea5e7f4b16b9',
+ version: '8.0.0',
+ version_major: 8
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.785Z',
+ ecs: {
+ version: '1.4.0'
+ },
+ service: {
+ node: {
+ name:
+ '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e'
+ },
+ environment: 'production',
+ name: 'opbeans-java',
+ runtime: {
+ name: 'Java',
+ version: '10.0.2'
+ },
+ language: {
+ name: 'Java',
+ version: '10.0.2'
+ },
+ version: 'None'
+ },
+ host: {
+ hostname: '4cf84d094553',
+ os: {
+ platform: 'Linux'
+ },
+ ip: '172.19.0.9',
+ name: '4cf84d094553',
+ architecture: 'amd64'
+ },
+ connection: {
+ hash:
+ "{service.environment:'production'}/{service.name:'opbeans-java'}/{span.subtype:'http'}/{destination.address:'opbeans-go'}/{span.type:'external'}"
+ },
+ transaction: {
+ id: '49809ad3c26adf74'
+ },
+ timestamp: {
+ us: 1584975868785273
+ },
+ span: {
+ duration: {
+ us: 17530
+ },
+ subtype: 'http',
+ name: 'GET opbeans-go',
+ destination: {
+ service: {
+ resource: 'opbeans-go:3000',
+ name: 'http://opbeans-go:3000',
+ type: 'external'
+ }
+ },
+ http: {
+ response: {
+ status_code: 200
+ },
+ url: {
+ original: 'http://opbeans-go:3000/api/orders'
+ }
+ },
+ id: 'fc107f7b556eb49b',
+ type: 'external'
+ }
+ },
+ {
+ parent: {
+ id: '975c8d5bfd1dd20b'
+ },
+ agent: {
+ name: 'go',
+ version: '1.7.2'
+ },
+ processor: {
+ name: 'transaction',
+ event: 'span'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.787Z',
+ service: {
+ node: {
+ name:
+ 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29'
+ },
+ environment: 'production',
+ name: 'opbeans-go',
+ runtime: {
+ name: 'gc',
+ version: 'go1.14.1'
+ },
+ language: {
+ name: 'go',
+ version: 'go1.14.1'
+ },
+ version: 'None'
+ },
+ transaction: {
+ id: '975c8d5bfd1dd20b'
+ },
+ timestamp: {
+ us: 1584975868787174
+ },
+ span: {
+ duration: {
+ us: 16250
+ },
+ subtype: 'http',
+ destination: {
+ service: {
+ resource: 'opbeans-python:3000',
+ name: 'http://opbeans-python:3000',
+ type: 'external'
+ }
+ },
+ name: 'GET opbeans-python:3000',
+ http: {
+ response: {
+ status_code: 200
+ },
+ url: {
+ original: 'http://opbeans-python:3000/api/orders'
+ }
+ },
+ id: 'daae24d83c269918',
+ type: 'external'
+ }
+ },
+ {
+ container: {
+ id: 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51'
+ },
+ parent: {
+ id: '6fb0ff7365b87298'
+ },
+ agent: {
+ name: 'python',
+ version: '5.5.2'
+ },
+ processor: {
+ name: 'transaction',
+ event: 'span'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.790Z',
+ service: {
+ node: {
+ name:
+ 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51'
+ },
+ environment: 'production',
+ framework: {
+ name: 'django',
+ version: '2.1.13'
+ },
+ name: 'opbeans-python',
+ runtime: {
+ name: 'CPython',
+ version: '3.6.10'
+ },
+ language: {
+ name: 'python',
+ version: '3.6.10'
+ },
+ version: 'None'
+ },
+ transaction: {
+ id: '6fb0ff7365b87298'
+ },
+ timestamp: {
+ us: 1584975868790080
+ },
+ span: {
+ duration: {
+ us: 2519
+ },
+ subtype: 'postgresql',
+ name: 'SELECT FROM opbeans_order',
+ destination: {
+ service: {
+ resource: 'postgresql',
+ name: 'postgresql',
+ type: 'db'
+ }
+ },
+ action: 'query',
+ id: 'c9407abb4d08ead1',
+ type: 'db',
+ sync: true,
+ db: {
+ statement:
+ 'SELECT "opbeans_order"."id", "opbeans_order"."customer_id", "opbeans_customer"."full_name", "opbeans_order"."created_at" FROM "opbeans_order" INNER JOIN "opbeans_customer" ON ("opbeans_order"."customer_id" = "opbeans_customer"."id") LIMIT 1000',
+ type: 'sql'
+ }
+ }
+ }
+ ],
+ exceedsMax: false,
+ errorDocs: [
+ {
+ parent: {
+ id: '975c8d5bfd1dd20b'
+ },
+ agent: {
+ name: 'go',
+ version: '1.7.2'
+ },
+ error: {
+ culprit: 'logrusMiddleware',
+ log: {
+ level: 'error',
+ message: 'GET //api/products (502)'
+ },
+ id: '1f3cb98206b5c54225cb7c8908a658da',
+ grouping_key: '4dba2ff58fe6c036a5dee2ce411e512a'
+ },
+ processor: {
+ name: 'error',
+ event: 'error'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T16:04:28.787Z',
+ service: {
+ node: {
+ name:
+ 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29'
+ },
+ environment: 'production',
+ name: 'opbeans-go',
+ runtime: {
+ name: 'gc',
+ version: 'go1.14.1'
+ },
+ language: {
+ name: 'go',
+ version: 'go1.14.1'
+ },
+ version: 'None'
+ },
+ transaction: {
+ id: '975c8d5bfd1dd20b',
+ sampled: false
+ },
+ timestamp: {
+ us: 1584975868787052
+ }
+ },
+ {
+ parent: {
+ id: '6fb0ff7365b87298'
+ },
+ agent: {
+ name: 'python',
+ version: '5.5.2'
+ },
+ error: {
+ culprit: 'logrusMiddleware',
+ log: {
+ level: 'error',
+ message: 'GET //api/products (502)'
+ },
+ id: '1f3cb98206b5c54225cb7c8908a658d2',
+ grouping_key: '4dba2ff58fe6c036a5dee2ce411e512a'
+ },
+ processor: {
+ name: 'error',
+ event: 'error'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T16:04:28.790Z',
+ service: {
+ node: {
+ name:
+ 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29'
+ },
+ environment: 'production',
+ name: 'opbeans-python',
+ runtime: {
+ name: 'gc',
+ version: 'go1.14.1'
+ },
+ version: 'None'
+ },
+ transaction: {
+ id: '6fb0ff7365b87298',
+ sampled: false
+ },
+ timestamp: {
+ us: 1584975868790000
+ }
+ }
+ ]
+ },
+ errorsPerTransaction: {
+ '975c8d5bfd1dd20b': 1,
+ '6fb0ff7365b87298': 1
+ }
+};
+
+export const traceChildStartBeforeParent = {
+ trace: {
+ items: [
+ {
+ container: {
+ id: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e'
+ },
+ process: {
+ pid: 6,
+ title: '/usr/lib/jvm/java-10-openjdk-amd64/bin/java',
+ ppid: 1
+ },
+ agent: {
+ name: 'java',
+ ephemeral_id: '99ce8403-5875-4945-b074-d37dc10563eb',
+ version: '1.14.1-SNAPSHOT'
+ },
+ internal: {
+ sampler: {
+ value: 46
+ }
+ },
+ source: {
+ ip: '172.19.0.13'
+ },
+ processor: {
+ name: 'transaction',
+ event: 'transaction'
+ },
+ url: {
+ path: '/api/orders',
+ scheme: 'http',
+ port: 3000,
+ domain: '172.19.0.9',
+ full: 'http://172.19.0.9:3000/api/orders'
+ },
+ observer: {
+ hostname: 'f37f48d8b60b',
+ id: 'd8522e1f-be8e-43c2-b290-ac6b6c0f171e',
+ ephemeral_id: '6ed88f14-170e-478d-a4f5-ea5e7f4b16b9',
+ type: 'apm-server',
+ version: '8.0.0',
+ version_major: 8
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.785Z',
+ ecs: {
+ version: '1.4.0'
+ },
+ service: {
+ node: {
+ name:
+ '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e'
+ },
+ environment: 'production',
+ name: 'opbeans-java',
+ runtime: {
+ name: 'Java',
+ version: '10.0.2'
+ },
+ language: {
+ name: 'Java',
+ version: '10.0.2'
+ },
+ version: 'None'
+ },
+ host: {
+ hostname: '4cf84d094553',
+ os: {
+ platform: 'Linux'
+ },
+ ip: '172.19.0.9',
+ name: '4cf84d094553',
+ architecture: 'amd64'
+ },
+ http: {
+ request: {
+ headers: {
+ Accept: ['*/*'],
+ 'User-Agent': ['Python/3.7 aiohttp/3.3.2'],
+ Host: ['172.19.0.9:3000'],
+ 'Accept-Encoding': ['gzip, deflate']
+ },
+ method: 'get',
+ socket: {
+ encrypted: false,
+ remote_address: '172.19.0.13'
+ },
+ body: {
+ original: '[REDACTED]'
+ }
+ },
+ response: {
+ headers: {
+ 'Transfer-Encoding': ['chunked'],
+ Date: ['Mon, 23 Mar 2020 15:04:28 GMT'],
+ 'Content-Type': ['application/json;charset=ISO-8859-1']
+ },
+ status_code: 200,
+ finished: true,
+ headers_sent: true
+ },
+ version: '1.1'
+ },
+ client: {
+ ip: '172.19.0.13'
+ },
+ transaction: {
+ duration: {
+ us: 18842
+ },
+ result: 'HTTP 2xx',
+ name: 'DispatcherServlet#doGet',
+ id: '49809ad3c26adf74',
+ span_count: {
+ dropped: 0,
+ started: 1
+ },
+ type: 'request',
+ sampled: true
+ },
+ user_agent: {
+ original: 'Python/3.7 aiohttp/3.3.2',
+ name: 'Other',
+ device: {
+ name: 'Other'
+ }
+ },
+ timestamp: {
+ us: 1584975868785000
+ }
+ },
+ {
+ parent: {
+ id: 'fc107f7b556eb49b'
+ },
+ agent: {
+ name: 'go',
+ version: '1.7.2'
+ },
+ processor: {
+ name: 'transaction',
+ event: 'transaction'
+ },
+ url: {
+ path: '/api/orders',
+ scheme: 'http',
+ port: 3000,
+ domain: 'opbeans-go',
+ full: 'http://opbeans-go:3000/api/orders'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.787Z',
+ service: {
+ node: {
+ name:
+ 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29'
+ },
+ environment: 'production',
+ framework: {
+ name: 'gin',
+ version: 'v1.4.0'
+ },
+ name: 'opbeans-go',
+ runtime: {
+ name: 'gc',
+ version: 'go1.14.1'
+ },
+ language: {
+ name: 'go',
+ version: 'go1.14.1'
+ },
+ version: 'None'
+ },
+ transaction: {
+ duration: {
+ us: 16597
+ },
+ result: 'HTTP 2xx',
+ name: 'GET /api/orders',
+ id: '975c8d5bfd1dd20b',
+ span_count: {
+ dropped: 0,
+ started: 1
+ },
+ type: 'request',
+ sampled: true
+ },
+ timestamp: {
+ us: 1584975868787052
+ }
+ },
+ {
+ parent: {
+ id: 'daae24d83c269918'
+ },
+ agent: {
+ name: 'python',
+ version: '5.5.2'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ timestamp: {
+ us: 1584975868780000
+ },
+ processor: {
+ name: 'transaction',
+ event: 'transaction'
+ },
+ url: {
+ path: '/api/orders',
+ scheme: 'http',
+ port: 3000,
+ domain: 'opbeans-go',
+ full: 'http://opbeans-go:3000/api/orders'
+ },
+ '@timestamp': '2020-03-23T15:04:28.788Z',
+ service: {
+ node: {
+ name:
+ 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51'
+ },
+ environment: 'production',
+ framework: {
+ name: 'django',
+ version: '2.1.13'
+ },
+ name: 'opbeans-python',
+ runtime: {
+ name: 'CPython',
+ version: '3.6.10'
+ },
+ language: {
+ name: 'python',
+ version: '3.6.10'
+ },
+ version: 'None'
+ },
+ transaction: {
+ result: 'HTTP 2xx',
+ duration: {
+ us: 1464
+ },
+ name: 'I started before my parent 😰',
+ span_count: {
+ dropped: 0,
+ started: 1
+ },
+ id: '6fb0ff7365b87298',
+ type: 'request',
+ sampled: true
+ }
+ },
+ {
+ container: {
+ id: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e'
+ },
+ parent: {
+ id: '49809ad3c26adf74'
+ },
+ process: {
+ pid: 6,
+ title: '/usr/lib/jvm/java-10-openjdk-amd64/bin/java',
+ ppid: 1
+ },
+ agent: {
+ name: 'java',
+ ephemeral_id: '99ce8403-5875-4945-b074-d37dc10563eb',
+ version: '1.14.1-SNAPSHOT'
+ },
+ internal: {
+ sampler: {
+ value: 44
+ }
+ },
+ destination: {
+ address: 'opbeans-go',
+ port: 3000
+ },
+ processor: {
+ name: 'transaction',
+ event: 'span'
+ },
+ observer: {
+ hostname: 'f37f48d8b60b',
+ id: 'd8522e1f-be8e-43c2-b290-ac6b6c0f171e',
+ type: 'apm-server',
+ ephemeral_id: '6ed88f14-170e-478d-a4f5-ea5e7f4b16b9',
+ version: '8.0.0',
+ version_major: 8
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.785Z',
+ ecs: {
+ version: '1.4.0'
+ },
+ service: {
+ node: {
+ name:
+ '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e'
+ },
+ environment: 'production',
+ name: 'opbeans-java',
+ runtime: {
+ name: 'Java',
+ version: '10.0.2'
+ },
+ language: {
+ name: 'Java',
+ version: '10.0.2'
+ },
+ version: 'None'
+ },
+ host: {
+ hostname: '4cf84d094553',
+ os: {
+ platform: 'Linux'
+ },
+ ip: '172.19.0.9',
+ name: '4cf84d094553',
+ architecture: 'amd64'
+ },
+ connection: {
+ hash:
+ "{service.environment:'production'}/{service.name:'opbeans-java'}/{span.subtype:'http'}/{destination.address:'opbeans-go'}/{span.type:'external'}"
+ },
+ transaction: {
+ id: '49809ad3c26adf74'
+ },
+ timestamp: {
+ us: 1584975868785273
+ },
+ span: {
+ duration: {
+ us: 17530
+ },
+ subtype: 'http',
+ name: 'GET opbeans-go',
+ destination: {
+ service: {
+ resource: 'opbeans-go:3000',
+ name: 'http://opbeans-go:3000',
+ type: 'external'
+ }
+ },
+ http: {
+ response: {
+ status_code: 200
+ },
+ url: {
+ original: 'http://opbeans-go:3000/api/orders'
+ }
+ },
+ id: 'fc107f7b556eb49b',
+ type: 'external'
+ }
+ },
+ {
+ parent: {
+ id: '975c8d5bfd1dd20b'
+ },
+ agent: {
+ name: 'go',
+ version: '1.7.2'
+ },
+ processor: {
+ name: 'transaction',
+ event: 'span'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.787Z',
+ service: {
+ node: {
+ name:
+ 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29'
+ },
+ environment: 'production',
+ name: 'opbeans-go',
+ runtime: {
+ name: 'gc',
+ version: 'go1.14.1'
+ },
+ language: {
+ name: 'go',
+ version: 'go1.14.1'
+ },
+ version: 'None'
+ },
+ transaction: {
+ id: '975c8d5bfd1dd20b'
+ },
+ timestamp: {
+ us: 1584975868787174
+ },
+ span: {
+ duration: {
+ us: 16250
+ },
+ subtype: 'http',
+ destination: {
+ service: {
+ resource: 'opbeans-python:3000',
+ name: 'http://opbeans-python:3000',
+ type: 'external'
+ }
+ },
+ name: 'I am his 👇🏻 parent 😡',
+ http: {
+ response: {
+ status_code: 200
+ },
+ url: {
+ original: 'http://opbeans-python:3000/api/orders'
+ }
+ },
+ id: 'daae24d83c269918',
+ type: 'external'
+ }
+ },
+ {
+ container: {
+ id: 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51'
+ },
+ parent: {
+ id: '6fb0ff7365b87298'
+ },
+ agent: {
+ name: 'python',
+ version: '5.5.2'
+ },
+ processor: {
+ name: 'transaction',
+ event: 'span'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.790Z',
+ service: {
+ node: {
+ name:
+ 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51'
+ },
+ environment: 'production',
+ framework: {
+ name: 'django',
+ version: '2.1.13'
+ },
+ name: 'opbeans-python',
+ runtime: {
+ name: 'CPython',
+ version: '3.6.10'
+ },
+ language: {
+ name: 'python',
+ version: '3.6.10'
+ },
+ version: 'None'
+ },
+ transaction: {
+ id: '6fb0ff7365b87298'
+ },
+ timestamp: {
+ us: 1584975868781000
+ },
+ span: {
+ duration: {
+ us: 2519
+ },
+ subtype: 'postgresql',
+ name: 'I am using my parents skew 😇',
+ destination: {
+ service: {
+ resource: 'postgresql',
+ name: 'postgresql',
+ type: 'db'
+ }
+ },
+ action: 'query',
+ id: 'c9407abb4d08ead1',
+ type: 'db',
+ sync: true,
+ db: {
+ statement:
+ 'SELECT "opbeans_order"."id", "opbeans_order"."customer_id", "opbeans_customer"."full_name", "opbeans_order"."created_at" FROM "opbeans_order" INNER JOIN "opbeans_customer" ON ("opbeans_order"."customer_id" = "opbeans_customer"."id") LIMIT 1000',
+ type: 'sql'
+ }
+ }
+ }
+ ],
+ exceedsMax: false,
+ errorDocs: []
+ },
+ errorsPerTransaction: {}
+};
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx
index 9fcab049e224f..8c2829a515f83 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx
@@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
import { EuiLink, EuiLinkAnchorProps } from '@elastic/eui';
+import React from 'react';
import { useApmPluginContext } from '../../../hooks/useApmPluginContext';
// union type constisting of valid guide sections that we link to
@@ -17,8 +17,11 @@ interface Props extends EuiLinkAnchorProps {
}
export function ElasticDocsLink({ section, path, children, ...rest }: Props) {
- const { version } = useApmPluginContext().packageInfo;
- const href = `https://www.elastic.co/guide/en${section}/${version}${path}`;
+ const { docLinks } = useApmPluginContext().core;
+ const baseUrl = docLinks.ELASTIC_WEBSITE_URL;
+ const version = docLinks.DOC_LINK_VERSION;
+ const href = `${baseUrl}guide/en${section}/${version}${path}`;
+
return typeof children === 'function' ? (
children(href)
) : (
diff --git a/x-pack/legacy/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx b/x-pack/legacy/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx
index 8775dc98c3e1a..cc2e382611628 100644
--- a/x-pack/legacy/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx
+++ b/x-pack/legacy/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx
@@ -12,6 +12,10 @@ const mockCore = {
chrome: {
setBreadcrumbs: () => {}
},
+ docLinks: {
+ DOC_LINK_VERSION: '0',
+ ELASTIC_WEBSITE_URL: 'https://www.elastic.co/'
+ },
http: {
basePath: {
prepend: (path: string) => `/basepath${path}`
@@ -36,7 +40,6 @@ const mockConfig: ConfigSchema = {
export const mockApmPluginContextValue = {
config: mockConfig,
core: mockCore,
- packageInfo: { version: '0' },
plugins: {}
};
diff --git a/x-pack/legacy/plugins/apm/public/context/ApmPluginContext/index.tsx b/x-pack/legacy/plugins/apm/public/context/ApmPluginContext/index.tsx
index d8934ba4b0151..acc3886586889 100644
--- a/x-pack/legacy/plugins/apm/public/context/ApmPluginContext/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/context/ApmPluginContext/index.tsx
@@ -5,7 +5,7 @@
*/
import { createContext } from 'react';
-import { AppMountContext, PackageInfo } from 'kibana/public';
+import { AppMountContext } from 'kibana/public';
import { ApmPluginSetupDeps, ConfigSchema } from '../../new-platform/plugin';
export type AppMountContextBasePath = AppMountContext['core']['http']['basePath'];
@@ -13,7 +13,6 @@ export type AppMountContextBasePath = AppMountContext['core']['http']['basePath'
export interface ApmPluginContextValue {
config: ConfigSchema;
core: AppMountContext['core'];
- packageInfo: PackageInfo;
plugins: ApmPluginSetupDeps;
}
diff --git a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx
index e30bed1810c1d..a291678e9a20c 100644
--- a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx
+++ b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx
@@ -9,13 +9,11 @@ import ReactDOM from 'react-dom';
import { Route, Router, Switch } from 'react-router-dom';
import { ApmRoute } from '@elastic/apm-rum-react';
import styled from 'styled-components';
-import { metadata } from 'ui/metadata';
import { i18n } from '@kbn/i18n';
import { AlertType } from '../../../../../plugins/apm/common/alert_types';
import {
CoreSetup,
CoreStart,
- PackageInfo,
Plugin,
PluginInitializerContext
} from '../../../../../../src/core/public';
@@ -124,14 +122,6 @@ export class ApmPlugin
// Until then we use a shim to get it from legacy injectedMetadata:
const config = getConfigFromInjectedMetadata();
- // Once we're actually an NP plugin we'll get the package info from the
- // initializerContext like:
- //
- // const packageInfo = this.initializerContext.env.packageInfo
- //
- // Until then we use a shim to get it from legacy metadata:
- const packageInfo = metadata as PackageInfo;
-
// render APM feedback link in global help menu
setHelpExtension(core);
setReadonlyBadge(core);
@@ -140,7 +130,6 @@ export class ApmPlugin
const apmPluginContextValue = {
config,
core,
- packageInfo,
plugins
};
diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_lens.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_lens.ts
index 60026adc0998a..2985a68cf855c 100644
--- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_lens.ts
+++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_lens.ts
@@ -5,8 +5,8 @@
*/
import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common';
-import { TimeRange } from 'src/plugins/data/public';
-import { EmbeddableInput } from 'src/legacy/core_plugins/embeddable_api/public/np_ready/public';
+import { TimeRange, Filter as DataFilter } from 'src/plugins/data/public';
+import { EmbeddableInput } from 'src/plugins/embeddable/public';
import { getQueryFilters } from '../../../public/lib/build_embeddable_filters';
import { Filter, TimeRange as TimeRangeArg } from '../../../types';
import {
@@ -15,7 +15,6 @@ import {
EmbeddableExpression,
} from '../../expression_types';
import { getFunctionHelp } from '../../../i18n';
-import { Filter as DataFilter } from '../../../../../../../src/plugins/data/public';
interface Arguments {
id: string;
diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts
index 78240eee7ce13..4b045b0c5edcf 100644
--- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts
+++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts
@@ -5,8 +5,8 @@
*/
import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common';
-import { TimeRange } from 'src/plugins/data/public';
-import { EmbeddableInput } from 'src/legacy/core_plugins/embeddable_api/public/np_ready/public';
+import { TimeRange, Filter as DataFilter } from 'src/plugins/data/public';
+import { EmbeddableInput } from 'src/plugins/embeddable/public';
import { getQueryFilters } from '../../../public/lib/build_embeddable_filters';
import { Filter, MapCenter, TimeRange as TimeRangeArg } from '../../../types';
import {
@@ -15,7 +15,6 @@ import {
EmbeddableExpression,
} from '../../expression_types';
import { getFunctionHelp } from '../../../i18n';
-import { Filter as DataFilter } from '../../../../../../../src/plugins/data/public';
interface Arguments {
id: string;
diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx
index 3cdb6eb460224..817be6e144fc8 100644
--- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx
+++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx
@@ -14,7 +14,6 @@ import {
EmbeddablePanel,
EmbeddableFactoryNotFoundError,
} from '../../../../../../../src/plugins/embeddable/public';
-import { start } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public/legacy';
import { EmbeddableExpression } from '../../expression_types/embeddable';
import { RendererStrings } from '../../../i18n';
import { getSavedObjectFinder } from '../../../../../../../src/plugins/saved_objects/public';
@@ -39,8 +38,8 @@ const renderEmbeddable = (embeddableObject: IEmbeddable, domNode: HTMLElement) =
({
const uniqueId = handlers.getElementId();
if (!embeddablesRegistry[uniqueId]) {
- const factory = Array.from(start.getEmbeddableFactories()).find(
+ const factory = Array.from(npStart.plugins.embeddable.getEmbeddableFactories()).find(
embeddableFactory => embeddableFactory.type === embeddableType
) as EmbeddableFactory;
diff --git a/x-pack/legacy/plugins/canvas/public/components/embeddable_flyout/flyout.tsx b/x-pack/legacy/plugins/canvas/public/components/embeddable_flyout/flyout.tsx
index 576c7c4794b08..08cd3084c35cf 100644
--- a/x-pack/legacy/plugins/canvas/public/components/embeddable_flyout/flyout.tsx
+++ b/x-pack/legacy/plugins/canvas/public/components/embeddable_flyout/flyout.tsx
@@ -5,13 +5,12 @@
*/
import React from 'react';
-
+import { npStart } from 'ui/new_platform';
import { EuiFlyout, EuiFlyoutHeader, EuiFlyoutBody, EuiTitle } from '@elastic/eui';
import {
SavedObjectFinderUi,
SavedObjectMetaData,
} from '../../../../../../../src/plugins/saved_objects/public/';
-import { start } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public/legacy';
import { ComponentStrings } from '../../../i18n';
import { CoreStart } from '../../../../../../../src/core/public';
@@ -27,7 +26,7 @@ export interface Props {
export class AddEmbeddableFlyout extends React.Component {
onAddPanel = (id: string, savedObjectType: string, name: string) => {
- const embeddableFactories = start.getEmbeddableFactories();
+ const embeddableFactories = npStart.plugins.embeddable.getEmbeddableFactories();
// Find the embeddable type from the saved object type
const found = Array.from(embeddableFactories).find(embeddableFactory => {
@@ -43,7 +42,7 @@ export class AddEmbeddableFlyout extends React.Component {
};
render() {
- const embeddableFactories = start.getEmbeddableFactories();
+ const embeddableFactories = npStart.plugins.embeddable.getEmbeddableFactories();
const availableSavedObjects = Array.from(embeddableFactories)
.filter(factory => {
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts b/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts
index 2bde698e23562..1caea1b4b728f 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts
@@ -22,7 +22,7 @@ import {
ErrorEmbeddable,
EmbeddableInput,
IContainer,
-} from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public';
+} from '../../../../../../../src/plugins/embeddable/public';
import { Embeddable } from './embeddable';
import { SavedObjectIndexStore, DOC_TYPE } from '../../persistence';
import { getEditPath } from '../../../../../../plugins/lens/common';
diff --git a/x-pack/legacy/plugins/maps/public/actions/map_actions.d.ts b/x-pack/legacy/plugins/maps/public/actions/map_actions.d.ts
index c1f5c31eb4210..b4a8ff90c3512 100644
--- a/x-pack/legacy/plugins/maps/public/actions/map_actions.d.ts
+++ b/x-pack/legacy/plugins/maps/public/actions/map_actions.d.ts
@@ -5,10 +5,14 @@
*/
/* eslint-disable @typescript-eslint/consistent-type-definitions */
-import { Filter, Query } from 'src/plugins/data/public';
+import { Filter, Query, TimeRange } from 'src/plugins/data/public';
import { AnyAction } from 'redux';
import { LAYER_TYPE } from '../../common/constants';
import { DataMeta, MapFilters } from '../../common/descriptor_types';
+import {
+ MapCenterAndZoom,
+ MapRefreshConfig,
+} from '../../../../../plugins/maps/common/descriptor_types';
export type SyncContext = {
startLoading(dataId: string, requestToken: symbol, meta: DataMeta): void;
@@ -27,31 +31,20 @@ export function updateSourceProp(
newLayerType?: LAYER_TYPE
): void;
-export interface MapCenter {
- lat: number;
- lon: number;
- zoom: number;
-}
-
-export function setGotoWithCenter(config: MapCenter): AnyAction;
+export function setGotoWithCenter(config: MapCenterAndZoom): AnyAction;
export function replaceLayerList(layerList: unknown[]): AnyAction;
-export interface QueryGroup {
+export type QueryGroup = {
filters: Filter[];
query?: Query;
- timeFilters: unknown;
- refresh: unknown;
-}
+ timeFilters?: TimeRange;
+ refresh?: boolean;
+};
export function setQuery(query: QueryGroup): AnyAction;
-export interface RefreshConfig {
- isPaused: boolean;
- interval: number;
-}
-
-export function setRefreshConfig(config: RefreshConfig): AnyAction;
+export function setRefreshConfig(config: MapRefreshConfig): AnyAction;
export function disableScrollZoom(): AnyAction;
diff --git a/x-pack/legacy/plugins/maps/public/angular/map_controller.js b/x-pack/legacy/plugins/maps/public/angular/map_controller.js
index 519ba0b1e3d96..bc97643689e12 100644
--- a/x-pack/legacy/plugins/maps/public/angular/map_controller.js
+++ b/x-pack/legacy/plugins/maps/public/angular/map_controller.js
@@ -310,9 +310,15 @@ app.controller(
const layerListConfigOnly = copyPersistentState(layerList);
const savedLayerList = savedMap.getLayerList();
- const oldConfig = savedLayerList ? savedLayerList : initialLayerListConfig;
- return !_.isEqual(layerListConfigOnly, oldConfig);
+ return !savedLayerList
+ ? !_.isEqual(layerListConfigOnly, initialLayerListConfig)
+ : // savedMap stores layerList as a JSON string using JSON.stringify.
+ // JSON.stringify removes undefined properties from objects.
+ // savedMap.getLayerList converts the JSON string back into Javascript array of objects.
+ // Need to perform the same process for layerListConfigOnly to compare apples to apples
+ // and avoid undefined properties in layerListConfigOnly triggering unsaved changes.
+ !_.isEqual(JSON.parse(JSON.stringify(layerListConfigOnly)), savedLayerList);
}
function isOnMapNow() {
diff --git a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/index.js b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/index.js
index e51e59ec41e18..04de5f71f5bfc 100644
--- a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/index.js
+++ b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/index.js
@@ -7,7 +7,7 @@
import { connect } from 'react-redux';
import { LayerControl } from './view';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { FLYOUT_STATE } from '../../../../../../../plugins/maps/public/reducers/ui.js';
+import { FLYOUT_STATE } from '../../../../../../../plugins/maps/public/reducers/ui';
import { updateFlyout, setIsLayerTOCOpen } from '../../../actions/ui_actions';
import { setSelectedLayer } from '../../../actions/map_actions';
import {
diff --git a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/index.js b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/index.js
index ececc5a90ab89..588445d0b4992 100644
--- a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/index.js
+++ b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/index.js
@@ -8,7 +8,7 @@ import _ from 'lodash';
import { connect } from 'react-redux';
import { TOCEntry } from './view';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { FLYOUT_STATE } from '../../../../../../../../../plugins/maps/public/reducers/ui.js';
+import { FLYOUT_STATE } from '../../../../../../../../../plugins/maps/public/reducers/ui';
import { updateFlyout, hideTOCDetails, showTOCDetails } from '../../../../../actions/ui_actions';
import { getIsReadOnly, getOpenTOCDetails } from '../../../../../selectors/ui_selectors';
import {
diff --git a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx
index 69f55815d16a0..3c9069c7a836f 100644
--- a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx
+++ b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx
@@ -45,8 +45,8 @@ import {
hideLayerControl,
hideViewControl,
setHiddenLayers,
- MapCenter,
} from '../actions/map_actions';
+import { MapCenterAndZoom } from '../../../../../plugins/maps/common/descriptor_types';
import { setReadOnly, setIsLayerTOCOpen, setOpenTOCDetails } from '../actions/ui_actions';
import { getIsLayerTOCOpen, getOpenTOCDetails } from '../selectors/ui_selectors';
import {
@@ -71,7 +71,6 @@ export interface MapEmbeddableInput extends EmbeddableInput {
timeRange?: TimeRange;
filters: Filter[];
query?: Query;
- refresh?: unknown;
refreshConfig: RefreshInterval;
isLayerTOCOpen: boolean;
openTOCDetails?: string[];
@@ -80,7 +79,7 @@ export interface MapEmbeddableInput extends EmbeddableInput {
hideToolbarOverlay?: boolean;
hideLayerControl?: boolean;
hideViewControl?: boolean;
- mapCenter?: MapCenter;
+ mapCenter?: MapCenterAndZoom;
hiddenLayers?: string[];
hideFilterActions?: boolean;
}
@@ -153,7 +152,12 @@ export class MapEmbeddable extends Embeddable) {
+ }: {
+ query?: Query;
+ timeRange?: TimeRange;
+ filters: Filter[];
+ refresh?: boolean;
+ }) {
this._prevTimeRange = timeRange;
this._prevQuery = query;
this._prevFilters = filters;
diff --git a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts
index ddb937dd98926..b9cb66f831281 100644
--- a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts
+++ b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts
@@ -11,14 +11,13 @@ import { i18n } from '@kbn/i18n';
import { npSetup, npStart } from 'ui/new_platform';
import { SavedObjectLoader } from 'src/plugins/saved_objects/public';
import { IIndexPattern } from 'src/plugins/data/public';
+import { MapEmbeddable, MapEmbeddableInput } from './map_embeddable';
+import { getIndexPatternService } from '../kibana_services';
import {
EmbeddableFactory,
ErrorEmbeddable,
IContainer,
-} from '../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public';
-import { setup } from '../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public/legacy';
-import { MapEmbeddable, MapEmbeddableInput } from './map_embeddable';
-import { getIndexPatternService } from '../kibana_services';
+} from '../../../../../../src/plugins/embeddable/public';
import { createMapPath, MAP_SAVED_OBJECT_TYPE, APP_ICON } from '../../common/constants';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
@@ -171,4 +170,7 @@ export class MapEmbeddableFactory extends EmbeddableFactory {
}
}
-setup.registerEmbeddableFactory(MAP_SAVED_OBJECT_TYPE, new MapEmbeddableFactory());
+npSetup.plugins.embeddable.registerEmbeddableFactory(
+ MAP_SAVED_OBJECT_TYPE,
+ new MapEmbeddableFactory()
+);
diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js
index a1c15e27c9eb3..5e8f720fcc5e3 100644
--- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js
+++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js
@@ -28,12 +28,20 @@ export function DynamicColorForm({
};
if (type === COLOR_MAP_TYPE.ORDINAL) {
newColorOptions.useCustomColorRamp = useCustomColorMap;
- newColorOptions.customColorRamp = customColorMap;
- newColorOptions.color = color;
+ if (customColorMap) {
+ newColorOptions.customColorRamp = customColorMap;
+ }
+ if (color) {
+ newColorOptions.color = color;
+ }
} else {
newColorOptions.useCustomColorPalette = useCustomColorMap;
- newColorOptions.customColorPalette = customColorMap;
- newColorOptions.colorCategory = color;
+ if (customColorMap) {
+ newColorOptions.customColorPalette = customColorMap;
+ }
+ if (color) {
+ newColorOptions.colorCategory = color;
+ }
}
onDynamicStyleChange(styleProperty.getStyleName(), newColorOptions);
diff --git a/x-pack/legacy/plugins/maps/public/layers/tooltips/tooltip_property.ts b/x-pack/legacy/plugins/maps/public/layers/tooltips/tooltip_property.ts
index c77af11d0ae24..46e27bbd770a1 100644
--- a/x-pack/legacy/plugins/maps/public/layers/tooltips/tooltip_property.ts
+++ b/x-pack/legacy/plugins/maps/public/layers/tooltips/tooltip_property.ts
@@ -6,6 +6,7 @@
import _ from 'lodash';
import { PhraseFilter } from '../../../../../../../src/plugins/data/public';
+import { TooltipFeature } from '../../../../../../plugins/maps/common/descriptor_types';
export interface ITooltipProperty {
getPropertyKey(): string;
@@ -16,11 +17,6 @@ export interface ITooltipProperty {
getESFilters(): Promise;
}
-export interface MapFeature {
- id: number;
- layerId: string;
-}
-
export interface LoadFeatureProps {
layerId: string;
featureId: number;
@@ -34,7 +30,7 @@ export interface FeatureGeometry {
export interface RenderTooltipContentParams {
addFilters(filter: object): void;
closeTooltip(): void;
- features: MapFeature[];
+ features: TooltipFeature[];
isLocked: boolean;
getLayerName(layerId: string): Promise;
loadFeatureProperties({ layerId, featureId }: LoadFeatureProps): Promise;
diff --git a/x-pack/legacy/plugins/maps/public/layers/util/is_refresh_only_query.ts b/x-pack/legacy/plugins/maps/public/layers/util/is_refresh_only_query.ts
index a56da4b23aa1e..3599f18671ced 100644
--- a/x-pack/legacy/plugins/maps/public/layers/util/is_refresh_only_query.ts
+++ b/x-pack/legacy/plugins/maps/public/layers/util/is_refresh_only_query.ts
@@ -4,13 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { Query } from '../../../common/descriptor_types';
+import { MapQuery } from '../../../common/descriptor_types';
// Refresh only query is query where timestamps are different but query is the same.
// Triggered by clicking "Refresh" button in QueryBar
export function isRefreshOnlyQuery(
- prevQuery: Query | undefined,
- newQuery: Query | undefined
+ prevQuery: MapQuery | undefined,
+ newQuery: MapQuery | undefined
): boolean {
if (!prevQuery || !newQuery) {
return false;
diff --git a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.d.ts b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.d.ts
index 237a04027e21b..8c99e0adcc14f 100644
--- a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.d.ts
+++ b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.d.ts
@@ -5,12 +5,14 @@
*/
import { AnyAction } from 'redux';
-import { MapCenter } from '../actions/map_actions';
+import { MapCenter } from '../../common/descriptor_types';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { MapStoreState } from '../../../../../plugins/maps/public/reducers/store';
-export function getHiddenLayerIds(state: unknown): string[];
+export function getHiddenLayerIds(state: MapStoreState): string[];
-export function getMapZoom(state: unknown): number;
+export function getMapZoom(state: MapStoreState): number;
-export function getMapCenter(state: unknown): MapCenter;
+export function getMapCenter(state: MapStoreState): MapCenter;
-export function getQueryableUniqueIndexPatternIds(state: unknown): string[];
+export function getQueryableUniqueIndexPatternIds(state: MapStoreState): string[];
diff --git a/x-pack/legacy/plugins/maps/public/selectors/ui_selectors.js b/x-pack/legacy/plugins/maps/public/selectors/ui_selectors.js
deleted file mode 100644
index 912ee08396212..0000000000000
--- a/x-pack/legacy/plugins/maps/public/selectors/ui_selectors.js
+++ /dev/null
@@ -1,13 +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 const getFlyoutDisplay = ({ ui }) => ui.flyoutDisplay;
-export const getIsSetViewOpen = ({ ui }) => ui.isSetViewOpen;
-export const getIsLayerTOCOpen = ({ ui }) => ui.isLayerTOCOpen;
-export const getOpenTOCDetails = ({ ui }) => ui.openTOCDetails;
-export const getIsFullScreen = ({ ui }) => ui.isFullScreen;
-export const getIsReadOnly = ({ ui }) => ui.isReadOnly;
-export const getIndexingStage = ({ ui }) => ui.importIndexingStage;
diff --git a/x-pack/legacy/plugins/maps/public/selectors/ui_selectors.ts b/x-pack/legacy/plugins/maps/public/selectors/ui_selectors.ts
new file mode 100644
index 0000000000000..fdf2a8ea0e4f3
--- /dev/null
+++ b/x-pack/legacy/plugins/maps/public/selectors/ui_selectors.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { MapStoreState } from '../../../../../plugins/maps/public/reducers/store';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { FLYOUT_STATE, INDEXING_STAGE } from '../../../../../plugins/maps/public/reducers/ui';
+
+export const getFlyoutDisplay = ({ ui }: MapStoreState): FLYOUT_STATE => ui.flyoutDisplay;
+export const getIsSetViewOpen = ({ ui }: MapStoreState): boolean => ui.isSetViewOpen;
+export const getIsLayerTOCOpen = ({ ui }: MapStoreState): boolean => ui.isLayerTOCOpen;
+export const getOpenTOCDetails = ({ ui }: MapStoreState): string[] => ui.openTOCDetails;
+export const getIsFullScreen = ({ ui }: MapStoreState): boolean => ui.isFullScreen;
+export const getIsReadOnly = ({ ui }: MapStoreState): boolean => ui.isReadOnly;
+export const getIndexingStage = ({ ui }: MapStoreState): INDEXING_STAGE | null =>
+ ui.importIndexingStage;
diff --git a/x-pack/legacy/plugins/siem/public/components/ml/helpers/index.test.ts b/x-pack/legacy/plugins/siem/common/detection_engine/ml_helpers.test.ts
similarity index 96%
rename from x-pack/legacy/plugins/siem/public/components/ml/helpers/index.test.ts
rename to x-pack/legacy/plugins/siem/common/detection_engine/ml_helpers.test.ts
index 693f0bd0dd0fd..ba93b2e4b8a0d 100644
--- a/x-pack/legacy/plugins/siem/public/components/ml/helpers/index.test.ts
+++ b/x-pack/legacy/plugins/siem/common/detection_engine/ml_helpers.test.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { isJobStarted, isJobLoading, isJobFailed } from './';
+import { isJobStarted, isJobLoading, isJobFailed } from './ml_helpers';
describe('isJobStarted', () => {
test('returns false if only jobState is enabled', () => {
diff --git a/x-pack/legacy/plugins/siem/public/components/ml/helpers/index.ts b/x-pack/legacy/plugins/siem/common/detection_engine/ml_helpers.ts
similarity index 89%
rename from x-pack/legacy/plugins/siem/public/components/ml/helpers/index.ts
rename to x-pack/legacy/plugins/siem/common/detection_engine/ml_helpers.ts
index c06596b49317d..e4158d08d448d 100644
--- a/x-pack/legacy/plugins/siem/public/components/ml/helpers/index.ts
+++ b/x-pack/legacy/plugins/siem/common/detection_engine/ml_helpers.ts
@@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { RuleType } from './types';
+
// Based on ML Job/Datafeed States from x-pack/legacy/plugins/ml/common/constants/states.js
const enabledStates = ['started', 'opened'];
const loadingStates = ['starting', 'stopping', 'opening', 'closing'];
@@ -20,3 +22,5 @@ export const isJobLoading = (jobState: string, datafeedState: string): boolean =
export const isJobFailed = (jobState: string, datafeedState: string): boolean => {
return failureStates.includes(jobState) || failureStates.includes(datafeedState);
};
+
+export const isMlRule = (ruleType: RuleType) => ruleType === 'machine_learning';
diff --git a/x-pack/legacy/plugins/siem/common/detection_engine/types.ts b/x-pack/legacy/plugins/siem/common/detection_engine/types.ts
index 0de370b11cdaf..39012d0b4b683 100644
--- a/x-pack/legacy/plugins/siem/common/detection_engine/types.ts
+++ b/x-pack/legacy/plugins/siem/common/detection_engine/types.ts
@@ -3,9 +3,17 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
+import * as t from 'io-ts';
import { AlertAction } from '../../../../../plugins/alerting/common';
export type RuleAlertAction = Omit & {
action_type_id: string;
};
+
+export const RuleTypeSchema = t.keyof({
+ query: null,
+ saved_query: null,
+ machine_learning: null,
+});
+export type RuleType = t.TypeOf;
diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx
index a3c4a655a4937..c7e368da1338f 100644
--- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx
@@ -8,9 +8,9 @@ import { EuiLink, EuiText } from '@elastic/eui';
import React, { useEffect, useState } from 'react';
import { createPortalNode, InPortal } from 'react-reverse-portal';
import styled, { css } from 'styled-components';
+import { npStart } from 'ui/new_platform';
-import { EmbeddablePanel } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public';
-import { start } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public/legacy';
+import { EmbeddablePanel } from '../../../../../../../src/plugins/embeddable/public';
import { DEFAULT_INDEX_KEY } from '../../../common/constants';
import { getIndexPatternTitleIdMapping } from '../../hooks/api/helpers';
import { useIndexPatterns } from '../../hooks/use_index_patterns';
@@ -198,8 +198,8 @@ export const EmbeddedMapComponent = ({
data-test-subj="embeddable-panel"
embeddable={embeddable}
getActions={services.uiActions.getTriggerCompatibleActions}
- getEmbeddableFactory={start.getEmbeddableFactory}
- getAllEmbeddableFactories={start.getEmbeddableFactories}
+ getEmbeddableFactory={npStart.plugins.embeddable.getEmbeddableFactory}
+ getAllEmbeddableFactories={npStart.plugins.embeddable.getEmbeddableFactories}
notifications={services.notifications}
overlays={services.overlays}
inspector={services.inspector}
diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx
index 4b32fd8299ef7..56211c9ff8935 100644
--- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx
@@ -8,14 +8,13 @@ import uuid from 'uuid';
import React from 'react';
import { OutPortal, PortalNode } from 'react-reverse-portal';
import minimatch from 'minimatch';
-import { ViewMode } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public';
import { IndexPatternMapping, SetQuery } from './types';
import { getLayerList } from './map_config';
import { MAP_SAVED_OBJECT_TYPE } from '../../../../../../plugins/maps/public';
import { MapEmbeddable, RenderTooltipContentParams } from '../../../../maps/public';
import * as i18n from './translations';
import { Query, Filter } from '../../../../../../../src/plugins/data/public';
-import { EmbeddableStart } from '../../../../../../../src/plugins/embeddable/public';
+import { EmbeddableStart, ViewMode } from '../../../../../../../src/plugins/embeddable/public';
import { IndexPatternSavedObject } from '../../hooks/types';
/**
diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/editable_title.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/editable_title.test.tsx.snap
index 24b1756aade2e..c8d4b6ec3b4c8 100644
--- a/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/editable_title.test.tsx.snap
+++ b/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/editable_title.test.tsx.snap
@@ -19,6 +19,7 @@ exports[`EditableTitle it renders 1`] = `
aria-label="You can edit Test title by clicking"
data-test-subj="editable-title-edit-icon"
iconType="pencil"
+ isDisabled={false}
onClick={[Function]}
/>
diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx
index 29cc1579f9bcc..165be00384779 100644
--- a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx
@@ -34,12 +34,18 @@ const MySpinner = styled(EuiLoadingSpinner)`
`;
interface Props {
+ disabled?: boolean;
isLoading: boolean;
title: string | React.ReactNode;
onSubmit: (title: string) => void;
}
-const EditableTitleComponent: React.FC = ({ onSubmit, isLoading, title }) => {
+const EditableTitleComponent: React.FC = ({
+ disabled = false,
+ onSubmit,
+ isLoading,
+ title,
+}) => {
const [editMode, setEditMode] = useState(false);
const [changedTitle, onTitleChange] = useState(typeof title === 'string' ? title : '');
@@ -104,6 +110,7 @@ const EditableTitleComponent: React.FC = ({ onSubmit, isLoading, title })
{isLoading && }
{!isLoading && (
= ({
const insertTimelineButton = useMemo(
() => (
-
+ {i18n.INSERT_TIMELINE}
}>
+
+
),
[handleOpenPopover, isDisabled]
);
diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/use_insert_timeline.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/use_insert_timeline.tsx
index 0cfb07cccfd6c..e4d828b68f3dc 100644
--- a/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/use_insert_timeline.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/use_insert_timeline.tsx
@@ -17,7 +17,7 @@ export const useInsertTimeline = (form: FormHook, fieldNa
});
const handleOnTimelineChange = useCallback(
(title: string, id: string | null) => {
- const builtLink = `${basePath}/app/siem#/timelines?timeline=(id:${id},isOpen:!t)`;
+ const builtLink = `${basePath}/app/siem#/timelines?timeline=(id:'${id}',isOpen:!t)`;
const currentValue = form.getFormData()[fieldName];
const newValue: string = [
currentValue.slice(0, cursorPosition.start),
diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/translations.ts b/x-pack/legacy/plugins/siem/public/components/timeline/translations.ts
index de3e3c8e792fe..101837168350f 100644
--- a/x-pack/legacy/plugins/siem/public/components/timeline/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/components/timeline/translations.ts
@@ -25,5 +25,5 @@ export const SEARCH_BOX_TIMELINE_PLACEHOLDER = i18n.translate(
);
export const INSERT_TIMELINE = i18n.translate('xpack.siem.insert.timeline.insertTimelineButton', {
- defaultMessage: 'Insert Timeline…',
+ defaultMessage: 'Insert timeline link',
});
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/api.ts b/x-pack/legacy/plugins/siem/public/containers/case/api.ts
index 7d5ae53b78ff8..bd243d0ba5f64 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/api.ts
+++ b/x-pack/legacy/plugins/siem/public/containers/case/api.ts
@@ -119,7 +119,7 @@ export const getCases = async ({
signal,
}: FetchCasesProps): Promise => {
const query = {
- reporters: filterOptions.reporters.map(r => r.username),
+ reporters: filterOptions.reporters.map(r => r.username ?? '').filter(r => r !== ''),
tags: filterOptions.tags,
...(filterOptions.status !== '' ? { status: filterOptions.status } : {}),
...(filterOptions.search.length > 0 ? { search: filterOptions.search } : {}),
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/configure/translations.ts b/x-pack/legacy/plugins/siem/public/containers/case/configure/translations.ts
new file mode 100644
index 0000000000000..dbd618f40155d
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/containers/case/configure/translations.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 { i18n } from '@kbn/i18n';
+
+export * from '../translations';
+
+export const SUCCESS_CONFIGURE = i18n.translate('xpack.siem.case.configure.successSaveToast', {
+ defaultMessage: 'Saved external connection settings',
+});
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx b/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx
index b25667f070fdf..6524c40a8e6e4 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx
@@ -7,8 +7,8 @@
import { useState, useEffect, useCallback } from 'react';
import { getCaseConfigure, patchCaseConfigure, postCaseConfigure } from './api';
-import { useStateToaster, errorToToaster } from '../../../components/toasters';
-import * as i18n from '../translations';
+import { useStateToaster, errorToToaster, displaySuccessToast } from '../../../components/toasters';
+import * as i18n from './translations';
import { ClosureType } from './types';
import { CurrentConfiguration } from '../../../pages/case/components/configure_cases/reducer';
@@ -124,6 +124,8 @@ export const useCaseConfigure = ({
closureType: res.closureType,
});
}
+
+ displaySuccessToast(i18n.SUCCESS_CONFIGURE, dispatchToaster);
}
} catch (error) {
if (!didCancel) {
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/translations.ts b/x-pack/legacy/plugins/siem/public/containers/case/translations.ts
index 601db373f041e..a453be32480e2 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/containers/case/translations.ts
@@ -10,6 +10,46 @@ export const ERROR_TITLE = i18n.translate('xpack.siem.containers.case.errorTitle
defaultMessage: 'Error fetching data',
});
+export const ERROR_DELETING = i18n.translate('xpack.siem.containers.case.errorDeletingTitle', {
+ defaultMessage: 'Error deleting data',
+});
+
+export const UPDATED_CASE = (caseTitle: string) =>
+ i18n.translate('xpack.siem.containers.case.updatedCase', {
+ values: { caseTitle },
+ defaultMessage: 'Updated "{caseTitle}"',
+ });
+
+export const DELETED_CASES = (totalCases: number, caseTitle?: string) =>
+ i18n.translate('xpack.siem.containers.case.deletedCases', {
+ values: { caseTitle, totalCases },
+ defaultMessage: 'Deleted {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}',
+ });
+
+export const CLOSED_CASES = ({
+ totalCases,
+ caseTitle,
+}: {
+ totalCases: number;
+ caseTitle?: string;
+}) =>
+ i18n.translate('xpack.siem.containers.case.closedCases', {
+ values: { caseTitle, totalCases },
+ defaultMessage: 'Closed {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}',
+ });
+
+export const REOPENED_CASES = ({
+ totalCases,
+ caseTitle,
+}: {
+ totalCases: number;
+ caseTitle?: string;
+}) =>
+ i18n.translate('xpack.siem.containers.case.reopenedCases', {
+ values: { caseTitle, totalCases },
+ defaultMessage: 'Reopened {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}',
+ });
+
export const TAG_FETCH_FAILURE = i18n.translate(
'xpack.siem.containers.case.tagFetchFailDescription',
{
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/types.ts b/x-pack/legacy/plugins/siem/public/containers/case/types.ts
index bb215d6ac271c..d2a58e9eeeff4 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/types.ts
+++ b/x-pack/legacy/plugins/siem/public/containers/case/types.ts
@@ -90,7 +90,7 @@ export enum SortFieldCase {
export interface ElasticUser {
readonly email?: string | null;
readonly fullName?: string | null;
- readonly username: string;
+ readonly username?: string | null;
}
export interface FetchCasesProps extends ApiProps {
@@ -114,3 +114,8 @@ export interface ActionLicense {
enabledInConfig: boolean;
enabledInLicense: boolean;
}
+
+export interface DeleteCase {
+ id: string;
+ title?: string;
+}
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_bulk_update_case.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_bulk_update_case.tsx
index f1129bae9f537..7d040c49f1971 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/use_bulk_update_case.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_bulk_update_case.tsx
@@ -5,7 +5,7 @@
*/
import { useCallback, useReducer } from 'react';
-import { errorToToaster, useStateToaster } from '../../components/toasters';
+import { displaySuccessToast, errorToToaster, useStateToaster } from '../../components/toasters';
import * as i18n from './translations';
import { patchCasesStatus } from './api';
import { BulkUpdateStatus, Case } from './types';
@@ -71,9 +71,22 @@ export const useUpdateCases = (): UseUpdateCase => {
const patchData = async () => {
try {
dispatch({ type: 'FETCH_INIT' });
- await patchCasesStatus(cases, abortCtrl.signal);
+ const patchResponse = await patchCasesStatus(cases, abortCtrl.signal);
if (!cancel) {
+ const resultCount = Object.keys(patchResponse).length;
+ const firstTitle = patchResponse[0].title;
+
dispatch({ type: 'FETCH_SUCCESS', payload: true });
+ const messageArgs = {
+ totalCases: resultCount,
+ caseTitle: resultCount === 1 ? firstTitle : '',
+ };
+ const message =
+ resultCount && patchResponse[0].status === 'open'
+ ? i18n.REOPENED_CASES(messageArgs)
+ : i18n.CLOSED_CASES(messageArgs);
+
+ displaySuccessToast(message, dispatchToaster);
}
} catch (error) {
if (!cancel) {
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx
index b44e01d06acaf..07e3786758aeb 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx
@@ -5,9 +5,10 @@
*/
import { useCallback, useReducer } from 'react';
-import { errorToToaster, useStateToaster } from '../../components/toasters';
+import { displaySuccessToast, errorToToaster, useStateToaster } from '../../components/toasters';
import * as i18n from './translations';
import { deleteCases } from './api';
+import { DeleteCase } from './types';
interface DeleteState {
isDisplayConfirmDeleteModal: boolean;
@@ -57,9 +58,10 @@ const dataFetchReducer = (state: DeleteState, action: Action): DeleteState => {
return state;
}
};
+
interface UseDeleteCase extends DeleteState {
dispatchResetIsDeleted: () => void;
- handleOnDeleteConfirm: (caseIds: string[]) => void;
+ handleOnDeleteConfirm: (caseIds: DeleteCase[]) => void;
handleToggleModal: () => void;
}
@@ -72,21 +74,26 @@ export const useDeleteCases = (): UseDeleteCase => {
});
const [, dispatchToaster] = useStateToaster();
- const dispatchDeleteCases = useCallback((caseIds: string[]) => {
+ const dispatchDeleteCases = useCallback((cases: DeleteCase[]) => {
let cancel = false;
const abortCtrl = new AbortController();
const deleteData = async () => {
try {
dispatch({ type: 'FETCH_INIT' });
+ const caseIds = cases.map(theCase => theCase.id);
await deleteCases(caseIds, abortCtrl.signal);
if (!cancel) {
dispatch({ type: 'FETCH_SUCCESS', payload: true });
+ displaySuccessToast(
+ i18n.DELETED_CASES(cases.length, cases.length === 1 ? cases[0].title : ''),
+ dispatchToaster
+ );
}
} catch (error) {
if (!cancel) {
errorToToaster({
- title: i18n.ERROR_TITLE,
+ title: i18n.ERROR_DELETING,
error: error.body && error.body.message ? new Error(error.body.message) : error,
dispatchToaster,
});
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_reporters.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_reporters.tsx
index 6974000414a06..2478172a3394b 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_reporters.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_reporters.tsx
@@ -6,6 +6,7 @@
import { useCallback, useEffect, useState } from 'react';
+import { isEmpty } from 'lodash/fp';
import { User } from '../../../../../../plugins/case/common/api';
import { errorToToaster, useStateToaster } from '../../components/toasters';
import { getReporters } from './api';
@@ -44,9 +45,12 @@ export const useGetReporters = (): UseGetReporters => {
});
try {
const response = await getReporters(abortCtrl.signal);
+ const myReporters = response
+ .map(r => (r.full_name == null || isEmpty(r.full_name) ? r.username ?? '' : r.full_name))
+ .filter(u => !isEmpty(u));
if (!didCancel) {
setReporterState({
- reporters: response.map(r => r.full_name ?? r.username ?? 'N/A'),
+ reporters: myReporters,
respReporters: response,
isLoading: false,
isError: false,
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_post_push_to_service.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_post_push_to_service.tsx
index 03e10249317ee..d9a32f26f7fe7 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/use_post_push_to_service.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_post_push_to_service.tsx
@@ -148,7 +148,7 @@ const formatServiceRequestData = (myCase: Case): ServiceConnectorCaseParams => {
createdAt,
createdBy: {
fullName: createdBy.fullName ?? null,
- username: createdBy?.username,
+ username: createdBy?.username ?? '',
},
comments: comments
.filter(c => {
@@ -168,14 +168,14 @@ const formatServiceRequestData = (myCase: Case): ServiceConnectorCaseParams => {
createdAt: c.createdAt,
createdBy: {
fullName: c.createdBy.fullName ?? null,
- username: c.createdBy.username,
+ username: c.createdBy.username ?? '',
},
updatedAt: c.updatedAt,
updatedBy:
c.updatedBy != null
? {
fullName: c.updatedBy.fullName ?? null,
- username: c.updatedBy.username,
+ username: c.updatedBy.username ?? '',
}
: null,
})),
@@ -187,7 +187,7 @@ const formatServiceRequestData = (myCase: Case): ServiceConnectorCaseParams => {
updatedBy != null
? {
fullName: updatedBy.fullName ?? null,
- username: updatedBy.username,
+ username: updatedBy.username ?? '',
}
: null,
};
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx
index 85ad4fd3fc47a..4973deef4d91a 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx
@@ -5,8 +5,8 @@
*/
import { useReducer, useCallback } from 'react';
+import { displaySuccessToast, errorToToaster, useStateToaster } from '../../components/toasters';
import { CasePatchRequest } from '../../../../../../plugins/case/common/api';
-import { errorToToaster, useStateToaster } from '../../components/toasters';
import { patchCase } from './api';
import * as i18n from './translations';
@@ -94,6 +94,7 @@ export const useUpdateCase = ({ caseId }: { caseId: string }): UseUpdateCase =>
updateCase(response[0]);
}
dispatch({ type: 'FETCH_SUCCESS' });
+ displaySuccessToast(i18n.UPDATED_CASE(response[0].title), dispatchToaster);
}
} catch (error) {
if (!cancel) {
diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts
index f676ab944fce4..bc559c5ac4972 100644
--- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts
+++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts
@@ -6,12 +6,7 @@
import * as t from 'io-ts';
-export const RuleTypeSchema = t.keyof({
- query: null,
- saved_query: null,
- machine_learning: null,
-});
-export type RuleType = t.TypeOf;
+import { RuleTypeSchema } from '../../../../common/detection_engine/types';
/**
* Params is an "record", since it is a type of AlertActionParams which is action templates.
diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule_status.test.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule_status.test.tsx
index 7269bf1baa5e5..0a30329baf68d 100644
--- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule_status.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule_status.test.tsx
@@ -12,7 +12,7 @@ import {
ReturnRulesStatuses,
} from './use_rule_status';
import * as api from './api';
-import { RuleType, Rule } from '../rules/types';
+import { Rule } from '../rules/types';
jest.mock('./api');
@@ -57,7 +57,7 @@ const testRule: Rule = {
threat: [],
throttle: null,
to: 'now',
- type: 'query' as RuleType,
+ type: 'query',
updated_at: 'mm/dd/yyyyTHH:MM:sssz',
updated_by: 'mockUser',
};
diff --git a/x-pack/legacy/plugins/siem/public/lib/connectors/servicenow.tsx b/x-pack/legacy/plugins/siem/public/lib/connectors/servicenow.tsx
index d67007399abea..536798ffad41b 100644
--- a/x-pack/legacy/plugins/siem/public/lib/connectors/servicenow.tsx
+++ b/x-pack/legacy/plugins/siem/public/lib/connectors/servicenow.tsx
@@ -181,6 +181,7 @@ const ServiceNowConnectorFields: React.FunctionComponent
+
+
+
-
{
let didCancel = false;
const fetchData = async () => {
try {
- const response = await security.authc.getCurrentUser();
- if (!didCancel) {
- setUser(convertToCamelCase(response));
+ if (security != null) {
+ const response = await security.authc.getCurrentUser();
+ if (!didCancel) {
+ setUser(convertToCamelCase(response));
+ }
+ } else {
+ setUser({
+ username: i18n.translate('xpack.siem.getCurrentUser.unknownUser', {
+ defaultMessage: 'Unknown',
+ }),
+ email: '',
+ fullName: '',
+ roles: [],
+ enabled: false,
+ authenticationRealm: { name: '', type: '' },
+ lookupRealm: { name: '', type: '' },
+ authenticationProvider: '',
+ });
}
} catch (error) {
if (!didCancel) {
@@ -81,3 +96,29 @@ export const useCurrentUser = (): AuthenticatedElasticUser | null => {
}, []);
return user;
};
+
+export interface UseGetUserSavedObjectPermissions {
+ crud: boolean;
+ read: boolean;
+}
+
+export const useGetUserSavedObjectPermissions = () => {
+ const [
+ savedObjectsPermissions,
+ setSavedObjectsPermissions,
+ ] = useState(null);
+ const uiCapabilities = useKibana().services.application.capabilities;
+
+ useEffect(() => {
+ const capabilitiesCanUserCRUD: boolean =
+ typeof uiCapabilities.siem.crud === 'boolean' ? uiCapabilities.siem.crud : false;
+ const capabilitiesCanUserRead: boolean =
+ typeof uiCapabilities.siem.show === 'boolean' ? uiCapabilities.siem.show : false;
+ setSavedObjectsPermissions({
+ crud: capabilitiesCanUserCRUD,
+ read: capabilitiesCanUserRead,
+ });
+ }, [uiCapabilities]);
+
+ return savedObjectsPermissions;
+};
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/case.tsx b/x-pack/legacy/plugins/siem/public/pages/case/case.tsx
index 9255dee461940..2ae35796387b8 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/case.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/case.tsx
@@ -7,16 +7,34 @@
import React from 'react';
import { WrapperPage } from '../../components/wrapper_page';
-import { AllCases } from './components/all_cases';
+import { useGetUserSavedObjectPermissions } from '../../lib/kibana';
import { SpyRoute } from '../../utils/route/spy_routes';
+import { AllCases } from './components/all_cases';
+
+import { getSavedObjectReadOnly, CaseCallOut } from './components/callout';
+import { CaseSavedObjectNoPermissions } from './saved_object_no_permissions';
+
+const infoReadSavedObject = getSavedObjectReadOnly();
+
+export const CasesPage = React.memo(() => {
+ const userPermissions = useGetUserSavedObjectPermissions();
-export const CasesPage = React.memo(() => (
- <>
-
-
-
-
- >
-));
+ return userPermissions == null || userPermissions?.read ? (
+ <>
+
+ {userPermissions != null && !userPermissions?.crud && userPermissions?.read && (
+
+ )}
+
+
+
+ >
+ ) : (
+
+ );
+});
CasesPage.displayName = 'CasesPage';
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/case_details.tsx b/x-pack/legacy/plugins/siem/public/pages/case/case_details.tsx
index 890df91c8560e..cbc7bbc62fbf9 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/case_details.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/case_details.tsx
@@ -5,22 +5,36 @@
*/
import React from 'react';
-import { useParams } from 'react-router-dom';
+import { useParams, Redirect } from 'react-router-dom';
-import { CaseView } from './components/case_view';
+import { useGetUrlSearch } from '../../components/navigation/use_get_url_search';
+import { useGetUserSavedObjectPermissions } from '../../lib/kibana';
import { SpyRoute } from '../../utils/route/spy_routes';
+import { getCaseUrl } from '../../components/link_to';
+import { navTabs } from '../home/home_navigations';
+import { CaseView } from './components/case_view';
+import { getSavedObjectReadOnly, CaseCallOut } from './components/callout';
+
+const infoReadSavedObject = getSavedObjectReadOnly();
export const CaseDetailsPage = React.memo(() => {
+ const userPermissions = useGetUserSavedObjectPermissions();
const { detailName: caseId } = useParams();
- if (!caseId) {
- return null;
+ const search = useGetUrlSearch(navTabs.case);
+
+ if (userPermissions != null && !userPermissions.read) {
+ return ;
}
- return (
+
+ return caseId != null ? (
<>
-
+ {userPermissions != null && !userPermissions?.crud && userPermissions?.read && (
+
+ )}
+
>
- );
+ ) : null;
});
CaseDetailsPage.displayName = 'CaseDetailsPage';
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.tsx
index 46a777984c6e0..ecc57c50e28eb 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.tsx
@@ -31,6 +31,7 @@ const initialCommentValue: CommentRequest = {
interface AddCommentProps {
caseId: string;
+ disabled?: boolean;
insertQuote: string | null;
onCommentSaving?: () => void;
onCommentPosted: (newCase: Case) => void;
@@ -38,7 +39,7 @@ interface AddCommentProps {
}
export const AddComment = React.memo(
- ({ caseId, insertQuote, showLoading = true, onCommentPosted, onCommentSaving }) => {
+ ({ caseId, disabled, insertQuote, showLoading = true, onCommentPosted, onCommentSaving }) => {
const { isLoading, postComment } = usePostComment(caseId);
const { form } = useForm({
defaultValue: initialCommentValue,
@@ -87,7 +88,7 @@ export const AddComment = React.memo(
bottomRightContent: (
- {createdBy.fullName ?? createdBy.username ?? 'N/A'}
+ {createdBy.fullName ?? createdBy.username ?? ''}
>
);
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx
index bdcb87b483851..a6da45a8c5bb1 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx
@@ -87,7 +87,7 @@ describe('AllCases', () => {
it('should render AllCases', () => {
const wrapper = mount(
-
+
);
expect(
@@ -132,7 +132,7 @@ describe('AllCases', () => {
it('should tableHeaderSortButton AllCases', () => {
const wrapper = mount(
-
+
);
wrapper
@@ -149,7 +149,7 @@ describe('AllCases', () => {
it('closes case when row action icon clicked', () => {
const wrapper = mount(
-
+
);
wrapper
@@ -182,7 +182,7 @@ describe('AllCases', () => {
const wrapper = mount(
-
+
);
wrapper
@@ -202,7 +202,7 @@ describe('AllCases', () => {
.last()
.simulate('click');
expect(handleOnDeleteConfirm.mock.calls[0][0]).toStrictEqual(
- useGetCasesMockState.data.cases.map(theCase => theCase.id)
+ useGetCasesMockState.data.cases.map(({ id }) => ({ id }))
);
});
it('Bulk close status update', () => {
@@ -213,7 +213,7 @@ describe('AllCases', () => {
const wrapper = mount(
-
+
);
wrapper
@@ -238,7 +238,7 @@ describe('AllCases', () => {
const wrapper = mount(
-
+
);
wrapper
@@ -259,7 +259,7 @@ describe('AllCases', () => {
mount(
-
+
);
expect(refetchCases).toBeCalled();
@@ -274,7 +274,7 @@ describe('AllCases', () => {
mount(
-
+
);
expect(refetchCases).toBeCalled();
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx
index 27316ab8427cb..161910bb5498a 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx
@@ -17,11 +17,12 @@ import {
EuiTableSortingType,
} from '@elastic/eui';
import { EuiTableSelectionType } from '@elastic/eui/src/components/basic_table/table_types';
+import { isEmpty } from 'lodash/fp';
import styled, { css } from 'styled-components';
import * as i18n from './translations';
import { getCasesColumns } from './columns';
-import { Case, FilterOptions, SortFieldCase } from '../../../../containers/case/types';
+import { Case, DeleteCase, FilterOptions, SortFieldCase } from '../../../../containers/case/types';
import { useGetCases, UpdateCase } from '../../../../containers/case/use_get_cases';
import { useGetCasesStatus } from '../../../../containers/case/use_get_cases_status';
import { useDeleteCases } from '../../../../containers/case/use_delete_cases';
@@ -35,7 +36,7 @@ import {
UtilityBarSection,
UtilityBarText,
} from '../../../../components/utility_bar';
-import { getConfigureCasesUrl, getCreateCaseUrl } from '../../../../components/link_to';
+import { getCreateCaseUrl } from '../../../../components/link_to';
import { getBulkItems } from '../bulk_actions';
import { CaseHeaderPage } from '../case_header_page';
import { ConfirmDeleteCaseModal } from '../confirm_delete_case';
@@ -45,6 +46,11 @@ import { navTabs } from '../../../home/home_navigations';
import { getActions } from './actions';
import { CasesTableFilters } from './table_filters';
import { useUpdateCases } from '../../../../containers/case/use_bulk_update_case';
+import { useGetActionLicense } from '../../../../containers/case/use_get_action_license';
+import { getActionLicenseError } from '../use_push_to_service/helpers';
+import { CaseCallOut } from '../callout';
+import { ConfigureCaseButton } from '../configure_cases/button';
+import { ERROR_PUSH_SERVICE_CALLOUT_TITLE } from '../use_push_to_service/translations';
const Div = styled.div`
margin-top: ${({ theme }) => theme.eui.paddingSizes.m};
@@ -75,9 +81,13 @@ const getSortField = (field: string): SortFieldCase => {
}
return SortFieldCase.createdAt;
};
-export const AllCases = React.memo(() => {
- const urlSearch = useGetUrlSearch(navTabs.case);
+interface AllCasesProps {
+ userCanCrud: boolean;
+}
+export const AllCases = React.memo(({ userCanCrud }) => {
+ const urlSearch = useGetUrlSearch(navTabs.case);
+ const { actionLicense } = useGetActionLicense();
const {
countClosedCases,
countOpenCases,
@@ -107,11 +117,24 @@ export const AllCases = React.memo(() => {
isDisplayConfirmDeleteModal,
} = useDeleteCases();
- const { dispatchResetIsUpdated, isUpdated, updateBulkStatus } = useUpdateCases();
+ // Update case
+ const {
+ dispatchResetIsUpdated,
+ isLoading: isUpdating,
+ isUpdated,
+ updateBulkStatus,
+ } = useUpdateCases();
+ const [deleteThisCase, setDeleteThisCase] = useState({
+ title: '',
+ id: '',
+ });
+ const [deleteBulk, setDeleteBulk] = useState([]);
const refreshCases = useCallback(() => {
refetchCases(filterOptions, queryParams);
fetchCasesStatus();
+ setSelectedCases([]);
+ setDeleteBulk([]);
}, [filterOptions, queryParams]);
useEffect(() => {
@@ -124,11 +147,6 @@ export const AllCases = React.memo(() => {
dispatchResetIsUpdated();
}
}, [isDeleted, isUpdated]);
- const [deleteThisCase, setDeleteThisCase] = useState({
- title: '',
- id: '',
- });
- const [deleteBulk, setDeleteBulk] = useState([]);
const confirmDeleteModal = useMemo(
() => (
{
onCancel={handleToggleModal}
onConfirm={handleOnDeleteConfirm.bind(
null,
- deleteBulk.length > 0 ? deleteBulk : [deleteThisCase.id]
+ deleteBulk.length > 0 ? deleteBulk : [deleteThisCase]
)}
/>
),
@@ -150,10 +168,20 @@ export const AllCases = React.memo(() => {
setDeleteThisCase(deleteCase);
}, []);
- const toggleBulkDeleteModal = useCallback((deleteCases: string[]) => {
- handleToggleModal();
- setDeleteBulk(deleteCases);
- }, []);
+ const toggleBulkDeleteModal = useCallback(
+ (caseIds: string[]) => {
+ handleToggleModal();
+ if (caseIds.length === 1) {
+ const singleCase = selectedCases.find(theCase => theCase.id === caseIds[0]);
+ if (singleCase) {
+ return setDeleteThisCase({ id: singleCase.id, title: singleCase.title });
+ }
+ }
+ const convertToDeleteCases: DeleteCase[] = caseIds.map(id => ({ id }));
+ setDeleteBulk(convertToDeleteCases);
+ },
+ [selectedCases]
+ );
const handleUpdateCaseStatus = useCallback(
(status: string) => {
@@ -199,6 +227,8 @@ export const AllCases = React.memo(() => {
[filterOptions.status, toggleDeleteModal, handleDispatchUpdate]
);
+ const actionsErrors = useMemo(() => getActionLicenseError(actionLicense), [actionLicense]);
+
const tableOnChangeCallback = useCallback(
({ page, sort }: EuiBasicTableOnChange) => {
let newQueryParams = queryParams;
@@ -233,10 +263,10 @@ export const AllCases = React.memo(() => {
[filterOptions, queryParams]
);
- const memoizedGetCasesColumns = useMemo(() => getCasesColumns(actions, filterOptions.status), [
- actions,
- filterOptions.status,
- ]);
+ const memoizedGetCasesColumns = useMemo(
+ () => getCasesColumns(userCanCrud ? actions : [], filterOptions.status),
+ [actions, filterOptions.status, userCanCrud]
+ );
const memoizedPagination = useMemo(
() => ({
pageIndex: queryParams.page - 1,
@@ -259,8 +289,12 @@ export const AllCases = React.memo(() => {
[loading]
);
const isDataEmpty = useMemo(() => data.total === 0, [data]);
+
return (
<>
+ {!isEmpty(actionsErrors) && (
+
+ )}
@@ -278,18 +312,28 @@ export const AllCases = React.memo(() => {
/>
-
- {i18n.CONFIGURE_CASES_BUTTON}
-
+ >}
+ titleTooltip={!isEmpty(actionsErrors) ? actionsErrors[0].title : ''}
+ urlSearch={urlSearch}
+ />
-
+
{i18n.CREATE_TITLE}
- {(isCasesLoading || isDeleting) && !isDataEmpty && (
+ {(isCasesLoading || isDeleting || isUpdating) && !isDataEmpty && (
)}
@@ -321,15 +365,16 @@ export const AllCases = React.memo(() => {
{i18n.SHOWING_SELECTED_CASES(selectedCases.length)}
-
- {i18n.BULK_ACTIONS}
-
-
+ {userCanCrud && (
+
+ {i18n.BULK_ACTIONS}
+
+ )}
{i18n.REFRESH}
@@ -339,7 +384,7 @@ export const AllCases = React.memo(() => {
{
body={i18n.NO_CASES_BODY}
actions={
{
}
onChange={tableOnChangeCallback}
pagination={memoizedPagination}
- selection={euiBasicTableSelectionProps}
+ selection={userCanCrud ? euiBasicTableSelectionProps : {}}
sorting={sorting}
/>
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/table_filters.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/table_filters.tsx
index a71ad1c45a980..a344dd7891010 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/table_filters.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/table_filters.tsx
@@ -43,7 +43,7 @@ const CasesTableFiltersComponent = ({
initial = defaultInitial,
}: CasesTableFiltersProps) => {
const [selectedReporters, setselectedReporters] = useState(
- initial.reporters.map(r => r.full_name ?? r.username)
+ initial.reporters.map(r => r.full_name ?? r.username ?? '')
);
const [search, setSearch] = useState(initial.search);
const [selectedTags, setSelectedTags] = useState(initial.tags);
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/callout/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/callout/helpers.tsx
new file mode 100644
index 0000000000000..929e8640dceb6
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/callout/helpers.tsx
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import * as i18n from './translations';
+
+export const getSavedObjectReadOnly = () => ({
+ title: i18n.READ_ONLY_SAVED_OBJECT_TITLE,
+ description: i18n.READ_ONLY_SAVED_OBJECT_MSG,
+});
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/errors_push_service_callout/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/callout/index.tsx
similarity index 59%
rename from x-pack/legacy/plugins/siem/public/pages/case/components/errors_push_service_callout/index.tsx
rename to x-pack/legacy/plugins/siem/public/pages/case/components/callout/index.tsx
index 15b50e4b4cd8d..30a95db2d82a5 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/errors_push_service_callout/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/callout/index.tsx
@@ -5,22 +5,28 @@
*/
import { EuiCallOut, EuiButton, EuiDescriptionList, EuiSpacer } from '@elastic/eui';
+import { isEmpty } from 'lodash/fp';
import React, { memo, useCallback, useState } from 'react';
import * as i18n from './translations';
-interface ErrorsPushServiceCallOut {
- errors: Array<{ title: string; description: JSX.Element }>;
+export * from './helpers';
+
+interface CaseCallOutProps {
+ title: string;
+ message?: string;
+ messages?: Array<{ title: string; description: JSX.Element }>;
}
-const ErrorsPushServiceCallOutComponent = ({ errors }: ErrorsPushServiceCallOut) => {
+const CaseCallOutComponent = ({ title, message, messages }: CaseCallOutProps) => {
const [showCallOut, setShowCallOut] = useState(true);
const handleCallOut = useCallback(() => setShowCallOut(false), [setShowCallOut]);
return showCallOut ? (
<>
-
-
+
+ {!isEmpty(messages) && }
+ {!isEmpty(message) && {message}
}
{i18n.DISMISS_CALLOUT}
@@ -30,4 +36,4 @@ const ErrorsPushServiceCallOutComponent = ({ errors }: ErrorsPushServiceCallOut)
) : null;
};
-export const ErrorsPushServiceCallOut = memo(ErrorsPushServiceCallOutComponent);
+export const CaseCallOut = memo(CaseCallOutComponent);
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/errors_push_service_callout/translations.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/callout/translations.ts
similarity index 50%
rename from x-pack/legacy/plugins/siem/public/pages/case/components/errors_push_service_callout/translations.ts
rename to x-pack/legacy/plugins/siem/public/pages/case/components/callout/translations.ts
index 57712e720f6d0..f70225b841162 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/errors_push_service_callout/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/callout/translations.ts
@@ -6,10 +6,18 @@
import { i18n } from '@kbn/i18n';
-export const ERROR_PUSH_SERVICE_CALLOUT_TITLE = i18n.translate(
- 'xpack.siem.case.errorsPushServiceCallOutTitle',
+export const READ_ONLY_SAVED_OBJECT_TITLE = i18n.translate(
+ 'xpack.siem.case.readOnlySavedObjectTitle',
{
- defaultMessage: 'To send cases to external systems, you need to:',
+ defaultMessage: 'You have read-only feature privileges',
+ }
+);
+
+export const READ_ONLY_SAVED_OBJECT_MSG = i18n.translate(
+ 'xpack.siem.case.readOnlySavedObjectDescription',
+ {
+ defaultMessage:
+ 'You are only allowed to view cases. If you need to open and update cases, contact your Kibana administrator',
}
);
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_status/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_status/index.tsx
index 5037987845326..2b16dfa150d61 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_status/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_status/index.tsx
@@ -35,6 +35,7 @@ interface CaseStatusProps {
badgeColor: string;
buttonLabel: string;
caseData: Case;
+ disabled?: boolean;
icon: string;
isLoading: boolean;
isSelected: boolean;
@@ -49,6 +50,7 @@ const CaseStatusComp: React.FC = ({
badgeColor,
buttonLabel,
caseData,
+ disabled = false,
icon,
isLoading,
isSelected,
@@ -89,6 +91,7 @@ const CaseStatusComp: React.FC = ({
= ({
/>
-
+
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx
index c4f1888df39e9..0e57326707e97 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx
@@ -12,6 +12,7 @@ const fetchCase = jest.fn();
export const caseProps: CaseProps = {
caseId: '3c4ddcc0-4e99-11ea-9290-35d05cb55c15',
+ userCanCrud: true,
caseData: {
closedAt: null,
closedBy: null,
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.test.tsx
index 1be0d6a3b5fcc..49f5f44cba271 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.test.tsx
@@ -60,6 +60,6 @@ describe('CaseView actions', () => {
expect(wrapper.find('[data-test-subj="confirm-delete-case-modal"]').exists()).toBeTruthy();
wrapper.find('button[data-test-subj="confirmModalConfirmButton"]').simulate('click');
- expect(handleOnDeleteConfirm.mock.calls[0][0]).toEqual([data.id]);
+ expect(handleOnDeleteConfirm.mock.calls[0][0]).toEqual([{ id: data.id, title: data.title }]);
});
});
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.tsx
index 1d90470eab0e1..0b08b866df964 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.tsx
@@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { isEmpty } from 'lodash/fp';
import React, { useMemo } from 'react';
-
import { Redirect } from 'react-router-dom';
import * as i18n from './translations';
import { useDeleteCases } from '../../../../containers/case/use_delete_cases';
@@ -16,9 +16,10 @@ import { Case } from '../../../../containers/case/types';
interface CaseViewActions {
caseData: Case;
+ disabled?: boolean;
}
-const CaseViewActionsComponent: React.FC = ({ caseData }) => {
+const CaseViewActionsComponent: React.FC = ({ caseData, disabled = false }) => {
// Delete case
const {
handleToggleModal,
@@ -34,7 +35,7 @@ const CaseViewActionsComponent: React.FC = ({ caseData }) => {
isModalVisible={isDisplayConfirmDeleteModal}
isPlural={false}
onCancel={handleToggleModal}
- onConfirm={handleOnDeleteConfirm.bind(null, [caseData.id])}
+ onConfirm={handleOnDeleteConfirm.bind(null, [{ id: caseData.id, title: caseData.title }])}
/>
),
[isDisplayConfirmDeleteModal, caseData]
@@ -43,11 +44,12 @@ const CaseViewActionsComponent: React.FC = ({ caseData }) => {
const propertyActions = useMemo(
() => [
{
+ disabled,
iconType: 'trash',
label: i18n.DELETE_CASE,
onClick: handleToggleModal,
},
- ...(caseData.externalService?.externalUrl !== null
+ ...(caseData.externalService != null && !isEmpty(caseData.externalService?.externalUrl)
? [
{
iconType: 'popout',
@@ -57,7 +59,7 @@ const CaseViewActionsComponent: React.FC = ({ caseData }) => {
]
: []),
],
- [handleToggleModal, caseData]
+ [disabled, handleToggleModal, caseData]
);
if (isDeleted) {
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx
index 92fc43eff53e9..3f5b3a3127177 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx
@@ -16,10 +16,10 @@ import { TestProviders } from '../../../../mock';
import { useUpdateCase } from '../../../../containers/case/use_update_case';
import { useGetCaseUserActions } from '../../../../containers/case/use_get_case_user_actions';
import { wait } from '../../../../lib/helpers';
-import { usePushToService } from './push_to_service';
+import { usePushToService } from '../use_push_to_service';
jest.mock('../../../../containers/case/use_update_case');
jest.mock('../../../../containers/case/use_get_case_user_actions');
-jest.mock('./push_to_service');
+jest.mock('../use_push_to_service');
const useUpdateCaseMock = useUpdateCase as jest.Mock;
const useGetCaseUserActionsMock = useGetCaseUserActions as jest.Mock;
const usePushToServiceMock = usePushToService as jest.Mock;
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx
index 07834c3fb0678..947da51365d66 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx
@@ -34,10 +34,11 @@ import { CaseStatus } from '../case_status';
import { navTabs } from '../../../home/home_navigations';
import { SpyRoute } from '../../../../utils/route/spy_routes';
import { useGetCaseUserActions } from '../../../../containers/case/use_get_case_user_actions';
-import { usePushToService } from './push_to_service';
+import { usePushToService } from '../use_push_to_service';
interface Props {
caseId: string;
+ userCanCrud: boolean;
}
const MyWrapper = styled(WrapperPage)`
@@ -55,15 +56,14 @@ const MyEuiHorizontalRule = styled(EuiHorizontalRule)`
}
`;
-export interface CaseProps {
- caseId: string;
+export interface CaseProps extends Props {
fetchCase: () => void;
caseData: Case;
updateCase: (newCase: Case) => void;
}
export const CaseComponent = React.memo(
- ({ caseId, caseData, fetchCase, updateCase }) => {
+ ({ caseId, caseData, fetchCase, updateCase, userCanCrud }) => {
const basePath = window.location.origin + useBasePath();
const caseLink = `${basePath}/app/siem#/case/${caseId}`;
const search = useGetUrlSearch(navTabs.case);
@@ -152,6 +152,7 @@ export const CaseComponent = React.memo(
caseStatus: caseData.status,
isNew: caseUserActions.filter(cua => cua.action === 'push-to-service').length === 0,
updateCase: handleUpdateCase,
+ userCanCrud,
});
const onSubmitTags = useCallback(newTags => onUpdateField('tags', newTags), [onUpdateField]);
@@ -219,6 +220,7 @@ export const CaseComponent = React.memo(
data-test-subj="case-view-title"
titleNode={
(
>
(
lastIndexPushToService={lastIndexPushToService}
onUpdateField={onUpdateField}
updateCase={updateCase}
+ userCanCrud={userCanCrud}
/>
@@ -260,6 +264,7 @@ export const CaseComponent = React.memo(
(
/>
(
}
);
-export const CaseView = React.memo(({ caseId }: Props) => {
+export const CaseView = React.memo(({ caseId, userCanCrud }: Props) => {
const { data, isLoading, isError, fetchCase, updateCase } = useGetCase(caseId);
if (isError) {
return null;
@@ -317,7 +323,13 @@ export const CaseView = React.memo(({ caseId }: Props) => {
}
return (
-
+
);
});
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/translations.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/translations.ts
index 3fc963fc23102..17132b9610754 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/translations.ts
@@ -118,56 +118,3 @@ export const EMAIL_BODY = (caseUrl: string) =>
values: { caseUrl },
defaultMessage: 'Case reference: {caseUrl}',
});
-
-export const PUSH_SERVICENOW = i18n.translate('xpack.siem.case.caseView.pushAsServicenowIncident', {
- defaultMessage: 'Push as ServiceNow incident',
-});
-
-export const UPDATE_PUSH_SERVICENOW = i18n.translate(
- 'xpack.siem.case.caseView.updatePushAsServicenowIncident',
- {
- defaultMessage: 'Update ServiceNow incident',
- }
-);
-
-export const PUSH_DISABLE_BY_NO_CASE_CONFIG_TITLE = i18n.translate(
- 'xpack.siem.case.caseView.pushToServiceDisableByNoCaseConfigTitle',
- {
- defaultMessage: 'Configure external connector',
- }
-);
-
-export const PUSH_DISABLE_BECAUSE_CASE_CLOSED_TITLE = i18n.translate(
- 'xpack.siem.case.caseView.pushToServiceDisableBecauseCaseClosedTitle',
- {
- defaultMessage: 'Reopen the case',
- }
-);
-
-export const PUSH_DISABLE_BY_KIBANA_CONFIG_TITLE = i18n.translate(
- 'xpack.siem.case.caseView.pushToServiceDisableByConfigTitle',
- {
- defaultMessage: 'Enable ServiceNow in Kibana configuration file',
- }
-);
-
-export const PUSH_DISABLE_BY_LICENSE_TITLE = i18n.translate(
- 'xpack.siem.case.caseView.pushToServiceDisableByLicenseTitle',
- {
- defaultMessage: 'Upgrade to Elastic Platinum',
- }
-);
-
-export const LINK_CLOUD_DEPLOYMENT = i18n.translate(
- 'xpack.siem.case.caseView.cloudDeploymentLink',
- {
- defaultMessage: 'cloud deployment',
- }
-);
-
-export const LINK_CONNECTOR_CONFIGURE = i18n.translate(
- 'xpack.siem.case.caseView.connectorConfigureLink',
- {
- defaultMessage: 'connector',
- }
-);
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.tsx
new file mode 100644
index 0000000000000..9cfc51da22e87
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.tsx
@@ -0,0 +1,49 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { EuiButton, EuiToolTip } from '@elastic/eui';
+import React, { memo, useMemo } from 'react';
+import { getConfigureCasesUrl } from '../../../../components/link_to';
+
+interface ConfigureCaseButtonProps {
+ label: string;
+ isDisabled: boolean;
+ msgTooltip: JSX.Element;
+ showToolTip: boolean;
+ titleTooltip: string;
+ urlSearch: string;
+}
+
+const ConfigureCaseButtonComponent: React.FC = ({
+ isDisabled,
+ label,
+ msgTooltip,
+ showToolTip,
+ titleTooltip,
+ urlSearch,
+}: ConfigureCaseButtonProps) => {
+ const configureCaseButton = useMemo(
+ () => (
+
+ {label}
+
+ ),
+ [label, isDisabled, urlSearch]
+ );
+ return showToolTip ? (
+ {msgTooltip}}>
+ {configureCaseButton}
+
+ ) : (
+ <>{configureCaseButton}>
+ );
+};
+
+export const ConfigureCaseButton = memo(ConfigureCaseButtonComponent);
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx
index bb0c50b3b193a..8fb1cfb1aa6cc 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx
@@ -48,7 +48,9 @@ const ConnectorsComponent: React.FC = ({
{i18n.INCIDENT_MANAGEMENT_SYSTEM_LABEL}
- {i18n.ADD_NEW_CONNECTOR}
+
+ {i18n.ADD_NEW_CONNECTOR}
+
);
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx
index a1f24275df6cd..241b0b1230274 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx
@@ -57,8 +57,12 @@ const FormWrapper = styled.div`
margin-top 40px;
}
- padding-top: ${theme.eui.paddingSizes.l};
- padding-bottom: ${theme.eui.paddingSizes.l};
+ & > :first-child {
+ margin-top: 0;
+ }
+
+ padding-top: ${theme.eui.paddingSizes.xl};
+ padding-bottom: ${theme.eui.paddingSizes.xl};
`}
`;
@@ -80,7 +84,11 @@ const actionTypes: ActionType[] = [
},
];
-const ConfigureCasesComponent: React.FC = () => {
+interface ConfigureCasesComponentProps {
+ userCanCrud: boolean;
+}
+
+const ConfigureCasesComponent: React.FC = ({ userCanCrud }) => {
const search = useGetUrlSearch(navTabs.case);
const { http, triggers_actions_ui, notifications, application } = useKibana().services;
@@ -251,7 +259,7 @@ const ConfigureCasesComponent: React.FC = () => {
{
void;
iconType: string;
label: string;
@@ -16,13 +17,14 @@ export interface PropertyActionButtonProps {
const ComponentId = 'property-actions';
const PropertyActionButton = React.memo(
- ({ onClick, iconType, label }) => (
+ ({ disabled = false, onClick, iconType, label }) => (
{label}
@@ -76,6 +78,7 @@ export const PropertyActions = React.memo(({ propertyActio
{propertyActions.map((action, key) => (
onClosePopover(action.onClick)}
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/tag_list/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/tag_list/index.tsx
index 3513d4de12aa1..7c456d27aceda 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/tag_list/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/tag_list/index.tsx
@@ -23,6 +23,7 @@ import { schema } from './schema';
import { CommonUseField } from '../create';
interface TagListProps {
+ disabled?: boolean;
isLoading: boolean;
onSubmit: (a: string[]) => void;
tags: string[];
@@ -37,89 +38,98 @@ const MyFlexGroup = styled(EuiFlexGroup)`
`}
`;
-export const TagList = React.memo(({ isLoading, onSubmit, tags }: TagListProps) => {
- const { form } = useForm({
- defaultValue: { tags },
- options: { stripEmptyFields: false },
- schema,
- });
- const [isEditTags, setIsEditTags] = useState(false);
+export const TagList = React.memo(
+ ({ disabled = false, isLoading, onSubmit, tags }: TagListProps) => {
+ const { form } = useForm({
+ defaultValue: { tags },
+ options: { stripEmptyFields: false },
+ schema,
+ });
+ const [isEditTags, setIsEditTags] = useState(false);
- const onSubmitTags = useCallback(async () => {
- const { isValid, data: newData } = await form.submit();
- if (isValid && newData.tags) {
- onSubmit(newData.tags);
- setIsEditTags(false);
- }
- }, [form, onSubmit]);
+ const onSubmitTags = useCallback(async () => {
+ const { isValid, data: newData } = await form.submit();
+ if (isValid && newData.tags) {
+ onSubmit(newData.tags);
+ setIsEditTags(false);
+ }
+ }, [form, onSubmit]);
- return (
-
-
-
- {i18n.TAGS}
-
- {isLoading && }
- {!isLoading && (
+ return (
+
+
-
+ {i18n.TAGS}
- )}
-
-
-
- {tags.length === 0 && !isEditTags && {i18n.NO_TAGS}
}
- {tags.length > 0 &&
- !isEditTags &&
- tags.map((tag, key) => (
-
- {tag}
-
- ))}
- {isEditTags && (
-
-
-
-
-
-
-
-
- {i18n.SAVE}
-
-
-
-
- {i18n.CANCEL}
-
-
-
+ {isLoading && }
+ {!isLoading && (
+
+
-
- )}
-
-
- );
-});
+ )}
+
+
+
+ {tags.length === 0 && !isEditTags && {i18n.NO_TAGS}
}
+ {tags.length > 0 &&
+ !isEditTags &&
+ tags.map((tag, key) => (
+
+ {tag}
+
+ ))}
+ {isEditTags && (
+
+
+
+
+
+
+
+
+ {i18n.SAVE}
+
+
+
+
+ {i18n.CANCEL}
+
+
+
+
+
+ )}
+
+
+ );
+ }
+);
TagList.displayName = 'TagList';
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/use_push_to_service/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/use_push_to_service/helpers.tsx
new file mode 100644
index 0000000000000..1e4fd92058e8d
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/use_push_to_service/helpers.tsx
@@ -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 { EuiLink } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+import React from 'react';
+
+import * as i18n from './translations';
+import { ActionLicense } from '../../../../containers/case/types';
+
+export const getLicenseError = () => ({
+ title: i18n.PUSH_DISABLE_BY_LICENSE_TITLE,
+ description: (
+
+ {i18n.LINK_CLOUD_DEPLOYMENT}
+
+ ),
+ }}
+ />
+ ),
+});
+
+export const getKibanaConfigError = () => ({
+ title: i18n.PUSH_DISABLE_BY_KIBANA_CONFIG_TITLE,
+ description: (
+
+ {'coming soon...'}
+
+ ),
+ }}
+ />
+ ),
+});
+
+export const getActionLicenseError = (
+ actionLicense: ActionLicense | null
+): Array<{ title: string; description: JSX.Element }> => {
+ let errors: Array<{ title: string; description: JSX.Element }> = [];
+ if (actionLicense != null && !actionLicense.enabledInLicense) {
+ errors = [...errors, getLicenseError()];
+ }
+ if (actionLicense != null && !actionLicense.enabledInConfig) {
+ errors = [...errors, getKibanaConfigError()];
+ }
+ return errors;
+};
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/push_to_service.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/use_push_to_service/index.tsx
similarity index 71%
rename from x-pack/legacy/plugins/siem/public/pages/case/components/case_view/push_to_service.tsx
rename to x-pack/legacy/plugins/siem/public/pages/case/components/use_push_to_service/index.tsx
index 944302c1940ee..aeb694e52b7fa 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/push_to_service.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/use_push_to_service/index.tsx
@@ -5,8 +5,8 @@
*/
import { EuiButton, EuiLink, EuiToolTip } from '@elastic/eui';
-import React, { useCallback, useState, useMemo } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
+import React, { useCallback, useState, useMemo } from 'react';
import { useCaseConfigure } from '../../../../containers/case/configure/use_configure';
import { Case } from '../../../../containers/case/types';
@@ -15,7 +15,8 @@ import { usePostPushToService } from '../../../../containers/case/use_post_push_
import { getConfigureCasesUrl } from '../../../../components/link_to';
import { useGetUrlSearch } from '../../../../components/navigation/use_get_url_search';
import { navTabs } from '../../../home/home_navigations';
-import { ErrorsPushServiceCallOut } from '../errors_push_service_callout';
+import { CaseCallOut } from '../callout';
+import { getLicenseError, getKibanaConfigError } from './helpers';
import * as i18n from './translations';
interface UsePushToService {
@@ -23,6 +24,7 @@ interface UsePushToService {
caseStatus: string;
isNew: boolean;
updateCase: (newCase: Case) => void;
+ userCanCrud: boolean;
}
interface Connector {
@@ -38,8 +40,9 @@ interface ReturnUsePushToService {
export const usePushToService = ({
caseId,
caseStatus,
- updateCase,
isNew,
+ updateCase,
+ userCanCrud,
}: UsePushToService): ReturnUsePushToService => {
const urlSearch = useGetUrlSearch(navTabs.case);
const [connector, setConnector] = useState(null);
@@ -69,25 +72,7 @@ export const usePushToService = ({
const errorsMsg = useMemo(() => {
let errors: Array<{ title: string; description: JSX.Element }> = [];
if (actionLicense != null && !actionLicense.enabledInLicense) {
- errors = [
- ...errors,
- {
- title: i18n.PUSH_DISABLE_BY_LICENSE_TITLE,
- description: (
-
- {i18n.LINK_CLOUD_DEPLOYMENT}
-
- ),
- }}
- />
- ),
- },
- ];
+ errors = [...errors, getLicenseError()];
}
if (connector == null && !loadingCaseConfigure && !loadingLicense) {
errors = [
@@ -125,25 +110,7 @@ export const usePushToService = ({
];
}
if (actionLicense != null && !actionLicense.enabledInConfig) {
- errors = [
- ...errors,
- {
- title: i18n.PUSH_DISABLE_BY_KIBANA_CONFIG_TITLE,
- description: (
-
- {'coming soon...'}
-
- ),
- }}
- />
- ),
- },
- ];
+ errors = [...errors, getKibanaConfigError()];
}
return errors;
}, [actionLicense, caseStatus, connector, loadingCaseConfigure, loadingLicense, urlSearch]);
@@ -154,13 +121,27 @@ export const usePushToService = ({
fill
iconType="importAction"
onClick={handlePushToService}
- disabled={isLoading || loadingLicense || loadingCaseConfigure || errorsMsg.length > 0}
+ disabled={
+ isLoading ||
+ loadingLicense ||
+ loadingCaseConfigure ||
+ errorsMsg.length > 0 ||
+ !userCanCrud
+ }
isLoading={isLoading}
>
{isNew ? i18n.PUSH_SERVICENOW : i18n.UPDATE_PUSH_SERVICENOW}
),
- [isNew, handlePushToService, isLoading, loadingLicense, loadingCaseConfigure, errorsMsg]
+ [
+ isNew,
+ handlePushToService,
+ isLoading,
+ loadingLicense,
+ loadingCaseConfigure,
+ errorsMsg,
+ userCanCrud,
+ ]
);
const objToReturn = useMemo(
@@ -177,7 +158,10 @@ export const usePushToService = ({
) : (
<>{pushToServiceButton}>
),
- pushCallouts: errorsMsg.length > 0 ? : null,
+ pushCallouts:
+ errorsMsg.length > 0 ? (
+
+ ) : null,
}),
[errorsMsg, pushToServiceButton]
);
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/use_push_to_service/translations.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/use_push_to_service/translations.ts
new file mode 100644
index 0000000000000..14bdb0c69712c
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/use_push_to_service/translations.ts
@@ -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 { i18n } from '@kbn/i18n';
+
+export const ERROR_PUSH_SERVICE_CALLOUT_TITLE = i18n.translate(
+ 'xpack.siem.case.caseView.errorsPushServiceCallOutTitle',
+ {
+ defaultMessage: 'To send cases to external systems, you need to:',
+ }
+);
+
+export const PUSH_SERVICENOW = i18n.translate('xpack.siem.case.caseView.pushAsServicenowIncident', {
+ defaultMessage: 'Push as ServiceNow incident',
+});
+
+export const UPDATE_PUSH_SERVICENOW = i18n.translate(
+ 'xpack.siem.case.caseView.updatePushAsServicenowIncident',
+ {
+ defaultMessage: 'Update ServiceNow incident',
+ }
+);
+
+export const PUSH_DISABLE_BY_NO_CASE_CONFIG_TITLE = i18n.translate(
+ 'xpack.siem.case.caseView.pushToServiceDisableByNoCaseConfigTitle',
+ {
+ defaultMessage: 'Configure external connector',
+ }
+);
+
+export const PUSH_DISABLE_BECAUSE_CASE_CLOSED_TITLE = i18n.translate(
+ 'xpack.siem.case.caseView.pushToServiceDisableBecauseCaseClosedTitle',
+ {
+ defaultMessage: 'Reopen the case',
+ }
+);
+
+export const PUSH_DISABLE_BY_KIBANA_CONFIG_TITLE = i18n.translate(
+ 'xpack.siem.case.caseView.pushToServiceDisableByConfigTitle',
+ {
+ defaultMessage: 'Enable ServiceNow in Kibana configuration file',
+ }
+);
+
+export const PUSH_DISABLE_BY_LICENSE_TITLE = i18n.translate(
+ 'xpack.siem.case.caseView.pushToServiceDisableByLicenseTitle',
+ {
+ defaultMessage: 'Upgrade to Elastic Platinum',
+ }
+);
+
+export const LINK_CLOUD_DEPLOYMENT = i18n.translate(
+ 'xpack.siem.case.caseView.cloudDeploymentLink',
+ {
+ defaultMessage: 'cloud deployment',
+ }
+);
+
+export const LINK_CONNECTOR_CONFIGURE = i18n.translate(
+ 'xpack.siem.case.caseView.connectorConfigureLink',
+ {
+ defaultMessage: 'connector',
+ }
+);
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx
index 75013c0afde5d..0892d5dcb3ee7 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx
@@ -29,6 +29,7 @@ export interface UserActionTreeProps {
lastIndexPushToService: number;
onUpdateField: (updateKey: keyof Case, updateValue: string | string[]) => void;
updateCase: (newCase: Case) => void;
+ userCanCrud: boolean;
}
const MyEuiFlexGroup = styled(EuiFlexGroup)`
@@ -49,6 +50,7 @@ export const UserActionTree = React.memo(
lastIndexPushToService,
onUpdateField,
updateCase,
+ userCanCrud,
}: UserActionTreeProps) => {
const { commentId } = useParams();
const handlerTimeoutId = useRef(0);
@@ -146,13 +148,14 @@ export const UserActionTree = React.memo(
() => (
),
- [caseData.id, handleUpdate, insertQuote]
+ [caseData.id, handleUpdate, insertQuote, userCanCrud]
);
useEffect(() => {
@@ -168,17 +171,18 @@ export const UserActionTree = React.memo(
<>
{i18n.ADDED_DESCRIPTION}>}
- fullName={caseData.createdBy.fullName ?? caseData.createdBy.username}
+ fullName={caseData.createdBy.fullName ?? caseData.createdBy.username ?? ''}
markdown={MarkdownDescription}
onEdit={handleManageMarkdownEditId.bind(null, DESCRIPTION_ID)}
onQuote={handleManageQuote.bind(null, caseData.description)}
- username={caseData.createdBy.username}
+ username={caseData.createdBy.username ?? 'Unknown'}
/>
{caseUserActions.map((action, index) => {
@@ -189,6 +193,7 @@ export const UserActionTree = React.memo(
-
+
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/create_case.tsx b/x-pack/legacy/plugins/siem/public/pages/case/create_case.tsx
index bd1f6da0ca28b..06cb7fadfb8d3 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/create_case.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/create_case.tsx
@@ -5,17 +5,20 @@
*/
import React, { useMemo } from 'react';
+import { Redirect } from 'react-router-dom';
+import { getCaseUrl } from '../../components/link_to';
+import { useGetUrlSearch } from '../../components/navigation/use_get_url_search';
import { WrapperPage } from '../../components/wrapper_page';
-import { Create } from './components/create';
+import { useGetUserSavedObjectPermissions } from '../../lib/kibana';
import { SpyRoute } from '../../utils/route/spy_routes';
+import { navTabs } from '../home/home_navigations';
import { CaseHeaderPage } from './components/case_header_page';
+import { Create } from './components/create';
import * as i18n from './translations';
-import { getCaseUrl } from '../../components/link_to';
-import { useGetUrlSearch } from '../../components/navigation/use_get_url_search';
-import { navTabs } from '../home/home_navigations';
export const CreateCasePage = React.memo(() => {
+ const userPermissions = useGetUserSavedObjectPermissions();
const search = useGetUrlSearch(navTabs.case);
const backOptions = useMemo(
@@ -26,6 +29,10 @@ export const CreateCasePage = React.memo(() => {
[search]
);
+ if (userPermissions != null && !userPermissions.crud) {
+ return ;
+ }
+
return (
<>
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/saved_object_no_permissions.tsx b/x-pack/legacy/plugins/siem/public/pages/case/saved_object_no_permissions.tsx
new file mode 100644
index 0000000000000..689c290c91019
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/saved_object_no_permissions.tsx
@@ -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 React from 'react';
+
+import { EmptyPage } from '../../components/empty_page';
+import * as i18n from './translations';
+import { useKibana } from '../../lib/kibana';
+
+export const CaseSavedObjectNoPermissions = React.memo(() => {
+ const docLinks = useKibana().services.docLinks;
+
+ return (
+
+ );
+});
+
+CaseSavedObjectNoPermissions.displayName = 'CaseSavedObjectNoPermissions';
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/translations.ts b/x-pack/legacy/plugins/siem/public/pages/case/translations.ts
index 8f9d2087699f8..0d1e6d1435ca3 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/case/translations.ts
@@ -6,6 +6,21 @@
import { i18n } from '@kbn/i18n';
+export const SAVED_OBJECT_NO_PERMISSIONS_TITLE = i18n.translate(
+ 'xpack.siem.case.caseSavedObjectNoPermissionsTitle',
+ {
+ defaultMessage: 'Kibana feature privileges required',
+ }
+);
+
+export const SAVED_OBJECT_NO_PERMISSIONS_MSG = i18n.translate(
+ 'xpack.siem.case.caseSavedObjectNoPermissionsMessage',
+ {
+ defaultMessage:
+ 'To view cases, you must have privileges for the Saved Object Management feature in the Kibana space. For more information, contact your Kibana administrator.',
+ }
+);
+
export const BACK_TO_ALL = i18n.translate('xpack.siem.case.caseView.backLabel', {
defaultMessage: 'Back to cases',
});
@@ -169,3 +184,10 @@ export const ADD_COMMENT_HELP_TEXT = i18n.translate(
export const SAVE = i18n.translate('xpack.siem.case.caseView.description.save', {
defaultMessage: 'Save',
});
+
+export const GO_TO_DOCUMENTATION = i18n.translate(
+ 'xpack.siem.case.caseView.goToDocumentationButton',
+ {
+ defaultMessage: 'View documentation',
+ }
+);
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts
index 6d76fde49634d..5e0293325289b 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts
@@ -71,7 +71,7 @@ export const mockRule = (id: string): Rule => ({
to: 'now',
type: 'saved_query',
threat: [],
- throttle: null,
+ throttle: 'no_actions',
note: '# this is some markdown documentation',
version: 1,
});
@@ -145,7 +145,7 @@ export const mockRuleWithEverything = (id: string): Rule => ({
],
},
],
- throttle: null,
+ throttle: 'no_actions',
note: '# this is some markdown documentation',
version: 1,
});
@@ -184,7 +184,7 @@ export const mockActionsStepRule = (isNew = false, enabled = false): ActionsStep
actions: [],
kibanaSiemAppUrl: 'http://localhost:5601/app/siem',
enabled,
- throttle: null,
+ throttle: 'no_actions',
});
export const mockDefineStepRule = (isNew = false): DefineStepRule => ({
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx
index ebdf0fc27b6c8..a155f3eb2803c 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx
@@ -48,7 +48,6 @@ export const getActions = (
icon: 'controlsHorizontal',
name: i18n.EDIT_RULE_SETTINGS,
onClick: (rowItem: Rule) => editRuleAction(rowItem, history),
- enabled: (rowItem: Rule) => !rowItem.immutable,
},
{
description: i18n.DUPLICATE_RULE,
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx
index f9b255a95d869..79da7999b081a 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx
@@ -21,13 +21,13 @@ import styled from 'styled-components';
import { esFilters } from '../../../../../../../../../../src/plugins/data/public';
+import { RuleType } from '../../../../../../common/detection_engine/types';
import { tacticsOptions, techniquesOptions } from '../../../mitre/mitre_tactics_techniques';
import * as i18n from './translations';
import { BuildQueryBarDescription, BuildThreatDescription, ListItems } from './types';
import { SeverityBadge } from '../severity_badge';
import ListTreeIcon from './assets/list_tree_icon.svg';
-import { RuleType } from '../../../../../containers/detection_engine/rules';
import { assertUnreachable } from '../../../../../lib/helpers';
const NoteDescriptionContainer = styled(EuiFlexItem)`
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx
index 69c4ee1017155..05e47225c8f4b 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx
@@ -15,7 +15,7 @@ import {
esFilters,
FilterManager,
} from '../../../../../../../../../../src/plugins/data/public';
-import { RuleType } from '../../../../../containers/detection_engine/rules';
+import { RuleType } from '../../../../../../common/detection_engine/types';
import { DEFAULT_TIMELINE_TITLE } from '../../../../../components/timeline/translations';
import { useKibana } from '../../../../../lib/kibana';
import { IMitreEnterpriseAttack } from '../../types';
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.tsx
index 947bf29c07148..8276aa3578563 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.tsx
@@ -11,8 +11,8 @@ import { EuiBadge, EuiIcon, EuiLink, EuiToolTip } from '@elastic/eui';
import { useKibana } from '../../../../../lib/kibana';
import { SiemJob } from '../../../../../components/ml_popover/types';
import { ListItems } from './types';
-import { isJobStarted } from '../../../../../components/ml/helpers';
import { ML_JOB_STARTED, ML_JOB_STOPPED } from './translations';
+import { isJobStarted } from '../../../../../../common/detection_engine/ml_helpers';
enum MessageLevels {
info = 'info',
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/select_rule_type/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/select_rule_type/index.tsx
index 4ccde78f3cda7..9d3b37f1788fa 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/select_rule_type/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/select_rule_type/index.tsx
@@ -16,10 +16,10 @@ import {
EuiText,
} from '@elastic/eui';
+import { isMlRule } from '../../../../../../common/detection_engine/ml_helpers';
+import { RuleType } from '../../../../../../common/detection_engine/types';
import { FieldHook } from '../../../../../shared_imports';
-import { RuleType } from '../../../../../containers/detection_engine/rules/types';
import * as i18n from './translations';
-import { isMlRule } from '../../helpers';
const MlCardDescription = ({ hasValidLicense = false }: { hasValidLicense?: boolean }) => (
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx
index 6c46ab0b171a2..05043e5b96a30 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx
@@ -12,10 +12,11 @@ import deepEqual from 'fast-deep-equal';
import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/public';
import { useFetchIndexPatterns } from '../../../../../containers/detection_engine/rules';
import { DEFAULT_INDEX_KEY } from '../../../../../../common/constants';
+import { isMlRule } from '../../../../../../common/detection_engine/ml_helpers';
import { DEFAULT_TIMELINE_TITLE } from '../../../../../components/timeline/translations';
import { useMlCapabilities } from '../../../../../components/ml_popover/hooks/use_ml_capabilities';
import { useUiSetting$ } from '../../../../../lib/kibana';
-import { setFieldValue, isMlRule } from '../../helpers';
+import { setFieldValue } from '../../helpers';
import { DefineStepRule, RuleStep, RuleStepProps } from '../../types';
import { StepRuleDescription } from '../description_step';
import { QueryBarDefineRule } from '../query_bar';
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/schema.tsx
index 271c8fabed3a5..4a132f94a9871 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/schema.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/schema.tsx
@@ -10,6 +10,7 @@ import { isEmpty } from 'lodash/fp';
import React from 'react';
import { esKuery } from '../../../../../../../../../../src/plugins/data/public';
+import { isMlRule } from '../../../../../../common/detection_engine/ml_helpers';
import { FieldValueQueryBar } from '../query_bar';
import {
ERROR_CODE,
@@ -19,7 +20,6 @@ import {
ValidationFunc,
} from '../../../../../shared_imports';
import { CUSTOM_QUERY_REQUIRED, INVALID_CUSTOM_QUERY, INDEX_HELPER_TEXT } from './translations';
-import { isMlRule } from '../../helpers';
export const schema: FormSchema = {
index: {
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.tsx
index 9c16a61822662..aec315938b6ae 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.tsx
@@ -141,6 +141,7 @@ const StepRuleActionsComponent: FC = ({
/>
>
)}
+
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx
index 511427978db3a..bc3b0dfe720bc 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx
@@ -10,6 +10,7 @@ import { FormSchema } from '../../../../../shared_imports';
export const schema: FormSchema = {
actions: {},
+ enabled: {},
kibanaSiemAppUrl: {},
throttle: {
label: i18n.translate(
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.test.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.test.ts
index 212147ec6d4d8..efb601b6bd207 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.test.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.test.ts
@@ -515,10 +515,9 @@ describe('helpers', () => {
actions: [],
enabled: false,
meta: {
- throttle: 'no_actions',
kibanaSiemAppUrl: 'http://localhost:5601/app/siem',
},
- throttle: null,
+ throttle: 'no_actions',
};
expect(result).toEqual(expected);
@@ -534,10 +533,9 @@ describe('helpers', () => {
actions: [],
enabled: false,
meta: {
- throttle: mockStepData.throttle,
kibanaSiemAppUrl: mockStepData.kibanaSiemAppUrl,
},
- throttle: null,
+ throttle: 'no_actions',
};
expect(result).toEqual(expected);
@@ -568,10 +566,9 @@ describe('helpers', () => {
],
enabled: false,
meta: {
- throttle: mockStepData.throttle,
kibanaSiemAppUrl: mockStepData.kibanaSiemAppUrl,
},
- throttle: null,
+ throttle: 'rule',
};
expect(result).toEqual(expected);
@@ -602,7 +599,6 @@ describe('helpers', () => {
],
enabled: false,
meta: {
- throttle: mockStepData.throttle,
kibanaSiemAppUrl: mockStepData.kibanaSiemAppUrl,
},
throttle: mockStepData.throttle,
@@ -635,10 +631,9 @@ describe('helpers', () => {
],
enabled: false,
meta: {
- throttle: null,
kibanaSiemAppUrl: mockStepData.kibanaSiemAppUrl,
},
- throttle: null,
+ throttle: 'no_actions',
};
expect(result).toEqual(expected);
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.ts
index 7abe5a576c0e5..151e3a9bdf4d6 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.ts
@@ -8,12 +8,11 @@ import { has, isEmpty } from 'lodash/fp';
import moment from 'moment';
import deepmerge from 'deepmerge';
-import {
- NOTIFICATION_THROTTLE_RULE,
- NOTIFICATION_THROTTLE_NO_ACTIONS,
-} from '../../../../../common/constants';
-import { NewRule, RuleType } from '../../../../containers/detection_engine/rules';
+import { NOTIFICATION_THROTTLE_NO_ACTIONS } from '../../../../../common/constants';
import { transformAlertToRuleAction } from '../../../../../common/detection_engine/transform_actions';
+import { RuleType } from '../../../../../common/detection_engine/types';
+import { isMlRule } from '../../../../../common/detection_engine/ml_helpers';
+import { NewRule } from '../../../../containers/detection_engine/rules';
import {
AboutStepRule,
@@ -25,7 +24,6 @@ import {
AboutStepRuleJson,
ActionsStepRuleJson,
} from '../types';
-import { isMlRule } from '../helpers';
export const getTimeTypeValue = (time: string): { unit: string; value: number } => {
const timeObj = {
@@ -144,11 +142,6 @@ export const formatAboutStepData = (aboutStepData: AboutStepRule): AboutStepRule
};
};
-export const getAlertThrottle = (throttle: string | null) =>
- throttle && ![NOTIFICATION_THROTTLE_NO_ACTIONS, NOTIFICATION_THROTTLE_RULE].includes(throttle)
- ? throttle
- : null;
-
export const formatActionsStepData = (actionsStepData: ActionsStepRule): ActionsStepRuleJson => {
const {
actions = [],
@@ -160,9 +153,8 @@ export const formatActionsStepData = (actionsStepData: ActionsStepRule): Actions
return {
actions: actions.map(transformAlertToRuleAction),
enabled,
- throttle: actions.length ? getAlertThrottle(throttle) : null,
+ throttle: actions.length ? throttle : NOTIFICATION_THROTTLE_NO_ACTIONS,
meta: {
- throttle: actions.length ? throttle : NOTIFICATION_THROTTLE_NO_ACTIONS,
kibanaSiemAppUrl,
},
};
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx
index a35caf4acf67b..b8e2310ef0614 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx
@@ -277,7 +277,7 @@ const RuleDetailsPageComponent: FC = ({
{ruleI18n.EDIT_RULE_SETTINGS}
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx
index f89e3206cc67d..60d6158987a1d 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx
@@ -119,6 +119,7 @@ const EditRulePageComponent: FC = () => {
{
id: RuleStep.defineRule,
name: ruleI18n.DEFINITION,
+ disabled: rule?.immutable,
content: (
<>
@@ -140,6 +141,7 @@ const EditRulePageComponent: FC = () => {
{
id: RuleStep.aboutRule,
name: ruleI18n.ABOUT,
+ disabled: rule?.immutable,
content: (
<>
@@ -161,6 +163,7 @@ const EditRulePageComponent: FC = () => {
{
id: RuleStep.scheduleRule,
name: ruleI18n.SCHEDULE,
+ disabled: rule?.immutable,
content: (
<>
@@ -203,6 +206,7 @@ const EditRulePageComponent: FC = () => {
},
],
[
+ rule,
loading,
initLoading,
isLoading,
@@ -331,10 +335,11 @@ const EditRulePageComponent: FC = () => {
}, [rule]);
useEffect(() => {
- setSelectedTab(tabs[0]);
- }, []);
+ const tabIndex = rule?.immutable ? 3 : 0;
+ setSelectedTab(tabs[tabIndex]);
+ }, [rule]);
- if (isSaved || (rule != null && rule.immutable)) {
+ if (isSaved) {
displaySuccessToast(i18n.SUCCESSFULLY_SAVED_RULE(rule?.name ?? ''), dispatchToaster);
return ;
}
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.test.tsx
index fbdfcf4fc75d8..522464d585cca 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.test.tsx
@@ -107,7 +107,12 @@ describe('rule helpers', () => {
],
};
const scheduleRuleStepData = { from: '0s', interval: '5m', isNew: false };
- const ruleActionsStepData = { enabled: true, throttle: undefined, isNew: false, actions: [] };
+ const ruleActionsStepData = {
+ enabled: true,
+ throttle: 'no_actions',
+ isNew: false,
+ actions: [],
+ };
const aboutRuleDataDetailsData = {
note: '# this is some markdown documentation',
description: '24/7',
@@ -303,7 +308,7 @@ describe('rule helpers', () => {
actions: [],
enabled: mockedRule.enabled,
isNew: false,
- throttle: undefined,
+ throttle: 'no_actions',
};
expect(result).toEqual(expected);
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx
index 50b76552ddc8f..b6afba527ccdc 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx
@@ -10,10 +10,11 @@ import moment from 'moment';
import memoizeOne from 'memoize-one';
import { useLocation } from 'react-router-dom';
-import { RuleAlertAction } from '../../../../common/detection_engine/types';
+import { RuleAlertAction, RuleType } from '../../../../common/detection_engine/types';
+import { isMlRule } from '../../../../common/detection_engine/ml_helpers';
import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions';
import { Filter } from '../../../../../../../../src/plugins/data/public';
-import { Rule, RuleType } from '../../../containers/detection_engine/rules';
+import { Rule } from '../../../containers/detection_engine/rules';
import { FormData, FormHook, FormSchema } from '../../../shared_imports';
import {
AboutStepRule,
@@ -57,12 +58,12 @@ export const getStepsData = ({
export const getActionsStepsData = (
rule: Omit & { actions: RuleAlertAction[] }
): ActionsStepRule => {
- const { enabled, actions = [], meta } = rule;
+ const { enabled, throttle, meta, actions = [] } = rule;
return {
actions: actions?.map(transformRuleToAlertAction),
isNew: false,
- throttle: meta?.throttle,
+ throttle,
kibanaSiemAppUrl: meta?.kibanaSiemAppUrl,
enabled,
};
@@ -214,8 +215,6 @@ export const setFieldValue = (
}
});
-export const isMlRule = (ruleType: RuleType) => ruleType === 'machine_learning';
-
export const redirectToDetections = (
isSignalIndexExists: boolean | null,
isAuthenticated: boolean | null,
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts
index c1db24991c17c..1c366e6640b29 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts
@@ -5,9 +5,8 @@
*/
import { AlertAction } from '../../../../../../../plugins/alerting/common';
-import { RuleAlertAction } from '../../../../common/detection_engine/types';
+import { RuleAlertAction, RuleType } from '../../../../common/detection_engine/types';
import { Filter } from '../../../../../../../../src/plugins/data/common';
-import { RuleType } from '../../../containers/detection_engine/rules/types';
import { FieldValueQueryBar } from './components/query_bar';
import { FormData, FormHook } from '../../../shared_imports';
import { FieldValueTimeline } from './components/pick_timeline';
diff --git a/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.test.tsx b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.test.tsx
new file mode 100644
index 0000000000000..62399891c9606
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.test.tsx
@@ -0,0 +1,89 @@
+/*
+ * 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 { TimelinesPageComponent } from './timelines_page';
+import { useKibana } from '../../lib/kibana';
+import { shallow, ShallowWrapper } from 'enzyme';
+import React from 'react';
+import ApolloClient from 'apollo-client';
+
+jest.mock('../../lib/kibana', () => {
+ return {
+ useKibana: jest.fn(),
+ };
+});
+describe('TimelinesPageComponent', () => {
+ const mockAppollloClient = {} as ApolloClient