]: NP[K] };
+
+type ExtendedObjectType = ObjectType<
+ ExtendedProps
+>;
+
+type ExtendedObjectTypeOptions
= ObjectTypeOptions<
+ ExtendedProps
+>;
+
interface UnknownOptions {
/**
* Options for dealing with unknown keys:
@@ -61,10 +81,13 @@ export type ObjectTypeOptions
= TypeOptions extends Type> {
- private props: Record;
+ private props: P;
+ private options: ObjectTypeOptions;
+ private propSchemas: Record;
- constructor(props: P, { unknowns = 'forbid', ...typeOptions }: ObjectTypeOptions = {}) {
+ constructor(props: P, options: ObjectTypeOptions
= {}) {
const schemaKeys = {} as Record;
+ const { unknowns = 'forbid', ...typeOptions } = options;
for (const [key, value] of Object.entries(props)) {
schemaKeys[key] = value.getSchema();
}
@@ -77,7 +100,93 @@ export class ObjectType extends Type>
.options({ stripUnknown: { objects: unknowns === 'ignore' } });
super(schema, typeOptions);
- this.props = schemaKeys;
+ this.props = props;
+ this.propSchemas = schemaKeys;
+ this.options = options;
+ }
+
+ /**
+ * Return a new `ObjectType` instance extended with given `newProps` properties.
+ * Original properties can be deleted from the copy by passing a `null` or `undefined` value for the key.
+ *
+ * @example
+ * How to add a new key to an object schema
+ * ```ts
+ * const origin = schema.object({
+ * initial: schema.string(),
+ * });
+ *
+ * const extended = origin.extends({
+ * added: schema.number(),
+ * });
+ * ```
+ *
+ * How to remove an existing key from an object schema
+ * ```ts
+ * const origin = schema.object({
+ * initial: schema.string(),
+ * toRemove: schema.number(),
+ * });
+ *
+ * const extended = origin.extends({
+ * toRemove: undefined,
+ * });
+ * ```
+ *
+ * How to override the schema's options
+ * ```ts
+ * const origin = schema.object({
+ * initial: schema.string(),
+ * }, { defaultValue: { initial: 'foo' }});
+ *
+ * const extended = origin.extends({
+ * added: schema.number(),
+ * }, { defaultValue: { initial: 'foo', added: 'bar' }});
+ *
+ * @remarks
+ * `extends` only support extending first-level properties. It's currently not possible to perform deep/nested extensions.
+ *
+ * ```ts
+ * const origin = schema.object({
+ * foo: schema.string(),
+ * nested: schema.object({
+ * a: schema.string(),
+ * b: schema.string(),
+ * }),
+ * });
+ *
+ * const extended = origin.extends({
+ * nested: schema.object({
+ * c: schema.string(),
+ * }),
+ * });
+ *
+ * // TypeOf is `{ foo: string; nested: { c: string } }`
+ * ```
+ */
+ public extends(
+ newProps: NP,
+ newOptions?: ExtendedObjectTypeOptions
+ ): ExtendedObjectType
{
+ const extendedProps = Object.entries({
+ ...this.props,
+ ...newProps,
+ }).reduce((memo, [key, value]) => {
+ if (value !== null && value !== undefined) {
+ return {
+ ...memo,
+ [key]: value,
+ };
+ }
+ return memo;
+ }, {} as ExtendedProps
);
+
+ const extendedOptions = {
+ ...this.options,
+ ...newOptions,
+ } as ExtendedObjectTypeOptions
;
+
+ return new ObjectType(extendedProps, extendedOptions);
}
protected handleError(type: string, { reason, value }: Record) {
@@ -95,10 +204,10 @@ export class ObjectType extends Type>
}
validateKey(key: string, value: any) {
- if (!this.props[key]) {
+ if (!this.propSchemas[key]) {
throw new Error(`${key} is not a valid part of this schema`);
}
- const { value: validatedValue, error } = this.props[key].validate(value);
+ const { value: validatedValue, error } = this.propSchemas[key].validate(value);
if (error) {
throw new ValidationError(error as any, key);
}
diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json
index 0ab0048619358b..8e2fd1c9182ff7 100644
--- a/packages/kbn-test/package.json
+++ b/packages/kbn-test/package.json
@@ -14,6 +14,7 @@
"@kbn/babel-preset": "1.0.0",
"@kbn/dev-utils": "1.0.0",
"@types/parse-link-header": "^1.0.0",
+ "@types/puppeteer": "^3.0.0",
"@types/strip-ansi": "^5.2.1",
"@types/xml2js": "^0.4.5",
"diff": "^4.0.1"
@@ -25,6 +26,7 @@
"getopts": "^2.2.4",
"glob": "^7.1.2",
"parse-link-header": "^1.0.1",
+ "puppeteer": "^3.3.0",
"strip-ansi": "^5.2.0",
"rxjs": "^6.5.3",
"tar-fs": "^1.16.3",
diff --git a/packages/kbn-test/src/index.ts b/packages/kbn-test/src/index.ts
index 585ce8181df5f4..0bc7cc664df68f 100644
--- a/packages/kbn-test/src/index.ts
+++ b/packages/kbn-test/src/index.ts
@@ -58,3 +58,5 @@ export { runFailedTestsReporterCli } from './failed_tests_reporter';
export { makeJunitReportPath } from './junit_report_path';
export { CI_PARALLEL_PROCESS_PREFIX } from './ci_parallel_process_prefix';
+
+export * from './page_load_metrics';
diff --git a/packages/kbn-test/src/page_load_metrics/capture_page_load_metrics.ts b/packages/kbn-test/src/page_load_metrics/capture_page_load_metrics.ts
new file mode 100644
index 00000000000000..013d49a29a51cf
--- /dev/null
+++ b/packages/kbn-test/src/page_load_metrics/capture_page_load_metrics.ts
@@ -0,0 +1,81 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { ToolingLog } from '@kbn/dev-utils';
+import { NavigationOptions, createUrl, navigateToApps } from './navigation';
+
+export async function capturePageLoadMetrics(log: ToolingLog, options: NavigationOptions) {
+ const responsesByPageView = await navigateToApps(log, options);
+
+ const assetSizeMeasurements = new Map();
+
+ const numberOfPagesVisited = responsesByPageView.size;
+
+ for (const [, frameResponses] of responsesByPageView) {
+ for (const [, { url, dataLength }] of frameResponses) {
+ if (url.length === 0) {
+ throw new Error('navigateToApps(); failed to identify the url of the request');
+ }
+ if (assetSizeMeasurements.has(url)) {
+ assetSizeMeasurements.set(url, [dataLength].concat(assetSizeMeasurements.get(url) || []));
+ } else {
+ assetSizeMeasurements.set(url, [dataLength]);
+ }
+ }
+ }
+
+ return Array.from(assetSizeMeasurements.entries())
+ .map(([url, measurements]) => {
+ const baseUrl = createUrl('/', options.appConfig.url);
+ const relativeUrl = url
+ // remove the baseUrl (expect the trailing slash) to make url relative
+ .replace(baseUrl.slice(0, -1), '')
+ // strip the build number from asset urls
+ .replace(/^\/\d+\//, '/');
+ return [relativeUrl, measurements] as const;
+ })
+ .filter(([url, measurements]) => {
+ if (measurements.length !== numberOfPagesVisited) {
+ // ignore urls seen only on some pages
+ return false;
+ }
+
+ if (url.startsWith('data:')) {
+ // ignore data urls since they are already counted by other assets
+ return false;
+ }
+
+ if (url.startsWith('/api/') || url.startsWith('/internal/')) {
+ // ignore api requests since they don't have deterministic sizes
+ return false;
+ }
+
+ const allMetricsAreEqual = measurements.every((x, i) =>
+ i === 0 ? true : x === measurements[i - 1]
+ );
+ if (!allMetricsAreEqual) {
+ throw new Error(`measurements for url [${url}] are not equal [${measurements.join(',')}]`);
+ }
+
+ return true;
+ })
+ .map(([url, measurements]) => {
+ return { group: 'page load asset size', id: url, value: measurements[0] };
+ });
+}
diff --git a/packages/kbn-test/src/page_load_metrics/cli.ts b/packages/kbn-test/src/page_load_metrics/cli.ts
new file mode 100644
index 00000000000000..95421384c79cb0
--- /dev/null
+++ b/packages/kbn-test/src/page_load_metrics/cli.ts
@@ -0,0 +1,90 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import Url from 'url';
+
+import { run, createFlagError } from '@kbn/dev-utils';
+import { resolve, basename } from 'path';
+import { capturePageLoadMetrics } from './capture_page_load_metrics';
+
+const defaultScreenshotsDir = resolve(__dirname, 'screenshots');
+
+export function runPageLoadMetricsCli() {
+ run(
+ async ({ flags, log }) => {
+ const kibanaUrl = flags['kibana-url'];
+ if (!kibanaUrl || typeof kibanaUrl !== 'string') {
+ throw createFlagError('Expect --kibana-url to be a string');
+ }
+
+ const parsedUrl = Url.parse(kibanaUrl);
+
+ const [username, password] = parsedUrl.auth
+ ? parsedUrl.auth.split(':')
+ : [flags.username, flags.password];
+
+ if (typeof username !== 'string' || typeof password !== 'string') {
+ throw createFlagError(
+ 'Mising username and/or password, either specify in --kibana-url or pass --username and --password'
+ );
+ }
+
+ const headless = !flags.head;
+
+ const screenshotsDir = flags.screenshotsDir || defaultScreenshotsDir;
+
+ if (typeof screenshotsDir !== 'string' || screenshotsDir === basename(screenshotsDir)) {
+ throw createFlagError('Expect screenshotsDir to be valid path string');
+ }
+
+ const metrics = await capturePageLoadMetrics(log, {
+ headless,
+ appConfig: {
+ url: kibanaUrl,
+ username,
+ password,
+ },
+ screenshotsDir,
+ });
+ for (const metric of metrics) {
+ log.info(`${metric.id}: ${metric.value}`);
+ }
+ },
+ {
+ description: `Loads several pages with Puppeteer to capture the size of assets`,
+ flags: {
+ string: ['kibana-url', 'username', 'password', 'screenshotsDir'],
+ boolean: ['head'],
+ default: {
+ username: 'elastic',
+ password: 'changeme',
+ debug: true,
+ screenshotsDir: defaultScreenshotsDir,
+ },
+ help: `
+ --kibana-url Url for Kibana we should connect to, can include login info
+ --head Run puppeteer with graphical user interface
+ --username Set username, defaults to 'elastic'
+ --password Set password, defaults to 'changeme'
+ --screenshotsDir Set screenshots directory, defaults to '${defaultScreenshotsDir}'
+ `,
+ },
+ }
+ );
+}
diff --git a/packages/kbn-test/src/page_load_metrics/event.ts b/packages/kbn-test/src/page_load_metrics/event.ts
new file mode 100644
index 00000000000000..481954bbf672e2
--- /dev/null
+++ b/packages/kbn-test/src/page_load_metrics/event.ts
@@ -0,0 +1,34 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export interface ResponseReceivedEvent {
+ frameId: string;
+ loaderId: string;
+ requestId: string;
+ response: Record;
+ timestamp: number;
+ type: string;
+}
+
+export interface DataReceivedEvent {
+ encodedDataLength: number;
+ dataLength: number;
+ requestId: string;
+ timestamp: number;
+}
diff --git a/packages/kbn-test/src/page_load_metrics/index.ts b/packages/kbn-test/src/page_load_metrics/index.ts
new file mode 100644
index 00000000000000..4309d558518a60
--- /dev/null
+++ b/packages/kbn-test/src/page_load_metrics/index.ts
@@ -0,0 +1,21 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export * from './cli';
+export { capturePageLoadMetrics } from './capture_page_load_metrics';
diff --git a/packages/kbn-test/src/page_load_metrics/navigation.ts b/packages/kbn-test/src/page_load_metrics/navigation.ts
new file mode 100644
index 00000000000000..21dc681951b212
--- /dev/null
+++ b/packages/kbn-test/src/page_load_metrics/navigation.ts
@@ -0,0 +1,165 @@
+/*
+ * 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 Fs from 'fs';
+import Url from 'url';
+import _ from 'lodash';
+import puppeteer from 'puppeteer';
+import { resolve } from 'path';
+import { ToolingLog } from '@kbn/dev-utils';
+import { ResponseReceivedEvent, DataReceivedEvent } from './event';
+
+export interface NavigationOptions {
+ headless: boolean;
+ appConfig: { url: string; username: string; password: string };
+ screenshotsDir: string;
+}
+
+export type NavigationResults = Map>;
+
+interface FrameResponse {
+ url: string;
+ dataLength: number;
+}
+
+function joinPath(pathA: string, pathB: string) {
+ return `${pathA.endsWith('/') ? pathA.slice(0, -1) : pathA}/${
+ pathB.startsWith('/') ? pathB.slice(1) : pathB
+ }`;
+}
+
+export function createUrl(path: string, url: string) {
+ const baseUrl = Url.parse(url);
+ return Url.format({
+ protocol: baseUrl.protocol,
+ hostname: baseUrl.hostname,
+ port: baseUrl.port,
+ pathname: joinPath(baseUrl.pathname || '', path),
+ });
+}
+
+async function loginToKibana(
+ log: ToolingLog,
+ browser: puppeteer.Browser,
+ options: NavigationOptions
+) {
+ log.debug(`log in to the app..`);
+ const page = await browser.newPage();
+ const loginUrl = createUrl('/login', options.appConfig.url);
+ await page.goto(loginUrl, {
+ waitUntil: 'networkidle0',
+ });
+ await page.type('[data-test-subj="loginUsername"]', options.appConfig.username);
+ await page.type('[data-test-subj="loginPassword"]', options.appConfig.password);
+ await page.click('[data-test-subj="loginSubmit"]');
+ await page.waitForNavigation({ waitUntil: 'networkidle0' });
+ await page.close();
+}
+
+export async function navigateToApps(log: ToolingLog, options: NavigationOptions) {
+ const browser = await puppeteer.launch({ headless: options.headless, args: ['--no-sandbox'] });
+ const devToolsResponses: NavigationResults = new Map();
+ const apps = [
+ { path: '/app/discover', locator: '[data-test-subj="discover-sidebar"]' },
+ { path: '/app/home', locator: '[data-test-subj="homeApp"]' },
+ { path: '/app/canvas', locator: '[data-test-subj="create-workpad-button"]' },
+ { path: '/app/maps', locator: '[title="Maps"]' },
+ { path: '/app/apm', locator: '[data-test-subj="apmMainContainer"]' },
+ ];
+
+ await loginToKibana(log, browser, options);
+
+ await Promise.all(
+ apps.map(async (app) => {
+ const page = await browser.newPage();
+ page.setCacheEnabled(false);
+ page.setDefaultNavigationTimeout(0);
+ const frameResponses = new Map();
+ devToolsResponses.set(app.path, frameResponses);
+
+ const client = await page.target().createCDPSession();
+ await client.send('Network.enable');
+
+ function getRequestData(requestId: string) {
+ if (!frameResponses.has(requestId)) {
+ frameResponses.set(requestId, { url: '', dataLength: 0 });
+ }
+
+ return frameResponses.get(requestId)!;
+ }
+
+ client.on('Network.responseReceived', (event: ResponseReceivedEvent) => {
+ getRequestData(event.requestId).url = event.response.url;
+ });
+
+ client.on('Network.dataReceived', (event: DataReceivedEvent) => {
+ getRequestData(event.requestId).dataLength += event.dataLength;
+ });
+
+ const url = createUrl(app.path, options.appConfig.url);
+ log.debug(`goto ${url}`);
+ await page.goto(url, {
+ waitUntil: 'networkidle0',
+ });
+
+ let readyAttempt = 0;
+ let selectorFound = false;
+ while (!selectorFound) {
+ readyAttempt += 1;
+ try {
+ await page.waitForSelector(app.locator, { timeout: 5000 });
+ selectorFound = true;
+ } catch (error) {
+ log.error(
+ `Page '${app.path}' was not loaded properly, unable to find '${
+ app.locator
+ }', url: ${page.url()}`
+ );
+
+ if (readyAttempt < 6) {
+ continue;
+ }
+
+ const failureDir = resolve(options.screenshotsDir, 'failure');
+ const screenshotPath = resolve(
+ failureDir,
+ `${app.path.slice(1).split('/').join('_')}_navigation.png`
+ );
+ Fs.mkdirSync(failureDir, { recursive: true });
+
+ await page.bringToFront();
+ await page.screenshot({
+ path: screenshotPath,
+ type: 'png',
+ fullPage: true,
+ });
+ log.debug(`Saving screenshot to ${screenshotPath}`);
+
+ throw new Error(`Page load timeout: ${app.path} not loaded after 30 seconds`);
+ }
+ }
+
+ await page.close();
+ })
+ );
+
+ await browser.close();
+
+ return devToolsResponses;
+}
diff --git a/scripts/page_load_metrics.js b/scripts/page_load_metrics.js
new file mode 100644
index 00000000000000..37500c26e0b20b
--- /dev/null
+++ b/scripts/page_load_metrics.js
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+require('../src/setup_node_env');
+require('@kbn/test').runPageLoadMetricsCli();
diff --git a/src/dev/build/build_distributables.js b/src/dev/build/build_distributables.js
index 3a8709893565df..66f0c0355c2d92 100644
--- a/src/dev/build/build_distributables.js
+++ b/src/dev/build/build_distributables.js
@@ -30,6 +30,7 @@ import {
CleanTypescriptTask,
CleanNodeBuildsTask,
CleanTask,
+ CopyBinScriptsTask,
CopySourceTask,
CreateArchivesSourcesTask,
CreateArchivesTask,
@@ -110,6 +111,7 @@ export async function buildDistributables(options) {
* run platform-generic build tasks
*/
await run(CopySourceTask);
+ await run(CopyBinScriptsTask);
await run(CreateEmptyDirsAndFilesTask);
await run(CreateReadmeTask);
await run(TranspileBabelTask);
diff --git a/src/dev/build/tasks/bin/copy_bin_scripts_task.js b/src/dev/build/tasks/bin/copy_bin_scripts_task.js
new file mode 100644
index 00000000000000..f620f12b17d882
--- /dev/null
+++ b/src/dev/build/tasks/bin/copy_bin_scripts_task.js
@@ -0,0 +1,31 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { copyAll } from '../../lib';
+
+export const CopyBinScriptsTask = {
+ description: 'Copying bin scripts into platform-generic build directory',
+
+ async run(config, log, build) {
+ await copyAll(
+ config.resolveFromRepo('src/dev/build/tasks/bin/scripts'),
+ build.resolvePath('bin')
+ );
+ },
+};
diff --git a/src/dev/build/tasks/bin/index.js b/src/dev/build/tasks/bin/index.js
new file mode 100644
index 00000000000000..e970ac5ec044ba
--- /dev/null
+++ b/src/dev/build/tasks/bin/index.js
@@ -0,0 +1,20 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export { CopyBinScriptsTask } from './copy_bin_scripts_task';
diff --git a/bin/kibana b/src/dev/build/tasks/bin/scripts/kibana
similarity index 100%
rename from bin/kibana
rename to src/dev/build/tasks/bin/scripts/kibana
diff --git a/bin/kibana-keystore b/src/dev/build/tasks/bin/scripts/kibana-keystore
similarity index 100%
rename from bin/kibana-keystore
rename to src/dev/build/tasks/bin/scripts/kibana-keystore
diff --git a/bin/kibana-keystore.bat b/src/dev/build/tasks/bin/scripts/kibana-keystore.bat
similarity index 100%
rename from bin/kibana-keystore.bat
rename to src/dev/build/tasks/bin/scripts/kibana-keystore.bat
diff --git a/bin/kibana-plugin b/src/dev/build/tasks/bin/scripts/kibana-plugin
similarity index 100%
rename from bin/kibana-plugin
rename to src/dev/build/tasks/bin/scripts/kibana-plugin
diff --git a/bin/kibana-plugin.bat b/src/dev/build/tasks/bin/scripts/kibana-plugin.bat
similarity index 100%
rename from bin/kibana-plugin.bat
rename to src/dev/build/tasks/bin/scripts/kibana-plugin.bat
diff --git a/bin/kibana.bat b/src/dev/build/tasks/bin/scripts/kibana.bat
similarity index 100%
rename from bin/kibana.bat
rename to src/dev/build/tasks/bin/scripts/kibana.bat
diff --git a/src/dev/build/tasks/copy_source_task.js b/src/dev/build/tasks/copy_source_task.js
index ee9dc159de47fa..ddc6d000bca194 100644
--- a/src/dev/build/tasks/copy_source_task.js
+++ b/src/dev/build/tasks/copy_source_task.js
@@ -42,7 +42,6 @@ export const CopySourceTask = {
'!src/es_archiver/**',
'!src/functional_test_runner/**',
'!src/dev/**',
- 'bin/**',
'typings/**',
'webpackShims/**',
'config/kibana.yml',
diff --git a/src/dev/build/tasks/index.js b/src/dev/build/tasks/index.js
index 8105fa8a7d5d47..bafb5a2fe115e4 100644
--- a/src/dev/build/tasks/index.js
+++ b/src/dev/build/tasks/index.js
@@ -17,6 +17,7 @@
* under the License.
*/
+export * from './bin';
export * from './build_packages_task';
export * from './clean_tasks';
export * from './copy_source_task';
diff --git a/src/dev/code_coverage/nyc_config/nyc.functional.config.js b/src/dev/code_coverage/nyc_config/nyc.functional.config.js
new file mode 100644
index 00000000000000..20d266ab9e2c3f
--- /dev/null
+++ b/src/dev/code_coverage/nyc_config/nyc.functional.config.js
@@ -0,0 +1,31 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+const defaultExclude = require('@istanbuljs/schema/default-exclude');
+const extraExclude = ['data/optimize/**', 'src/core/server/**', '**/test/**'];
+const path = require('path');
+
+module.exports = {
+ 'temp-dir': process.env.COVERAGE_TEMP_DIR
+ ? path.resolve(process.env.COVERAGE_TEMP_DIR, 'functional')
+ : 'target/kibana-coverage/functional',
+ 'report-dir': 'target/kibana-coverage/functional-combined',
+ reporter: ['html', 'json-summary'],
+ exclude: extraExclude.concat(defaultExclude),
+};
diff --git a/src/dev/code_coverage/nyc_config/nyc.jest.config.js b/src/dev/code_coverage/nyc_config/nyc.jest.config.js
new file mode 100644
index 00000000000000..1f73347837ab30
--- /dev/null
+++ b/src/dev/code_coverage/nyc_config/nyc.jest.config.js
@@ -0,0 +1,28 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+const path = require('path');
+
+module.exports = {
+ 'temp-dir': process.env.COVERAGE_TEMP_DIR
+ ? path.resolve(process.env.COVERAGE_TEMP_DIR, 'jest')
+ : 'target/kibana-coverage/jest',
+ 'report-dir': 'target/kibana-coverage/jest-combined',
+ reporter: ['html', 'json-summary'],
+};
diff --git a/src/dev/code_coverage/shell_scripts/merge_jest_and_functional.sh b/src/dev/code_coverage/shell_scripts/merge_jest_and_functional.sh
index ff9cb36c894f82..707c6de3f88a08 100644
--- a/src/dev/code_coverage/shell_scripts/merge_jest_and_functional.sh
+++ b/src/dev/code_coverage/shell_scripts/merge_jest_and_functional.sh
@@ -1,10 +1,9 @@
#!/bin/bash
-EXTRACT_START_DIR=tmp/extracted_coverage
-EXTRACT_END_DIR=target/kibana-coverage
-COMBINED_EXTRACT_DIR=/${EXTRACT_START_DIR}/${EXTRACT_END_DIR}
+COVERAGE_TEMP_DIR=/tmp/extracted_coverage/target/kibana-coverage/
+export COVERAGE_TEMP_DIR
echo "### Merge coverage reports"
for x in jest functional; do
- yarn nyc report --temp-dir $COMBINED_EXTRACT_DIR/${x} --report-dir $EXTRACT_END_DIR/${x}-combined --reporter=html --reporter=json-summary
+ yarn nyc report --nycrc-path src/dev/code_coverage/nyc_config/nyc.${x}.config.js
done
diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts
index 4ed8f8e7db1903..2f785896da8d55 100644
--- a/src/dev/storybook/aliases.ts
+++ b/src/dev/storybook/aliases.ts
@@ -18,7 +18,6 @@
*/
export const storybookAliases = {
- advanced_ui_actions: 'x-pack/plugins/advanced_ui_actions/scripts/storybook.js',
apm: 'x-pack/plugins/apm/scripts/storybook.js',
canvas: 'x-pack/plugins/canvas/scripts/storybook_new.js',
codeeditor: 'src/plugins/kibana_react/public/code_editor/scripts/storybook.ts',
@@ -27,4 +26,5 @@ export const storybookAliases = {
embeddable: 'src/plugins/embeddable/scripts/storybook.js',
infra: 'x-pack/legacy/plugins/infra/scripts/storybook.js',
security_solution: 'x-pack/plugins/security_solution/scripts/storybook.js',
+ ui_actions_enhanced: 'x-pack/plugins/ui_actions_enhanced/scripts/storybook.js',
};
diff --git a/src/legacy/core_plugins/kibana/server/ui_setting_defaults.js b/src/legacy/core_plugins/kibana/server/ui_setting_defaults.js
index 0d1b69778263c3..b7af6a73e1bc18 100644
--- a/src/legacy/core_plugins/kibana/server/ui_setting_defaults.js
+++ b/src/legacy/core_plugins/kibana/server/ui_setting_defaults.js
@@ -18,53 +18,32 @@
*/
import moment from 'moment-timezone';
-import numeralLanguages from '@elastic/numeral/languages';
import { i18n } from '@kbn/i18n';
import { schema } from '@kbn/config-schema';
import { isRelativeUrl } from '../../../../core/server';
-import { DEFAULT_QUERY_LANGUAGE } from '../../../../plugins/data/common';
export function getUiSettingDefaults() {
const weekdays = moment.weekdays().slice();
const [defaultWeekday] = weekdays;
- // We add the `en` key manually here, since that's not a real numeral locale, but the
- // default fallback in case the locale is not found.
- const numeralLanguageIds = [
- 'en',
- ...numeralLanguages.map(function (numeralLanguage) {
- return numeralLanguage.id;
- }),
- ];
-
- const luceneQueryLanguageLabel = i18n.translate(
- 'kbn.advancedSettings.searchQueryLanguageLucene',
- {
- defaultMessage: 'Lucene',
- }
- );
-
- const queryLanguageSettingName = i18n.translate('kbn.advancedSettings.searchQueryLanguageTitle', {
- defaultMessage: 'Query language',
- });
-
- const requestPreferenceOptionLabels = {
- sessionId: i18n.translate('kbn.advancedSettings.courier.requestPreferenceSessionId', {
- defaultMessage: 'Session ID',
- }),
- custom: i18n.translate('kbn.advancedSettings.courier.requestPreferenceCustom', {
- defaultMessage: 'Custom',
- }),
- none: i18n.translate('kbn.advancedSettings.courier.requestPreferenceNone', {
- defaultMessage: 'None',
- }),
- };
// wrapped in provider so that a new instance is given to each app/test
return {
buildNum: {
readonly: true,
},
+ 'state:storeInSessionStorage': {
+ name: i18n.translate('kbn.advancedSettings.storeUrlTitle', {
+ defaultMessage: 'Store URLs in session storage',
+ }),
+ value: false,
+ description: i18n.translate('kbn.advancedSettings.storeUrlText', {
+ defaultMessage:
+ 'The URL can sometimes grow to be too large for some browsers to handle. ' +
+ 'To counter-act this we are testing if storing parts of the URL in session storage could help. ' +
+ 'Please let us know how it goes!',
+ }),
+ },
defaultRoute: {
name: i18n.translate('kbn.advancedSettings.defaultRoute.defaultRouteTitle', {
defaultMessage: 'Default route',
@@ -89,83 +68,6 @@ export function getUiSettingDefaults() {
'The route must be a relative URL.',
}),
},
- 'query:queryString:options': {
- name: i18n.translate('kbn.advancedSettings.query.queryStringOptionsTitle', {
- defaultMessage: 'Query string options',
- }),
- value: '{ "analyze_wildcard": true }',
- description: i18n.translate('kbn.advancedSettings.query.queryStringOptionsText', {
- defaultMessage:
- '{optionsLink} for the lucene query string parser. Is only used when "{queryLanguage}" is set ' +
- 'to {luceneLanguage}.',
- description:
- 'Part of composite text: kbn.advancedSettings.query.queryStringOptions.optionsLinkText + ' +
- 'kbn.advancedSettings.query.queryStringOptionsText',
- values: {
- optionsLink:
- '' +
- i18n.translate('kbn.advancedSettings.query.queryStringOptions.optionsLinkText', {
- defaultMessage: 'Options',
- }) +
- '',
- luceneLanguage: luceneQueryLanguageLabel,
- queryLanguage: queryLanguageSettingName,
- },
- }),
- type: 'json',
- },
- 'query:allowLeadingWildcards': {
- name: i18n.translate('kbn.advancedSettings.query.allowWildcardsTitle', {
- defaultMessage: 'Allow leading wildcards in query',
- }),
- value: true,
- description: i18n.translate('kbn.advancedSettings.query.allowWildcardsText', {
- defaultMessage:
- 'When set, * is allowed as the first character in a query clause. ' +
- 'Currently only applies when experimental query features are enabled in the query bar. ' +
- 'To disallow leading wildcards in basic lucene queries, use {queryStringOptionsPattern}.',
- values: {
- queryStringOptionsPattern: 'query:queryString:options',
- },
- }),
- },
- 'search:queryLanguage': {
- name: queryLanguageSettingName,
- value: DEFAULT_QUERY_LANGUAGE,
- description: i18n.translate('kbn.advancedSettings.searchQueryLanguageText', {
- defaultMessage:
- 'Query language used by the query bar. KQL is a new language built specifically for Kibana.',
- }),
- type: 'select',
- options: ['lucene', 'kuery'],
- optionLabels: {
- lucene: luceneQueryLanguageLabel,
- kuery: i18n.translate('kbn.advancedSettings.searchQueryLanguageKql', {
- defaultMessage: 'KQL',
- }),
- },
- },
- 'sort:options': {
- name: i18n.translate('kbn.advancedSettings.sortOptionsTitle', {
- defaultMessage: 'Sort options',
- }),
- value: '{ "unmapped_type": "boolean" }',
- description: i18n.translate('kbn.advancedSettings.sortOptionsText', {
- defaultMessage: '{optionsLink} for the Elasticsearch sort parameter',
- description:
- 'Part of composite text: kbn.advancedSettings.sortOptions.optionsLinkText + ' +
- 'kbn.advancedSettings.sortOptionsText',
- values: {
- optionsLink:
- '' +
- i18n.translate('kbn.advancedSettings.sortOptions.optionsLinkText', {
- defaultMessage: 'Options',
- }) +
- '',
- },
- }),
- type: 'json',
- },
dateFormat: {
name: i18n.translate('kbn.advancedSettings.dateFormatTitle', {
defaultMessage: 'Date format',
@@ -261,160 +163,6 @@ export function getUiSettingDefaults() {
},
}),
},
- defaultIndex: {
- name: i18n.translate('kbn.advancedSettings.defaultIndexTitle', {
- defaultMessage: 'Default index',
- }),
- value: null,
- type: 'string',
- description: i18n.translate('kbn.advancedSettings.defaultIndexText', {
- defaultMessage: 'The index to access if no index is set',
- }),
- },
- 'courier:ignoreFilterIfFieldNotInIndex': {
- name: i18n.translate('kbn.advancedSettings.courier.ignoreFilterTitle', {
- defaultMessage: 'Ignore filter(s)',
- }),
- value: false,
- description: i18n.translate('kbn.advancedSettings.courier.ignoreFilterText', {
- defaultMessage:
- 'This configuration enhances support for dashboards containing visualizations accessing dissimilar indexes. ' +
- 'When disabled, all filters are applied to all visualizations. ' +
- 'When enabled, filter(s) will be ignored for a visualization ' +
- `when the visualization's index does not contain the filtering field.`,
- }),
- category: ['search'],
- },
- 'courier:setRequestPreference': {
- name: i18n.translate('kbn.advancedSettings.courier.requestPreferenceTitle', {
- defaultMessage: 'Request preference',
- }),
- value: 'sessionId',
- options: ['sessionId', 'custom', 'none'],
- optionLabels: requestPreferenceOptionLabels,
- type: 'select',
- description: i18n.translate('kbn.advancedSettings.courier.requestPreferenceText', {
- defaultMessage: `Allows you to set which shards handle your search requests.
-
- - {sessionId}: restricts operations to execute all search requests on the same shards.
- This has the benefit of reusing shard caches across requests.
- - {custom}: allows you to define a your own preference.
- Use courier:customRequestPreference to customize your preference value.
- - {none}: means do not set a preference.
- This might provide better performance because requests can be spread across all shard copies.
- However, results might be inconsistent because different shards might be in different refresh states.
-
`,
- values: {
- sessionId: requestPreferenceOptionLabels.sessionId,
- custom: requestPreferenceOptionLabels.custom,
- none: requestPreferenceOptionLabels.none,
- },
- }),
- category: ['search'],
- },
- 'courier:customRequestPreference': {
- name: i18n.translate('kbn.advancedSettings.courier.customRequestPreferenceTitle', {
- defaultMessage: 'Custom request preference',
- }),
- value: '_local',
- type: 'string',
- description: i18n.translate('kbn.advancedSettings.courier.customRequestPreferenceText', {
- defaultMessage:
- '{requestPreferenceLink} used when {setRequestReferenceSetting} is set to {customSettingValue}.',
- description:
- 'Part of composite text: kbn.advancedSettings.courier.customRequestPreference.requestPreferenceLinkText + ' +
- 'kbn.advancedSettings.courier.customRequestPreferenceText',
- values: {
- setRequestReferenceSetting: 'courier:setRequestPreference',
- customSettingValue: '"custom"',
- requestPreferenceLink:
- '' +
- i18n.translate(
- 'kbn.advancedSettings.courier.customRequestPreference.requestPreferenceLinkText',
- {
- defaultMessage: 'Request Preference',
- }
- ) +
- '',
- },
- }),
- category: ['search'],
- },
- 'courier:maxConcurrentShardRequests': {
- name: i18n.translate('kbn.advancedSettings.courier.maxRequestsTitle', {
- defaultMessage: 'Max Concurrent Shard Requests',
- }),
- value: 0,
- type: 'number',
- description: i18n.translate('kbn.advancedSettings.courier.maxRequestsText', {
- defaultMessage:
- 'Controls the {maxRequestsLink} setting used for _msearch requests sent by Kibana. ' +
- 'Set to 0 to disable this config and use the Elasticsearch default.',
- values: {
- maxRequestsLink: `max_concurrent_shard_requests`,
- },
- }),
- category: ['search'],
- },
- 'courier:batchSearches': {
- name: i18n.translate('kbn.advancedSettings.courier.batchSearchesTitle', {
- defaultMessage: 'Batch concurrent searches',
- }),
- value: false,
- type: 'boolean',
- description: i18n.translate('kbn.advancedSettings.courier.batchSearchesText', {
- defaultMessage: `When disabled, dashboard panels will load individually, and search requests will terminate when users navigate
- away or update the query. When enabled, dashboard panels will load together when all of the data is loaded, and
- searches will not terminate.`,
- }),
- deprecation: {
- message: i18n.translate('kbn.advancedSettings.courier.batchSearchesTextDeprecation', {
- defaultMessage: 'This setting is deprecated and will be removed in Kibana 8.0.',
- }),
- docLinksKey: 'kibanaSearchSettings',
- },
- category: ['search'],
- },
- 'search:includeFrozen': {
- name: 'Search in frozen indices',
- description: `Will include frozen indices in results if enabled. Searching through frozen indices
- might increase the search time.`,
- value: false,
- category: ['search'],
- },
- 'histogram:barTarget': {
- name: i18n.translate('kbn.advancedSettings.histogram.barTargetTitle', {
- defaultMessage: 'Target bars',
- }),
- value: 50,
- description: i18n.translate('kbn.advancedSettings.histogram.barTargetText', {
- defaultMessage:
- 'Attempt to generate around this many bars when using "auto" interval in date histograms',
- }),
- },
- 'histogram:maxBars': {
- name: i18n.translate('kbn.advancedSettings.histogram.maxBarsTitle', {
- defaultMessage: 'Maximum bars',
- }),
- value: 100,
- description: i18n.translate('kbn.advancedSettings.histogram.maxBarsText', {
- defaultMessage:
- 'Never show more than this many bars in date histograms, scale values if needed',
- }),
- },
- 'visualize:enableLabs': {
- name: i18n.translate('kbn.advancedSettings.visualizeEnableLabsTitle', {
- defaultMessage: 'Enable experimental visualizations',
- }),
- value: true,
- description: i18n.translate('kbn.advancedSettings.visualizeEnableLabsText', {
- defaultMessage: `Allows users to create, view, and edit experimental visualizations. If disabled,
- only visualizations that are considered production-ready are available to the user.`,
- }),
- category: ['visualization'],
- },
'visualization:tileMap:maxPrecision': {
name: i18n.translate('kbn.advancedSettings.visualization.tileMap.maxPrecisionTitle', {
defaultMessage: 'Maximum tile map precision',
@@ -493,43 +241,6 @@ export function getUiSettingDefaults() {
}),
category: ['visualization'],
},
- 'csv:separator': {
- name: i18n.translate('kbn.advancedSettings.csv.separatorTitle', {
- defaultMessage: 'CSV separator',
- }),
- value: ',',
- description: i18n.translate('kbn.advancedSettings.csv.separatorText', {
- defaultMessage: 'Separate exported values with this string',
- }),
- },
- 'csv:quoteValues': {
- name: i18n.translate('kbn.advancedSettings.csv.quoteValuesTitle', {
- defaultMessage: 'Quote CSV values',
- }),
- value: true,
- description: i18n.translate('kbn.advancedSettings.csv.quoteValuesText', {
- defaultMessage: 'Should values be quoted in csv exports?',
- }),
- },
- 'history:limit': {
- name: i18n.translate('kbn.advancedSettings.historyLimitTitle', {
- defaultMessage: 'History limit',
- }),
- value: 10,
- description: i18n.translate('kbn.advancedSettings.historyLimitText', {
- defaultMessage:
- 'In fields that have history (e.g. query inputs), show this many recent values',
- }),
- },
- 'shortDots:enable': {
- name: i18n.translate('kbn.advancedSettings.shortenFieldsTitle', {
- defaultMessage: 'Shorten fields',
- }),
- value: false,
- description: i18n.translate('kbn.advancedSettings.shortenFieldsText', {
- defaultMessage: 'Shorten long fields, for example, instead of foo.bar.baz, show f.b.baz',
- }),
- },
'truncate:maxHeight': {
name: i18n.translate('kbn.advancedSettings.maxCellHeightTitle', {
defaultMessage: 'Maximum table cell height',
@@ -540,138 +251,6 @@ export function getUiSettingDefaults() {
'The maximum height that a cell in a table should occupy. Set to 0 to disable truncation',
}),
},
- 'format:defaultTypeMap': {
- name: i18n.translate('kbn.advancedSettings.format.defaultTypeMapTitle', {
- defaultMessage: 'Field type format name',
- }),
- value: `{
- "ip": { "id": "ip", "params": {} },
- "date": { "id": "date", "params": {} },
- "date_nanos": { "id": "date_nanos", "params": {}, "es": true },
- "number": { "id": "number", "params": {} },
- "boolean": { "id": "boolean", "params": {} },
- "_source": { "id": "_source", "params": {} },
- "_default_": { "id": "string", "params": {} }
-}`,
- type: 'json',
- description: i18n.translate('kbn.advancedSettings.format.defaultTypeMapText', {
- defaultMessage:
- 'Map of the format name to use by default for each field type. ' +
- '{defaultFormat} is used if the field type is not mentioned explicitly',
- values: {
- defaultFormat: '"_default_"',
- },
- }),
- },
- 'format:number:defaultPattern': {
- name: i18n.translate('kbn.advancedSettings.format.numberFormatTitle', {
- defaultMessage: 'Number format',
- }),
- value: '0,0.[000]',
- type: 'string',
- description: i18n.translate('kbn.advancedSettings.format.numberFormatText', {
- defaultMessage: 'Default {numeralFormatLink} for the "number" format',
- description:
- 'Part of composite text: kbn.advancedSettings.format.numberFormatText + ' +
- 'kbn.advancedSettings.format.numberFormat.numeralFormatLinkText',
- values: {
- numeralFormatLink:
- '' +
- i18n.translate('kbn.advancedSettings.format.numberFormat.numeralFormatLinkText', {
- defaultMessage: 'numeral format',
- }) +
- '',
- },
- }),
- },
- 'format:bytes:defaultPattern': {
- name: i18n.translate('kbn.advancedSettings.format.bytesFormatTitle', {
- defaultMessage: 'Bytes format',
- }),
- value: '0,0.[0]b',
- type: 'string',
- description: i18n.translate('kbn.advancedSettings.format.bytesFormatText', {
- defaultMessage: 'Default {numeralFormatLink} for the "bytes" format',
- description:
- 'Part of composite text: kbn.advancedSettings.format.bytesFormatText + ' +
- 'kbn.advancedSettings.format.bytesFormat.numeralFormatLinkText',
- values: {
- numeralFormatLink:
- '' +
- i18n.translate('kbn.advancedSettings.format.bytesFormat.numeralFormatLinkText', {
- defaultMessage: 'numeral format',
- }) +
- '',
- },
- }),
- },
- 'format:percent:defaultPattern': {
- name: i18n.translate('kbn.advancedSettings.format.percentFormatTitle', {
- defaultMessage: 'Percent format',
- }),
- value: '0,0.[000]%',
- type: 'string',
- description: i18n.translate('kbn.advancedSettings.format.percentFormatText', {
- defaultMessage: 'Default {numeralFormatLink} for the "percent" format',
- description:
- 'Part of composite text: kbn.advancedSettings.format.percentFormatText + ' +
- 'kbn.advancedSettings.format.percentFormat.numeralFormatLinkText',
- values: {
- numeralFormatLink:
- '' +
- i18n.translate('kbn.advancedSettings.format.percentFormat.numeralFormatLinkText', {
- defaultMessage: 'numeral format',
- }) +
- '',
- },
- }),
- },
- 'format:currency:defaultPattern': {
- name: i18n.translate('kbn.advancedSettings.format.currencyFormatTitle', {
- defaultMessage: 'Currency format',
- }),
- value: '($0,0.[00])',
- type: 'string',
- description: i18n.translate('kbn.advancedSettings.format.currencyFormatText', {
- defaultMessage: 'Default {numeralFormatLink} for the "currency" format',
- description:
- 'Part of composite text: kbn.advancedSettings.format.currencyFormatText + ' +
- 'kbn.advancedSettings.format.currencyFormat.numeralFormatLinkText',
- values: {
- numeralFormatLink:
- '' +
- i18n.translate('kbn.advancedSettings.format.currencyFormat.numeralFormatLinkText', {
- defaultMessage: 'numeral format',
- }) +
- '',
- },
- }),
- },
- 'format:number:defaultLocale': {
- name: i18n.translate('kbn.advancedSettings.format.formattingLocaleTitle', {
- defaultMessage: 'Formatting locale',
- }),
- value: 'en',
- type: 'select',
- options: numeralLanguageIds,
- optionLabels: Object.fromEntries(
- numeralLanguages.map((language) => [language.id, language.name])
- ),
- description: i18n.translate('kbn.advancedSettings.format.formattingLocaleText', {
- defaultMessage: `{numeralLanguageLink} locale`,
- description:
- 'Part of composite text: kbn.advancedSettings.format.formattingLocale.numeralLanguageLinkText + ' +
- 'kbn.advancedSettings.format.formattingLocaleText',
- values: {
- numeralLanguageLink:
- '' +
- i18n.translate('kbn.advancedSettings.format.formattingLocale.numeralLanguageLinkText', {
- defaultMessage: 'Numeral language',
- }) +
- '',
- },
- }),
- },
'timepicker:timeDefaults': {
name: i18n.translate('kbn.advancedSettings.timepicker.timeDefaultsTitle', {
defaultMessage: 'Time filter defaults',
@@ -686,120 +265,6 @@ export function getUiSettingDefaults() {
}),
requiresPageReload: true,
},
- 'timepicker:refreshIntervalDefaults': {
- name: i18n.translate('kbn.advancedSettings.timepicker.refreshIntervalDefaultsTitle', {
- defaultMessage: 'Time filter refresh interval',
- }),
- value: `{
- "pause": false,
- "value": 0
-}`,
- type: 'json',
- description: i18n.translate('kbn.advancedSettings.timepicker.refreshIntervalDefaultsText', {
- defaultMessage: `The timefilter's default refresh interval`,
- }),
- requiresPageReload: true,
- },
- 'timepicker:quickRanges': {
- name: i18n.translate('kbn.advancedSettings.timepicker.quickRangesTitle', {
- defaultMessage: 'Time filter quick ranges',
- }),
- value: JSON.stringify(
- [
- {
- from: 'now/d',
- to: 'now/d',
- display: i18n.translate('kbn.advancedSettings.timepicker.today', {
- defaultMessage: 'Today',
- }),
- },
- {
- from: 'now/w',
- to: 'now/w',
- display: i18n.translate('kbn.advancedSettings.timepicker.thisWeek', {
- defaultMessage: 'This week',
- }),
- },
- {
- from: 'now-15m',
- to: 'now',
- display: i18n.translate('kbn.advancedSettings.timepicker.last15Minutes', {
- defaultMessage: 'Last 15 minutes',
- }),
- },
- {
- from: 'now-30m',
- to: 'now',
- display: i18n.translate('kbn.advancedSettings.timepicker.last30Minutes', {
- defaultMessage: 'Last 30 minutes',
- }),
- },
- {
- from: 'now-1h',
- to: 'now',
- display: i18n.translate('kbn.advancedSettings.timepicker.last1Hour', {
- defaultMessage: 'Last 1 hour',
- }),
- },
- {
- from: 'now-24h',
- to: 'now',
- display: i18n.translate('kbn.advancedSettings.timepicker.last24Hours', {
- defaultMessage: 'Last 24 hours',
- }),
- },
- {
- from: 'now-7d',
- to: 'now',
- display: i18n.translate('kbn.advancedSettings.timepicker.last7Days', {
- defaultMessage: 'Last 7 days',
- }),
- },
- {
- from: 'now-30d',
- to: 'now',
- display: i18n.translate('kbn.advancedSettings.timepicker.last30Days', {
- defaultMessage: 'Last 30 days',
- }),
- },
- {
- from: 'now-90d',
- to: 'now',
- display: i18n.translate('kbn.advancedSettings.timepicker.last90Days', {
- defaultMessage: 'Last 90 days',
- }),
- },
- {
- from: 'now-1y',
- to: 'now',
- display: i18n.translate('kbn.advancedSettings.timepicker.last1Year', {
- defaultMessage: 'Last 1 year',
- }),
- },
- ],
- null,
- 2
- ),
- type: 'json',
- description: i18n.translate('kbn.advancedSettings.timepicker.quickRangesText', {
- defaultMessage:
- 'The list of ranges to show in the Quick section of the time filter. This should be an array of objects, ' +
- 'with each object containing "from", "to" (see {acceptedFormatsLink}), and ' +
- '"display" (the title to be displayed).',
- description:
- 'Part of composite text: kbn.advancedSettings.timepicker.quickRangesText + ' +
- 'kbn.advancedSettings.timepicker.quickRanges.acceptedFormatsLinkText',
- values: {
- acceptedFormatsLink:
- `` +
- i18n.translate('kbn.advancedSettings.timepicker.quickRanges.acceptedFormatsLinkText', {
- defaultMessage: 'accepted formats',
- }) +
- '',
- },
- }),
- },
'theme:darkMode': {
name: i18n.translate('kbn.advancedSettings.darkModeTitle', {
defaultMessage: 'Dark mode',
@@ -822,26 +287,6 @@ export function getUiSettingDefaults() {
}),
requiresPageReload: true,
},
- 'filters:pinnedByDefault': {
- name: i18n.translate('kbn.advancedSettings.pinFiltersTitle', {
- defaultMessage: 'Pin filters by default',
- }),
- value: false,
- description: i18n.translate('kbn.advancedSettings.pinFiltersText', {
- defaultMessage: 'Whether the filters should have a global state (be pinned) by default',
- }),
- },
- 'filterEditor:suggestValues': {
- name: i18n.translate('kbn.advancedSettings.suggestFilterValuesTitle', {
- defaultMessage: 'Filter editor suggest values',
- description: '"Filter editor" refers to the UI you create filters in.',
- }),
- value: true,
- description: i18n.translate('kbn.advancedSettings.suggestFilterValuesText', {
- defaultMessage:
- 'Set this property to false to prevent the filter editor from suggesting values for fields.',
- }),
- },
'notifications:banner': {
name: i18n.translate('kbn.advancedSettings.notifications.bannerTitle', {
defaultMessage: 'Custom banner notification',
@@ -930,28 +375,6 @@ export function getUiSettingDefaults() {
type: 'number',
category: ['notifications'],
},
- 'state:storeInSessionStorage': {
- name: i18n.translate('kbn.advancedSettings.storeUrlTitle', {
- defaultMessage: 'Store URLs in session storage',
- }),
- value: false,
- description: i18n.translate('kbn.advancedSettings.storeUrlText', {
- defaultMessage:
- 'The URL can sometimes grow to be too large for some browsers to handle. ' +
- 'To counter-act this we are testing if storing parts of the URL in session storage could help. ' +
- 'Please let us know how it goes!',
- }),
- },
- 'indexPattern:placeholder': {
- name: i18n.translate('kbn.advancedSettings.indexPatternPlaceholderTitle', {
- defaultMessage: 'Index pattern placeholder',
- }),
- value: '',
- description: i18n.translate('kbn.advancedSettings.indexPatternPlaceholderText', {
- defaultMessage:
- 'The placeholder for the "Index pattern name" field in "Management > Index Patterns > Create Index Pattern".',
- }),
- },
'accessibility:disableAnimations': {
name: i18n.translate('kbn.advancedSettings.disableAnimationsTitle', {
defaultMessage: 'Disable Animations',
diff --git a/src/legacy/core_plugins/timelion/public/directives/saved_object_finder.js b/src/legacy/core_plugins/timelion/public/directives/saved_object_finder.js
index 08a347fbf72959..879fab206b99df 100644
--- a/src/legacy/core_plugins/timelion/public/directives/saved_object_finder.js
+++ b/src/legacy/core_plugins/timelion/public/directives/saved_object_finder.js
@@ -29,6 +29,7 @@ import {
PaginateDirectiveProvider,
} from '../../../../../plugins/kibana_legacy/public';
import { PER_PAGE_SETTING } from '../../../../../plugins/saved_objects/common';
+import { VISUALIZE_ENABLE_LABS_SETTING } from '../../../../../plugins/visualizations/public';
const module = uiModules.get('kibana');
@@ -294,7 +295,7 @@ module
prevSearch = filter;
- const isLabsEnabled = config.get('visualize:enableLabs');
+ const isLabsEnabled = config.get(VISUALIZE_ENABLE_LABS_SETTING);
self.service.find(filter).then(function (hits) {
hits.hits = hits.hits.filter(
(hit) => isLabsEnabled || _.get(hit, 'type.stage') !== 'experimental'
diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js
index 229bfb1978a4ef..d98770842a0f09 100644
--- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js
+++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js
@@ -28,6 +28,11 @@ import {
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
} from '../../../../../src/plugins/data/public/search/aggs';
import { ComponentRegistry } from '../../../../../src/plugins/advanced_settings/public/';
+import { UI_SETTINGS } from '../../../../../src/plugins/data/public/';
+import {
+ CSV_SEPARATOR_SETTING,
+ CSV_QUOTE_VALUES_SETTING,
+} from '../../../../../src/plugins/share/public';
const mockObservable = () => {
return {
@@ -49,18 +54,31 @@ let isTimeRangeSelectorEnabled = true;
let isAutoRefreshSelectorEnabled = true;
export const mockUiSettings = {
- get: (item) => {
- return mockUiSettings[item];
+ get: (item, defaultValue) => {
+ const defaultValues = {
+ dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS',
+ 'dateFormat:tz': 'UTC',
+ [UI_SETTINGS.SHORT_DOTS_ENABLE]: true,
+ [UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX]: true,
+ [UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS]: true,
+ [UI_SETTINGS.QUERY_STRING_OPTIONS]: {},
+ [UI_SETTINGS.FORMAT_CURRENCY_DEFAULT_PATTERN]: '($0,0.[00])',
+ [UI_SETTINGS.FORMAT_NUMBER_DEFAULT_PATTERN]: '0,0.[000]',
+ [UI_SETTINGS.FORMAT_PERCENT_DEFAULT_PATTERN]: '0,0.[000]%',
+ [UI_SETTINGS.FORMAT_NUMBER_DEFAULT_LOCALE]: 'en',
+ [UI_SETTINGS.FORMAT_DEFAULT_TYPE_MAP]: {},
+ [CSV_SEPARATOR_SETTING]: ',',
+ [CSV_QUOTE_VALUES_SETTING]: true,
+ [UI_SETTINGS.SEARCH_QUERY_LANGUAGE]: 'kuery',
+ 'state:storeInSessionStorage': false,
+ };
+
+ return defaultValues[item] || defaultValue;
},
getUpdate$: () => ({
subscribe: sinon.fake(),
}),
isDefault: sinon.fake(),
- 'query:allowLeadingWildcards': true,
- 'query:queryString:options': {},
- 'courier:ignoreFilterIfFieldNotInIndex': true,
- 'dateFormat:tz': 'Browser',
- 'format:defaultTypeMap': {},
};
const mockCoreSetup = {
@@ -236,6 +254,9 @@ export const npSetup = {
},
share: {
register: () => {},
+ urlGenerators: {
+ registerUrlGenerator: () => {},
+ },
},
devTools: {
register: () => {},
@@ -524,6 +545,8 @@ export function __setup__(coreSetup) {
// bootstrap an LP plugin outside of tests)
npSetup.core.application.register = () => {};
+ npSetup.core.uiSettings.get = mockUiSettings.get;
+
// Services that need to be set in the legacy platform since the legacy data
// & vis plugins which previously provided them have been removed.
setSetupServices(npSetup);
@@ -532,6 +555,8 @@ export function __setup__(coreSetup) {
export function __start__(coreStart) {
npStart.core = coreStart;
+ npStart.core.uiSettings.get = mockUiSettings.get;
+
// Services that need to be set in the legacy platform since the legacy data
// & vis plugins which previously provided them have been removed.
setStartServices(npStart);
diff --git a/src/legacy/ui/public/new_platform/set_services.ts b/src/legacy/ui/public/new_platform/set_services.ts
index 9d02ad67b39378..ee92eda064aa86 100644
--- a/src/legacy/ui/public/new_platform/set_services.ts
+++ b/src/legacy/ui/public/new_platform/set_services.ts
@@ -65,6 +65,7 @@ export function setStartServices(npStart: NpStart) {
visualizationsServices.setCapabilities(npStart.core.application.capabilities);
visualizationsServices.setHttp(npStart.core.http);
visualizationsServices.setApplication(npStart.core.application);
+ visualizationsServices.setEmbeddable(npStart.plugins.embeddable);
visualizationsServices.setSavedObjects(npStart.core.savedObjects);
visualizationsServices.setIndexPatterns(npStart.plugins.data.indexPatterns);
visualizationsServices.setFilterManager(npStart.plugins.data.query.filterManager);
diff --git a/src/legacy/ui/public/timefilter/setup_router.ts b/src/legacy/ui/public/timefilter/setup_router.ts
index a7492e538b3af1..7c25c6aa3166e3 100644
--- a/src/legacy/ui/public/timefilter/setup_router.ts
+++ b/src/legacy/ui/public/timefilter/setup_router.ts
@@ -21,10 +21,15 @@ import _ from 'lodash';
import { IScope } from 'angular';
import moment from 'moment';
import chrome from 'ui/chrome';
-import { RefreshInterval, TimeRange, TimefilterContract } from 'src/plugins/data/public';
import { Subscription } from 'rxjs';
import { fatalError } from 'ui/notify/fatal_error';
import { subscribeWithScope } from '../../../../plugins/kibana_legacy/public';
+import {
+ RefreshInterval,
+ TimeRange,
+ TimefilterContract,
+ UI_SETTINGS,
+} from '../../../../plugins/data/public';
// TODO
// remove everything underneath once globalState is no longer an angular service
@@ -38,7 +43,7 @@ export function getTimefilterConfig() {
const settings = chrome.getUiSettingsClient();
return {
timeDefaults: settings.get('timepicker:timeDefaults'),
- refreshIntervalDefaults: settings.get('timepicker:refreshIntervalDefaults'),
+ refreshIntervalDefaults: settings.get(UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS),
};
}
diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx
index 8dd0a766da97b7..a59d1e8c546d4b 100644
--- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx
+++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx
@@ -41,6 +41,7 @@ import {
QueryState,
SavedQuery,
syncQueryStateWithUrl,
+ UI_SETTINGS,
} from '../../../data/public';
import { getSavedObjectFinder, SaveResult, showSaveModal } from '../../../saved_objects/public';
@@ -430,7 +431,8 @@ export class DashboardAppController {
dashboardStateManager.getQuery() || {
query: '',
language:
- localStorage.get('kibana.userQueryLanguage') || uiSettings.get('search:queryLanguage'),
+ localStorage.get('kibana.userQueryLanguage') ||
+ uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE),
},
queryFilter.getFilters()
);
diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx
index 7e25d80c9d6191..5d4cc851cf4554 100644
--- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx
+++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx
@@ -186,7 +186,11 @@ export class DashboardContainer extends Container
-
+
,
dom
diff --git a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx
index 9b7cec2f182ba8..9a2610a82b97d0 100644
--- a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx
+++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx
@@ -80,6 +80,7 @@ function prepare(props?: Partial) {
dashboardContainer = new DashboardContainer(initialInput, options);
const defaultTestProps: DashboardGridProps = {
container: dashboardContainer,
+ PanelComponent: () => ,
kibana: null as any,
intl: null as any,
};
diff --git a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx
index 19d9ad34e729c8..dcd07fe394c7d1 100644
--- a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx
+++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx
@@ -30,7 +30,7 @@ import React from 'react';
import { Subscription } from 'rxjs';
import ReactGridLayout, { Layout } from 'react-grid-layout';
import { GridData } from '../../../../common';
-import { ViewMode, EmbeddableChildPanel } from '../../../embeddable_plugin';
+import { ViewMode, EmbeddableChildPanel, EmbeddableStart } from '../../../embeddable_plugin';
import { DASHBOARD_GRID_COLUMN_COUNT, DASHBOARD_GRID_HEIGHT } from '../dashboard_constants';
import { DashboardPanelState } from '../types';
import { withKibana } from '../../../../../kibana_react/public';
@@ -115,6 +115,7 @@ const ResponsiveSizedGrid = sizeMe(config)(ResponsiveGrid);
export interface DashboardGridProps extends ReactIntl.InjectedIntlProps {
kibana: DashboardReactContextValue;
+ PanelComponent: EmbeddableStart['EmbeddablePanel'];
container: DashboardContainer;
}
@@ -271,14 +272,7 @@ class DashboardGridUi extends React.Component {
);
diff --git a/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts b/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts
index 62a39ee898d3a6..1b060c186db973 100644
--- a/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts
+++ b/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts
@@ -108,16 +108,32 @@ interface IplacementDirection {
fits: boolean;
}
+/**
+ * Compare grid data by an ending y coordinate. Grid data with a smaller ending y coordinate
+ * comes first.
+ * @param a
+ * @param b
+ */
+function comparePanels(a: GridData, b: GridData): number {
+ if (a.y + a.h < b.y + b.h) {
+ return -1;
+ }
+ if (a.y + a.h > b.y + b.h) {
+ return 1;
+ }
+ // a.y === b.y
+ if (a.x + a.w <= b.x + b.w) {
+ return -1;
+ }
+ return 1;
+}
+
export function placePanelBeside({
width,
height,
currentPanels,
placeBesideId,
}: IPanelPlacementBesideArgs): Omit {
- // const clonedPanels = _.cloneDeep(currentPanels);
- if (!placeBesideId) {
- throw new Error('Place beside method called without placeBesideId');
- }
const panelToPlaceBeside = currentPanels[placeBesideId];
if (!panelToPlaceBeside) {
throw new PanelNotFoundError();
@@ -130,10 +146,11 @@ export function placePanelBeside({
const possiblePlacementDirections: IplacementDirection[] = [
{ grid: { x: beside.x + beside.w, y: beside.y, w: width, h: height }, fits: true }, // right
- { grid: { x: beside.x - width, y: beside.y, w: width, h: height }, fits: true }, // left
+ { grid: { x: 0, y: beside.y + beside.h, w: width, h: height }, fits: true }, // left side of next row
{ grid: { x: beside.x, y: beside.y + beside.h, w: width, h: height }, fits: true }, // bottom
];
+ // first, we check if there is place around the current panel
for (const direction of possiblePlacementDirections) {
if (
direction.grid.x >= 0 &&
@@ -156,13 +173,32 @@ export function placePanelBeside({
}
}
// if we get here that means there is no blank space around the panel we are placing beside. This means it's time to mess up the dashboard's groove. Fun!
- const [, , bottomPlacement] = possiblePlacementDirections;
- for (const currentPanelGrid of otherPanels) {
- if (bottomPlacement.grid.y <= currentPanelGrid.y) {
- const movedPanel = _.cloneDeep(currentPanels[currentPanelGrid.i]);
- movedPanel.gridData.y = movedPanel.gridData.y + bottomPlacement.grid.h;
- currentPanels[currentPanelGrid.i] = movedPanel;
+ /**
+ * 1. sort the panels in the grid
+ * 2. place the cloned panel to the bottom
+ * 3. reposition the panels after the cloned panel in the grid
+ */
+ const grid = otherPanels.sort(comparePanels);
+
+ let position = 0;
+ for (position; position < grid.length; position++) {
+ if (beside.i === grid[position].i) {
+ break;
}
}
+ const bottomPlacement = possiblePlacementDirections[2];
+ // place to the bottom and move all other panels
+ let originalPositionInTheGrid = grid[position + 1].i;
+ const diff =
+ bottomPlacement.grid.y +
+ bottomPlacement.grid.h -
+ currentPanels[originalPositionInTheGrid].gridData.y;
+
+ for (let j = position + 1; j < grid.length; j++) {
+ originalPositionInTheGrid = grid[j].i;
+ const movedPanel = _.cloneDeep(currentPanels[originalPositionInTheGrid]);
+ movedPanel.gridData.y = movedPanel.gridData.y + diff;
+ currentPanels[originalPositionInTheGrid] = movedPanel;
+ }
return bottomPlacement.grid;
}
diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx
index 1b257880b94013..25e451dc7f793b 100644
--- a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx
+++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx
@@ -87,6 +87,7 @@ function getProps(
dashboardContainer = new DashboardContainer(input, options);
const defaultTestProps: DashboardViewportProps = {
container: dashboardContainer,
+ PanelComponent: () => ,
};
return {
diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx
index ae239bc27fdbaa..429837583b6481 100644
--- a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx
+++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx
@@ -19,7 +19,7 @@
import React from 'react';
import { Subscription } from 'rxjs';
-import { PanelState } from '../../../embeddable_plugin';
+import { PanelState, EmbeddableStart } from '../../../embeddable_plugin';
import { DashboardContainer, DashboardReactContextValue } from '../dashboard_container';
import { DashboardGrid } from '../grid';
import { context } from '../../../../../kibana_react/public';
@@ -27,6 +27,7 @@ import { context } from '../../../../../kibana_react/public';
export interface DashboardViewportProps {
container: DashboardContainer;
renderEmpty?: () => React.ReactNode;
+ PanelComponent: EmbeddableStart['EmbeddablePanel'];
}
interface State {
@@ -114,7 +115,7 @@ export class DashboardViewport extends React.Component
)}
-
+
);
}
diff --git a/src/plugins/data/common/constants.ts b/src/plugins/data/common/constants.ts
index 66a96e3e6e1293..8ec72dc1f9a744 100644
--- a/src/plugins/data/common/constants.ts
+++ b/src/plugins/data/common/constants.ts
@@ -18,5 +18,33 @@
*/
export const DEFAULT_QUERY_LANGUAGE = 'kuery';
-export const META_FIELDS_SETTING = 'metaFields';
-export const DOC_HIGHLIGHT_SETTING = 'doc_table:highlight';
+
+export const UI_SETTINGS = {
+ META_FIELDS: 'metaFields',
+ DOC_HIGHLIGHT: 'doc_table:highlight',
+ QUERY_STRING_OPTIONS: 'query:queryString:options',
+ QUERY_ALLOW_LEADING_WILDCARDS: 'query:allowLeadingWildcards',
+ SEARCH_QUERY_LANGUAGE: 'search:queryLanguage',
+ SORT_OPTIONS: 'sort:options',
+ COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX: 'courier:ignoreFilterIfFieldNotInIndex',
+ COURIER_SET_REQUEST_PREFERENCE: 'courier:setRequestPreference',
+ COURIER_CUSTOM_REQUEST_PREFERENCE: 'courier:customRequestPreference',
+ COURIER_MAX_CONCURRENT_SHARD_REQUESTS: 'courier:maxConcurrentShardRequests',
+ COURIER_BATCH_SEARCHES: 'courier:batchSearches',
+ SEARCH_INCLUDE_FROZEN: 'search:includeFrozen',
+ HISTOGRAM_BAR_TARGET: 'histogram:barTarget',
+ HISTOGRAM_MAX_BARS: 'histogram:maxBars',
+ HISTORY_LIMIT: 'history:limit',
+ SHORT_DOTS_ENABLE: 'shortDots:enable',
+ FORMAT_DEFAULT_TYPE_MAP: 'format:defaultTypeMap',
+ FORMAT_NUMBER_DEFAULT_PATTERN: 'format:number:defaultPattern',
+ FORMAT_PERCENT_DEFAULT_PATTERN: 'format:percent:defaultPattern',
+ FORMAT_BYTES_DEFAULT_PATTERN: 'format:bytes:defaultPattern',
+ FORMAT_CURRENCY_DEFAULT_PATTERN: 'format:currency:defaultPattern',
+ FORMAT_NUMBER_DEFAULT_LOCALE: 'format:number:defaultLocale',
+ TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: 'timepicker:refreshIntervalDefaults',
+ TIMEPICKER_QUICK_RANGES: 'timepicker:quickRanges',
+ INDEXPATTERN_PLACEHOLDER: 'indexPattern:placeholder',
+ FILTERS_PINNED_BY_DEFAULT: 'filters:pinnedByDefault',
+ FILTERS_EDITOR_SUGGEST_VALUES: 'filterEditor:suggestValues',
+};
diff --git a/src/plugins/data/common/es_query/es_query/get_es_query_config.test.ts b/src/plugins/data/common/es_query/es_query/get_es_query_config.test.ts
index d146d81973d0d7..5fa3c67dea400c 100644
--- a/src/plugins/data/common/es_query/es_query/get_es_query_config.test.ts
+++ b/src/plugins/data/common/es_query/es_query/get_es_query_config.test.ts
@@ -19,18 +19,19 @@
import { get } from 'lodash';
import { getEsQueryConfig } from './get_es_query_config';
import { IUiSettingsClient } from 'kibana/public';
+import { UI_SETTINGS } from '../../';
const config = ({
get(item: string) {
return get(config, item);
},
- 'query:allowLeadingWildcards': {
+ [UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS]: {
allowLeadingWildcards: true,
},
- 'query:queryString:options': {
+ [UI_SETTINGS.QUERY_STRING_OPTIONS]: {
queryStringOptions: {},
},
- 'courier:ignoreFilterIfFieldNotInIndex': {
+ [UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX]: {
ignoreFilterIfFieldNotInIndex: true,
},
'dateFormat:tz': {
diff --git a/src/plugins/data/common/es_query/es_query/get_es_query_config.ts b/src/plugins/data/common/es_query/es_query/get_es_query_config.ts
index 0a82cf03bdb445..ff8fc5b11b26e8 100644
--- a/src/plugins/data/common/es_query/es_query/get_es_query_config.ts
+++ b/src/plugins/data/common/es_query/es_query/get_es_query_config.ts
@@ -18,15 +18,18 @@
*/
import { EsQueryConfig } from './build_es_query';
+import { UI_SETTINGS } from '../../';
interface KibanaConfig {
get(key: string): T;
}
export function getEsQueryConfig(config: KibanaConfig) {
- const allowLeadingWildcards = config.get('query:allowLeadingWildcards');
- const queryStringOptions = config.get('query:queryString:options');
- const ignoreFilterIfFieldNotInIndex = config.get('courier:ignoreFilterIfFieldNotInIndex');
+ const allowLeadingWildcards = config.get(UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS);
+ const queryStringOptions = config.get(UI_SETTINGS.QUERY_STRING_OPTIONS);
+ const ignoreFilterIfFieldNotInIndex = config.get(
+ UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX
+ );
const dateFormatTZ = config.get('dateFormat:tz');
return {
diff --git a/src/plugins/data/common/field_formats/converters/bytes.test.ts b/src/plugins/data/common/field_formats/converters/bytes.test.ts
index 8dad9fc206e721..e0c26170c29075 100644
--- a/src/plugins/data/common/field_formats/converters/bytes.test.ts
+++ b/src/plugins/data/common/field_formats/converters/bytes.test.ts
@@ -18,11 +18,12 @@
*/
import { BytesFormat } from './bytes';
+import { UI_SETTINGS } from '../../constants';
describe('BytesFormat', () => {
const config: Record = {};
- config['format:bytes:defaultPattern'] = '0,0.[000]b';
+ config[UI_SETTINGS.FORMAT_BYTES_DEFAULT_PATTERN] = '0,0.[000]b';
const getConfig = (key: string) => config[key];
diff --git a/src/plugins/data/common/field_formats/converters/number.test.ts b/src/plugins/data/common/field_formats/converters/number.test.ts
index fe36d5b12e8736..31c5ea41bf5afd 100644
--- a/src/plugins/data/common/field_formats/converters/number.test.ts
+++ b/src/plugins/data/common/field_formats/converters/number.test.ts
@@ -18,11 +18,12 @@
*/
import { NumberFormat } from './number';
+import { UI_SETTINGS } from '../../constants';
describe('NumberFormat', () => {
const config: Record = {};
- config['format:number:defaultPattern'] = '0,0.[000]';
+ config[UI_SETTINGS.FORMAT_NUMBER_DEFAULT_PATTERN] = '0,0.[000]';
const getConfig = (key: string) => config[key];
diff --git a/src/plugins/data/common/field_formats/converters/numeral.ts b/src/plugins/data/common/field_formats/converters/numeral.ts
index a483b5a1e4f99a..1d844bca3f89ac 100644
--- a/src/plugins/data/common/field_formats/converters/numeral.ts
+++ b/src/plugins/data/common/field_formats/converters/numeral.ts
@@ -24,6 +24,7 @@ import numeralLanguages from '@elastic/numeral/languages';
import { KBN_FIELD_TYPES } from '../../kbn_field_types/types';
import { FieldFormat } from '../field_format';
import { TextContextTypeConvert } from '../types';
+import { UI_SETTINGS } from '../../constants';
const numeralInst = numeral();
@@ -51,7 +52,8 @@ export abstract class NumeralFormat extends FieldFormat {
if (isNaN(val)) return '';
const previousLocale = numeral.language();
- const defaultLocale = (this.getConfig && this.getConfig('format:number:defaultLocale')) || 'en';
+ const defaultLocale =
+ (this.getConfig && this.getConfig(UI_SETTINGS.FORMAT_NUMBER_DEFAULT_LOCALE)) || 'en';
numeral.language(defaultLocale);
const formatted = numeralInst.set(val).format(this.param('pattern'));
diff --git a/src/plugins/data/common/field_formats/converters/percent.test.ts b/src/plugins/data/common/field_formats/converters/percent.test.ts
index 8b26564814af3d..754234bdeb78b4 100644
--- a/src/plugins/data/common/field_formats/converters/percent.test.ts
+++ b/src/plugins/data/common/field_formats/converters/percent.test.ts
@@ -18,11 +18,12 @@
*/
import { PercentFormat } from './percent';
+import { UI_SETTINGS } from '../../constants';
describe('PercentFormat', () => {
const config: Record = {};
- config['format:percent:defaultPattern'] = '0,0.[000]%';
+ config[UI_SETTINGS.FORMAT_PERCENT_DEFAULT_PATTERN] = '0,0.[000]%';
const getConfig = (key: string) => config[key];
diff --git a/src/plugins/data/common/field_formats/converters/percent.ts b/src/plugins/data/common/field_formats/converters/percent.ts
index ef3b0a1503a981..ecf9c7d19108d9 100644
--- a/src/plugins/data/common/field_formats/converters/percent.ts
+++ b/src/plugins/data/common/field_formats/converters/percent.ts
@@ -20,6 +20,7 @@
import { i18n } from '@kbn/i18n';
import { NumeralFormat } from './numeral';
import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types';
+import { UI_SETTINGS } from '../../constants';
export class PercentFormat extends NumeralFormat {
static id = FIELD_FORMAT_IDS.PERCENT;
@@ -32,7 +33,7 @@ export class PercentFormat extends NumeralFormat {
allowsNumericalAggregations = true;
getParamDefaults = () => ({
- pattern: this.getConfig!('format:percent:defaultPattern'),
+ pattern: this.getConfig!(UI_SETTINGS.FORMAT_PERCENT_DEFAULT_PATTERN),
fractional: true,
});
diff --git a/src/plugins/data/common/field_formats/converters/source.ts b/src/plugins/data/common/field_formats/converters/source.ts
index 9e50d47bb2624b..f00261e00971aa 100644
--- a/src/plugins/data/common/field_formats/converters/source.ts
+++ b/src/plugins/data/common/field_formats/converters/source.ts
@@ -22,6 +22,7 @@ import { shortenDottedString } from '../../utils';
import { KBN_FIELD_TYPES } from '../../kbn_field_types/types';
import { FieldFormat } from '../field_format';
import { TextContextTypeConvert, HtmlContextTypeConvert, FIELD_FORMAT_IDS } from '../types';
+import { UI_SETTINGS } from '../../';
/**
* Remove all of the whitespace between html tags
@@ -71,7 +72,7 @@ export class SourceFormat extends FieldFormat {
const formatted = field.indexPattern.formatHit(hit);
const highlightPairs: any[] = [];
const sourcePairs: any[] = [];
- const isShortDots = this.getConfig!('shortDots:enable');
+ const isShortDots = this.getConfig!(UI_SETTINGS.SHORT_DOTS_ENABLE);
keys(formatted).forEach((key) => {
const pairs = highlights[key] ? highlightPairs : sourcePairs;
diff --git a/src/plugins/data/common/field_formats/field_formats_registry.ts b/src/plugins/data/common/field_formats/field_formats_registry.ts
index c04a371066de33..9325485bce75d6 100644
--- a/src/plugins/data/common/field_formats/field_formats_registry.ts
+++ b/src/plugins/data/common/field_formats/field_formats_registry.ts
@@ -33,6 +33,7 @@ import { baseFormatters } from './constants/base_formatters';
import { FieldFormat } from './field_format';
import { SerializedFieldFormat } from '../../../expressions/common/types';
import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '../types';
+import { UI_SETTINGS } from '../';
export class FieldFormatsRegistry {
protected fieldFormats: Map = new Map();
@@ -49,7 +50,7 @@ export class FieldFormatsRegistry {
metaParamsOptions: Record = {},
defaultFieldConverters: FieldFormatInstanceType[] = baseFormatters
) {
- const defaultTypeMap = getConfig('format:defaultTypeMap');
+ const defaultTypeMap = getConfig(UI_SETTINGS.FORMAT_DEFAULT_TYPE_MAP);
this.register(defaultFieldConverters);
this.parseDefaultTypeMap(defaultTypeMap);
this.getConfig = getConfig;
diff --git a/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts
index ef7a0e8c3a5ad0..a6a45a26f06b39 100644
--- a/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts
+++ b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts
@@ -19,7 +19,7 @@
import { memoize } from 'lodash';
import { CoreSetup } from 'src/core/public';
-import { IIndexPattern, IFieldType } from '../../../common';
+import { IIndexPattern, IFieldType, UI_SETTINGS } from '../../../common';
function resolver(title: string, field: IFieldType, query: string, boolFilter: any) {
// Only cache results for a minute
@@ -58,7 +58,9 @@ export const setupValueSuggestionProvider = (core: CoreSetup): ValueSuggestionsG
boolFilter,
signal,
}: ValueSuggestionsGetFnArgs): Promise => {
- const shouldSuggestValues = core!.uiSettings.get('filterEditor:suggestValues');
+ const shouldSuggestValues = core!.uiSettings.get(
+ UI_SETTINGS.FILTERS_EDITOR_SUGGEST_VALUES
+ );
const { title } = indexPattern;
if (field.type === 'boolean') {
diff --git a/src/plugins/data/public/field_formats/field_formats_service.ts b/src/plugins/data/public/field_formats/field_formats_service.ts
index 22c7e90c06130b..3ddc8d0b68a5bb 100644
--- a/src/plugins/data/public/field_formats/field_formats_service.ts
+++ b/src/plugins/data/public/field_formats/field_formats_service.ts
@@ -18,7 +18,7 @@
*/
import { CoreSetup } from 'src/core/public';
-import { FieldFormatsRegistry } from '../../common';
+import { FieldFormatsRegistry, UI_SETTINGS } from '../../common';
import { deserializeFieldFormat } from './utils/deserialize';
import { FormatFactory } from '../../common/field_formats/utils';
import { baseFormattersPublic } from './constants';
@@ -28,7 +28,7 @@ export class FieldFormatsService {
public setup(core: CoreSetup) {
core.uiSettings.getUpdate$().subscribe(({ key, newValue }) => {
- if (key === 'format:defaultTypeMap') {
+ if (key === UI_SETTINGS.FORMAT_DEFAULT_TYPE_MAP) {
this.fieldFormatsRegistry.parseDefaultTypeMap(newValue);
}
});
diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts
index 0c946fc6e1858b..55400393237567 100644
--- a/src/plugins/data/public/index.ts
+++ b/src/plugins/data/public/index.ts
@@ -17,8 +17,6 @@
* under the License.
*/
-import './index.scss';
-
import { PluginInitializerContext } from '../../../core/public';
import { ConfigSchema } from '../config';
@@ -267,6 +265,7 @@ export {
ES_FIELD_TYPES,
KBN_FIELD_TYPES,
IndexPatternAttributes,
+ UI_SETTINGS,
} from '../common';
/*
diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts
index 43404c32cb3d4f..3d54009d0fdcab 100644
--- a/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts
+++ b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts
@@ -33,7 +33,7 @@ import {
KBN_FIELD_TYPES,
IIndexPattern,
IFieldType,
- META_FIELDS_SETTING,
+ UI_SETTINGS,
} from '../../../common';
import { findByTitle } from '../utils';
import { IndexPatternMissingIndices } from '../lib';
@@ -108,8 +108,8 @@ export class IndexPattern implements IIndexPattern {
// which cause problems when being consumed from angular
this.getConfig = getConfig;
- this.shortDotsEnable = this.getConfig('shortDots:enable');
- this.metaFields = this.getConfig(META_FIELDS_SETTING);
+ this.shortDotsEnable = this.getConfig(UI_SETTINGS.SHORT_DOTS_ENABLE);
+ this.metaFields = this.getConfig(UI_SETTINGS.META_FIELDS);
this.createFieldList = getIndexPatternFieldListCreator({
fieldFormats: getFieldFormats(),
@@ -117,8 +117,12 @@ export class IndexPattern implements IIndexPattern {
});
this.fields = this.createFieldList(this, [], this.shortDotsEnable);
- this.fieldsFetcher = createFieldsFetcher(this, apiClient, this.getConfig(META_FIELDS_SETTING));
- this.flattenHit = flattenHitWrapper(this, this.getConfig(META_FIELDS_SETTING));
+ this.fieldsFetcher = createFieldsFetcher(
+ this,
+ apiClient,
+ this.getConfig(UI_SETTINGS.META_FIELDS)
+ );
+ this.flattenHit = flattenHitWrapper(this, this.getConfig(UI_SETTINGS.META_FIELDS));
this.formatHit = formatHitProvider(
this,
getFieldFormats().getDefaultInstance(KBN_FIELD_TYPES.STRING)
diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts
index 66e8d5a6e739a1..06b5cbdfdfdfb5 100644
--- a/src/plugins/data/public/plugin.ts
+++ b/src/plugins/data/public/plugin.ts
@@ -17,6 +17,8 @@
* under the License.
*/
+import './index.scss';
+
import {
PluginInitializerContext,
CoreSetup,
diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md
index ebb4e9d583bc0e..dcdb528ac8b7d5 100644
--- a/src/plugins/data/public/public.api.md
+++ b/src/plugins/data/public/public.api.md
@@ -1792,6 +1792,39 @@ export interface TimeRange {
// @public
export type TSearchStrategyProvider = (context: ISearchContext) => ISearchStrategy;
+// Warning: (ae-missing-release-tag) "UI_SETTINGS" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export const UI_SETTINGS: {
+ META_FIELDS: string;
+ DOC_HIGHLIGHT: string;
+ QUERY_STRING_OPTIONS: string;
+ QUERY_ALLOW_LEADING_WILDCARDS: string;
+ SEARCH_QUERY_LANGUAGE: string;
+ SORT_OPTIONS: string;
+ COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX: string;
+ COURIER_SET_REQUEST_PREFERENCE: string;
+ COURIER_CUSTOM_REQUEST_PREFERENCE: string;
+ COURIER_MAX_CONCURRENT_SHARD_REQUESTS: string;
+ COURIER_BATCH_SEARCHES: string;
+ SEARCH_INCLUDE_FROZEN: string;
+ HISTOGRAM_BAR_TARGET: string;
+ HISTOGRAM_MAX_BARS: string;
+ HISTORY_LIMIT: string;
+ SHORT_DOTS_ENABLE: string;
+ FORMAT_DEFAULT_TYPE_MAP: string;
+ FORMAT_NUMBER_DEFAULT_PATTERN: string;
+ FORMAT_PERCENT_DEFAULT_PATTERN: string;
+ FORMAT_BYTES_DEFAULT_PATTERN: string;
+ FORMAT_CURRENCY_DEFAULT_PATTERN: string;
+ FORMAT_NUMBER_DEFAULT_LOCALE: string;
+ TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: string;
+ TIMEPICKER_QUICK_RANGES: string;
+ INDEXPATTERN_PLACEHOLDER: string;
+ FILTERS_PINNED_BY_DEFAULT: string;
+ FILTERS_EDITOR_SUGGEST_VALUES: string;
+};
+
// Warnings were encountered during analysis:
//
@@ -1800,52 +1833,52 @@ export type TSearchStrategyProvider = (context: ISearc
// src/plugins/data/common/es_query/filters/match_all_filter.ts:28:3 - (ae-forgotten-export) The symbol "MatchAllFilterMeta" needs to be exported by the entry point index.d.ts
// src/plugins/data/common/es_query/filters/phrase_filter.ts:33:3 - (ae-forgotten-export) The symbol "PhraseFilterMeta" needs to be exported by the entry point index.d.ts
// src/plugins/data/common/es_query/filters/phrases_filter.ts:31:3 - (ae-forgotten-export) The symbol "PhrasesFilterMeta" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:67:23 - (ae-forgotten-export) The symbol "FilterLabel" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:67:23 - (ae-forgotten-export) The symbol "FILTERS" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:67:23 - (ae-forgotten-export) The symbol "getDisplayValueFromFilter" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:67:23 - (ae-forgotten-export) The symbol "generateFilters" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:67:23 - (ae-forgotten-export) The symbol "changeTimeFilter" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:67:23 - (ae-forgotten-export) The symbol "convertRangeFilterToTimeRangeString" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:67:23 - (ae-forgotten-export) The symbol "extractTimeFilter" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:138:21 - (ae-forgotten-export) The symbol "buildEsQuery" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:138:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:138:21 - (ae-forgotten-export) The symbol "luceneStringToDsl" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:138:21 - (ae-forgotten-export) The symbol "decorateQuery" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:180:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:180:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:180:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:180:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:180:26 - (ae-forgotten-export) The symbol "DateNanosFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:180:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:180:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:180:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:180:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:180:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:180:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:180:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:180:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:180:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:180:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:378:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:378:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:378:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:378:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:380:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:381:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:390:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:391:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:392:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:396:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:397:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:404:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "FilterLabel" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "FILTERS" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "getDisplayValueFromFilter" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "generateFilters" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "changeTimeFilter" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "convertRangeFilterToTimeRangeString" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "extractTimeFilter" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:136:21 - (ae-forgotten-export) The symbol "buildEsQuery" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:136:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:136:21 - (ae-forgotten-export) The symbol "luceneStringToDsl" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:136:21 - (ae-forgotten-export) The symbol "decorateQuery" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "DateNanosFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:377:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:377:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:377:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:377:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:379:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:380:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:389:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:390:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:391:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:395:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:396:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:403:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/query/state_sync/connect_to_query_state.ts:33:33 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/query/state_sync/connect_to_query_state.ts:37:1 - (ae-forgotten-export) The symbol "QueryStateChange" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/types.ts:52:5 - (ae-forgotten-export) The symbol "createFiltersFromValueClickAction" needs to be exported by the entry point index.d.ts
diff --git a/src/plugins/data/public/query/filter_manager/filter_manager.test.ts b/src/plugins/data/public/query/filter_manager/filter_manager.test.ts
index 3c69a498e74cd3..878142906f54bd 100644
--- a/src/plugins/data/public/query/filter_manager/filter_manager.test.ts
+++ b/src/plugins/data/public/query/filter_manager/filter_manager.test.ts
@@ -24,14 +24,14 @@ import { Subscription } from 'rxjs';
import { FilterManager } from './filter_manager';
import { getFilter } from './test_helpers/get_stub_filter';
import { getFiltersArray } from './test_helpers/get_filters_array';
-import { Filter, FilterStateStore } from '../../../common';
+import { Filter, FilterStateStore, UI_SETTINGS } from '../../../common';
import { coreMock } from '../../../../../core/public/mocks';
const setupMock = coreMock.createSetup();
const uiSettingsMock = (pinnedByDefault: boolean) => (key: string) => {
switch (key) {
- case 'filters:pinnedByDefault':
+ case UI_SETTINGS.FILTERS_PINNED_BY_DEFAULT:
return pinnedByDefault;
default:
throw new Error(`Unexpected uiSettings key in FilterManager mock: ${key}`);
diff --git a/src/plugins/data/public/query/filter_manager/filter_manager.ts b/src/plugins/data/public/query/filter_manager/filter_manager.ts
index d58a0eb45c04f7..60a49a4bd50f4a 100644
--- a/src/plugins/data/public/query/filter_manager/filter_manager.ts
+++ b/src/plugins/data/public/query/filter_manager/filter_manager.ts
@@ -34,6 +34,7 @@ import {
isFilterPinned,
compareFilters,
COMPARE_ALL_OPTIONS,
+ UI_SETTINGS,
} from '../../../common';
export class FilterManager {
@@ -129,7 +130,7 @@ export class FilterManager {
public addFilters(
filters: Filter[] | Filter,
- pinFilterStatus: boolean = this.uiSettings.get('filters:pinnedByDefault')
+ pinFilterStatus: boolean = this.uiSettings.get(UI_SETTINGS.FILTERS_PINNED_BY_DEFAULT)
) {
if (!Array.isArray(filters)) {
filters = [filters];
@@ -157,7 +158,7 @@ export class FilterManager {
public setFilters(
newFilters: Filter[],
- pinFilterStatus: boolean = this.uiSettings.get('filters:pinnedByDefault')
+ pinFilterStatus: boolean = this.uiSettings.get(UI_SETTINGS.FILTERS_PINNED_BY_DEFAULT)
) {
const store = pinFilterStatus ? FilterStateStore.GLOBAL_STATE : FilterStateStore.APP_STATE;
diff --git a/src/plugins/data/public/query/lib/get_query_log.ts b/src/plugins/data/public/query/lib/get_query_log.ts
index a71eb7580cf07e..b7827d2c8de021 100644
--- a/src/plugins/data/public/query/lib/get_query_log.ts
+++ b/src/plugins/data/public/query/lib/get_query_log.ts
@@ -20,6 +20,7 @@
import { IUiSettingsClient } from 'src/core/public';
import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
import { PersistedLog } from '../persisted_log';
+import { UI_SETTINGS } from '../../../common';
export function getQueryLog(
uiSettings: IUiSettingsClient,
@@ -30,7 +31,7 @@ export function getQueryLog(
return new PersistedLog(
`typeahead:${appName}-${language}`,
{
- maxLength: uiSettings.get('history:limit'),
+ maxLength: uiSettings.get(UI_SETTINGS.HISTORY_LIMIT),
filterDuplicates: true,
},
storage
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 06e4c1c8be6d5d..4e394445b75ae3 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
@@ -20,7 +20,7 @@
import { Subscription } from 'rxjs';
import { FilterManager } from '../filter_manager';
import { getFilter } from '../filter_manager/test_helpers/get_stub_filter';
-import { Filter, FilterStateStore } from '../../../common';
+import { Filter, FilterStateStore, UI_SETTINGS } from '../../../common';
import { coreMock } from '../../../../../core/public/mocks';
import { BaseStateContainer, createStateContainer, Storage } from '../../../../kibana_utils/public';
import { QueryService, QueryStart } from '../query_service';
@@ -46,11 +46,11 @@ const startMock = coreMock.createStart();
setupMock.uiSettings.get.mockImplementation((key: string) => {
switch (key) {
- case 'filters:pinnedByDefault':
+ case UI_SETTINGS.FILTERS_PINNED_BY_DEFAULT:
return true;
case 'timepicker:timeDefaults':
return { from: 'now-15m', to: 'now' };
- case 'timepicker:refreshIntervalDefaults':
+ case UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS:
return { pause: false, value: 0 };
default:
throw new Error(`sync_query test: not mocked uiSetting: ${key}`);
diff --git a/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts b/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts
index 50dc35ea955ee3..77271535372579 100644
--- a/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts
+++ b/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts
@@ -21,7 +21,7 @@ import { Subscription } from 'rxjs';
import { createBrowserHistory, History } from 'history';
import { FilterManager } from '../filter_manager';
import { getFilter } from '../filter_manager/test_helpers/get_stub_filter';
-import { Filter, FilterStateStore } from '../../../common';
+import { Filter, FilterStateStore, UI_SETTINGS } from '../../../common';
import { coreMock } from '../../../../../core/public/mocks';
import {
createKbnUrlStateStorage,
@@ -39,11 +39,11 @@ const startMock = coreMock.createStart();
setupMock.uiSettings.get.mockImplementation((key: string) => {
switch (key) {
- case 'filters:pinnedByDefault':
+ case UI_SETTINGS.FILTERS_PINNED_BY_DEFAULT:
return true;
case 'timepicker:timeDefaults':
return { from: 'now-15m', to: 'now' };
- case 'timepicker:refreshIntervalDefaults':
+ case UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS:
return { pause: false, value: 0 };
default:
throw new Error(`sync_query test: not mocked uiSetting: ${key}`);
diff --git a/src/plugins/data/public/query/timefilter/timefilter_service.ts b/src/plugins/data/public/query/timefilter/timefilter_service.ts
index 413163ed059ad4..df2fbc8e5a8f3e 100644
--- a/src/plugins/data/public/query/timefilter/timefilter_service.ts
+++ b/src/plugins/data/public/query/timefilter/timefilter_service.ts
@@ -20,6 +20,7 @@
import { IUiSettingsClient } from 'src/core/public';
import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
import { TimeHistory, Timefilter, TimeHistoryContract, TimefilterContract } from './index';
+import { UI_SETTINGS } from '../../../common';
/**
* Filter Service
@@ -35,7 +36,7 @@ export class TimefilterService {
public setup({ uiSettings, storage }: TimeFilterServiceDependencies): TimefilterSetup {
const timefilterConfig = {
timeDefaults: uiSettings.get('timepicker:timeDefaults'),
- refreshIntervalDefaults: uiSettings.get('timepicker:refreshIntervalDefaults'),
+ refreshIntervalDefaults: uiSettings.get(UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS),
};
const history = new TimeHistory(storage);
const timefilter = new Timefilter(timefilterConfig, history);
diff --git a/src/plugins/data/public/search/aggs/buckets/date_histogram.ts b/src/plugins/data/public/search/aggs/buckets/date_histogram.ts
index d5c97d0c95c5cf..8a5596f669cb78 100644
--- a/src/plugins/data/public/search/aggs/buckets/date_histogram.ts
+++ b/src/plugins/data/public/search/aggs/buckets/date_histogram.ts
@@ -31,7 +31,7 @@ import { dateHistogramInterval, TimeRange } from '../../../../common';
import { writeParams } from '../agg_params';
import { isMetricAggType } from '../metrics/metric_agg_type';
-import { FIELD_FORMAT_IDS, KBN_FIELD_TYPES } from '../../../../common';
+import { FIELD_FORMAT_IDS, KBN_FIELD_TYPES, UI_SETTINGS } from '../../../../common';
import { TimefilterContract } from '../../../query';
import { QuerySetup } from '../../../query/query_service';
import { GetInternalStartServicesFn } from '../../../types';
@@ -125,8 +125,8 @@ export const getDateHistogramBucketAgg = ({
const { timefilter } = query.timefilter;
buckets = new TimeBuckets({
- 'histogram:maxBars': uiSettings.get('histogram:maxBars'),
- 'histogram:barTarget': uiSettings.get('histogram:barTarget'),
+ 'histogram:maxBars': uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS),
+ 'histogram:barTarget': uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET),
dateFormat: uiSettings.get('dateFormat'),
'dateFormat:scaled': uiSettings.get('dateFormat:scaled'),
});
diff --git a/src/plugins/data/public/search/aggs/buckets/filters.ts b/src/plugins/data/public/search/aggs/buckets/filters.ts
index 3f3f13bb955c18..4052c0b3901556 100644
--- a/src/plugins/data/public/search/aggs/buckets/filters.ts
+++ b/src/plugins/data/public/search/aggs/buckets/filters.ts
@@ -26,7 +26,7 @@ import { toAngularJSON } from '../utils';
import { BucketAggType } from './bucket_agg_type';
import { BUCKET_TYPES } from './bucket_agg_types';
import { Storage } from '../../../../../../plugins/kibana_utils/public';
-import { getEsQueryConfig, buildEsQuery, Query } from '../../../../common';
+import { getEsQueryConfig, buildEsQuery, Query, UI_SETTINGS } from '../../../../common';
import { getQueryLog } from '../../../query';
import { GetInternalStartServicesFn } from '../../../types';
import { BaseAggParams } from '../types';
@@ -69,7 +69,10 @@ export const getFiltersBucketAgg = ({
{
name: 'filters',
default: [
- { input: { query: '', language: uiSettings.get('search:queryLanguage') }, label: '' },
+ {
+ input: { query: '', language: uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE) },
+ label: '',
+ },
],
write(aggConfig, output) {
const inFilters: FilterValue[] = aggConfig.params.filters;
diff --git a/src/plugins/data/public/search/aggs/buckets/histogram.ts b/src/plugins/data/public/search/aggs/buckets/histogram.ts
index d04df4f8aac6ba..c1fad17f488db2 100644
--- a/src/plugins/data/public/search/aggs/buckets/histogram.ts
+++ b/src/plugins/data/public/search/aggs/buckets/histogram.ts
@@ -24,7 +24,7 @@ import { IUiSettingsClient } from 'src/core/public';
import { BucketAggType, IBucketAggConfig } from './bucket_agg_type';
import { createFilterHistogram } from './create_filter/histogram';
import { BUCKET_TYPES } from './bucket_agg_types';
-import { KBN_FIELD_TYPES } from '../../../../common';
+import { KBN_FIELD_TYPES, UI_SETTINGS } from '../../../../common';
import { GetInternalStartServicesFn } from '../../../types';
import { BaseAggParams } from '../types';
import { ExtendedBounds } from './lib/extended_bounds';
@@ -155,8 +155,8 @@ export const getHistogramBucketAgg = ({
const range = autoBounds.max - autoBounds.min;
const bars = range / interval;
- if (bars > uiSettings.get('histogram:maxBars')) {
- const minInterval = range / uiSettings.get('histogram:maxBars');
+ if (bars > uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS)) {
+ const minInterval = range / uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS);
// Round interval by order of magnitude to provide clean intervals
// Always round interval up so there will always be less buckets than histogram:maxBars
diff --git a/src/plugins/data/public/search/aggs/utils/calculate_auto_time_expression.ts b/src/plugins/data/public/search/aggs/utils/calculate_auto_time_expression.ts
index 9d976784329ccf..30fcdd9d83a389 100644
--- a/src/plugins/data/public/search/aggs/utils/calculate_auto_time_expression.ts
+++ b/src/plugins/data/public/search/aggs/utils/calculate_auto_time_expression.ts
@@ -19,7 +19,7 @@
import moment from 'moment';
import { IUiSettingsClient } from 'src/core/public';
import { TimeBuckets } from '../buckets/lib/time_buckets';
-import { toAbsoluteDates, TimeRange } from '../../../../common';
+import { toAbsoluteDates, TimeRange, UI_SETTINGS } from '../../../../common';
export function getCalculateAutoTimeExpression(uiSettings: IUiSettingsClient) {
return function calculateAutoTimeExpression(range: TimeRange) {
@@ -29,8 +29,8 @@ export function getCalculateAutoTimeExpression(uiSettings: IUiSettingsClient) {
}
const buckets = new TimeBuckets({
- 'histogram:maxBars': uiSettings.get('histogram:maxBars'),
- 'histogram:barTarget': uiSettings.get('histogram:barTarget'),
+ 'histogram:maxBars': uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS),
+ 'histogram:barTarget': uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET),
dateFormat: uiSettings.get('dateFormat'),
'dateFormat:scaled': uiSettings.get('dateFormat:scaled'),
});
diff --git a/src/plugins/data/public/search/es_search/get_es_preference.test.ts b/src/plugins/data/public/search/es_search/get_es_preference.test.ts
index 8b8156b4519d64..05a74b3e6205ac 100644
--- a/src/plugins/data/public/search/es_search/get_es_preference.test.ts
+++ b/src/plugins/data/public/search/es_search/get_es_preference.test.ts
@@ -20,6 +20,7 @@
import { getEsPreference } from './get_es_preference';
import { CoreStart } from '../../../../../core/public';
import { coreMock } from '../../../../../core/public/mocks';
+import { UI_SETTINGS } from '../../../common';
describe('Get ES preference', () => {
let mockCoreStart: MockedKeys;
@@ -30,8 +31,8 @@ describe('Get ES preference', () => {
test('returns the session ID if set to sessionId', () => {
mockCoreStart.uiSettings.get.mockImplementation((key: string) => {
- if (key === 'courier:setRequestPreference') return 'sessionId';
- if (key === 'courier:customRequestPreference') return 'foobar';
+ if (key === UI_SETTINGS.COURIER_SET_REQUEST_PREFERENCE) return 'sessionId';
+ if (key === UI_SETTINGS.COURIER_CUSTOM_REQUEST_PREFERENCE) return 'foobar';
});
const preference = getEsPreference(mockCoreStart.uiSettings, 'my_session_id');
expect(preference).toBe('my_session_id');
@@ -39,8 +40,8 @@ describe('Get ES preference', () => {
test('returns the custom preference if set to custom', () => {
mockCoreStart.uiSettings.get.mockImplementation((key: string) => {
- if (key === 'courier:setRequestPreference') return 'custom';
- if (key === 'courier:customRequestPreference') return 'foobar';
+ if (key === UI_SETTINGS.COURIER_SET_REQUEST_PREFERENCE) return 'custom';
+ if (key === UI_SETTINGS.COURIER_CUSTOM_REQUEST_PREFERENCE) return 'foobar';
});
const preference = getEsPreference(mockCoreStart.uiSettings);
expect(preference).toBe('foobar');
@@ -48,8 +49,8 @@ describe('Get ES preference', () => {
test('returns undefined if set to none', () => {
mockCoreStart.uiSettings.get.mockImplementation((key: string) => {
- if (key === 'courier:setRequestPreference') return 'none';
- if (key === 'courier:customRequestPreference') return 'foobar';
+ if (key === UI_SETTINGS.COURIER_SET_REQUEST_PREFERENCE) return 'none';
+ if (key === UI_SETTINGS.COURIER_CUSTOM_REQUEST_PREFERENCE) return 'foobar';
});
const preference = getEsPreference(mockCoreStart.uiSettings);
expect(preference).toBe(undefined);
diff --git a/src/plugins/data/public/search/es_search/get_es_preference.ts b/src/plugins/data/public/search/es_search/get_es_preference.ts
index 3f1c2b9b3b7369..5e40712067bb02 100644
--- a/src/plugins/data/public/search/es_search/get_es_preference.ts
+++ b/src/plugins/data/public/search/es_search/get_es_preference.ts
@@ -18,12 +18,13 @@
*/
import { IUiSettingsClient } from '../../../../../core/public';
+import { UI_SETTINGS } from '../../../common';
const defaultSessionId = `${Date.now()}`;
export function getEsPreference(uiSettings: IUiSettingsClient, sessionId = defaultSessionId) {
- const setPreference = uiSettings.get('courier:setRequestPreference');
+ const setPreference = uiSettings.get(UI_SETTINGS.COURIER_SET_REQUEST_PREFERENCE);
if (setPreference === 'sessionId') return `${sessionId}`;
- const customPreference = uiSettings.get('courier:customRequestPreference');
+ const customPreference = uiSettings.get(UI_SETTINGS.COURIER_CUSTOM_REQUEST_PREFERENCE);
return setPreference === 'custom' ? customPreference : undefined;
}
diff --git a/src/plugins/data/public/search/fetch/get_search_params.test.ts b/src/plugins/data/public/search/fetch/get_search_params.test.ts
index 4809d76a46f59f..f9b62fdd4fc613 100644
--- a/src/plugins/data/public/search/fetch/get_search_params.test.ts
+++ b/src/plugins/data/public/search/fetch/get_search_params.test.ts
@@ -19,6 +19,7 @@
import { getSearchParams } from './get_search_params';
import { IUiSettingsClient } from 'kibana/public';
+import { UI_SETTINGS } from '../../../common';
function getConfigStub(config: any = {}) {
return {
@@ -40,21 +41,21 @@ describe('getSearchParams', () => {
});
test('includes ignore_throttled according to search:includeFrozen', () => {
- let config = getConfigStub({ 'search:includeFrozen': true });
+ let config = getConfigStub({ [UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: true });
let searchParams = getSearchParams(config);
expect(searchParams.ignore_throttled).toBe(false);
- config = getConfigStub({ 'search:includeFrozen': false });
+ config = getConfigStub({ [UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: false });
searchParams = getSearchParams(config);
expect(searchParams.ignore_throttled).toBe(true);
});
test('includes max_concurrent_shard_requests according to courier:maxConcurrentShardRequests', () => {
- let config = getConfigStub({ 'courier:maxConcurrentShardRequests': 0 });
+ let config = getConfigStub({ [UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS]: 0 });
let searchParams = getSearchParams(config);
expect(searchParams.max_concurrent_shard_requests).toBe(undefined);
- config = getConfigStub({ 'courier:maxConcurrentShardRequests': 5 });
+ config = getConfigStub({ [UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS]: 5 });
searchParams = getSearchParams(config);
expect(searchParams.max_concurrent_shard_requests).toBe(5);
});
diff --git a/src/plugins/data/public/search/fetch/get_search_params.ts b/src/plugins/data/public/search/fetch/get_search_params.ts
index f0c43bd2e74cd1..60bdc9ed6473ad 100644
--- a/src/plugins/data/public/search/fetch/get_search_params.ts
+++ b/src/plugins/data/public/search/fetch/get_search_params.ts
@@ -18,6 +18,7 @@
*/
import { IUiSettingsClient } from 'kibana/public';
+import { UI_SETTINGS } from '../../../common';
const sessionId = Date.now();
@@ -33,19 +34,19 @@ export function getSearchParams(config: IUiSettingsClient, esShardTimeout: numbe
}
export function getIgnoreThrottled(config: IUiSettingsClient) {
- return !config.get('search:includeFrozen');
+ return !config.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN);
}
export function getMaxConcurrentShardRequests(config: IUiSettingsClient) {
- const maxConcurrentShardRequests = config.get('courier:maxConcurrentShardRequests');
+ const maxConcurrentShardRequests = config.get(UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS);
return maxConcurrentShardRequests > 0 ? maxConcurrentShardRequests : undefined;
}
export function getPreference(config: IUiSettingsClient) {
- const setRequestPreference = config.get('courier:setRequestPreference');
+ const setRequestPreference = config.get(UI_SETTINGS.COURIER_SET_REQUEST_PREFERENCE);
if (setRequestPreference === 'sessionId') return sessionId;
return setRequestPreference === 'custom'
- ? config.get('courier:customRequestPreference')
+ ? config.get(UI_SETTINGS.COURIER_CUSTOM_REQUEST_PREFERENCE)
: undefined;
}
diff --git a/src/plugins/data/public/search/legacy/default_search_strategy.test.ts b/src/plugins/data/public/search/legacy/default_search_strategy.test.ts
index c619c9b17d9a84..436b522744622d 100644
--- a/src/plugins/data/public/search/legacy/default_search_strategy.test.ts
+++ b/src/plugins/data/public/search/legacy/default_search_strategy.test.ts
@@ -21,6 +21,7 @@ import { IUiSettingsClient } from 'kibana/public';
import { defaultSearchStrategy } from './default_search_strategy';
import { searchStartMock } from '../mocks';
import { SearchStrategySearchParams } from './types';
+import { UI_SETTINGS } from '../../../common';
const { search } = defaultSearchStrategy;
@@ -69,30 +70,30 @@ describe('defaultSearchStrategy', function () {
});
test('does not send max_concurrent_shard_requests by default', async () => {
- const config = getConfigStub({ 'courier:batchSearches': true });
+ const config = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true });
await search({ ...searchArgs, config });
expect(es.msearch.mock.calls[0][0].max_concurrent_shard_requests).toBe(undefined);
});
test('allows configuration of max_concurrent_shard_requests', async () => {
const config = getConfigStub({
- 'courier:batchSearches': true,
- 'courier:maxConcurrentShardRequests': 42,
+ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true,
+ [UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS]: 42,
});
await search({ ...searchArgs, config });
expect(es.msearch.mock.calls[0][0].max_concurrent_shard_requests).toBe(42);
});
test('should set rest_total_hits_as_int to true on a request', async () => {
- const config = getConfigStub({ 'courier:batchSearches': true });
+ const config = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true });
await search({ ...searchArgs, config });
expect(es.msearch.mock.calls[0][0]).toHaveProperty('rest_total_hits_as_int', true);
});
test('should set ignore_throttled=false when including frozen indices', async () => {
const config = getConfigStub({
- 'courier:batchSearches': true,
- 'search:includeFrozen': true,
+ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true,
+ [UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: true,
});
await search({ ...searchArgs, config });
expect(es.msearch.mock.calls[0][0]).toHaveProperty('ignore_throttled', false);
@@ -100,7 +101,7 @@ describe('defaultSearchStrategy', function () {
test('should properly call abort with msearch', () => {
const config = getConfigStub({
- 'courier:batchSearches': true,
+ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true,
});
search({ ...searchArgs, config }).abort();
expect(msearchMockResponse.abort).toHaveBeenCalled();
diff --git a/src/plugins/data/public/search/legacy/fetch_soon.test.ts b/src/plugins/data/public/search/legacy/fetch_soon.test.ts
index e99e13ba33d1a6..61d3568350b6b1 100644
--- a/src/plugins/data/public/search/legacy/fetch_soon.test.ts
+++ b/src/plugins/data/public/search/legacy/fetch_soon.test.ts
@@ -22,6 +22,7 @@ import { callClient } from './call_client';
import { IUiSettingsClient } from 'kibana/public';
import { FetchHandlers, FetchOptions } from '../fetch/types';
import { SearchRequest, SearchResponse } from '../index';
+import { UI_SETTINGS } from '../../../common';
function getConfigStub(config: any = {}) {
return {
@@ -60,7 +61,7 @@ describe('fetchSoon', () => {
test('should execute asap if config is set to not batch searches', () => {
const config = getConfigStub({
- 'courier:batchSearches': false,
+ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: false,
});
const request = {};
const options = {};
@@ -72,7 +73,7 @@ describe('fetchSoon', () => {
test('should delay by 50ms if config is set to batch searches', () => {
const config = getConfigStub({
- 'courier:batchSearches': true,
+ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true,
});
const request = {};
const options = {};
@@ -88,7 +89,7 @@ describe('fetchSoon', () => {
test('should send a batch of requests to callClient', () => {
const config = getConfigStub({
- 'courier:batchSearches': true,
+ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true,
});
const requests = [{ foo: 1 }, { foo: 2 }];
const options = [{ bar: 1 }, { bar: 2 }];
@@ -105,7 +106,7 @@ describe('fetchSoon', () => {
test('should return the response to the corresponding call for multiple batched requests', async () => {
const config = getConfigStub({
- 'courier:batchSearches': true,
+ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true,
});
const requests = [{ _mockResponseId: 'foo' }, { _mockResponseId: 'bar' }];
@@ -120,7 +121,7 @@ describe('fetchSoon', () => {
test('should wait for the previous batch to start before starting a new batch', () => {
const config = getConfigStub({
- 'courier:batchSearches': true,
+ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true,
});
const firstBatch = [{ foo: 1 }, { foo: 2 }];
const secondBatch = [{ bar: 1 }, { bar: 2 }];
diff --git a/src/plugins/data/public/search/legacy/fetch_soon.ts b/src/plugins/data/public/search/legacy/fetch_soon.ts
index 304c1c4d63f5bb..fed2c52bc491f8 100644
--- a/src/plugins/data/public/search/legacy/fetch_soon.ts
+++ b/src/plugins/data/public/search/legacy/fetch_soon.ts
@@ -20,6 +20,7 @@
import { callClient } from './call_client';
import { FetchHandlers, FetchOptions } from '../fetch/types';
import { SearchRequest, SearchResponse } from '../index';
+import { UI_SETTINGS } from '../../../common';
/**
* This function introduces a slight delay in the request process to allow multiple requests to queue
@@ -30,7 +31,7 @@ export async function fetchSoon(
options: FetchOptions,
fetchHandlers: FetchHandlers
) {
- const msToDelay = fetchHandlers.config.get('courier:batchSearches') ? 50 : 0;
+ const msToDelay = fetchHandlers.config.get(UI_SETTINGS.COURIER_BATCH_SEARCHES) ? 50 : 0;
return delayedFetch(request, options, fetchHandlers, msToDelay);
}
diff --git a/src/plugins/data/public/search/legacy/get_msearch_params.test.ts b/src/plugins/data/public/search/legacy/get_msearch_params.test.ts
index ce98f6ab2a7bbc..dc61e194066314 100644
--- a/src/plugins/data/public/search/legacy/get_msearch_params.test.ts
+++ b/src/plugins/data/public/search/legacy/get_msearch_params.test.ts
@@ -19,6 +19,7 @@
import { getMSearchParams } from './get_msearch_params';
import { IUiSettingsClient } from '../../../../../core/public';
+import { UI_SETTINGS } from '../../../common';
function getConfigStub(config: any = {}) {
return {
@@ -34,29 +35,29 @@ describe('getMSearchParams', () => {
});
test('includes ignore_throttled according to search:includeFrozen', () => {
- let config = getConfigStub({ 'search:includeFrozen': true });
+ let config = getConfigStub({ [UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: true });
let msearchParams = getMSearchParams(config);
expect(msearchParams.ignore_throttled).toBe(false);
- config = getConfigStub({ 'search:includeFrozen': false });
+ config = getConfigStub({ [UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: false });
msearchParams = getMSearchParams(config);
expect(msearchParams.ignore_throttled).toBe(true);
});
test('includes max_concurrent_shard_requests according to courier:maxConcurrentShardRequests if greater than 0', () => {
- let config = getConfigStub({ 'courier:maxConcurrentShardRequests': 0 });
+ let config = getConfigStub({ [UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS]: 0 });
let msearchParams = getMSearchParams(config);
expect(msearchParams.max_concurrent_shard_requests).toBe(undefined);
- config = getConfigStub({ 'courier:maxConcurrentShardRequests': 5 });
+ config = getConfigStub({ [UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS]: 5 });
msearchParams = getMSearchParams(config);
expect(msearchParams.max_concurrent_shard_requests).toBe(5);
});
test('does not include other search params that are included in the msearch header or body', () => {
const config = getConfigStub({
- 'search:includeFrozen': false,
- 'courier:maxConcurrentShardRequests': 5,
+ [UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: false,
+ [UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS]: 5,
});
const msearchParams = getMSearchParams(config);
expect(msearchParams.hasOwnProperty('ignore_unavailable')).toBe(false);
diff --git a/src/plugins/data/public/search/search_source/search_source.ts b/src/plugins/data/public/search/search_source/search_source.ts
index 38f4ce73713c88..b926739112e0e8 100644
--- a/src/plugins/data/public/search/search_source/search_source.ts
+++ b/src/plugins/data/public/search/search_source/search_source.ts
@@ -75,12 +75,11 @@ import { CoreStart } from 'kibana/public';
import { normalizeSortRequest } from './normalize_sort_request';
import { filterDocvalueFields } from './filter_docvalue_fields';
import { fieldWildcardFilter } from '../../../../kibana_utils/public';
-import { META_FIELDS_SETTING, DOC_HIGHLIGHT_SETTING } from '../../../common';
import { IIndexPattern, ISearchGeneric, SearchRequest } from '../..';
import { SearchSourceOptions, SearchSourceFields } from './types';
import { FetchOptions, RequestFailure, getSearchParams, handleResponse } from '../fetch';
-import { getEsQueryConfig, buildEsQuery, Filter } from '../../../common';
+import { getEsQueryConfig, buildEsQuery, Filter, UI_SETTINGS } from '../../../common';
import { getHighlightRequest } from '../../../common/field_formats';
import { fetchSoon } from '../legacy';
import { extractReferences } from './extract_references';
@@ -251,7 +250,7 @@ export class SearchSource {
this.history = [searchRequest];
let response;
- if (uiSettings.get('courier:batchSearches')) {
+ if (uiSettings.get(UI_SETTINGS.COURIER_BATCH_SEARCHES)) {
response = await this.legacyFetch(searchRequest, options);
} else {
response = this.fetch$(searchRequest, options.abortSignal).toPromise();
@@ -365,7 +364,7 @@ export class SearchSource {
const sort = normalizeSortRequest(
val,
this.getField('index'),
- uiSettings.get('sort:options')
+ uiSettings.get(UI_SETTINGS.SORT_OPTIONS)
);
return addToBody(key, sort);
default:
@@ -425,7 +424,7 @@ export class SearchSource {
// exclude source fields for this index pattern specified by the user
const filter = fieldWildcardFilter(
body._source.excludes,
- uiSettings.get(META_FIELDS_SETTING)
+ uiSettings.get(UI_SETTINGS.META_FIELDS)
);
body.docvalue_fields = body.docvalue_fields.filter((docvalueField: any) =>
filter(docvalueField.field)
@@ -448,7 +447,7 @@ export class SearchSource {
body.query = buildEsQuery(index, query, filters, esQueryConfigs);
if (highlightAll && body.query) {
- body.highlight = getHighlightRequest(body.query, uiSettings.get(DOC_HIGHLIGHT_SETTING));
+ body.highlight = getHighlightRequest(body.query, uiSettings.get(UI_SETTINGS.DOC_HIGHLIGHT));
delete searchRequest.highlightAll;
}
diff --git a/src/plugins/data/public/ui/filter_bar/filter_bar.tsx b/src/plugins/data/public/ui/filter_bar/filter_bar.tsx
index a54a25acc59134..43dba150bf8d44 100644
--- a/src/plugins/data/public/ui/filter_bar/filter_bar.tsx
+++ b/src/plugins/data/public/ui/filter_bar/filter_bar.tsx
@@ -36,6 +36,7 @@ import {
toggleFilterDisabled,
toggleFilterNegated,
unpinFilter,
+ UI_SETTINGS,
} from '../../../common';
interface Props {
@@ -76,7 +77,7 @@ function FilterBarUI(props: Props) {
}
function renderAddFilter() {
- const isPinned = uiSettings!.get('filters:pinnedByDefault');
+ const isPinned = uiSettings!.get(UI_SETTINGS.FILTERS_PINNED_BY_DEFAULT);
const [indexPattern] = props.indexPatterns;
const index = indexPattern && indexPattern.id;
const newFilter = buildEmptyFilter(isPinned, index);
diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx b/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx
index 546365b89d9be1..94138f60b52b1f 100644
--- a/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx
+++ b/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx
@@ -22,6 +22,7 @@ import { debounce } from 'lodash';
import { withKibana, KibanaReactContextValue } from '../../../../../kibana_react/public';
import { IDataPluginServices, IIndexPattern, IFieldType } from '../../..';
+import { UI_SETTINGS } from '../../../../common';
export interface PhraseSuggestorProps {
kibana: KibanaReactContextValue;
@@ -54,7 +55,9 @@ export class PhraseSuggestorUI extends React.Com
}
protected isSuggestingValues() {
- const shouldSuggestValues = this.services.uiSettings.get('filterEditor:suggestValues');
+ const shouldSuggestValues = this.services.uiSettings.get(
+ UI_SETTINGS.FILTERS_EDITOR_SUGGEST_VALUES
+ );
const { field } = this.props;
return shouldSuggestValues && field && field.aggregatable && field.type === 'string';
}
diff --git a/src/plugins/data/public/ui/filter_bar/filter_item.tsx b/src/plugins/data/public/ui/filter_bar/filter_item.tsx
index c44e1faeb8e7f4..053fca7d5773bc 100644
--- a/src/plugins/data/public/ui/filter_bar/filter_item.tsx
+++ b/src/plugins/data/public/ui/filter_bar/filter_item.tsx
@@ -74,7 +74,8 @@ export function FilterItem(props: Props) {
setIndexPatternExists(false);
});
} else {
- setIndexPatternExists(false);
+ // Allow filters without an index pattern and don't validate them.
+ setIndexPatternExists(true);
}
}, [props.filter.meta.index]);
@@ -244,6 +245,9 @@ export function FilterItem(props: Props) {
* This function makes this behavior explicit, but it needs to be revised.
*/
function isFilterApplicable() {
+ // Any filter is applicable if no index patterns were provided to FilterBar.
+ if (!props.indexPatterns.length) return true;
+
const ip = getIndexPatternFromFilter(filter, indexPatterns);
if (ip) return true;
diff --git a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.test.tsx b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.test.tsx
index f579adbc0c7e27..5f2d4c00cd6b6f 100644
--- a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.test.tsx
+++ b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.test.tsx
@@ -28,6 +28,7 @@ import { dataPluginMock } from '../../mocks';
import { KibanaContextProvider } from 'src/plugins/kibana_react/public';
import { I18nProvider } from '@kbn/i18n/react';
import { stubIndexPatternWithFields } from '../../stubs';
+import { UI_SETTINGS } from '../../../common';
const startMock = coreMock.createStart();
const mockTimeHistory = {
@@ -38,7 +39,7 @@ const mockTimeHistory = {
startMock.uiSettings.get.mockImplementation((key: string) => {
switch (key) {
- case 'timepicker:quickRanges':
+ case UI_SETTINGS.TIMEPICKER_QUICK_RANGES:
return [
{
from: 'now/d',
@@ -48,7 +49,7 @@ startMock.uiSettings.get.mockImplementation((key: string) => {
];
case 'dateFormat':
return 'MMM D, YYYY @ HH:mm:ss.SSS';
- case 'history:limit':
+ case UI_SETTINGS.HISTORY_LIMIT:
return 10;
case 'timepicker:timeDefaults':
return {
diff --git a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx
index 433cb652ee5ce6..f65bf97e391e22 100644
--- a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx
+++ b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx
@@ -38,7 +38,7 @@ import { Toast } from 'src/core/public';
import { IDataPluginServices, IIndexPattern, TimeRange, TimeHistoryContract, Query } from '../..';
import { useKibana, toMountPoint } from '../../../../kibana_react/public';
import { QueryStringInput } from './query_string_input';
-import { doesKueryExpressionHaveLuceneSyntaxError } from '../../../common';
+import { doesKueryExpressionHaveLuceneSyntaxError, UI_SETTINGS } from '../../../common';
import { PersistedLog, getQueryLog } from '../../query';
interface Props {
@@ -255,7 +255,7 @@ export function QueryBarTopRow(props: Props) {
}
const commonlyUsedRanges = uiSettings!
- .get('timepicker:quickRanges')
+ .get(UI_SETTINGS.TIMEPICKER_QUICK_RANGES)
.map(({ from, to, display }: { from: string; to: string; display: string }) => {
return {
start: from,
diff --git a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx
index 7723254f3aa51e..18ed632e0a8ecc 100644
--- a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx
+++ b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx
@@ -27,7 +27,7 @@ import { useFilterManager } from './lib/use_filter_manager';
import { useTimefilter } from './lib/use_timefilter';
import { useSavedQuery } from './lib/use_saved_query';
import { DataPublicPluginStart } from '../../types';
-import { Filter, Query, TimeRange } from '../../../common';
+import { Filter, Query, TimeRange, UI_SETTINGS } from '../../../common';
interface StatefulSearchBarDeps {
core: CoreStart;
@@ -125,7 +125,8 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps)
const defaultQuery = {
query: '',
language:
- storage.get('kibana.userQueryLanguage') || core.uiSettings.get('search:queryLanguage'),
+ storage.get('kibana.userQueryLanguage') ||
+ core.uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE),
};
const [query, setQuery] = useState(props.query || defaultQuery);
diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts
index 72a29e377ac54e..831d23864d2287 100644
--- a/src/plugins/data/server/index.ts
+++ b/src/plugins/data/server/index.ts
@@ -146,6 +146,7 @@ export {
ES_FIELD_TYPES,
KBN_FIELD_TYPES,
IndexPatternAttributes,
+ UI_SETTINGS,
} from '../common';
/**
diff --git a/src/plugins/data/server/kql_telemetry/usage_collector/fetch.test.ts b/src/plugins/data/server/kql_telemetry/usage_collector/fetch.test.ts
index 446320b09757a5..81e352fea51b26 100644
--- a/src/plugins/data/server/kql_telemetry/usage_collector/fetch.test.ts
+++ b/src/plugins/data/server/kql_telemetry/usage_collector/fetch.test.ts
@@ -22,6 +22,9 @@ import { APICaller } from 'kibana/server';
jest.mock('../../../common', () => ({
DEFAULT_QUERY_LANGUAGE: 'lucene',
+ UI_SETTINGS: {
+ SEARCH_QUERY_LANGUAGE: 'search:queryLanguage',
+ },
}));
let fetch: ReturnType;
diff --git a/src/plugins/data/server/kql_telemetry/usage_collector/fetch.ts b/src/plugins/data/server/kql_telemetry/usage_collector/fetch.ts
index 9f3437161541f1..157716b38f5234 100644
--- a/src/plugins/data/server/kql_telemetry/usage_collector/fetch.ts
+++ b/src/plugins/data/server/kql_telemetry/usage_collector/fetch.ts
@@ -19,7 +19,7 @@
import { get } from 'lodash';
import { APICaller } from 'kibana/server';
-import { DEFAULT_QUERY_LANGUAGE } from '../../../common';
+import { DEFAULT_QUERY_LANGUAGE, UI_SETTINGS } from '../../../common';
const defaultSearchQueryLanguageSetting = DEFAULT_QUERY_LANGUAGE;
@@ -40,7 +40,7 @@ export function fetchProvider(index: string) {
const queryLanguageConfigValue = get(
config,
- 'hits.hits[0]._source.config.search:queryLanguage'
+ `hits.hits[0]._source.config.${UI_SETTINGS.SEARCH_QUERY_LANGUAGE}`
);
// search:queryLanguage can potentially be in four states in the .kibana index:
diff --git a/src/plugins/data/server/plugin.ts b/src/plugins/data/server/plugin.ts
index df7a7b9cf4d011..8c9d0df2ed8947 100644
--- a/src/plugins/data/server/plugin.ts
+++ b/src/plugins/data/server/plugin.ts
@@ -28,7 +28,7 @@ import { KqlTelemetryService } from './kql_telemetry';
import { UsageCollectionSetup } from '../../usage_collection/server';
import { AutocompleteService } from './autocomplete';
import { FieldFormatsService, FieldFormatsSetup, FieldFormatsStart } from './field_formats';
-import { uiSettings } from './ui_settings';
+import { getUiSettings } from './ui_settings';
export interface DataPluginSetup {
search: ISearchSetup;
@@ -65,7 +65,8 @@ export class DataServerPlugin implements Plugin = (context: ISearchContext, caller: APICaller_2, search: ISearchGeneric) => ISearchStrategy;
+// Warning: (ae-missing-release-tag) "UI_SETTINGS" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export const UI_SETTINGS: {
+ META_FIELDS: string;
+ DOC_HIGHLIGHT: string;
+ QUERY_STRING_OPTIONS: string;
+ QUERY_ALLOW_LEADING_WILDCARDS: string;
+ SEARCH_QUERY_LANGUAGE: string;
+ SORT_OPTIONS: string;
+ COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX: string;
+ COURIER_SET_REQUEST_PREFERENCE: string;
+ COURIER_CUSTOM_REQUEST_PREFERENCE: string;
+ COURIER_MAX_CONCURRENT_SHARD_REQUESTS: string;
+ COURIER_BATCH_SEARCHES: string;
+ SEARCH_INCLUDE_FROZEN: string;
+ HISTOGRAM_BAR_TARGET: string;
+ HISTOGRAM_MAX_BARS: string;
+ HISTORY_LIMIT: string;
+ SHORT_DOTS_ENABLE: string;
+ FORMAT_DEFAULT_TYPE_MAP: string;
+ FORMAT_NUMBER_DEFAULT_PATTERN: string;
+ FORMAT_PERCENT_DEFAULT_PATTERN: string;
+ FORMAT_BYTES_DEFAULT_PATTERN: string;
+ FORMAT_CURRENCY_DEFAULT_PATTERN: string;
+ FORMAT_NUMBER_DEFAULT_LOCALE: string;
+ TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: string;
+ TIMEPICKER_QUICK_RANGES: string;
+ INDEXPATTERN_PLACEHOLDER: string;
+ FILTERS_PINNED_BY_DEFAULT: string;
+ FILTERS_EDITOR_SUGGEST_VALUES: string;
+};
+
// Warnings were encountered during analysis:
//
@@ -745,12 +778,12 @@ export type TSearchStrategyProvider = (context: ISearc
// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:131:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:131:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:183:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:184:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:185:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:186:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:187:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:190:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:184:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:185:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:186:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:187:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:188:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:191:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/plugin.ts:66:14 - (ae-forgotten-export) The symbol "ISearchSetup" needs to be exported by the entry point index.d.ts
// (No @packageDocumentation comment for this package)
diff --git a/src/plugins/data/server/ui_settings.ts b/src/plugins/data/server/ui_settings.ts
index 5af62be2952011..de978c7968aee5 100644
--- a/src/plugins/data/server/ui_settings.ts
+++ b/src/plugins/data/server/ui_settings.ts
@@ -19,33 +19,652 @@
import { i18n } from '@kbn/i18n';
import { schema } from '@kbn/config-schema';
-
import { UiSettingsParams } from 'kibana/server';
-import { META_FIELDS_SETTING, DOC_HIGHLIGHT_SETTING } from '../common';
+// @ts-ignore untyped module
+import numeralLanguages from '@elastic/numeral/languages';
+import { DEFAULT_QUERY_LANGUAGE, UI_SETTINGS } from '../common';
+
+const luceneQueryLanguageLabel = i18n.translate('data.advancedSettings.searchQueryLanguageLucene', {
+ defaultMessage: 'Lucene',
+});
+
+const queryLanguageSettingName = i18n.translate('data.advancedSettings.searchQueryLanguageTitle', {
+ defaultMessage: 'Query language',
+});
-export const uiSettings: Record = {
- [META_FIELDS_SETTING]: {
- name: i18n.translate('data.advancedSettings.metaFieldsTitle', {
- defaultMessage: 'Meta fields',
- }),
- value: ['_source', '_id', '_type', '_index', '_score'],
- description: i18n.translate('data.advancedSettings.metaFieldsText', {
- defaultMessage:
- 'Fields that exist outside of _source to merge into our document when displaying it',
- }),
- schema: schema.arrayOf(schema.string()),
- },
- [DOC_HIGHLIGHT_SETTING]: {
- name: i18n.translate('data.advancedSettings.docTableHighlightTitle', {
- defaultMessage: 'Highlight results',
- }),
- value: true,
- description: i18n.translate('data.advancedSettings.docTableHighlightText', {
- defaultMessage:
- 'Highlight results in Discover and Saved Searches Dashboard. ' +
- 'Highlighting makes requests slow when working on big documents.',
- }),
- category: ['discover'],
- schema: schema.boolean(),
- },
+const requestPreferenceOptionLabels = {
+ sessionId: i18n.translate('data.advancedSettings.courier.requestPreferenceSessionId', {
+ defaultMessage: 'Session ID',
+ }),
+ custom: i18n.translate('data.advancedSettings.courier.requestPreferenceCustom', {
+ defaultMessage: 'Custom',
+ }),
+ none: i18n.translate('data.advancedSettings.courier.requestPreferenceNone', {
+ defaultMessage: 'None',
+ }),
};
+
+// We add the `en` key manually here, since that's not a real numeral locale, but the
+// default fallback in case the locale is not found.
+const numeralLanguageIds = [
+ 'en',
+ ...numeralLanguages.map((numeralLanguage: any) => {
+ return numeralLanguage.id;
+ }),
+];
+
+export function getUiSettings(): Record> {
+ return {
+ [UI_SETTINGS.META_FIELDS]: {
+ name: i18n.translate('data.advancedSettings.metaFieldsTitle', {
+ defaultMessage: 'Meta fields',
+ }),
+ value: ['_source', '_id', '_type', '_index', '_score'],
+ description: i18n.translate('data.advancedSettings.metaFieldsText', {
+ defaultMessage:
+ 'Fields that exist outside of _source to merge into our document when displaying it',
+ }),
+ schema: schema.arrayOf(schema.string()),
+ },
+ [UI_SETTINGS.DOC_HIGHLIGHT]: {
+ name: i18n.translate('data.advancedSettings.docTableHighlightTitle', {
+ defaultMessage: 'Highlight results',
+ }),
+ value: true,
+ description: i18n.translate('data.advancedSettings.docTableHighlightText', {
+ defaultMessage:
+ 'Highlight results in Discover and Saved Searches Dashboard. ' +
+ 'Highlighting makes requests slow when working on big documents.',
+ }),
+ category: ['discover'],
+ schema: schema.boolean(),
+ },
+ [UI_SETTINGS.QUERY_STRING_OPTIONS]: {
+ name: i18n.translate('data.advancedSettings.query.queryStringOptionsTitle', {
+ defaultMessage: 'Query string options',
+ }),
+ value: '{ "analyze_wildcard": true }',
+ description: i18n.translate('data.advancedSettings.query.queryStringOptionsText', {
+ defaultMessage:
+ '{optionsLink} for the lucene query string parser. Is only used when "{queryLanguage}" is set ' +
+ 'to {luceneLanguage}.',
+ description:
+ 'Part of composite text: data.advancedSettings.query.queryStringOptions.optionsLinkText + ' +
+ 'data.advancedSettings.query.queryStringOptionsText',
+ values: {
+ optionsLink:
+ '' +
+ i18n.translate('data.advancedSettings.query.queryStringOptions.optionsLinkText', {
+ defaultMessage: 'Options',
+ }) +
+ '',
+ luceneLanguage: luceneQueryLanguageLabel,
+ queryLanguage: queryLanguageSettingName,
+ },
+ }),
+ type: 'json',
+ schema: schema.object({
+ analyze_wildcard: schema.boolean(),
+ }),
+ },
+ [UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS]: {
+ name: i18n.translate('data.advancedSettings.query.allowWildcardsTitle', {
+ defaultMessage: 'Allow leading wildcards in query',
+ }),
+ value: true,
+ description: i18n.translate('data.advancedSettings.query.allowWildcardsText', {
+ defaultMessage:
+ 'When set, * is allowed as the first character in a query clause. ' +
+ 'Currently only applies when experimental query features are enabled in the query bar. ' +
+ 'To disallow leading wildcards in basic lucene queries, use {queryStringOptionsPattern}.',
+ values: {
+ queryStringOptionsPattern: UI_SETTINGS.QUERY_STRING_OPTIONS,
+ },
+ }),
+ schema: schema.boolean(),
+ },
+ [UI_SETTINGS.SEARCH_QUERY_LANGUAGE]: {
+ name: queryLanguageSettingName,
+ value: DEFAULT_QUERY_LANGUAGE,
+ description: i18n.translate('data.advancedSettings.searchQueryLanguageText', {
+ defaultMessage:
+ 'Query language used by the query bar. KQL is a new language built specifically for Kibana.',
+ }),
+ type: 'select',
+ options: ['lucene', 'kuery'],
+ optionLabels: {
+ lucene: luceneQueryLanguageLabel,
+ kuery: i18n.translate('data.advancedSettings.searchQueryLanguageKql', {
+ defaultMessage: 'KQL',
+ }),
+ },
+ schema: schema.string(),
+ },
+ [UI_SETTINGS.SORT_OPTIONS]: {
+ name: i18n.translate('data.advancedSettings.sortOptionsTitle', {
+ defaultMessage: 'Sort options',
+ }),
+ value: '{ "unmapped_type": "boolean" }',
+ description: i18n.translate('data.advancedSettings.sortOptionsText', {
+ defaultMessage: '{optionsLink} for the Elasticsearch sort parameter',
+ description:
+ 'Part of composite text: data.advancedSettings.sortOptions.optionsLinkText + ' +
+ 'data.advancedSettings.sortOptionsText',
+ values: {
+ optionsLink:
+ '' +
+ i18n.translate('data.advancedSettings.sortOptions.optionsLinkText', {
+ defaultMessage: 'Options',
+ }) +
+ '',
+ },
+ }),
+ type: 'json',
+ schema: schema.object({
+ unmapped_type: schema.string(),
+ }),
+ },
+ defaultIndex: {
+ name: i18n.translate('data.advancedSettings.defaultIndexTitle', {
+ defaultMessage: 'Default index',
+ }),
+ value: null,
+ type: 'string',
+ description: i18n.translate('data.advancedSettings.defaultIndexText', {
+ defaultMessage: 'The index to access if no index is set',
+ }),
+ schema: schema.nullable(schema.string()),
+ },
+ [UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX]: {
+ name: i18n.translate('data.advancedSettings.courier.ignoreFilterTitle', {
+ defaultMessage: 'Ignore filter(s)',
+ }),
+ value: false,
+ description: i18n.translate('data.advancedSettings.courier.ignoreFilterText', {
+ defaultMessage:
+ 'This configuration enhances support for dashboards containing visualizations accessing dissimilar indexes. ' +
+ 'When disabled, all filters are applied to all visualizations. ' +
+ 'When enabled, filter(s) will be ignored for a visualization ' +
+ `when the visualization's index does not contain the filtering field.`,
+ }),
+ category: ['search'],
+ schema: schema.boolean(),
+ },
+ [UI_SETTINGS.COURIER_SET_REQUEST_PREFERENCE]: {
+ name: i18n.translate('data.advancedSettings.courier.requestPreferenceTitle', {
+ defaultMessage: 'Request preference',
+ }),
+ value: 'sessionId',
+ options: ['sessionId', 'custom', 'none'],
+ optionLabels: requestPreferenceOptionLabels,
+ type: 'select',
+ description: i18n.translate('data.advancedSettings.courier.requestPreferenceText', {
+ defaultMessage: `Allows you to set which shards handle your search requests.
+
+ - {sessionId}: restricts operations to execute all search requests on the same shards.
+ This has the benefit of reusing shard caches across requests.
+ - {custom}: allows you to define a your own preference.
+ Use 'courier:customRequestPreference' to customize your preference value.
+ - {none}: means do not set a preference.
+ This might provide better performance because requests can be spread across all shard copies.
+ However, results might be inconsistent because different shards might be in different refresh states.
+
`,
+ values: {
+ sessionId: requestPreferenceOptionLabels.sessionId,
+ custom: requestPreferenceOptionLabels.custom,
+ none: requestPreferenceOptionLabels.none,
+ },
+ }),
+ category: ['search'],
+ schema: schema.string(),
+ },
+ [UI_SETTINGS.COURIER_CUSTOM_REQUEST_PREFERENCE]: {
+ name: i18n.translate('data.advancedSettings.courier.customRequestPreferenceTitle', {
+ defaultMessage: 'Custom request preference',
+ }),
+ value: '_local',
+ type: 'string',
+ description: i18n.translate('data.advancedSettings.courier.customRequestPreferenceText', {
+ defaultMessage:
+ '{requestPreferenceLink} used when {setRequestReferenceSetting} is set to {customSettingValue}.',
+ description:
+ 'Part of composite text: data.advancedSettings.courier.customRequestPreference.requestPreferenceLinkText + ' +
+ 'data.advancedSettings.courier.customRequestPreferenceText',
+ values: {
+ setRequestReferenceSetting: `${UI_SETTINGS.COURIER_SET_REQUEST_PREFERENCE}`,
+ customSettingValue: '"custom"',
+ requestPreferenceLink:
+ '' +
+ i18n.translate(
+ 'data.advancedSettings.courier.customRequestPreference.requestPreferenceLinkText',
+ {
+ defaultMessage: 'Request Preference',
+ }
+ ) +
+ '',
+ },
+ }),
+ category: ['search'],
+ schema: schema.string(),
+ },
+ [UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS]: {
+ name: i18n.translate('data.advancedSettings.courier.maxRequestsTitle', {
+ defaultMessage: 'Max Concurrent Shard Requests',
+ }),
+ value: 0,
+ type: 'number',
+ description: i18n.translate('data.advancedSettings.courier.maxRequestsText', {
+ defaultMessage:
+ 'Controls the {maxRequestsLink} setting used for _msearch requests sent by Kibana. ' +
+ 'Set to 0 to disable this config and use the Elasticsearch default.',
+ values: {
+ maxRequestsLink: `max_concurrent_shard_requests`,
+ },
+ }),
+ category: ['search'],
+ schema: schema.number(),
+ },
+ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: {
+ name: i18n.translate('data.advancedSettings.courier.batchSearchesTitle', {
+ defaultMessage: 'Batch concurrent searches',
+ }),
+ value: false,
+ type: 'boolean',
+ description: i18n.translate('data.advancedSettings.courier.batchSearchesText', {
+ defaultMessage: `When disabled, dashboard panels will load individually, and search requests will terminate when users navigate
+ away or update the query. When enabled, dashboard panels will load together when all of the data is loaded, and
+ searches will not terminate.`,
+ }),
+ deprecation: {
+ message: i18n.translate('data.advancedSettings.courier.batchSearchesTextDeprecation', {
+ defaultMessage: 'This setting is deprecated and will be removed in Kibana 8.0.',
+ }),
+ docLinksKey: 'kibanaSearchSettings',
+ },
+ category: ['search'],
+ schema: schema.boolean(),
+ },
+ [UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: {
+ name: 'Search in frozen indices',
+ description: `Will include frozen indices in results if enabled. Searching through frozen indices
+ might increase the search time.`,
+ value: false,
+ category: ['search'],
+ schema: schema.boolean(),
+ },
+ [UI_SETTINGS.HISTOGRAM_BAR_TARGET]: {
+ name: i18n.translate('data.advancedSettings.histogram.barTargetTitle', {
+ defaultMessage: 'Target bars',
+ }),
+ value: 50,
+ description: i18n.translate('data.advancedSettings.histogram.barTargetText', {
+ defaultMessage:
+ 'Attempt to generate around this many bars when using "auto" interval in date histograms',
+ }),
+ schema: schema.number(),
+ },
+ [UI_SETTINGS.HISTOGRAM_MAX_BARS]: {
+ name: i18n.translate('data.advancedSettings.histogram.maxBarsTitle', {
+ defaultMessage: 'Maximum bars',
+ }),
+ value: 100,
+ description: i18n.translate('data.advancedSettings.histogram.maxBarsText', {
+ defaultMessage:
+ 'Never show more than this many bars in date histograms, scale values if needed',
+ }),
+ schema: schema.number(),
+ },
+ [UI_SETTINGS.HISTORY_LIMIT]: {
+ name: i18n.translate('data.advancedSettings.historyLimitTitle', {
+ defaultMessage: 'History limit',
+ }),
+ value: 10,
+ description: i18n.translate('data.advancedSettings.historyLimitText', {
+ defaultMessage:
+ 'In fields that have history (e.g. query inputs), show this many recent values',
+ }),
+ schema: schema.number(),
+ },
+ [UI_SETTINGS.SHORT_DOTS_ENABLE]: {
+ name: i18n.translate('data.advancedSettings.shortenFieldsTitle', {
+ defaultMessage: 'Shorten fields',
+ }),
+ value: false,
+ description: i18n.translate('data.advancedSettings.shortenFieldsText', {
+ defaultMessage: 'Shorten long fields, for example, instead of foo.bar.baz, show f.b.baz',
+ }),
+ schema: schema.boolean(),
+ },
+ [UI_SETTINGS.FORMAT_DEFAULT_TYPE_MAP]: {
+ name: i18n.translate('data.advancedSettings.format.defaultTypeMapTitle', {
+ defaultMessage: 'Field type format name',
+ }),
+ value: `{
+ "ip": { "id": "ip", "params": {} },
+ "date": { "id": "date", "params": {} },
+ "date_nanos": { "id": "date_nanos", "params": {}, "es": true },
+ "number": { "id": "number", "params": {} },
+ "boolean": { "id": "boolean", "params": {} },
+ "_source": { "id": "_source", "params": {} },
+ "_default_": { "id": "string", "params": {} }
+}`,
+ type: 'json',
+ description: i18n.translate('data.advancedSettings.format.defaultTypeMapText', {
+ defaultMessage:
+ 'Map of the format name to use by default for each field type. ' +
+ '{defaultFormat} is used if the field type is not mentioned explicitly',
+ values: {
+ defaultFormat: '"_default_"',
+ },
+ }),
+ schema: schema.object({
+ ip: schema.object({
+ id: schema.string(),
+ params: schema.object({}),
+ }),
+ date: schema.object({
+ id: schema.string(),
+ params: schema.object({}),
+ }),
+ date_nanos: schema.object({
+ id: schema.string(),
+ params: schema.object({}),
+ es: schema.boolean(),
+ }),
+ number: schema.object({
+ id: schema.string(),
+ params: schema.object({}),
+ }),
+ boolean: schema.object({
+ id: schema.string(),
+ params: schema.object({}),
+ }),
+ _source: schema.object({
+ id: schema.string(),
+ params: schema.object({}),
+ }),
+ _default_: schema.object({
+ id: schema.string(),
+ params: schema.object({}),
+ }),
+ }),
+ },
+ [UI_SETTINGS.FORMAT_NUMBER_DEFAULT_PATTERN]: {
+ name: i18n.translate('data.advancedSettings.format.numberFormatTitle', {
+ defaultMessage: 'Number format',
+ }),
+ value: '0,0.[000]',
+ type: 'string',
+ description: i18n.translate('data.advancedSettings.format.numberFormatText', {
+ defaultMessage: 'Default {numeralFormatLink} for the "number" format',
+ description:
+ 'Part of composite text: data.advancedSettings.format.numberFormatText + ' +
+ 'data.advancedSettings.format.numberFormat.numeralFormatLinkText',
+ values: {
+ numeralFormatLink:
+ '' +
+ i18n.translate('data.advancedSettings.format.numberFormat.numeralFormatLinkText', {
+ defaultMessage: 'numeral format',
+ }) +
+ '',
+ },
+ }),
+ schema: schema.string(),
+ },
+ [UI_SETTINGS.FORMAT_PERCENT_DEFAULT_PATTERN]: {
+ name: i18n.translate('data.advancedSettings.format.percentFormatTitle', {
+ defaultMessage: 'Percent format',
+ }),
+ value: '0,0.[000]%',
+ type: 'string',
+ description: i18n.translate('data.advancedSettings.format.percentFormatText', {
+ defaultMessage: 'Default {numeralFormatLink} for the "percent" format',
+ description:
+ 'Part of composite text: data.advancedSettings.format.percentFormatText + ' +
+ 'data.advancedSettings.format.percentFormat.numeralFormatLinkText',
+ values: {
+ numeralFormatLink:
+ '' +
+ i18n.translate('data.advancedSettings.format.percentFormat.numeralFormatLinkText', {
+ defaultMessage: 'numeral format',
+ }) +
+ '',
+ },
+ }),
+ schema: schema.string(),
+ },
+ [UI_SETTINGS.FORMAT_BYTES_DEFAULT_PATTERN]: {
+ name: i18n.translate('data.advancedSettings.format.bytesFormatTitle', {
+ defaultMessage: 'Bytes format',
+ }),
+ value: '0,0.[0]b',
+ type: 'string',
+ description: i18n.translate('data.advancedSettings.format.bytesFormatText', {
+ defaultMessage: 'Default {numeralFormatLink} for the "bytes" format',
+ description:
+ 'Part of composite text: data.advancedSettings.format.bytesFormatText + ' +
+ 'data.advancedSettings.format.bytesFormat.numeralFormatLinkText',
+ values: {
+ numeralFormatLink:
+ '' +
+ i18n.translate('data.advancedSettings.format.bytesFormat.numeralFormatLinkText', {
+ defaultMessage: 'numeral format',
+ }) +
+ '',
+ },
+ }),
+ schema: schema.string(),
+ },
+ [UI_SETTINGS.FORMAT_CURRENCY_DEFAULT_PATTERN]: {
+ name: i18n.translate('data.advancedSettings.format.currencyFormatTitle', {
+ defaultMessage: 'Currency format',
+ }),
+ value: '($0,0.[00])',
+ type: 'string',
+ description: i18n.translate('data.advancedSettings.format.currencyFormatText', {
+ defaultMessage: 'Default {numeralFormatLink} for the "currency" format',
+ description:
+ 'Part of composite text: data.advancedSettings.format.currencyFormatText + ' +
+ 'data.advancedSettings.format.currencyFormat.numeralFormatLinkText',
+ values: {
+ numeralFormatLink:
+ '' +
+ i18n.translate('data.advancedSettings.format.currencyFormat.numeralFormatLinkText', {
+ defaultMessage: 'numeral format',
+ }) +
+ '',
+ },
+ }),
+ schema: schema.string(),
+ },
+ [UI_SETTINGS.FORMAT_NUMBER_DEFAULT_LOCALE]: {
+ name: i18n.translate('data.advancedSettings.format.formattingLocaleTitle', {
+ defaultMessage: 'Formatting locale',
+ }),
+ value: 'en',
+ type: 'select',
+ options: numeralLanguageIds,
+ optionLabels: Object.fromEntries(
+ numeralLanguages.map((language: Record) => [language.id, language.name])
+ ),
+ description: i18n.translate('data.advancedSettings.format.formattingLocaleText', {
+ defaultMessage: `{numeralLanguageLink} locale`,
+ description:
+ 'Part of composite text: data.advancedSettings.format.formattingLocale.numeralLanguageLinkText + ' +
+ 'data.advancedSettings.format.formattingLocaleText',
+ values: {
+ numeralLanguageLink:
+ '' +
+ i18n.translate(
+ 'data.advancedSettings.format.formattingLocale.numeralLanguageLinkText',
+ {
+ defaultMessage: 'Numeral language',
+ }
+ ) +
+ '',
+ },
+ }),
+ schema: schema.string(),
+ },
+ [UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS]: {
+ name: i18n.translate('data.advancedSettings.timepicker.refreshIntervalDefaultsTitle', {
+ defaultMessage: 'Time filter refresh interval',
+ }),
+ value: `{
+ "pause": false,
+ "value": 0
+}`,
+ type: 'json',
+ description: i18n.translate('data.advancedSettings.timepicker.refreshIntervalDefaultsText', {
+ defaultMessage: `The timefilter's default refresh interval`,
+ }),
+ requiresPageReload: true,
+ schema: schema.object({
+ pause: schema.boolean(),
+ value: schema.number(),
+ }),
+ },
+ [UI_SETTINGS.TIMEPICKER_QUICK_RANGES]: {
+ name: i18n.translate('data.advancedSettings.timepicker.quickRangesTitle', {
+ defaultMessage: 'Time filter quick ranges',
+ }),
+ value: JSON.stringify(
+ [
+ {
+ from: 'now/d',
+ to: 'now/d',
+ display: i18n.translate('data.advancedSettings.timepicker.today', {
+ defaultMessage: 'Today',
+ }),
+ },
+ {
+ from: 'now/w',
+ to: 'now/w',
+ display: i18n.translate('data.advancedSettings.timepicker.thisWeek', {
+ defaultMessage: 'This week',
+ }),
+ },
+ {
+ from: 'now-15m',
+ to: 'now',
+ display: i18n.translate('data.advancedSettings.timepicker.last15Minutes', {
+ defaultMessage: 'Last 15 minutes',
+ }),
+ },
+ {
+ from: 'now-30m',
+ to: 'now',
+ display: i18n.translate('data.advancedSettings.timepicker.last30Minutes', {
+ defaultMessage: 'Last 30 minutes',
+ }),
+ },
+ {
+ from: 'now-1h',
+ to: 'now',
+ display: i18n.translate('data.advancedSettings.timepicker.last1Hour', {
+ defaultMessage: 'Last 1 hour',
+ }),
+ },
+ {
+ from: 'now-24h',
+ to: 'now',
+ display: i18n.translate('data.advancedSettings.timepicker.last24Hours', {
+ defaultMessage: 'Last 24 hours',
+ }),
+ },
+ {
+ from: 'now-7d',
+ to: 'now',
+ display: i18n.translate('data.advancedSettings.timepicker.last7Days', {
+ defaultMessage: 'Last 7 days',
+ }),
+ },
+ {
+ from: 'now-30d',
+ to: 'now',
+ display: i18n.translate('data.advancedSettings.timepicker.last30Days', {
+ defaultMessage: 'Last 30 days',
+ }),
+ },
+ {
+ from: 'now-90d',
+ to: 'now',
+ display: i18n.translate('data.advancedSettings.timepicker.last90Days', {
+ defaultMessage: 'Last 90 days',
+ }),
+ },
+ {
+ from: 'now-1y',
+ to: 'now',
+ display: i18n.translate('data.advancedSettings.timepicker.last1Year', {
+ defaultMessage: 'Last 1 year',
+ }),
+ },
+ ],
+ null,
+ 2
+ ),
+ type: 'json',
+ description: i18n.translate('data.advancedSettings.timepicker.quickRangesText', {
+ defaultMessage:
+ 'The list of ranges to show in the Quick section of the time filter. This should be an array of objects, ' +
+ 'with each object containing "from", "to" (see {acceptedFormatsLink}), and ' +
+ '"display" (the title to be displayed).',
+ description:
+ 'Part of composite text: data.advancedSettings.timepicker.quickRangesText + ' +
+ 'data.advancedSettings.timepicker.quickRanges.acceptedFormatsLinkText',
+ values: {
+ acceptedFormatsLink:
+ `` +
+ i18n.translate('data.advancedSettings.timepicker.quickRanges.acceptedFormatsLinkText', {
+ defaultMessage: 'accepted formats',
+ }) +
+ '',
+ },
+ }),
+ schema: schema.arrayOf(
+ schema.object({
+ from: schema.string(),
+ to: schema.string(),
+ display: schema.string(),
+ })
+ ),
+ },
+ [UI_SETTINGS.INDEXPATTERN_PLACEHOLDER]: {
+ name: i18n.translate('data.advancedSettings.indexPatternPlaceholderTitle', {
+ defaultMessage: 'Index pattern placeholder',
+ }),
+ value: '',
+ description: i18n.translate('data.advancedSettings.indexPatternPlaceholderText', {
+ defaultMessage:
+ 'The placeholder for the "Index pattern name" field in "Management > Index Patterns > Create Index Pattern".',
+ }),
+ schema: schema.string(),
+ },
+ [UI_SETTINGS.FILTERS_PINNED_BY_DEFAULT]: {
+ name: i18n.translate('data.advancedSettings.pinFiltersTitle', {
+ defaultMessage: 'Pin filters by default',
+ }),
+ value: false,
+ description: i18n.translate('data.advancedSettings.pinFiltersText', {
+ defaultMessage: 'Whether the filters should have a global state (be pinned) by default',
+ }),
+ schema: schema.boolean(),
+ },
+ [UI_SETTINGS.FILTERS_EDITOR_SUGGEST_VALUES]: {
+ name: i18n.translate('data.advancedSettings.suggestFilterValuesTitle', {
+ defaultMessage: 'Filter editor suggest values',
+ description: '"Filter editor" refers to the UI you create filters in.',
+ }),
+ value: true,
+ description: i18n.translate('data.advancedSettings.suggestFilterValuesText', {
+ defaultMessage:
+ 'Set this property to false to prevent the filter editor from suggesting values for fields.',
+ }),
+ schema: schema.boolean(),
+ },
+ };
+}
diff --git a/src/plugins/discover/kibana.json b/src/plugins/discover/kibana.json
index 0b3a07e98624ef..14dd399697b568 100644
--- a/src/plugins/discover/kibana.json
+++ b/src/plugins/discover/kibana.json
@@ -1,6 +1,7 @@
{
"id": "discover",
"version": "kibana",
+ "optionalPlugins": ["share"],
"server": true,
"ui": true,
"requiredPlugins": [
diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js
index caba094bd0984e..88885b3eb211ba 100644
--- a/src/plugins/discover/public/application/angular/discover.js
+++ b/src/plugins/discover/public/application/angular/discover.js
@@ -72,6 +72,7 @@ import {
syncQueryStateWithUrl,
getDefaultQuery,
search,
+ UI_SETTINGS,
} from '../../../../data/public';
import { getIndexPatternId } from '../helpers/get_index_pattern_id';
import { addFatalError } from '../../../../kibana_legacy/public';
@@ -592,7 +593,8 @@ function discoverController(
const query =
$scope.searchSource.getField('query') ||
getDefaultQuery(
- localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage')
+ localStorage.get('kibana.userQueryLanguage') ||
+ config.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE)
);
return {
query,
diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_header.ts b/src/plugins/discover/public/application/angular/doc_table/components/table_header.ts
index 60dfb69e85e74b..82bfcc8bc42f97 100644
--- a/src/plugins/discover/public/application/angular/doc_table/components/table_header.ts
+++ b/src/plugins/discover/public/application/angular/doc_table/components/table_header.ts
@@ -19,6 +19,7 @@
import { TableHeader } from './table_header/table_header';
import { getServices } from '../../../../kibana_services';
import { SORT_DEFAULT_ORDER_SETTING, DOC_HIDE_TIME_COLUMN_SETTING } from '../../../../../common';
+import { UI_SETTINGS } from '../../../../../../data/public';
export function createTableHeaderDirective(reactDirective: any) {
const { uiSettings: config } = getServices();
@@ -38,7 +39,7 @@ export function createTableHeaderDirective(reactDirective: any) {
{ restrict: 'A' },
{
hideTimeColumn: config.get(DOC_HIDE_TIME_COLUMN_SETTING, false),
- isShortDots: config.get('shortDots:enable'),
+ isShortDots: config.get(UI_SETTINGS.SHORT_DOTS_ENABLE),
defaultSortOrder: config.get(SORT_DEFAULT_ORDER_SETTING, 'desc'),
}
);
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx
index 99a5547ed0760b..5a319d30b2515a 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx
@@ -33,6 +33,7 @@ import {
IIndexPatternFieldList,
IndexPatternField,
IndexPattern,
+ UI_SETTINGS,
} from '../../../../../data/public';
import { AppState } from '../../angular/discover_state';
import { getDetails } from './lib/get_details';
@@ -133,7 +134,7 @@ export function DiscoverSidebar({
);
const popularLimit = services.uiSettings.get(FIELDS_LIMIT_SETTING);
- const useShortDots = services.uiSettings.get('shortDots:enable');
+ const useShortDots = services.uiSettings.get(UI_SETTINGS.SHORT_DOTS_ENABLE);
const {
selected: selectedFields,
diff --git a/src/plugins/discover/public/index.ts b/src/plugins/discover/public/index.ts
index 359d91325f064e..4154fdfeb3ff48 100644
--- a/src/plugins/discover/public/index.ts
+++ b/src/plugins/discover/public/index.ts
@@ -27,3 +27,4 @@ export function plugin(initializerContext: PluginInitializerContext) {
export { SavedSearch, SavedSearchLoader, createSavedSearchesLoader } from './saved_searches';
export { ISearchEmbeddable, SEARCH_EMBEDDABLE_TYPE, SearchInput } from './application/embeddable';
+export { DISCOVER_APP_URL_GENERATOR } from './url_generator';
diff --git a/src/plugins/discover/public/mocks.ts b/src/plugins/discover/public/mocks.ts
index c394fe2c11a714..e4314426bfce53 100644
--- a/src/plugins/discover/public/mocks.ts
+++ b/src/plugins/discover/public/mocks.ts
@@ -34,6 +34,9 @@ const createSetupContract = (): Setup => {
const createStartContract = (): Start => {
const startContract: Start = {
savedSearchLoader: {} as any,
+ urlGenerator: {
+ createUrl: jest.fn(),
+ } as any,
};
return startContract;
};
diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts
index 4323e3d8deda46..091288e3e65aa4 100644
--- a/src/plugins/discover/public/plugin.ts
+++ b/src/plugins/discover/public/plugin.ts
@@ -34,7 +34,7 @@ import { UiActionsStart, UiActionsSetup } from 'src/plugins/ui_actions/public';
import { EmbeddableStart, EmbeddableSetup } from 'src/plugins/embeddable/public';
import { ChartsPluginStart } from 'src/plugins/charts/public';
import { NavigationPublicPluginStart as NavigationStart } from 'src/plugins/navigation/public';
-import { SharePluginStart } from 'src/plugins/share/public';
+import { SharePluginStart, SharePluginSetup, UrlGeneratorContract } from 'src/plugins/share/public';
import { VisualizationsStart, VisualizationsSetup } from 'src/plugins/visualizations/public';
import { KibanaLegacySetup } from 'src/plugins/kibana_legacy/public';
import { HomePublicPluginSetup } from 'src/plugins/home/public';
@@ -43,7 +43,7 @@ import { DataPublicPluginStart, DataPublicPluginSetup, esFilters } from '../../d
import { SavedObjectLoader } from '../../saved_objects/public';
import { createKbnUrlTracker } from '../../kibana_utils/public';
import { DEFAULT_APP_CATEGORIES } from '../../../core/public';
-
+import { UrlGeneratorState } from '../../share/public';
import { DocViewInput, DocViewInputFn } from './application/doc_views/doc_views_types';
import { DocViewsRegistry } from './application/doc_views/doc_views_registry';
import { DocViewTable } from './application/components/table/table';
@@ -59,6 +59,17 @@ import {
import { createSavedSearchesLoader } from './saved_searches';
import { registerFeature } from './register_feature';
import { buildServices } from './build_services';
+import {
+ DiscoverUrlGeneratorState,
+ DISCOVER_APP_URL_GENERATOR,
+ DiscoverUrlGenerator,
+} from './url_generator';
+
+declare module '../../share/public' {
+ export interface UrlGeneratorStateMapping {
+ [DISCOVER_APP_URL_GENERATOR]: UrlGeneratorState;
+ }
+}
/**
* @public
@@ -76,12 +87,31 @@ export interface DiscoverSetup {
export interface DiscoverStart {
savedSearchLoader: SavedObjectLoader;
+
+ /**
+ * `share` plugin URL generator for Discover app. Use it to generate links into
+ * Discover application, example:
+ *
+ * ```ts
+ * const url = await plugins.discover.urlGenerator.createUrl({
+ * savedSearchId: '571aaf70-4c88-11e8-b3d7-01146121b73d',
+ * indexPatternId: 'c367b774-a4c2-11ea-bb37-0242ac130002',
+ * timeRange: {
+ * to: 'now',
+ * from: 'now-15m',
+ * mode: 'relative',
+ * },
+ * });
+ * ```
+ */
+ readonly urlGenerator: undefined | UrlGeneratorContract<'DISCOVER_APP_URL_GENERATOR'>;
}
/**
* @internal
*/
export interface DiscoverSetupPlugins {
+ share?: SharePluginSetup;
uiActions: UiActionsSetup;
embeddable: EmbeddableSetup;
kibanaLegacy: KibanaLegacySetup;
@@ -122,6 +152,7 @@ export class DiscoverPlugin
private stopUrlTracking: (() => void) | undefined = undefined;
private servicesInitialized: boolean = false;
private innerAngularInitialized: boolean = false;
+ private urlGenerator?: DiscoverStart['urlGenerator'];
/**
* why are those functions public? they are needed for some mocha tests
@@ -131,6 +162,17 @@ export class DiscoverPlugin
public initializeServices?: () => Promise<{ core: CoreStart; plugins: DiscoverStartPlugins }>;
setup(core: CoreSetup, plugins: DiscoverSetupPlugins) {
+ const baseUrl = core.http.basePath.prepend('/app/discover');
+
+ if (plugins.share) {
+ this.urlGenerator = plugins.share.urlGenerators.registerUrlGenerator(
+ new DiscoverUrlGenerator({
+ appBasePath: baseUrl,
+ useHash: core.uiSettings.get('state:storeInSessionStorage'),
+ })
+ );
+ }
+
this.docViewsRegistry = new DocViewsRegistry();
setDocViewsRegistry(this.docViewsRegistry);
this.docViewsRegistry.addDocView({
@@ -158,7 +200,7 @@ export class DiscoverPlugin
// so history is lazily created (when app is mounted)
// this prevents redundant `#` when not in discover app
getHistory: getScopedHistory,
- baseUrl: core.http.basePath.prepend('/app/discover'),
+ baseUrl,
defaultSubUrl: '#/',
storageKey: `lastUrl:${core.http.basePath.get()}:discover`,
navLinkUpdater$: this.appStateUpdater,
@@ -266,6 +308,7 @@ export class DiscoverPlugin
};
return {
+ urlGenerator: this.urlGenerator,
savedSearchLoader: createSavedSearchesLoader({
savedObjectsClient: core.savedObjects.client,
indexPatterns: plugins.data.indexPatterns,
diff --git a/src/plugins/discover/public/url_generator.test.ts b/src/plugins/discover/public/url_generator.test.ts
new file mode 100644
index 00000000000000..cf9beb246fea29
--- /dev/null
+++ b/src/plugins/discover/public/url_generator.test.ts
@@ -0,0 +1,259 @@
+/*
+ * 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 { DiscoverUrlGenerator } from './url_generator';
+import { hashedItemStore, getStatesFromKbnUrl } from '../../kibana_utils/public';
+// eslint-disable-next-line
+import { mockStorage } from '../../kibana_utils/public/storage/hashed_item_store/mock';
+import { FilterStateStore } from '../../data/common';
+
+const appBasePath: string = 'xyz/app/discover';
+const indexPatternId: string = 'c367b774-a4c2-11ea-bb37-0242ac130002';
+const savedSearchId: string = '571aaf70-4c88-11e8-b3d7-01146121b73d';
+
+interface SetupParams {
+ useHash?: boolean;
+}
+
+const setup = async ({ useHash = false }: SetupParams = {}) => {
+ const generator = new DiscoverUrlGenerator({
+ appBasePath,
+ useHash,
+ });
+
+ return {
+ generator,
+ };
+};
+
+beforeEach(() => {
+ // @ts-ignore
+ hashedItemStore.storage = mockStorage;
+});
+
+describe('Discover url generator', () => {
+ test('can create a link to Discover with no state and no saved search', async () => {
+ const { generator } = await setup();
+ const url = await generator.createUrl({});
+ const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']);
+
+ expect(url.startsWith(appBasePath)).toBe(true);
+ expect(_a).toEqual({});
+ expect(_g).toEqual({});
+ });
+
+ test('can create a link to a saved search in Discover', async () => {
+ const { generator } = await setup();
+ const url = await generator.createUrl({ savedSearchId });
+ const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']);
+
+ expect(url.startsWith(`${appBasePath}#/${savedSearchId}`)).toBe(true);
+ expect(_a).toEqual({});
+ expect(_g).toEqual({});
+ });
+
+ test('can specify specific index pattern', async () => {
+ const { generator } = await setup();
+ const url = await generator.createUrl({
+ indexPatternId,
+ });
+ const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']);
+
+ expect(_a).toEqual({
+ index: indexPatternId,
+ });
+ expect(_g).toEqual({});
+ });
+
+ test('can specify specific time range', async () => {
+ const { generator } = await setup();
+ const url = await generator.createUrl({
+ timeRange: { to: 'now', from: 'now-15m', mode: 'relative' },
+ });
+ const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']);
+
+ expect(_a).toEqual({});
+ expect(_g).toEqual({
+ time: {
+ from: 'now-15m',
+ mode: 'relative',
+ to: 'now',
+ },
+ });
+ });
+
+ test('can specify query', async () => {
+ const { generator } = await setup();
+ const url = await generator.createUrl({
+ query: {
+ language: 'kuery',
+ query: 'foo',
+ },
+ });
+ const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']);
+
+ expect(_a).toEqual({
+ query: {
+ language: 'kuery',
+ query: 'foo',
+ },
+ });
+ expect(_g).toEqual({});
+ });
+
+ test('can specify local and global filters', async () => {
+ const { generator } = await setup();
+ const url = await generator.createUrl({
+ filters: [
+ {
+ meta: {
+ alias: 'foo',
+ disabled: false,
+ negate: false,
+ },
+ $state: {
+ store: FilterStateStore.APP_STATE,
+ },
+ },
+ {
+ meta: {
+ alias: 'bar',
+ disabled: false,
+ negate: false,
+ },
+ $state: {
+ store: FilterStateStore.GLOBAL_STATE,
+ },
+ },
+ ],
+ });
+ const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']);
+
+ expect(_a).toEqual({
+ filters: [
+ {
+ $state: {
+ store: 'appState',
+ },
+ meta: {
+ alias: 'foo',
+ disabled: false,
+ negate: false,
+ },
+ },
+ ],
+ });
+ expect(_g).toEqual({
+ filters: [
+ {
+ $state: {
+ store: 'globalState',
+ },
+ meta: {
+ alias: 'bar',
+ disabled: false,
+ negate: false,
+ },
+ },
+ ],
+ });
+ });
+
+ test('can set refresh interval', async () => {
+ const { generator } = await setup();
+ const url = await generator.createUrl({
+ refreshInterval: {
+ pause: false,
+ value: 666,
+ },
+ });
+ const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']);
+
+ expect(_a).toEqual({});
+ expect(_g).toEqual({
+ refreshInterval: {
+ pause: false,
+ value: 666,
+ },
+ });
+ });
+
+ test('can set time range', async () => {
+ const { generator } = await setup();
+ const url = await generator.createUrl({
+ timeRange: {
+ from: 'now-3h',
+ to: 'now',
+ },
+ });
+ const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']);
+
+ expect(_a).toEqual({});
+ expect(_g).toEqual({
+ time: {
+ from: 'now-3h',
+ to: 'now',
+ },
+ });
+ });
+
+ describe('useHash property', () => {
+ describe('when default useHash is set to false', () => {
+ test('when using default, sets index pattern ID in the generated URL', async () => {
+ const { generator } = await setup();
+ const url = await generator.createUrl({
+ indexPatternId,
+ });
+
+ expect(url.indexOf(indexPatternId) > -1).toBe(true);
+ });
+
+ test('when enabling useHash, does not set index pattern ID in the generated URL', async () => {
+ const { generator } = await setup();
+ const url = await generator.createUrl({
+ useHash: true,
+ indexPatternId,
+ });
+
+ expect(url.indexOf(indexPatternId) > -1).toBe(false);
+ });
+ });
+
+ describe('when default useHash is set to true', () => {
+ test('when using default, does not set index pattern ID in the generated URL', async () => {
+ const { generator } = await setup({ useHash: true });
+ const url = await generator.createUrl({
+ indexPatternId,
+ });
+
+ expect(url.indexOf(indexPatternId) > -1).toBe(false);
+ });
+
+ test('when disabling useHash, sets index pattern ID in the generated URL', async () => {
+ const { generator } = await setup();
+ const url = await generator.createUrl({
+ useHash: false,
+ indexPatternId,
+ });
+
+ expect(url.indexOf(indexPatternId) > -1).toBe(true);
+ });
+ });
+ });
+});
diff --git a/src/plugins/discover/public/url_generator.ts b/src/plugins/discover/public/url_generator.ts
new file mode 100644
index 00000000000000..42d689050d5ad4
--- /dev/null
+++ b/src/plugins/discover/public/url_generator.ts
@@ -0,0 +1,114 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {
+ TimeRange,
+ Filter,
+ Query,
+ esFilters,
+ QueryState,
+ RefreshInterval,
+} from '../../data/public';
+import { setStateToKbnUrl } from '../../kibana_utils/public';
+import { UrlGeneratorsDefinition } from '../../share/public';
+
+export const DISCOVER_APP_URL_GENERATOR = 'DISCOVER_APP_URL_GENERATOR';
+
+export interface DiscoverUrlGeneratorState {
+ /**
+ * Optionally set saved search ID.
+ */
+ savedSearchId?: string;
+
+ /**
+ * Optionally set index pattern ID.
+ */
+ indexPatternId?: string;
+
+ /**
+ * Optionally set the time range in the time picker.
+ */
+ timeRange?: TimeRange;
+
+ /**
+ * Optionally set the refresh interval.
+ */
+ refreshInterval?: RefreshInterval;
+
+ /**
+ * Optionally apply filers.
+ */
+ filters?: Filter[];
+
+ /**
+ * Optionally set a query. NOTE: if given and used in conjunction with `dashboardId`, and the
+ * saved dashboard has a query saved with it, this will _replace_ that query.
+ */
+ query?: Query;
+
+ /**
+ * If not given, will use the uiSettings configuration for `storeInSessionStorage`. useHash determines
+ * whether to hash the data in the url to avoid url length issues.
+ */
+ useHash?: boolean;
+}
+
+interface Params {
+ appBasePath: string;
+ useHash: boolean;
+}
+
+export class DiscoverUrlGenerator
+ implements UrlGeneratorsDefinition {
+ constructor(private readonly params: Params) {}
+
+ public readonly id = DISCOVER_APP_URL_GENERATOR;
+
+ public readonly createUrl = async ({
+ filters,
+ indexPatternId,
+ query,
+ refreshInterval,
+ savedSearchId,
+ timeRange,
+ useHash = this.params.useHash,
+ }: DiscoverUrlGeneratorState): Promise => {
+ const savedSearchPath = savedSearchId ? encodeURIComponent(savedSearchId) : '';
+ const appState: {
+ query?: Query;
+ filters?: Filter[];
+ index?: string;
+ } = {};
+ const queryState: QueryState = {};
+
+ if (query) appState.query = query;
+ if (filters) appState.filters = filters?.filter((f) => !esFilters.isFilterPinned(f));
+ if (indexPatternId) appState.index = indexPatternId;
+
+ if (timeRange) queryState.time = timeRange;
+ if (filters) queryState.filters = filters?.filter((f) => esFilters.isFilterPinned(f));
+ if (refreshInterval) queryState.refreshInterval = refreshInterval;
+
+ let url = `${this.params.appBasePath}#/${savedSearchPath}`;
+ url = setStateToKbnUrl('_g', queryState, { useHash }, url);
+ url = setStateToKbnUrl('_a', appState, { useHash }, url);
+
+ return url;
+ };
+}
diff --git a/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx b/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx
index b046376a304ae0..e29e941e898fbe 100644
--- a/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx
+++ b/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx
@@ -31,7 +31,7 @@ import {
// eslint-disable-next-line
import { inspectorPluginMock } from '../../../../inspector/public/mocks';
import { mount } from 'enzyme';
-import { embeddablePluginMock } from '../../mocks';
+import { embeddablePluginMock, createEmbeddablePanelMock } from '../../mocks';
test('EmbeddableChildPanel renders an embeddable when it is done loading', async () => {
const inspector = inspectorPluginMock.createStartContract();
@@ -58,18 +58,17 @@ test('EmbeddableChildPanel renders an embeddable when it is done loading', async
expect(newEmbeddable.id).toBeDefined();
+ const testPanel = createEmbeddablePanelMock({
+ getAllEmbeddableFactories: start.getEmbeddableFactories,
+ getEmbeddableFactory,
+ inspector,
+ });
+
const component = mount(
Promise.resolve([])}
- getAllEmbeddableFactories={start.getEmbeddableFactories}
- getEmbeddableFactory={getEmbeddableFactory}
- notifications={{} as any}
- application={{} as any}
- overlays={{} as any}
- inspector={inspector}
- SavedObjectFinder={() => null}
+ PanelComponent={testPanel}
/>
);
@@ -97,19 +96,9 @@ test(`EmbeddableChildPanel renders an error message if the factory doesn't exist
{ getEmbeddableFactory } as any
);
+ const testPanel = createEmbeddablePanelMock({ inspector });
const component = mount(
- Promise.resolve([])}
- getAllEmbeddableFactories={(() => []) as any}
- getEmbeddableFactory={(() => undefined) as any}
- notifications={{} as any}
- overlays={{} as any}
- application={{} as any}
- inspector={inspector}
- SavedObjectFinder={() => null}
- />
+
);
await nextTick();
diff --git a/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.tsx b/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.tsx
index 70628665e6e8c9..be8ff2c95fe096 100644
--- a/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.tsx
+++ b/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.tsx
@@ -22,12 +22,7 @@ import React from 'react';
import { EuiLoadingChart } from '@elastic/eui';
import { Subscription } from 'rxjs';
-import { CoreStart } from 'src/core/public';
-import { UiActionsService } from 'src/plugins/ui_actions/public';
-
-import { Start as InspectorStartContract } from 'src/plugins/inspector/public';
import { ErrorEmbeddable, IEmbeddable } from '../embeddables';
-import { EmbeddablePanel } from '../panel';
import { IContainer } from './i_container';
import { EmbeddableStart } from '../../plugin';
@@ -35,14 +30,7 @@ export interface EmbeddableChildPanelProps {
embeddableId: string;
className?: string;
container: IContainer;
- getActions: UiActionsService['getTriggerCompatibleActions'];
- getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'];
- getAllEmbeddableFactories: EmbeddableStart['getEmbeddableFactories'];
- overlays: CoreStart['overlays'];
- notifications: CoreStart['notifications'];
- application: CoreStart['application'];
- inspector: InspectorStartContract;
- SavedObjectFinder: React.ComponentType;
+ PanelComponent: EmbeddableStart['EmbeddablePanel'];
}
interface State {
@@ -87,6 +75,7 @@ export class EmbeddableChildPanel extends React.Component
) : (
-
+
)}
);
diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/hello_world_container.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/hello_world_container.tsx
index 31e14a0af59d7d..913c3a0b30826b 100644
--- a/src/plugins/embeddable/public/lib/test_samples/embeddables/hello_world_container.tsx
+++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/hello_world_container.tsx
@@ -19,9 +19,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { I18nProvider } from '@kbn/i18n/react';
-import { CoreStart } from 'src/core/public';
-import { UiActionsService } from 'src/plugins/ui_actions/public';
-import { Start as InspectorStartContract } from 'src/plugins/inspector/public';
import { Container, ViewMode, ContainerInput } from '../..';
import { HelloWorldContainerComponent } from './hello_world_container_component';
import { EmbeddableStart } from '../../../plugin';
@@ -45,14 +42,8 @@ interface HelloWorldContainerInput extends ContainerInput {
}
interface HelloWorldContainerOptions {
- getActions: UiActionsService['getTriggerCompatibleActions'];
getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'];
- getAllEmbeddableFactories: EmbeddableStart['getEmbeddableFactories'];
- overlays: CoreStart['overlays'];
- application: CoreStart['application'];
- notifications: CoreStart['notifications'];
- inspector: InspectorStartContract;
- SavedObjectFinder: React.ComponentType;
+ panelComponent: EmbeddableStart['EmbeddablePanel'];
}
export class HelloWorldContainer extends Container {
@@ -78,14 +69,7 @@ export class HelloWorldContainer extends Container
,
node
diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/hello_world_container_component.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/hello_world_container_component.tsx
index 6453046b86e205..5fefa1fc907201 100644
--- a/src/plugins/embeddable/public/lib/test_samples/embeddables/hello_world_container_component.tsx
+++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/hello_world_container_component.tsx
@@ -20,22 +20,12 @@ import React, { Component, RefObject } from 'react';
import { Subscription } from 'rxjs';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
-import { CoreStart } from 'src/core/public';
-import { UiActionsService } from 'src/plugins/ui_actions/public';
-import { Start as InspectorStartContract } from 'src/plugins/inspector/public';
import { IContainer, PanelState, EmbeddableChildPanel } from '../..';
import { EmbeddableStart } from '../../../plugin';
interface Props {
container: IContainer;
- getActions: UiActionsService['getTriggerCompatibleActions'];
- getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'];
- getAllEmbeddableFactories: EmbeddableStart['getEmbeddableFactories'];
- overlays: CoreStart['overlays'];
- application: CoreStart['application'];
- notifications: CoreStart['notifications'];
- inspector: InspectorStartContract;
- SavedObjectFinder: React.ComponentType;
+ panelComponent: EmbeddableStart['EmbeddablePanel'];
}
interface State {
@@ -108,14 +98,7 @@ export class HelloWorldContainerComponent extends Component {
);
diff --git a/src/plugins/embeddable/public/mocks.ts b/src/plugins/embeddable/public/mocks.tsx
similarity index 62%
rename from src/plugins/embeddable/public/mocks.ts
rename to src/plugins/embeddable/public/mocks.tsx
index f5487c381cfcb3..9da0b7602c4f8f 100644
--- a/src/plugins/embeddable/public/mocks.ts
+++ b/src/plugins/embeddable/public/mocks.tsx
@@ -16,14 +16,20 @@
* specific language governing permissions and limitations
* under the License.
*/
+import React from 'react';
import {
EmbeddableStart,
EmbeddableSetup,
EmbeddableSetupDependencies,
EmbeddableStartDependencies,
+ IEmbeddable,
+ EmbeddablePanel,
} from '.';
import { EmbeddablePublicPlugin } from './plugin';
import { coreMock } from '../../../core/public/mocks';
+import { UiActionsService } from './lib/ui_actions';
+import { CoreStart } from '../../../core/public';
+import { Start as InspectorStart } from '../../inspector/public';
// eslint-disable-next-line
import { inspectorPluginMock } from '../../inspector/public/mocks';
@@ -33,6 +39,42 @@ import { uiActionsPluginMock } from '../../ui_actions/public/mocks';
export type Setup = jest.Mocked;
export type Start = jest.Mocked;
+interface CreateEmbeddablePanelMockArgs {
+ getActions: UiActionsService['getTriggerCompatibleActions'];
+ getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'];
+ getAllEmbeddableFactories: EmbeddableStart['getEmbeddableFactories'];
+ overlays: CoreStart['overlays'];
+ notifications: CoreStart['notifications'];
+ application: CoreStart['application'];
+ inspector: InspectorStart;
+ SavedObjectFinder: React.ComponentType;
+}
+
+export const createEmbeddablePanelMock = ({
+ getActions,
+ getEmbeddableFactory,
+ getAllEmbeddableFactories,
+ overlays,
+ notifications,
+ application,
+ inspector,
+ SavedObjectFinder,
+}: Partial) => {
+ return ({ embeddable }: { embeddable: IEmbeddable }) => (
+ Promise.resolve([]))}
+ getAllEmbeddableFactories={getAllEmbeddableFactories || ((() => []) as any)}
+ getEmbeddableFactory={getEmbeddableFactory || ((() => undefined) as any)}
+ notifications={notifications || ({} as any)}
+ application={application || ({} as any)}
+ overlays={overlays || ({} as any)}
+ inspector={inspector || ({} as any)}
+ SavedObjectFinder={SavedObjectFinder || (() => null)}
+ />
+ );
+};
+
const createSetupContract = (): Setup => {
const setupContract: Setup = {
registerEmbeddableFactory: jest.fn(),
diff --git a/src/plugins/embeddable/public/tests/apply_filter_action.test.ts b/src/plugins/embeddable/public/tests/apply_filter_action.test.ts
index ebb76c743393bc..ec92f334267f58 100644
--- a/src/plugins/embeddable/public/tests/apply_filter_action.test.ts
+++ b/src/plugins/embeddable/public/tests/apply_filter_action.test.ts
@@ -31,8 +31,7 @@ import {
FilterableEmbeddableInput,
} from '../lib/test_samples';
// eslint-disable-next-line
-import { inspectorPluginMock } from '../../../../plugins/inspector/public/mocks';
-import { esFilters } from '../../../../plugins/data/public';
+import { esFilters } from '../../../data/public';
test('ApplyFilterAction applies the filter to the root of the container tree', async () => {
const { doStart, setup } = testPlugin();
@@ -95,26 +94,16 @@ test('ApplyFilterAction applies the filter to the root of the container tree', a
});
test('ApplyFilterAction is incompatible if the root container does not accept a filter as input', async () => {
- const { doStart, coreStart, setup } = testPlugin();
- const inspector = inspectorPluginMock.createStartContract();
+ const { doStart, setup } = testPlugin();
const factory = new FilterableEmbeddableFactory();
setup.registerEmbeddableFactory(factory.type, factory);
const api = doStart();
const applyFilterAction = createFilterAction();
- const parent = new HelloWorldContainer(
- { id: 'root', panels: {} },
- {
- getActions: () => Promise.resolve([]),
- getEmbeddableFactory: api.getEmbeddableFactory,
- getAllEmbeddableFactories: api.getEmbeddableFactories,
- overlays: coreStart.overlays,
- notifications: coreStart.notifications,
- application: coreStart.application,
- inspector,
- SavedObjectFinder: () => null,
- }
- );
+
+ const parent = new HelloWorldContainer({ id: 'root', panels: {} }, {
+ getEmbeddableFactory: api.getEmbeddableFactory,
+ } as any);
const embeddable = await parent.addNewEmbeddable<
FilterableContainerInput,
EmbeddableOutput,
@@ -130,27 +119,17 @@ test('ApplyFilterAction is incompatible if the root container does not accept a
});
test('trying to execute on incompatible context throws an error ', async () => {
- const { doStart, coreStart, setup } = testPlugin();
- const inspector = inspectorPluginMock.createStartContract();
+ const { doStart, setup } = testPlugin();
const factory = new FilterableEmbeddableFactory();
setup.registerEmbeddableFactory(factory.type, factory);
const api = doStart();
const applyFilterAction = createFilterAction();
- const parent = new HelloWorldContainer(
- { id: 'root', panels: {} },
- {
- getActions: () => Promise.resolve([]),
- getEmbeddableFactory: api.getEmbeddableFactory,
- getAllEmbeddableFactories: api.getEmbeddableFactories,
- overlays: coreStart.overlays,
- notifications: coreStart.notifications,
- application: coreStart.application,
- inspector,
- SavedObjectFinder: () => null,
- }
- );
+
+ const parent = new HelloWorldContainer({ id: 'root', panels: {} }, {
+ getEmbeddableFactory: api.getEmbeddableFactory,
+ } as any);
const embeddable = await parent.addNewEmbeddable<
FilterableContainerInput,
diff --git a/src/plugins/embeddable/public/tests/container.test.ts b/src/plugins/embeddable/public/tests/container.test.ts
index 4cd01abaf79953..490f0c00c7c4d0 100644
--- a/src/plugins/embeddable/public/tests/container.test.ts
+++ b/src/plugins/embeddable/public/tests/container.test.ts
@@ -48,6 +48,7 @@ import { coreMock } from '../../../../core/public/mocks';
import { testPlugin } from './test_plugin';
import { of } from './helpers';
import { esFilters, Filter } from '../../../../plugins/data/public';
+import { createEmbeddablePanelMock } from '../mocks';
async function creatHelloWorldContainerAndEmbeddable(
containerInput: ContainerInput = { id: 'hello', panels: {} },
@@ -68,15 +69,18 @@ async function creatHelloWorldContainerAndEmbeddable(
const start = doStart();
- const container = new HelloWorldContainer(containerInput, {
+ const testPanel = createEmbeddablePanelMock({
getActions: uiActions.getTriggerCompatibleActions,
getEmbeddableFactory: start.getEmbeddableFactory,
getAllEmbeddableFactories: start.getEmbeddableFactories,
overlays: coreStart.overlays,
notifications: coreStart.notifications,
application: coreStart.application,
- inspector: {} as any,
- SavedObjectFinder: () => null,
+ });
+
+ const container = new HelloWorldContainer(containerInput, {
+ getEmbeddableFactory: start.getEmbeddableFactory,
+ panelComponent: testPanel,
});
const embeddable = await container.addNewEmbeddable<
ContactCardEmbeddableInput,
@@ -88,7 +92,7 @@ async function creatHelloWorldContainerAndEmbeddable(
throw new Error('Error adding embeddable');
}
- return { container, embeddable, coreSetup, coreStart, setup, start, uiActions };
+ return { container, embeddable, coreSetup, coreStart, setup, start, uiActions, testPanel };
}
test('Container initializes embeddables', async (done) => {
@@ -131,7 +135,8 @@ test('Container.addNewEmbeddable', async () => {
});
test('Container.removeEmbeddable removes and cleans up', async (done) => {
- const { start, coreStart, uiActions } = await creatHelloWorldContainerAndEmbeddable();
+ const { start, testPanel } = await creatHelloWorldContainerAndEmbeddable();
+
const container = new HelloWorldContainer(
{
id: 'hello',
@@ -143,14 +148,8 @@ test('Container.removeEmbeddable removes and cleans up', async (done) => {
},
},
{
- getActions: uiActions.getTriggerCompatibleActions,
getEmbeddableFactory: start.getEmbeddableFactory,
- getAllEmbeddableFactories: start.getEmbeddableFactories,
- overlays: coreStart.overlays,
- notifications: coreStart.notifications,
- application: coreStart.application,
- inspector: {} as any,
- SavedObjectFinder: () => null,
+ panelComponent: testPanel,
}
);
const embeddable = await container.addNewEmbeddable<
@@ -323,15 +322,17 @@ test(`Container updates its state when a child's input is updated`, async (done)
// Make sure a brand new container built off the output of container also creates an embeddable
// with "Dr.", not the default the embeddable was first added with. Makes sure changed input
// is preserved with the container.
- const containerClone = new HelloWorldContainer(container.getInput(), {
+ const testPanel = createEmbeddablePanelMock({
getActions: uiActions.getTriggerCompatibleActions,
- getAllEmbeddableFactories: start.getEmbeddableFactories,
getEmbeddableFactory: start.getEmbeddableFactory,
- notifications: coreStart.notifications,
+ getAllEmbeddableFactories: start.getEmbeddableFactories,
overlays: coreStart.overlays,
+ notifications: coreStart.notifications,
application: coreStart.application,
- inspector: {} as any,
- SavedObjectFinder: () => null,
+ });
+ const containerClone = new HelloWorldContainer(container.getInput(), {
+ getEmbeddableFactory: start.getEmbeddableFactory,
+ panelComponent: testPanel,
});
const cloneSubscription = Rx.merge(
containerClone.getOutput$(),
@@ -575,6 +576,14 @@ test('Container changes made directly after adding a new embeddable are propagat
const start = doStart();
+ const testPanel = createEmbeddablePanelMock({
+ getActions: uiActions.getTriggerCompatibleActions,
+ getEmbeddableFactory: start.getEmbeddableFactory,
+ getAllEmbeddableFactories: start.getEmbeddableFactories,
+ overlays: coreStart.overlays,
+ notifications: coreStart.notifications,
+ application: coreStart.application,
+ });
const container = new HelloWorldContainer(
{
id: 'hello',
@@ -582,14 +591,8 @@ test('Container changes made directly after adding a new embeddable are propagat
viewMode: ViewMode.EDIT,
},
{
- getActions: uiActions.getTriggerCompatibleActions,
getEmbeddableFactory: start.getEmbeddableFactory,
- getAllEmbeddableFactories: start.getEmbeddableFactories,
- overlays: coreStart.overlays,
- notifications: coreStart.notifications,
- application: coreStart.application,
- inspector: {} as any,
- SavedObjectFinder: () => null,
+ panelComponent: testPanel,
}
);
@@ -701,20 +704,22 @@ test('untilEmbeddableLoaded() throws an error if there is no such child panel in
coreMock.createStart()
);
const start = doStart();
+ const testPanel = createEmbeddablePanelMock({
+ getActions: uiActions.getTriggerCompatibleActions,
+ getEmbeddableFactory: start.getEmbeddableFactory,
+ getAllEmbeddableFactories: start.getEmbeddableFactories,
+ overlays: coreStart.overlays,
+ notifications: coreStart.notifications,
+ application: coreStart.application,
+ });
const container = new HelloWorldContainer(
{
id: 'hello',
panels: {},
},
{
- getActions: uiActions.getTriggerCompatibleActions,
getEmbeddableFactory: start.getEmbeddableFactory,
- getAllEmbeddableFactories: start.getEmbeddableFactories,
- overlays: coreStart.overlays,
- notifications: coreStart.notifications,
- application: coreStart.application,
- inspector: {} as any,
- SavedObjectFinder: () => null,
+ panelComponent: testPanel,
}
);
@@ -731,6 +736,14 @@ test('untilEmbeddableLoaded() resolves if child is loaded in the container', asy
const factory = new HelloWorldEmbeddableFactory();
setup.registerEmbeddableFactory(factory.type, factory);
const start = doStart();
+ const testPanel = createEmbeddablePanelMock({
+ getActions: uiActions.getTriggerCompatibleActions,
+ getEmbeddableFactory: start.getEmbeddableFactory,
+ getAllEmbeddableFactories: start.getEmbeddableFactories,
+ overlays: coreStart.overlays,
+ notifications: coreStart.notifications,
+ application: coreStart.application,
+ });
const container = new HelloWorldContainer(
{
id: 'hello',
@@ -742,14 +755,8 @@ test('untilEmbeddableLoaded() resolves if child is loaded in the container', asy
},
},
{
- getActions: uiActions.getTriggerCompatibleActions,
getEmbeddableFactory: start.getEmbeddableFactory,
- getAllEmbeddableFactories: start.getEmbeddableFactories,
- overlays: coreStart.overlays,
- notifications: coreStart.notifications,
- application: coreStart.application,
- inspector: {} as any,
- SavedObjectFinder: () => null,
+ panelComponent: testPanel,
}
);
@@ -771,6 +778,14 @@ test('untilEmbeddableLoaded resolves with undefined if child is subsequently rem
setup.registerEmbeddableFactory(factory.type, factory);
const start = doStart();
+ const testPanel = createEmbeddablePanelMock({
+ getActions: uiActions.getTriggerCompatibleActions,
+ getEmbeddableFactory: start.getEmbeddableFactory,
+ getAllEmbeddableFactories: start.getEmbeddableFactories,
+ overlays: coreStart.overlays,
+ notifications: coreStart.notifications,
+ application: coreStart.application,
+ });
const container = new HelloWorldContainer(
{
id: 'hello',
@@ -782,14 +797,8 @@ test('untilEmbeddableLoaded resolves with undefined if child is subsequently rem
},
},
{
- getActions: uiActions.getTriggerCompatibleActions,
getEmbeddableFactory: start.getEmbeddableFactory,
- getAllEmbeddableFactories: start.getEmbeddableFactories,
- overlays: coreStart.overlays,
- notifications: coreStart.notifications,
- application: coreStart.application,
- inspector: {} as any,
- SavedObjectFinder: () => null,
+ panelComponent: testPanel,
}
);
@@ -812,6 +821,14 @@ test('adding a panel then subsequently removing it before its loaded removes the
});
setup.registerEmbeddableFactory(factory.type, factory);
const start = doStart();
+ const testPanel = createEmbeddablePanelMock({
+ getActions: uiActions.getTriggerCompatibleActions,
+ getEmbeddableFactory: start.getEmbeddableFactory,
+ getAllEmbeddableFactories: start.getEmbeddableFactories,
+ overlays: coreStart.overlays,
+ notifications: coreStart.notifications,
+ application: coreStart.application,
+ });
const container = new HelloWorldContainer(
{
id: 'hello',
@@ -823,14 +840,8 @@ test('adding a panel then subsequently removing it before its loaded removes the
},
},
{
- getActions: uiActions.getTriggerCompatibleActions,
getEmbeddableFactory: start.getEmbeddableFactory,
- getAllEmbeddableFactories: start.getEmbeddableFactories,
- overlays: coreStart.overlays,
- notifications: coreStart.notifications,
- application: coreStart.application,
- inspector: {} as any,
- SavedObjectFinder: () => null,
+ panelComponent: testPanel,
}
);
diff --git a/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx b/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx
index a9cb83504d9584..311efae49f7359 100644
--- a/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx
+++ b/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx
@@ -37,6 +37,7 @@ import { testPlugin } from './test_plugin';
import { CustomizePanelModal } from '../lib/panel/panel_header/panel_actions/customize_title/customize_panel_modal';
import { mount } from 'enzyme';
import { EmbeddableStart } from '../plugin';
+import { createEmbeddablePanelMock } from '../mocks';
let api: EmbeddableStart;
let container: Container;
@@ -55,17 +56,20 @@ beforeEach(async () => {
setup.registerEmbeddableFactory(contactCardFactory.type, contactCardFactory);
api = doStart();
+
+ const testPanel = createEmbeddablePanelMock({
+ getActions: uiActions.getTriggerCompatibleActions,
+ getEmbeddableFactory: api.getEmbeddableFactory,
+ getAllEmbeddableFactories: api.getEmbeddableFactories,
+ overlays: coreStart.overlays,
+ notifications: coreStart.notifications,
+ application: coreStart.application,
+ });
container = new HelloWorldContainer(
{ id: '123', panels: {} },
{
- getActions: uiActions.getTriggerCompatibleActions,
getEmbeddableFactory: api.getEmbeddableFactory,
- getAllEmbeddableFactories: api.getEmbeddableFactories,
- overlays: coreStart.overlays,
- notifications: coreStart.notifications,
- application: coreStart.application,
- inspector: {} as any,
- SavedObjectFinder: () => null,
+ panelComponent: testPanel,
}
);
const contactCardEmbeddable = await container.addNewEmbeddable<
diff --git a/src/plugins/embeddable/public/tests/explicit_input.test.ts b/src/plugins/embeddable/public/tests/explicit_input.test.ts
index 6bea4fe46a4973..d64ff94d71800e 100644
--- a/src/plugins/embeddable/public/tests/explicit_input.test.ts
+++ b/src/plugins/embeddable/public/tests/explicit_input.test.ts
@@ -36,6 +36,7 @@ import { HelloWorldContainer } from '../lib/test_samples/embeddables/hello_world
// eslint-disable-next-line
import { coreMock } from '../../../../core/public/mocks';
import { esFilters, Filter } from '../../../../plugins/data/public';
+import { createEmbeddablePanelMock } from '../mocks';
const { setup, doStart, coreStart, uiActions } = testPlugin(
coreMock.createSetup(),
@@ -80,17 +81,19 @@ test('Explicit embeddable input mapped to undefined will default to inherited',
});
test('Explicit embeddable input mapped to undefined with no inherited value will get passed to embeddable', async (done) => {
+ const testPanel = createEmbeddablePanelMock({
+ getActions: uiActions.getTriggerCompatibleActions,
+ getEmbeddableFactory: start.getEmbeddableFactory,
+ getAllEmbeddableFactories: start.getEmbeddableFactories,
+ overlays: coreStart.overlays,
+ notifications: coreStart.notifications,
+ application: coreStart.application,
+ });
const container = new HelloWorldContainer(
{ id: 'hello', panels: {} },
{
- getActions: uiActions.getTriggerCompatibleActions,
- getAllEmbeddableFactories: start.getEmbeddableFactories,
getEmbeddableFactory: start.getEmbeddableFactory,
- notifications: coreStart.notifications,
- overlays: coreStart.overlays,
- application: coreStart.application,
- inspector: {} as any,
- SavedObjectFinder: () => null,
+ panelComponent: testPanel,
}
);
@@ -121,6 +124,14 @@ test('Explicit embeddable input mapped to undefined with no inherited value will
// but before the embeddable factory returns the embeddable, that the `inheritedChildInput` and
// embeddable input comparisons won't cause explicit input to be set when it shouldn't.
test('Explicit input tests in async situations', (done: () => void) => {
+ const testPanel = createEmbeddablePanelMock({
+ getActions: uiActions.getTriggerCompatibleActions,
+ getEmbeddableFactory: start.getEmbeddableFactory,
+ getAllEmbeddableFactories: start.getEmbeddableFactories,
+ overlays: coreStart.overlays,
+ notifications: coreStart.notifications,
+ application: coreStart.application,
+ });
const container = new HelloWorldContainer(
{
id: 'hello',
@@ -132,14 +143,8 @@ test('Explicit input tests in async situations', (done: () => void) => {
},
},
{
- getActions: uiActions.getTriggerCompatibleActions,
- getAllEmbeddableFactories: start.getEmbeddableFactories,
getEmbeddableFactory: start.getEmbeddableFactory,
- notifications: coreStart.notifications,
- overlays: coreStart.overlays,
- application: coreStart.application,
- inspector: {} as any,
- SavedObjectFinder: () => null,
+ panelComponent: testPanel,
}
);
diff --git a/src/plugins/home/public/application/components/__snapshots__/add_data.test.js.snap b/src/plugins/home/public/application/components/__snapshots__/add_data.test.js.snap
index 3b3f86e579f1ad..2545bbcb5114d6 100644
--- a/src/plugins/home/public/application/components/__snapshots__/add_data.test.js.snap
+++ b/src/plugins/home/public/application/components/__snapshots__/add_data.test.js.snap
@@ -202,17 +202,17 @@ exports[`apmUiEnabled 1`] = `
}
textAlign="left"
- title="SIEM"
+ title="Security"
titleSize="xs"
/>
@@ -468,17 +468,17 @@ exports[`isNewKibanaInstance 1`] = `
}
textAlign="left"
- title="SIEM"
+ title="Security"
titleSize="xs"
/>
@@ -765,17 +765,17 @@ exports[`mlEnabled 1`] = `
}
textAlign="left"
- title="SIEM"
+ title="Security"
titleSize="xs"
/>
@@ -1067,17 +1067,17 @@ exports[`render 1`] = `
}
textAlign="left"
- title="SIEM"
+ title="Security"
titleSize="xs"
/>
diff --git a/src/plugins/home/public/application/components/add_data.js b/src/plugins/home/public/application/components/add_data.js
index 2f7f07a0e4549d..fa1327b3fcd080 100644
--- a/src/plugins/home/public/application/components/add_data.js
+++ b/src/plugins/home/public/application/components/add_data.js
@@ -80,11 +80,11 @@ const AddDataUi = ({ apmUiEnabled, isNewKibanaInstance, intl, mlEnabled }) => {
};
const siemData = {
title: intl.formatMessage({
- id: 'home.addData.siem.nameTitle',
- defaultMessage: 'SIEM',
+ id: 'home.addData.securitySolution.nameTitle',
+ defaultMessage: 'Security',
}),
description: intl.formatMessage({
- id: 'home.addData.siem.nameDescription',
+ id: 'home.addData.securitySolution.nameDescription',
defaultMessage:
'Centralize security events for interactive investigation in ready-to-go visualizations.',
}),
@@ -221,11 +221,11 @@ const AddDataUi = ({ apmUiEnabled, isNewKibanaInstance, intl, mlEnabled }) => {
footer={
diff --git a/src/plugins/home/public/application/components/tutorial_directory.js b/src/plugins/home/public/application/components/tutorial_directory.js
index 4d2cec158f63e5..774b23af11ac85 100644
--- a/src/plugins/home/public/application/components/tutorial_directory.js
+++ b/src/plugins/home/public/application/components/tutorial_directory.js
@@ -75,10 +75,10 @@ class TutorialDirectoryUi extends React.Component {
}),
},
{
- id: 'siem',
+ id: 'security',
name: this.props.intl.formatMessage({
- id: 'home.tutorial.tabs.siemTitle',
- defaultMessage: 'SIEM',
+ id: 'home.tutorial.tabs.securitySolutionTitle',
+ defaultMessage: 'Security',
}),
},
{
diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx
index edb96f119385ec..b6205a8731dfa9 100644
--- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx
+++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx
@@ -21,7 +21,11 @@ import React, { Component } from 'react';
import { EuiPanel, EuiSpacer, EuiCallOut } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-import { indexPatterns, IndexPatternAttributes } from '../../../../../../../plugins/data/public';
+import {
+ indexPatterns,
+ IndexPatternAttributes,
+ UI_SETTINGS,
+} from '../../../../../../../plugins/data/public';
import { MAX_SEARCH_SIZE } from '../../constants';
import {
getIndices,
@@ -82,7 +86,8 @@ export class StepIndexPattern extends Component fieldWildcardMatcher(filters, uiSettings.get('metaFields')),
+ (filters: string[]) => fieldWildcardMatcher(filters, uiSettings.get(UI_SETTINGS.META_FIELDS)),
[uiSettings]
);
diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/scripting_help/test_script.tsx b/src/plugins/index_pattern_management/public/components/field_editor/components/scripting_help/test_script.tsx
index a1b7289efee210..c97f19f59d340d 100644
--- a/src/plugins/index_pattern_management/public/components/field_editor/components/scripting_help/test_script.tsx
+++ b/src/plugins/index_pattern_management/public/components/field_editor/components/scripting_help/test_script.tsx
@@ -35,7 +35,12 @@ import {
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
-import { esQuery, IndexPattern, Query } from '../../../../../../../plugins/data/public';
+import {
+ esQuery,
+ IndexPattern,
+ Query,
+ UI_SETTINGS,
+} from '../../../../../../../plugins/data/public';
import { context as contextType } from '../../../../../../kibana_react/public';
import { IndexPatternManagmentContextValue } from '../../../../types';
import { ExecuteScript } from '../../types';
@@ -244,7 +249,7 @@ export class TestScript extends Component {
showDatePicker={false}
showQueryInput={true}
query={{
- language: this.context.services.uiSettings.get('search:queryLanguage'),
+ language: this.context.services.uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE),
query: '',
}}
onQuerySubmit={this.previewScript}
diff --git a/src/plugins/inspector/public/views/data/components/data_table.tsx b/src/plugins/inspector/public/views/data/components/data_table.tsx
index 69be069272f79c..0fdf3d9b13e333 100644
--- a/src/plugins/inspector/public/views/data/components/data_table.tsx
+++ b/src/plugins/inspector/public/views/data/components/data_table.tsx
@@ -37,6 +37,7 @@ import { DataDownloadOptions } from './download_options';
import { DataViewRow, DataViewColumn } from '../types';
import { TabularData } from '../../../../common/adapters/data/types';
import { IUiSettingsClient } from '../../../../../../core/public';
+import { CSV_SEPARATOR_SETTING, CSV_QUOTE_VALUES_SETTING } from '../../../../../share/public';
interface DataTableFormatState {
columns: DataViewColumn[];
@@ -58,8 +59,8 @@ export class DataTableFormat extends Component {
+export interface StartServices {
plugins: Plugins;
self: OwnContract;
- core: CoreStart;
+ core: Core;
}
-export type StartServicesGetter = () => StartServices<
- Plugins,
- OwnContract
->;
+export type StartServicesGetter<
+ Plugins = unknown,
+ OwnContract = unknown,
+ Core = CoreStart
+> = () => StartServices;
/**
* Use this utility to create a synchronous *start* service getter in *setup*
diff --git a/src/plugins/share/common/constants.ts b/src/plugins/share/common/constants.ts
new file mode 100644
index 00000000000000..7ad8e39c279d33
--- /dev/null
+++ b/src/plugins/share/common/constants.ts
@@ -0,0 +1,21 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export const CSV_SEPARATOR_SETTING = 'csv:separator';
+export const CSV_QUOTE_VALUES_SETTING = 'csv:quoteValues';
diff --git a/src/plugins/share/public/index.ts b/src/plugins/share/public/index.ts
index 183219645467e1..e3d6c41a278cd9 100644
--- a/src/plugins/share/public/index.ts
+++ b/src/plugins/share/public/index.ts
@@ -17,6 +17,8 @@
* under the License.
*/
+export { CSV_QUOTE_VALUES_SETTING, CSV_SEPARATOR_SETTING } from '../common/constants';
+
export { UrlGeneratorStateMapping } from './url_generators/url_generator_definition';
export { SharePluginSetup, SharePluginStart } from './plugin';
diff --git a/src/plugins/share/public/url_generators/url_generator_service.ts b/src/plugins/share/public/url_generators/url_generator_service.ts
index 13c1b94acdd07e..b63e2a45d6812c 100644
--- a/src/plugins/share/public/url_generators/url_generator_service.ts
+++ b/src/plugins/share/public/url_generators/url_generator_service.ts
@@ -24,7 +24,7 @@ import { UrlGeneratorInternal } from './url_generator_internal';
import { UrlGeneratorContract } from './url_generator_contract';
export interface UrlGeneratorsStart {
- getUrlGenerator: (urlGeneratorId: UrlGeneratorId) => UrlGeneratorContract;
+ getUrlGenerator: (urlGeneratorId: T) => UrlGeneratorContract;
}
export interface UrlGeneratorsSetup {
diff --git a/src/plugins/share/server/index.ts b/src/plugins/share/server/index.ts
index 9e574314f80000..ff419ce68d46b0 100644
--- a/src/plugins/share/server/index.ts
+++ b/src/plugins/share/server/index.ts
@@ -20,6 +20,8 @@
import { PluginInitializerContext } from '../../../core/server';
import { SharePlugin } from './plugin';
+export { CSV_QUOTE_VALUES_SETTING, CSV_SEPARATOR_SETTING } from '../common/constants';
+
export function plugin(initializerContext: PluginInitializerContext) {
return new SharePlugin(initializerContext);
}
diff --git a/src/plugins/share/server/plugin.ts b/src/plugins/share/server/plugin.ts
index 0d9f183d13404b..e444cb1658d957 100644
--- a/src/plugins/share/server/plugin.ts
+++ b/src/plugins/share/server/plugin.ts
@@ -17,9 +17,12 @@
* under the License.
*/
+import { i18n } from '@kbn/i18n';
+import { schema } from '@kbn/config-schema';
import { CoreSetup, Plugin, PluginInitializerContext } from 'kibana/server';
import { createRoutes } from './routes/create_routes';
import { url } from './saved_objects';
+import { CSV_SEPARATOR_SETTING, CSV_QUOTE_VALUES_SETTING } from '../common/constants';
export class SharePlugin implements Plugin {
constructor(private readonly initializerContext: PluginInitializerContext) {}
@@ -27,6 +30,28 @@ export class SharePlugin implements Plugin {
public async setup(core: CoreSetup) {
createRoutes(core, this.initializerContext.logger.get());
core.savedObjects.registerType(url);
+ core.uiSettings.register({
+ [CSV_SEPARATOR_SETTING]: {
+ name: i18n.translate('share.advancedSettings.csv.separatorTitle', {
+ defaultMessage: 'CSV separator',
+ }),
+ value: ',',
+ description: i18n.translate('share.advancedSettings.csv.separatorText', {
+ defaultMessage: 'Separate exported values with this string',
+ }),
+ schema: schema.string(),
+ },
+ [CSV_QUOTE_VALUES_SETTING]: {
+ name: i18n.translate('share.advancedSettings.csv.quoteValuesTitle', {
+ defaultMessage: 'Quote CSV values',
+ }),
+ value: true,
+ description: i18n.translate('share.advancedSettings.csv.quoteValuesText', {
+ defaultMessage: 'Should values be quoted in csv exports?',
+ }),
+ schema: schema.boolean(),
+ },
+ });
}
public start() {
diff --git a/src/plugins/vis_default_editor/public/components/controls/filters.tsx b/src/plugins/vis_default_editor/public/components/controls/filters.tsx
index 7a655b935a45e6..9a9933b5e1e833 100644
--- a/src/plugins/vis_default_editor/public/components/controls/filters.tsx
+++ b/src/plugins/vis_default_editor/public/components/controls/filters.tsx
@@ -23,7 +23,7 @@ import { htmlIdGenerator, EuiButton, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { useMount } from 'react-use';
-import { Query } from 'src/plugins/data/public';
+import { Query, UI_SETTINGS } from '../../../../data/public';
import { useKibana } from '../../../../kibana_react/public';
import { FilterRow } from './filter';
import { AggParamEditorProps } from '../agg_param_props';
@@ -68,7 +68,7 @@ function FiltersParamEditor({ agg, value = [], setValue }: AggParamEditorProps
}
type="questionInCircle"
diff --git a/src/plugins/vis_type_table/public/agg_table/agg_table.js b/src/plugins/vis_type_table/public/agg_table/agg_table.js
index f67dcf42adff6c..bd7626a493338e 100644
--- a/src/plugins/vis_type_table/public/agg_table/agg_table.js
+++ b/src/plugins/vis_type_table/public/agg_table/agg_table.js
@@ -17,6 +17,7 @@
* under the License.
*/
import _ from 'lodash';
+import { CSV_SEPARATOR_SETTING, CSV_QUOTE_VALUES_SETTING } from '../../../share/public';
import aggTableTemplate from './agg_table.html';
import { getFormatService } from '../services';
import { i18n } from '@kbn/i18n';
@@ -47,8 +48,8 @@ export function KbnAggTable(config, RecursionHelper) {
self._saveAs = require('@elastic/filesaver').saveAs;
self.csv = {
- separator: config.get('csv:separator'),
- quoteValues: config.get('csv:quoteValues'),
+ separator: config.get(CSV_SEPARATOR_SETTING),
+ quoteValues: config.get(CSV_QUOTE_VALUES_SETTING),
};
self.exportAsCsv = function (formatted) {
diff --git a/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js b/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js
index 1bc979399b1b16..a624ff72ead699 100644
--- a/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js
+++ b/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js
@@ -30,6 +30,7 @@ import _ from 'lodash';
import { expect } from 'chai';
import sinon from 'sinon';
import invoke from '../helpers/invoke_series_fn.js';
+import { UI_SETTINGS } from '../../../../data/server';
function stubRequestAndServer(response, indexPatternSavedObjects = []) {
return {
@@ -216,14 +217,14 @@ describe('es', () => {
it('sets ignore_throttled=true on the request', () => {
config.index = 'beer';
- tlConfig.settings['search:includeFrozen'] = false;
+ tlConfig.settings[UI_SETTINGS.SEARCH_INCLUDE_FROZEN] = false;
const request = fn(config, tlConfig, emptyScriptedFields);
expect(request.ignore_throttled).to.equal(true);
});
it('sets no timeout if elasticsearch.shardTimeout is set to 0', () => {
- tlConfig.settings['search:includeFrozen'] = true;
+ tlConfig.settings[UI_SETTINGS.SEARCH_INCLUDE_FROZEN] = true;
config.index = 'beer';
const request = fn(config, tlConfig, emptyScriptedFields);
diff --git a/src/plugins/vis_type_timelion/server/series_functions/es/lib/build_request.js b/src/plugins/vis_type_timelion/server/series_functions/es/lib/build_request.js
index 65b28fb833279a..bc0e368fbdab17 100644
--- a/src/plugins/vis_type_timelion/server/series_functions/es/lib/build_request.js
+++ b/src/plugins/vis_type_timelion/server/series_functions/es/lib/build_request.js
@@ -21,6 +21,7 @@ import _ from 'lodash';
import moment from 'moment';
import { buildAggBody } from './agg_body';
import createDateAgg from './create_date_agg';
+import { UI_SETTINGS } from '../../../../../data/server';
export default function buildRequest(config, tlConfig, scriptedFields, timeout) {
const bool = { must: [] };
@@ -78,7 +79,7 @@ export default function buildRequest(config, tlConfig, scriptedFields, timeout)
const request = {
index: config.index,
- ignore_throttled: !tlConfig.settings['search:includeFrozen'],
+ ignore_throttled: !tlConfig.settings[UI_SETTINGS.SEARCH_INCLUDE_FROZEN],
body: {
query: {
bool: bool,
diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/get_default_query_language.js b/src/plugins/vis_type_timeseries/public/application/components/lib/get_default_query_language.js
index 972f937ad109d5..84da28718e3235 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/lib/get_default_query_language.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/lib/get_default_query_language.js
@@ -18,7 +18,8 @@
*/
import { getUISettings } from '../../../services';
+import { UI_SETTINGS } from '../../../../../data/public';
export function getDefaultQueryLanguage() {
- return getUISettings().get('search:queryLanguage');
+ return getUISettings().get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE);
}
diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.test.js
index 71e82770bfa03e..308579126eeb1d 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.test.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.test.js
@@ -20,6 +20,7 @@
import { createTickFormatter } from './tick_formatter';
import { getFieldFormatsRegistry } from '../../../../../../test_utils/public/stub_field_formats';
import { setFieldFormats } from '../../../services';
+import { UI_SETTINGS } from '../../../../../data/public';
const mockUiSettings = {
get: (item) => {
@@ -28,11 +29,11 @@ const mockUiSettings = {
getUpdate$: () => ({
subscribe: jest.fn(),
}),
- 'query:allowLeadingWildcards': true,
- 'query:queryString:options': {},
- 'courier:ignoreFilterIfFieldNotInIndex': true,
+ [UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS]: true,
+ [UI_SETTINGS.QUERY_STRING_OPTIONS]: {},
+ [UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX]: true,
'dateFormat:tz': 'Browser',
- 'format:defaultTypeMap': {},
+ [UI_SETTINGS.FORMAT_DEFAULT_TYPE_MAP]: {},
};
const mockCore = {
@@ -55,7 +56,7 @@ describe('createTickFormatter(format, template)', () => {
test('returns a percent with percent formatter', () => {
const config = {
- 'format:percent:defaultPattern': '0.[00]%',
+ [UI_SETTINGS.FORMAT_PERCENT_DEFAULT_PATTERN]: '0.[00]%',
};
const fn = createTickFormatter('percent', null, (key) => config[key]);
expect(fn(0.5556)).toEqual('55.56%');
@@ -63,7 +64,7 @@ describe('createTickFormatter(format, template)', () => {
test('returns a byte formatted string with byte formatter', () => {
const config = {
- 'format:bytes:defaultPattern': '0.0b',
+ [UI_SETTINGS.FORMAT_BYTES_DEFAULT_PATTERN]: '0.0b',
};
const fn = createTickFormatter('bytes', null, (key) => config[key]);
expect(fn(1500 ^ 10)).toEqual('1.5KB');
@@ -76,7 +77,7 @@ describe('createTickFormatter(format, template)', () => {
test('returns a located string with custom locale setting', () => {
const config = {
- 'format:number:defaultLocale': 'fr',
+ [UI_SETTINGS.FORMAT_NUMBER_DEFAULT_LOCALE]: 'fr',
};
const fn = createTickFormatter('0,0.0', null, (key) => config[key]);
expect(fn(1500)).toEqual('1 500,0');
@@ -99,7 +100,7 @@ describe('createTickFormatter(format, template)', () => {
test('returns formatted value if passed a bad template', () => {
const config = {
- 'format:number:defaultPattern': '0,0.[00]',
+ [UI_SETTINGS.FORMAT_NUMBER_DEFAULT_PATTERN]: '0,0.[00]',
};
const fn = createTickFormatter('number', '{{value', (key) => config[key]);
expect(fn(1.5556)).toEqual('1.56');
diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js
index 0c53ddd3f0ba82..a96890d4d15029 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js
@@ -29,7 +29,7 @@ import { PanelConfig } from './panel_config';
import { createBrushHandler } from '../lib/create_brush_handler';
import { fetchFields } from '../lib/fetch_fields';
import { extractIndexPatterns } from '../../../../../plugins/vis_type_timeseries/common/extract_index_patterns';
-import { esKuery } from '../../../../../plugins/data/public';
+import { esKuery, UI_SETTINGS } from '../../../../../plugins/data/public';
import { getSavedObjectsClient, getUISettings, getDataStart, getCoreStart } from '../../services';
import { CoreStartContextProvider } from '../contexts/query_input_bar_context';
@@ -89,7 +89,9 @@ export class VisEditor extends Component {
isValidKueryQuery = (filterQuery) => {
if (filterQuery && filterQuery.language === 'kuery') {
try {
- const queryOptions = this.coreContext.uiSettings.get('query:allowLeadingWildcards');
+ const queryOptions = this.coreContext.uiSettings.get(
+ UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS
+ );
esKuery.fromKueryExpression(filterQuery.query, { allowLeadingWildcards: queryOptions });
} catch (error) {
return false;
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_requests/multi_search_request.js b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_requests/multi_search_request.js
index 93a4eaba4ad9ec..9ada39e3595890 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_requests/multi_search_request.js
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_requests/multi_search_request.js
@@ -17,12 +17,15 @@
* under the License.
*/
import { AbstractSearchRequest } from './abstract_request';
+import { UI_SETTINGS } from '../../../../../data/server';
const SEARCH_METHOD = 'msearch';
export class MultiSearchRequest extends AbstractSearchRequest {
async search(searches) {
- const includeFrozen = await this.req.getUiSettingsService().get('search:includeFrozen');
+ const includeFrozen = await this.req
+ .getUiSettingsService()
+ .get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN);
const multiSearchBody = searches.reduce(
(acc, { body, index }) => [
...acc,
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_requests/multi_search_request.test.js b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_requests/multi_search_request.test.js
index 1e28965a35793f..c113db76332b76 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_requests/multi_search_request.test.js
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_requests/multi_search_request.test.js
@@ -17,6 +17,7 @@
* under the License.
*/
import { MultiSearchRequest } from './multi_search_request';
+import { UI_SETTINGS } from '../../../../../data/server';
describe('MultiSearchRequest', () => {
let searchRequest;
@@ -51,7 +52,7 @@ describe('MultiSearchRequest', () => {
expect(responses).toEqual([]);
expect(req.getUiSettingsService).toHaveBeenCalled();
- expect(getServiceMock).toHaveBeenCalledWith('search:includeFrozen');
+ expect(getServiceMock).toHaveBeenCalledWith(UI_SETTINGS.SEARCH_INCLUDE_FROZEN);
expect(callWithRequest).toHaveBeenCalledWith(req, 'msearch', {
body: [
{ ignoreUnavailable: true, index: 'index' },
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_requests/single_search_request.js b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_requests/single_search_request.js
index 110deb6a9bc1b6..7d8b60a7e45957 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_requests/single_search_request.js
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_requests/single_search_request.js
@@ -17,12 +17,15 @@
* under the License.
*/
import { AbstractSearchRequest } from './abstract_request';
+import { UI_SETTINGS } from '../../../../../data/server';
const SEARCH_METHOD = 'search';
export class SingleSearchRequest extends AbstractSearchRequest {
async search([{ body, index }]) {
- const includeFrozen = await this.req.getUiSettingsService().get('search:includeFrozen');
+ const includeFrozen = await this.req
+ .getUiSettingsService()
+ .get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN);
const resp = await this.callWithRequest(this.req, SEARCH_METHOD, {
ignore_throttled: !includeFrozen,
body,
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_requests/single_search_request.test.js b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_requests/single_search_request.test.js
index 043bd52d87aad5..b899814f2fe133 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_requests/single_search_request.test.js
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_requests/single_search_request.test.js
@@ -17,6 +17,7 @@
* under the License.
*/
import { SingleSearchRequest } from './single_search_request';
+import { UI_SETTINGS } from '../../../../../data/server';
describe('SingleSearchRequest', () => {
let searchRequest;
@@ -48,7 +49,7 @@ describe('SingleSearchRequest', () => {
expect(responses).toEqual([{}]);
expect(req.getUiSettingsService).toHaveBeenCalled();
- expect(getServiceMock).toHaveBeenCalledWith('search:includeFrozen');
+ expect(getServiceMock).toHaveBeenCalledWith(UI_SETTINGS.SEARCH_INCLUDE_FROZEN);
expect(callWithRequest).toHaveBeenCalledWith(req, 'search', {
body: 'body',
index: 'index',
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_es_query_uisettings.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_es_query_uisettings.js
index 42b8681f142e05..b427e5f12cadc9 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_es_query_uisettings.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_es_query_uisettings.js
@@ -17,12 +17,14 @@
* under the License.
*/
+import { UI_SETTINGS } from '../../../../../data/server';
+
export async function getEsQueryConfig(req) {
const uiSettings = req.getUiSettingsService();
- const allowLeadingWildcards = await uiSettings.get('query:allowLeadingWildcards');
- const queryStringOptions = await uiSettings.get('query:queryString:options');
+ const allowLeadingWildcards = await uiSettings.get(UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS);
+ const queryStringOptions = await uiSettings.get(UI_SETTINGS.QUERY_STRING_OPTIONS);
const ignoreFilterIfFieldNotInIndex = await uiSettings.get(
- 'courier:ignoreFilterIfFieldNotInIndex'
+ UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX
);
return {
allowLeadingWildcards,
diff --git a/src/plugins/visualizations/common/constants.ts b/src/plugins/visualizations/common/constants.ts
new file mode 100644
index 00000000000000..9129f060c5eef4
--- /dev/null
+++ b/src/plugins/visualizations/common/constants.ts
@@ -0,0 +1,20 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export const VISUALIZE_ENABLE_LABS_SETTING = 'visualize:enableLabs';
diff --git a/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts b/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts
index 81794c31527adb..45c750de05ae17 100644
--- a/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts
+++ b/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts
@@ -29,6 +29,7 @@ import {
getCapabilities,
} from '../services';
import { VisualizeEmbeddableFactoryDeps } from './visualize_embeddable_factory';
+import { VISUALIZE_ENABLE_LABS_SETTING } from '../../common/constants';
export const createVisEmbeddableFromObject = (deps: VisualizeEmbeddableFactoryDeps) => async (
vis: Vis,
@@ -44,7 +45,7 @@ export const createVisEmbeddableFromObject = (deps: VisualizeEmbeddableFactoryDe
const editUrl = visId
? getHttp().basePath.prepend(`/app/visualize${savedVisualizations.urlFor(visId)}`)
: '';
- const isLabsEnabled = getUISettings().get('visualize:enableLabs');
+ const isLabsEnabled = getUISettings().get(VISUALIZE_ENABLE_LABS_SETTING);
if (!isLabsEnabled && vis.type.stage === 'experimental') {
return new DisabledLabEmbeddable(vis.title, input);
diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx
index c4aa4c262edb06..c4267c9a36f784 100644
--- a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx
+++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx
@@ -43,6 +43,7 @@ import { convertToSerializedVis } from '../saved_visualizations/_saved_vis';
import { createVisEmbeddableFromObject } from './create_vis_embeddable_from_object';
import { StartServicesGetter } from '../../../kibana_utils/public';
import { VisualizationsStartDeps } from '../plugin';
+import { VISUALIZE_ENABLE_LABS_SETTING } from '../../common/constants';
interface VisualizationAttributes extends SavedObjectAttributes {
visState: string;
@@ -82,7 +83,7 @@ export class VisualizeEmbeddableFactory
if (!visType) {
return false;
}
- if (getUISettings().get('visualize:enableLabs')) {
+ if (getUISettings().get(VISUALIZE_ENABLE_LABS_SETTING)) {
return true;
}
return visType.stage !== 'experimental';
diff --git a/src/plugins/visualizations/public/index.ts b/src/plugins/visualizations/public/index.ts
index e475684ed5934d..0bbf862216ed5b 100644
--- a/src/plugins/visualizations/public/index.ts
+++ b/src/plugins/visualizations/public/index.ts
@@ -17,8 +17,6 @@
* under the License.
*/
-import './index.scss';
-
import { PublicContract } from '@kbn/utility-types';
import { PluginInitializerContext } from 'src/core/public';
import { VisualizationsPlugin, VisualizationsSetup, VisualizationsStart } from './plugin';
@@ -53,3 +51,4 @@ export {
VisSavedObject,
VisResponseValue,
} from './types';
+export { VISUALIZE_ENABLE_LABS_SETTING } from '../common/constants';
diff --git a/src/plugins/visualizations/public/mocks.ts b/src/plugins/visualizations/public/mocks.ts
index 70c3bc2c1ed05d..05644eddc5fcac 100644
--- a/src/plugins/visualizations/public/mocks.ts
+++ b/src/plugins/visualizations/public/mocks.ts
@@ -66,6 +66,7 @@ const createInstance = async () => {
inspector: inspectorPluginMock.createStartContract(),
uiActions: uiActionsPluginMock.createStartContract(),
application: applicationServiceMock.createStartContract(),
+ embeddable: embeddablePluginMock.createStartContract(),
});
return {
diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts
index ef64eccfea31f2..3546fa40564915 100644
--- a/src/plugins/visualizations/public/plugin.ts
+++ b/src/plugins/visualizations/public/plugin.ts
@@ -17,6 +17,8 @@
* under the License.
*/
+import './index.scss';
+
import {
PluginInitializerContext,
CoreSetup,
@@ -45,6 +47,7 @@ import {
setChrome,
setOverlays,
setSavedSearchLoader,
+ setEmbeddable,
} from './services';
import {
VISUALIZE_EMBEDDABLE_TYPE,
@@ -52,7 +55,7 @@ import {
createVisEmbeddableFromObject,
} from './embeddable';
import { ExpressionsSetup, ExpressionsStart } from '../../expressions/public';
-import { EmbeddableSetup } from '../../embeddable/public';
+import { EmbeddableSetup, EmbeddableStart } from '../../embeddable/public';
import { visualization as visualizationFunction } from './expressions/visualization_function';
import { visualization as visualizationRenderer } from './expressions/visualization_renderer';
import { range as rangeExpressionFunction } from './expression_functions/range';
@@ -102,6 +105,7 @@ export interface VisualizationsSetupDeps {
export interface VisualizationsStartDeps {
data: DataPublicPluginStart;
expressions: ExpressionsStart;
+ embeddable: EmbeddableStart;
inspector: InspectorStart;
uiActions: UiActionsStart;
application: ApplicationStart;
@@ -151,11 +155,12 @@ export class VisualizationsPlugin
public start(
core: CoreStart,
- { data, expressions, uiActions }: VisualizationsStartDeps
+ { data, expressions, uiActions, embeddable }: VisualizationsStartDeps
): VisualizationsStart {
const types = this.types.start();
setI18n(core.i18n);
setTypes(types);
+ setEmbeddable(embeddable);
setApplication(core.application);
setCapabilities(core.application.capabilities);
setHttp(core.http);
diff --git a/src/plugins/visualizations/public/services.ts b/src/plugins/visualizations/public/services.ts
index 15055022af8aab..0761b8862e8e36 100644
--- a/src/plugins/visualizations/public/services.ts
+++ b/src/plugins/visualizations/public/services.ts
@@ -40,6 +40,7 @@ import { ExpressionsStart } from '../../../plugins/expressions/public';
import { UiActionsStart } from '../../../plugins/ui_actions/public';
import { SavedVisualizationsLoader } from './saved_visualizations';
import { SavedObjectLoader } from '../../saved_objects/public';
+import { EmbeddableStart } from '../../embeddable/public';
export const [getUISettings, setUISettings] = createGetterSetter('UISettings');
@@ -49,6 +50,8 @@ export const [getHttp, setHttp] = createGetterSetter('Http');
export const [getApplication, setApplication] = createGetterSetter('Application');
+export const [getEmbeddable, setEmbeddable] = createGetterSetter('Embeddable');
+
export const [getSavedObjects, setSavedObjects] = createGetterSetter(
'SavedObjects'
);
diff --git a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx
index cea92b1db93aad..1a970e505b7c71 100644
--- a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx
+++ b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx
@@ -29,6 +29,7 @@ import { TypeSelection } from './type_selection';
import { TypesStart, VisType, VisTypeAlias } from '../vis_types';
import { UsageCollectionSetup } from '../../../../plugins/usage_collection/public';
import { EMBEDDABLE_ORIGINATING_APP_PARAM } from '../../../embeddable/public';
+import { VISUALIZE_ENABLE_LABS_SETTING } from '../../common/constants';
interface TypeSelectionProps {
isOpen: boolean;
@@ -65,7 +66,7 @@ class NewVisModal extends React.Component originatingApp;
const visStateToEditorState = () => {
diff --git a/src/plugins/visualize/public/application/listing/visualize_listing.js b/src/plugins/visualize/public/application/listing/visualize_listing.js
index 228cfa1e9e4922..e8e8d92034113a 100644
--- a/src/plugins/visualize/public/application/listing/visualize_listing.js
+++ b/src/plugins/visualize/public/application/listing/visualize_listing.js
@@ -25,6 +25,8 @@ import { i18n } from '@kbn/i18n';
import { getServices } from '../../kibana_services';
import { syncQueryStateWithUrl } from '../../../../data/public';
+import { VISUALIZE_ENABLE_LABS_SETTING } from '../../../../visualizations/public';
+
import { EuiLink } from '@elastic/eui';
import React from 'react';
@@ -120,7 +122,7 @@ export function VisualizeListingController($scope, createNewVis, kbnUrlStateStor
}
this.fetchItems = (filter) => {
- const isLabsEnabled = uiSettings.get('visualize:enableLabs');
+ const isLabsEnabled = uiSettings.get(VISUALIZE_ENABLE_LABS_SETTING);
return savedVisualizations
.findListItems(filter, savedObjectsPublic.settings.getListingLimit())
.then((result) => {
diff --git a/test/functional/apps/bundles/index.js b/test/functional/apps/bundles/index.js
index 503517a98c69e8..ead6412564751f 100644
--- a/test/functional/apps/bundles/index.js
+++ b/test/functional/apps/bundles/index.js
@@ -25,7 +25,7 @@ export default function ({ getService }) {
const supertest = getService('supertest');
describe('bundle compression', function () {
- this.tags('ciGroup12');
+ this.tags(['ciGroup12', 'skipCoverage']);
let buildNum;
before(async () => {
diff --git a/test/functional/apps/context/_filters.js b/test/functional/apps/context/_filters.js
index 470ef462b9d9d2..66888d441954e5 100644
--- a/test/functional/apps/context/_filters.js
+++ b/test/functional/apps/context/_filters.js
@@ -17,8 +17,6 @@
* under the License.
*/
-import expect from '@kbn/expect';
-
const TEST_INDEX_PATTERN = 'logstash-*';
const TEST_ANCHOR_ID = 'AU_x3_BrGFA8no6QjjaI';
const TEST_ANCHOR_FILTER_FIELD = 'geo.src';
@@ -40,20 +38,19 @@ export default function ({ getService, getPageObjects }) {
});
it('inclusive filter should be addable via expanded doc table rows', async function () {
- await docTable.toggleRowExpanded({ isAnchorRow: true });
-
- await retry.try(async () => {
+ await retry.waitFor(`filter ${TEST_ANCHOR_FILTER_FIELD} in filterbar`, async () => {
+ await docTable.toggleRowExpanded({ isAnchorRow: true });
const anchorDetailsRow = await docTable.getAnchorDetailsRow();
await docTable.addInclusiveFilter(anchorDetailsRow, TEST_ANCHOR_FILTER_FIELD);
await PageObjects.context.waitUntilContextLoadingHasFinished();
- expect(
- await filterBar.hasFilter(TEST_ANCHOR_FILTER_FIELD, TEST_ANCHOR_FILTER_VALUE, true)
- ).to.be(true);
+
+ return await filterBar.hasFilter(TEST_ANCHOR_FILTER_FIELD, TEST_ANCHOR_FILTER_VALUE, true);
+ });
+ await retry.waitFor(`filter matching docs in docTable`, async () => {
const fields = await docTable.getFields();
- const hasOnlyFilteredRows = fields
+ return fields
.map((row) => row[2])
.every((fieldContent) => fieldContent === TEST_ANCHOR_FILTER_VALUE);
- expect(hasOnlyFilteredRows).to.be(true);
});
});
@@ -64,26 +61,27 @@ export default function ({ getService, getPageObjects }) {
await filterBar.toggleFilterEnabled(TEST_ANCHOR_FILTER_FIELD);
await PageObjects.context.waitUntilContextLoadingHasFinished();
- await retry.try(async () => {
- expect(
- await filterBar.hasFilter(TEST_ANCHOR_FILTER_FIELD, TEST_ANCHOR_FILTER_VALUE, false)
- ).to.be(true);
+ await retry.waitFor(`a disabled filter in filterbar`, async () => {
+ return await filterBar.hasFilter(TEST_ANCHOR_FILTER_FIELD, TEST_ANCHOR_FILTER_VALUE, false);
+ });
+
+ await retry.waitFor('filters are disabled', async () => {
const fields = await docTable.getFields();
const hasOnlyFilteredRows = fields
.map((row) => row[2])
.every((fieldContent) => fieldContent === TEST_ANCHOR_FILTER_VALUE);
- expect(hasOnlyFilteredRows).to.be(false);
+ return hasOnlyFilteredRows === false;
});
});
it('filter for presence should be addable via expanded doc table rows', async function () {
await docTable.toggleRowExpanded({ isAnchorRow: true });
- await retry.try(async () => {
+ await retry.waitFor('an exists filter in the filterbar', async () => {
const anchorDetailsRow = await docTable.getAnchorDetailsRow();
await docTable.addExistsFilter(anchorDetailsRow, TEST_ANCHOR_FILTER_FIELD);
await PageObjects.context.waitUntilContextLoadingHasFinished();
- expect(await filterBar.hasFilter(TEST_ANCHOR_FILTER_FIELD, 'exists', true)).to.be(true);
+ return await filterBar.hasFilter(TEST_ANCHOR_FILTER_FIELD, 'exists', true);
});
});
});
diff --git a/test/functional/apps/context/_size.js b/test/functional/apps/context/_size.js
index 3beb070b50deb1..067a23daacb4a6 100644
--- a/test/functional/apps/context/_size.js
+++ b/test/functional/apps/context/_size.js
@@ -16,69 +16,69 @@
* specific language governing permissions and limitations
* under the License.
*/
-
-import expect from '@kbn/expect';
-
const TEST_INDEX_PATTERN = 'logstash-*';
const TEST_ANCHOR_ID = 'AU_x3_BrGFA8no6QjjaI';
-const TEST_DEFAULT_CONTEXT_SIZE = 7;
-const TEST_STEP_SIZE = 3;
+const TEST_DEFAULT_CONTEXT_SIZE = 2;
+const TEST_STEP_SIZE = 2;
export default function ({ getService, getPageObjects }) {
const kibanaServer = getService('kibanaServer');
const retry = getService('retry');
const docTable = getService('docTable');
const PageObjects = getPageObjects(['context']);
+ let expectedRowLength = 2 * TEST_DEFAULT_CONTEXT_SIZE + 1;
- // FLAKY: https://github.com/elastic/kibana/issues/53888
- describe.skip('context size', function contextSize() {
+ describe('context size', function contextSize() {
before(async function () {
await kibanaServer.uiSettings.update({
'context:defaultSize': `${TEST_DEFAULT_CONTEXT_SIZE}`,
'context:step': `${TEST_STEP_SIZE}`,
});
+ await PageObjects.context.navigateTo(TEST_INDEX_PATTERN, TEST_ANCHOR_ID);
});
it('should default to the `context:defaultSize` setting', async function () {
- await PageObjects.context.navigateTo(TEST_INDEX_PATTERN, TEST_ANCHOR_ID);
-
- await retry.try(async function () {
- expect(await docTable.getRowsText()).to.have.length(2 * TEST_DEFAULT_CONTEXT_SIZE + 1);
- });
- await retry.try(async function () {
- const predecessorCountPicker = await PageObjects.context.getPredecessorCountPicker();
- expect(await predecessorCountPicker.getAttribute('value')).to.equal(
- `${TEST_DEFAULT_CONTEXT_SIZE}`
- );
- });
- await retry.try(async function () {
- const successorCountPicker = await PageObjects.context.getSuccessorCountPicker();
- expect(await successorCountPicker.getAttribute('value')).to.equal(
- `${TEST_DEFAULT_CONTEXT_SIZE}`
- );
- });
+ await retry.waitFor(
+ `number of rows displayed initially is ${expectedRowLength}`,
+ async function () {
+ const rows = await docTable.getRowsText();
+ return rows.length === expectedRowLength;
+ }
+ );
+ await retry.waitFor(
+ `predecessor count picker is set to ${TEST_DEFAULT_CONTEXT_SIZE}`,
+ async function () {
+ const predecessorCountPicker = await PageObjects.context.getPredecessorCountPicker();
+ const value = await predecessorCountPicker.getAttribute('value');
+ return value === String(TEST_DEFAULT_CONTEXT_SIZE);
+ }
+ );
});
it('should increase according to the `context:step` setting when clicking the `load newer` button', async function () {
- await PageObjects.context.navigateTo(TEST_INDEX_PATTERN, TEST_ANCHOR_ID);
await PageObjects.context.clickPredecessorLoadMoreButton();
+ expectedRowLength += TEST_STEP_SIZE;
- await retry.try(async function () {
- expect(await docTable.getRowsText()).to.have.length(
- 2 * TEST_DEFAULT_CONTEXT_SIZE + TEST_STEP_SIZE + 1
- );
- });
+ await retry.waitFor(
+ `number of rows displayed after clicking load more predecessors is ${expectedRowLength}`,
+ async function () {
+ const rows = await docTable.getRowsText();
+ return rows.length === expectedRowLength;
+ }
+ );
});
it('should increase according to the `context:step` setting when clicking the `load older` button', async function () {
- await PageObjects.context.navigateTo(TEST_INDEX_PATTERN, TEST_ANCHOR_ID);
await PageObjects.context.clickSuccessorLoadMoreButton();
+ expectedRowLength += TEST_STEP_SIZE;
- await retry.try(async function () {
- expect(await docTable.getRowsText()).to.have.length(
- 2 * TEST_DEFAULT_CONTEXT_SIZE + TEST_STEP_SIZE + 1
- );
- });
+ await retry.waitFor(
+ `number of rows displayed after clicking load more successors is ${expectedRowLength}`,
+ async function () {
+ const rows = await docTable.getRowsText();
+ return rows.length === expectedRowLength;
+ }
+ );
});
});
}
diff --git a/test/functional/apps/dashboard/create_and_add_embeddables.js b/test/functional/apps/dashboard/create_and_add_embeddables.js
index ba715f3472b98d..f5c2496a9a5aaf 100644
--- a/test/functional/apps/dashboard/create_and_add_embeddables.js
+++ b/test/functional/apps/dashboard/create_and_add_embeddables.js
@@ -20,6 +20,7 @@
import expect from '@kbn/expect';
import { VisualizeConstants } from '../../../../src/plugins/visualize/public/application/visualize_constants';
+import { VISUALIZE_ENABLE_LABS_SETTING } from '../../../../src/plugins/visualizations/common/constants';
export default function ({ getService, getPageObjects }) {
const retry = getService('retry');
@@ -102,7 +103,7 @@ export default function ({ getService, getPageObjects }) {
before(async () => {
await PageObjects.header.clickStackManagement();
await PageObjects.settings.clickKibanaSettings();
- await PageObjects.settings.toggleAdvancedSettingCheckbox('visualize:enableLabs');
+ await PageObjects.settings.toggleAdvancedSettingCheckbox(VISUALIZE_ENABLE_LABS_SETTING);
});
it('should not display lab visualizations in add panel', async () => {
@@ -117,7 +118,7 @@ export default function ({ getService, getPageObjects }) {
after(async () => {
await PageObjects.header.clickStackManagement();
await PageObjects.settings.clickKibanaSettings();
- await PageObjects.settings.clearAdvancedSettings('visualize:enableLabs');
+ await PageObjects.settings.clearAdvancedSettings(VISUALIZE_ENABLE_LABS_SETTING);
await PageObjects.header.clickDashboard();
});
});
diff --git a/test/functional/apps/dashboard/dashboard_filter_bar.js b/test/functional/apps/dashboard/dashboard_filter_bar.js
index 6bc34a8b998a49..c931e6763f4830 100644
--- a/test/functional/apps/dashboard/dashboard_filter_bar.js
+++ b/test/functional/apps/dashboard/dashboard_filter_bar.js
@@ -217,6 +217,11 @@ export default function ({ getService, getPageObjects }) {
const hasWarningFieldFilter = await filterBar.hasFilter('extension', 'warn', true);
expect(hasWarningFieldFilter).to.be(true);
});
+
+ it('filter without an index pattern is rendred as a warning, if the dashboard has an index pattern', async function () {
+ const noIndexPatternFilter = await filterBar.hasFilter('banana', 'warn', true);
+ expect(noIndexPatternFilter).to.be(true);
+ });
});
});
}
diff --git a/test/functional/apps/visualize/_lab_mode.js b/test/functional/apps/visualize/_lab_mode.js
index b356d01cdb63bc..27c149b9e0e0a1 100644
--- a/test/functional/apps/visualize/_lab_mode.js
+++ b/test/functional/apps/visualize/_lab_mode.js
@@ -18,6 +18,7 @@
*/
import expect from '@kbn/expect';
+import { VISUALIZE_ENABLE_LABS_SETTING } from '../../../../src/plugins/visualizations/common/constants';
export default function ({ getService, getPageObjects }) {
const log = getService('log');
@@ -37,7 +38,7 @@ export default function ({ getService, getPageObjects }) {
// Navigate to advanced setting and disable lab mode
await PageObjects.header.clickStackManagement();
await PageObjects.settings.clickKibanaSettings();
- await PageObjects.settings.toggleAdvancedSettingCheckbox('visualize:enableLabs');
+ await PageObjects.settings.toggleAdvancedSettingCheckbox(VISUALIZE_ENABLE_LABS_SETTING);
// Expect the discover still to list that saved visualization in the open list
await PageObjects.header.clickDiscover();
@@ -51,7 +52,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.discover.closeLoadSaveSearchPanel();
await PageObjects.header.clickStackManagement();
await PageObjects.settings.clickKibanaSettings();
- await PageObjects.settings.clearAdvancedSettings('visualize:enableLabs');
+ await PageObjects.settings.clearAdvancedSettings(VISUALIZE_ENABLE_LABS_SETTING);
});
});
}
diff --git a/test/functional/apps/visualize/index.ts b/test/functional/apps/visualize/index.ts
index bd427577cd7877..42b82486dc13f7 100644
--- a/test/functional/apps/visualize/index.ts
+++ b/test/functional/apps/visualize/index.ts
@@ -18,6 +18,7 @@
*/
import { FtrProviderContext } from '../../ftr_provider_context.d';
+import { UI_SETTINGS } from '../../../../src/plugins/data/common';
// eslint-disable-next-line @typescript-eslint/no-namespace, import/no-default-export
export default function ({ getService, getPageObjects, loadTestFile }: FtrProviderContext) {
@@ -37,7 +38,7 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid
await esArchiver.load('visualize');
await kibanaServer.uiSettings.replace({
defaultIndex: 'logstash-*',
- 'format:bytes:defaultPattern': '0,0.[000]b',
+ [UI_SETTINGS.FORMAT_BYTES_DEFAULT_PATTERN]: '0,0.[000]b',
});
isOss = await PageObjects.common.isOss();
});
diff --git a/test/functional/fixtures/es_archiver/dashboard/current/kibana/data.json.gz b/test/functional/fixtures/es_archiver/dashboard/current/kibana/data.json.gz
index a052aad9450f56..ae78761fef0d34 100644
Binary files a/test/functional/fixtures/es_archiver/dashboard/current/kibana/data.json.gz and b/test/functional/fixtures/es_archiver/dashboard/current/kibana/data.json.gz differ
diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts
index 91e9c020a0e7c1..fe5694efc35da6 100644
--- a/test/functional/page_objects/common_page.ts
+++ b/test/functional/page_objects/common_page.ts
@@ -67,17 +67,17 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo
* @param appUrl Kibana URL
*/
private async loginIfPrompted(appUrl: string, insertTimestamp: boolean) {
+ // Disable the welcome screen. This is relevant for environments
+ // which don't allow to use the yml setting, e.g. cloud production.
+ // It is done here so it applies to logins but also to a login re-use.
+ await browser.setLocalStorageItem('home:welcome:show', 'false');
+
let currentUrl = await browser.getCurrentUrl();
log.debug(`currentUrl = ${currentUrl}\n appUrl = ${appUrl}`);
await testSubjects.find('kibanaChrome', 6 * defaultFindTimeout); // 60 sec waiting
const loginPage = currentUrl.includes('/login');
const wantedLoginPage = appUrl.includes('/login') || appUrl.includes('/logout');
- // Disable the welcome screen. This is relevant for environments
- // which don't allow to use the yml setting, e.g. cloud production.
- // It is done here so it applies to logins but also to a login re-use.
- await browser.setLocalStorageItem('home:welcome:show', 'false');
-
if (loginPage && !wantedLoginPage) {
log.debug('Found login page');
if (config.get('security.disableTestUser')) {
diff --git a/test/scripts/jenkins_ci_group.sh b/test/scripts/jenkins_ci_group.sh
index 778142d95e4b4f..60d7f0406f4c9f 100755
--- a/test/scripts/jenkins_ci_group.sh
+++ b/test/scripts/jenkins_ci_group.sh
@@ -31,4 +31,12 @@ else
mkdir -p ../kibana/target/kibana-coverage/functional
mv target/kibana-coverage/functional/* ../kibana/target/kibana-coverage/functional/
fi
+
+ echo " -> moving junit output, silently fail in case of no report"
+ mkdir -p ../kibana/target/junit
+ mv target/junit/* ../kibana/target/junit/ || echo "copying junit failed"
+
+ echo " -> copying screenshots and html for failures"
+ cp -r test/functional/screenshots/* ../kibana/test/functional/screenshots/ || echo "copying screenshots failed"
+ cp -r test/functional/failure_debug ../kibana/test/functional/ || echo "copying html failed"
fi
diff --git a/test/scripts/jenkins_xpack_ci_group.sh b/test/scripts/jenkins_xpack_ci_group.sh
index a6e600630364eb..648605135b3595 100755
--- a/test/scripts/jenkins_xpack_ci_group.sh
+++ b/test/scripts/jenkins_xpack_ci_group.sh
@@ -32,4 +32,12 @@ else
mkdir -p ../../kibana/target/kibana-coverage/functional
mv ../target/kibana-coverage/functional/* ../../kibana/target/kibana-coverage/functional/
fi
+
+ echo " -> moving junit output, silently fail in case of no report"
+ mkdir -p ../../kibana/target/junit
+ mv ../target/junit/* ../../kibana/target/junit/ || echo "copying junit failed"
+
+ echo " -> copying screenshots and html for failures"
+ cp -r test/functional/screenshots/* ../../kibana/x-pack/test/functional/screenshots/ || echo "copying screenshots failed"
+ cp -r test/functional/failure_debug ../../kibana/x-pack/test/functional/ || echo "copying html failed"
fi
\ No newline at end of file
diff --git a/test/scripts/jenkins_xpack_page_load_metrics.sh b/test/scripts/jenkins_xpack_page_load_metrics.sh
new file mode 100644
index 00000000000000..679f0b8d2ddc59
--- /dev/null
+++ b/test/scripts/jenkins_xpack_page_load_metrics.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+source test/scripts/jenkins_test_setup_xpack.sh
+
+checks-reporter-with-killswitch "Capture Kibana page load metrics" \
+ node scripts/functional_tests \
+ --debug --bail \
+ --kibana-install-dir "$installDir" \
+ --config test/page_load_metrics/config.ts;
diff --git a/x-pack/.gitignore b/x-pack/.gitignore
index 92597a101c03d5..68262c4bf734ba 100644
--- a/x-pack/.gitignore
+++ b/x-pack/.gitignore
@@ -3,8 +3,11 @@
/target
/test/functional/failure_debug
/test/functional/screenshots
+/test/page_load_metrics/screenshots
/test/functional/apps/reporting/reports/session
/test/reporting/configs/failure_debug/
+/legacy/plugins/reporting/.chromium/
+/legacy/plugins/reporting/.phantom/
/plugins/reporting/.chromium/
/plugins/reporting/.phantom/
/.aws-config.json
diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json
index e453abb3a0f98b..d17e5d1a74a300 100644
--- a/x-pack/.i18nrc.json
+++ b/x-pack/.i18nrc.json
@@ -2,8 +2,7 @@
"prefix": "xpack",
"paths": {
"xpack.actions": "plugins/actions",
- "xpack.advancedUiActions": "plugins/advanced_ui_actions",
- "xpack.uiActionsEnhanced": "examples/ui_actions_enhanced_examples",
+ "xpack.uiActionsEnhanced": ["plugins/ui_actions_enhanced", "examples/ui_actions_enhanced_examples"],
"xpack.alerts": "plugins/alerts",
"xpack.alertingBuiltins": "plugins/alerting_builtins",
"xpack.apm": ["legacy/plugins/apm", "plugins/apm"],
@@ -39,7 +38,7 @@
"xpack.reporting": ["plugins/reporting"],
"xpack.rollupJobs": ["legacy/plugins/rollup", "plugins/rollup"],
"xpack.searchProfiler": "plugins/searchprofiler",
- "xpack.security": ["legacy/plugins/security", "plugins/security"],
+ "xpack.security": "plugins/security",
"xpack.server": "legacy/server",
"xpack.securitySolution": "plugins/security_solution",
"xpack.snapshotRestore": "plugins/snapshot_restore",
diff --git a/x-pack/README.md b/x-pack/README.md
index 744d97ca02c75e..03d2e3287c0f0b 100644
--- a/x-pack/README.md
+++ b/x-pack/README.md
@@ -25,8 +25,8 @@ Examples:
- Run the jest test case whose description matches 'filtering should skip values of null':
`cd x-pack && yarn test:jest -t 'filtering should skip values of null' plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.test.js`
- Run the x-pack api integration test case whose description matches the given string:
- `node scripts/functional_tests_server --config x-pack/test/api_integration/config.js`
- `node scripts/functional_test_runner --config x-pack/test/api_integration/config.js --grep='apis Monitoring Beats list with restarted beat instance should load multiple clusters'`
+ `node scripts/functional_tests_server --config x-pack/test/api_integration/config.ts`
+ `node scripts/functional_test_runner --config x-pack/test/api_integration/config.ts --grep='apis Monitoring Beats list with restarted beat instance should load multiple clusters'`
In addition to to providing a regular expression argument, specific tests can also be run by appeding `.only` to an `it` or `describe` function block. E.g. `describe(` to `describe.only(`.
@@ -63,7 +63,7 @@ yarn test:mocha
For more info, see [the Elastic functional test development guide](https://www.elastic.co/guide/en/kibana/current/development-functional-tests.html).
-The functional UI tests, the API integration tests, and the SAML API integration tests are all run against a live browser, Kibana, and Elasticsearch install. Each set of tests is specified with a unique config that describes how to start the Elasticsearch server, the Kibana server, and what tests to run against them. The sets of tests that exist today are *functional UI tests* ([specified by this config](test/functional/config.js)), *API integration tests* ([specified by this config](test/api_integration/config.js)), and *SAML API integration tests* ([specified by this config](test/saml_api_integration/config.js)).
+The functional UI tests, the API integration tests, and the SAML API integration tests are all run against a live browser, Kibana, and Elasticsearch install. Each set of tests is specified with a unique config that describes how to start the Elasticsearch server, the Kibana server, and what tests to run against them. The sets of tests that exist today are *functional UI tests* ([specified by this config](test/functional/config.js)), *API integration tests* ([specified by this config](test/api_integration/config.ts)), and *SAML API integration tests* ([specified by this config](test/saml_api_integration/config.ts)).
The script runs all sets of tests sequentially like so:
* builds Elasticsearch and X-Pack
diff --git a/x-pack/dev-tools/jest/create_jest_config.js b/x-pack/dev-tools/jest/create_jest_config.js
index 01b966ebe359b5..74553bbde0cd6e 100644
--- a/x-pack/dev-tools/jest/create_jest_config.js
+++ b/x-pack/dev-tools/jest/create_jest_config.js
@@ -43,6 +43,7 @@ export function createJestConfig({ kibanaDirectory, rootDir, xPackKibanaDirector
'!**/scripts/**',
'!**/mocks/**',
'!**/plugins/apm/e2e/**',
+ '!**/plugins/siem/cypress/**',
],
coveragePathIgnorePatterns: ['.*\\.d\\.ts'],
coverageDirectory: `${kibanaDirectory}/target/kibana-coverage/jest`,
diff --git a/x-pack/examples/ui_actions_enhanced_examples/kibana.json b/x-pack/examples/ui_actions_enhanced_examples/kibana.json
index e220cdd5cd297b..a1cd895bb3cd62 100644
--- a/x-pack/examples/ui_actions_enhanced_examples/kibana.json
+++ b/x-pack/examples/ui_actions_enhanced_examples/kibana.json
@@ -5,6 +5,6 @@
"configPath": ["ui_actions_enhanced_examples"],
"server": false,
"ui": true,
- "requiredPlugins": ["advancedUiActions", "data"],
+ "requiredPlugins": ["uiActionsEnhanced", "data", "discover"],
"optionalPlugins": []
}
diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/index.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/index.tsx
index 847035403da024..bfe853241ae1dc 100644
--- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/index.tsx
+++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/index.tsx
@@ -7,7 +7,7 @@
import React from 'react';
import { EuiFormRow, EuiFieldText } from '@elastic/eui';
import { reactToUiComponent } from '../../../../../src/plugins/kibana_react/public';
-import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../plugins/advanced_ui_actions/public';
+import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../plugins/ui_actions_enhanced/public';
import {
RangeSelectTriggerContext,
ValueClickTriggerContext,
diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/discover_drilldown_config.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/discover_drilldown_config.tsx
index 0237e128c5a2fc..da9b0e921fb1cb 100644
--- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/discover_drilldown_config.tsx
+++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/discover_drilldown_config.tsx
@@ -31,9 +31,7 @@ export const DiscoverDrilldownConfig: React.FC = (
onIndexPatternSelect,
customIndexPattern,
onCustomIndexPatternToggle,
- carryFiltersAndQuery,
onCarryFiltersAndQueryToggle,
- carryTimeRange,
onCarryTimeRangeToggle,
}) => {
return (
@@ -82,9 +80,10 @@ export const DiscoverDrilldownConfig: React.FC = (
{!!onCarryFiltersAndQueryToggle && (
@@ -92,9 +91,10 @@ export const DiscoverDrilldownConfig: React.FC = (
{!!onCarryTimeRangeToggle && (
diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx
index fef01c9640f0d9..ba88f49861ffef 100644
--- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx
+++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx
@@ -11,7 +11,7 @@ import { StartServicesGetter } from '../../../../../src/plugins/kibana_utils/pub
import { ActionContext, Config, CollectConfigProps } from './types';
import { CollectConfigContainer } from './collect_config_container';
import { SAMPLE_DASHBOARD_TO_DISCOVER_DRILLDOWN } from './constants';
-import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../plugins/advanced_ui_actions/public';
+import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../plugins/ui_actions_enhanced/public';
import { txtGoToDiscover } from './i18n';
const isOutputWithIndexPatterns = (
@@ -22,7 +22,7 @@ const isOutputWithIndexPatterns = (
};
export interface Params {
- start: StartServicesGetter>;
+ start: StartServicesGetter>;
}
export class DashboardToDiscoverDrilldown implements Drilldown {
@@ -54,6 +54,10 @@ export class DashboardToDiscoverDrilldown implements Drilldown => {
+ const { urlGenerator } = this.params.start().plugins.discover;
+
+ if (!urlGenerator) throw new Error('Discover URL generator not available.');
+
let indexPatternId =
!!config.customIndexPattern && !!config.indexPatternId ? config.indexPatternId : '';
@@ -64,8 +68,9 @@ export class DashboardToDiscoverDrilldown implements Drilldown => {
diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx
index 20267a8b7292bf..4810fb2d6ad8da 100644
--- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx
+++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx
@@ -7,7 +7,7 @@
import React from 'react';
import { EuiFormRow, EuiSwitch, EuiFieldText, EuiCallOut, EuiSpacer } from '@elastic/eui';
import { reactToUiComponent } from '../../../../../src/plugins/kibana_react/public';
-import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../plugins/advanced_ui_actions/public';
+import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../plugins/ui_actions_enhanced/public';
import {
RangeSelectTriggerContext,
ValueClickTriggerContext,
diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts b/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts
index 0d4f274caf57ff..8034c378cc64f7 100644
--- a/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts
+++ b/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts
@@ -9,27 +9,30 @@ import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/pl
import {
AdvancedUiActionsSetup,
AdvancedUiActionsStart,
-} from '../../../../x-pack/plugins/advanced_ui_actions/public';
+} from '../../../../x-pack/plugins/ui_actions_enhanced/public';
import { DashboardHelloWorldDrilldown } from './dashboard_hello_world_drilldown';
import { DashboardToUrlDrilldown } from './dashboard_to_url_drilldown';
import { DashboardToDiscoverDrilldown } from './dashboard_to_discover_drilldown';
import { createStartServicesGetter } from '../../../../src/plugins/kibana_utils/public';
+import { DiscoverSetup, DiscoverStart } from '../../../../src/plugins/discover/public';
export interface SetupDependencies {
data: DataPublicPluginSetup;
- advancedUiActions: AdvancedUiActionsSetup;
+ discover: DiscoverSetup;
+ uiActionsEnhanced: AdvancedUiActionsSetup;
}
export interface StartDependencies {
data: DataPublicPluginStart;
- advancedUiActions: AdvancedUiActionsStart;
+ discover: DiscoverStart;
+ uiActionsEnhanced: AdvancedUiActionsStart;
}
export class UiActionsEnhancedExamplesPlugin
implements Plugin {
public setup(
core: CoreSetup,
- { advancedUiActions: uiActions }: SetupDependencies
+ { uiActionsEnhanced: uiActions }: SetupDependencies
) {
const start = createStartServicesGetter(core.getStartServices);
diff --git a/x-pack/legacy/plugins/beats_management/readme.md b/x-pack/legacy/plugins/beats_management/readme.md
index 301caad683dd57..3414f09deed463 100644
--- a/x-pack/legacy/plugins/beats_management/readme.md
+++ b/x-pack/legacy/plugins/beats_management/readme.md
@@ -15,7 +15,7 @@ In one shell, from **~/kibana/x-pack**:
`node scripts/functional_tests-server.js`
In another shell, from **~kibana/x-pack**:
-`node ../scripts/functional_test_runner.js --config test/api_integration/config.js`.
+`node ../scripts/functional_test_runner.js --config test/api_integration/config.ts`.
### Manual e2e testing
diff --git a/x-pack/legacy/plugins/beats_management/server/lib/adapters/framework/adapter_types.ts b/x-pack/legacy/plugins/beats_management/server/lib/adapters/framework/adapter_types.ts
index bb1f68e1c03b37..80599f38d982ab 100644
--- a/x-pack/legacy/plugins/beats_management/server/lib/adapters/framework/adapter_types.ts
+++ b/x-pack/legacy/plugins/beats_management/server/lib/adapters/framework/adapter_types.ts
@@ -8,6 +8,7 @@
import { Lifecycle, ResponseToolkit } from 'hapi';
import * as t from 'io-ts';
+import { SecurityPluginSetup } from '../../../../../../../plugins/security/server';
import { LicenseType } from '../../../../common/constants/security';
export const internalAuthData = Symbol('internalAuthData');
@@ -39,6 +40,11 @@ export interface BackendFrameworkAdapter {
}
export interface KibanaLegacyServer {
+ newPlatform: {
+ setup: {
+ plugins: { security: SecurityPluginSetup };
+ };
+ };
plugins: {
xpack_main: {
status: {
@@ -53,9 +59,6 @@ export interface KibanaLegacyServer {
};
};
};
- security: {
- getUser: (request: KibanaServerRequest) => any;
- };
elasticsearch: {
status: {
on: (status: 'green' | 'yellow' | 'red', callback: () => void) => void;
diff --git a/x-pack/legacy/plugins/beats_management/server/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/legacy/plugins/beats_management/server/lib/adapters/framework/kibana_framework_adapter.ts
index 589f34ac746014..1bf9bbb22b3525 100644
--- a/x-pack/legacy/plugins/beats_management/server/lib/adapters/framework/kibana_framework_adapter.ts
+++ b/x-pack/legacy/plugins/beats_management/server/lib/adapters/framework/kibana_framework_adapter.ts
@@ -8,6 +8,7 @@ import { ResponseToolkit } from 'hapi';
import { PathReporter } from 'io-ts/lib/PathReporter';
import { get } from 'lodash';
import { isLeft } from 'fp-ts/lib/Either';
+import { KibanaRequest, LegacyRequest } from '../../../../../../../../src/core/server';
// @ts-ignore
import { mirrorPluginStatus } from '../../../../../../server/lib/mirror_plugin_status';
import {
@@ -128,13 +129,10 @@ export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter {
}
private async getUser(request: KibanaServerRequest): Promise {
- let user;
- try {
- user = await this.server.plugins.security.getUser(request);
- } catch (e) {
- return null;
- }
- if (user === null) {
+ const user = this.server.newPlatform.setup.plugins.security?.authc.getCurrentUser(
+ KibanaRequest.from((request as unknown) as LegacyRequest)
+ );
+ if (!user) {
return null;
}
const assertKibanaUser = RuntimeKibanaUser.decode(user);
diff --git a/x-pack/legacy/plugins/security/index.ts b/x-pack/legacy/plugins/security/index.ts
index 41371fcbc4c652..addeef34f63bfe 100644
--- a/x-pack/legacy/plugins/security/index.ts
+++ b/x-pack/legacy/plugins/security/index.ts
@@ -6,64 +6,17 @@
import { Root } from 'joi';
import { resolve } from 'path';
-import { Server } from 'src/legacy/server/kbn_server';
-import { KibanaRequest, LegacyRequest } from '../../../../src/core/server';
-// @ts-ignore
-import { watchStatusAndLicenseToInitialize } from '../../server/lib/watch_status_and_license_to_initialize';
-import { AuthenticatedUser, SecurityPluginSetup } from '../../../plugins/security/server';
-
-/**
- * Public interface of the security plugin.
- */
-export interface SecurityPlugin {
- getUser: (request: LegacyRequest) => Promise;
-}
-
-function getSecurityPluginSetup(server: Server) {
- const securityPlugin = server.newPlatform.setup.plugins.security as SecurityPluginSetup;
- if (!securityPlugin) {
- throw new Error('Kibana Platform Security plugin is not available.');
- }
-
- return securityPlugin;
-}
export const security = (kibana: Record) =>
new kibana.Plugin({
id: 'security',
publicDir: resolve(__dirname, 'public'),
- require: ['kibana', 'xpack_main'],
+ require: ['kibana'],
configPrefix: 'xpack.security',
- uiExports: {
- hacks: ['plugins/security/hacks/legacy'],
- injectDefaultVars: (server: Server) => {
- return { enableSpaceAwarePrivileges: server.config().get('xpack.spaces.enabled') };
- },
- },
-
- config(Joi: Root) {
- return Joi.object({
- enabled: Joi.boolean().default(true),
- })
+ uiExports: { hacks: ['plugins/security/hacks/legacy'] },
+ config: (Joi: Root) =>
+ Joi.object({ enabled: Joi.boolean().default(true) })
.unknown()
- .default();
- },
-
- async postInit(server: Server) {
- watchStatusAndLicenseToInitialize(server.plugins.xpack_main, this, async () => {
- const xpackInfo = server.plugins.xpack_main.info;
- if (xpackInfo.isAvailable() && xpackInfo.feature('security').isEnabled()) {
- await getSecurityPluginSetup(server).__legacyCompat.registerPrivilegesWithCluster();
- }
- });
- },
-
- async init(server: Server) {
- const securityPlugin = getSecurityPluginSetup(server);
-
- server.expose({
- getUser: async (request: LegacyRequest) =>
- securityPlugin.authc.getCurrentUser(KibanaRequest.from(request)),
- });
- },
+ .default(),
+ init() {},
});
diff --git a/x-pack/plugins/apm/public/application/index.tsx b/x-pack/plugins/apm/public/application/index.tsx
index cb8600ed2c214a..56c427e67ad4cc 100644
--- a/x-pack/plugins/apm/public/application/index.tsx
+++ b/x-pack/plugins/apm/public/application/index.tsx
@@ -9,6 +9,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { Route, Router, Switch } from 'react-router-dom';
import styled from 'styled-components';
+import { EuiThemeProvider } from '../../../observability/public';
import { CoreStart, AppMountParameters } from '../../../../../src/core/public';
import { ApmPluginSetupDeps } from '../plugin';
import { ApmPluginContext } from '../context/ApmPluginContext';
@@ -18,7 +19,10 @@ import { LocationProvider } from '../context/LocationContext';
import { MatchedRouteProvider } from '../context/MatchedRouteContext';
import { UrlParamsProvider } from '../context/UrlParamsContext';
import { AlertsContextProvider } from '../../../triggers_actions_ui/public';
-import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public';
+import {
+ KibanaContextProvider,
+ useUiSetting$,
+} from '../../../../../src/plugins/kibana_react/public';
import { px, unit, units } from '../style/variables';
import { UpdateBreadcrumbs } from '../components/app/Main/UpdateBreadcrumbs';
import { APMIndicesPermission } from '../components/app/APMIndicesPermission';
@@ -35,18 +39,22 @@ const MainContainer = styled.div`
`;
const App = () => {
+ const [darkMode] = useUiSetting$('theme:darkMode');
+
return (
-
-
-
-
-
- {routes.map((route, i) => (
-
- ))}
-
-
-
+
+
+
+
+
+
+ {routes.map((route, i) => (
+
+ ))}
+
+
+
+
);
};
diff --git a/x-pack/plugins/apm/readme.md b/x-pack/plugins/apm/readme.md
index ceed5e6c39716a..cb694712d7c97b 100644
--- a/x-pack/plugins/apm/readme.md
+++ b/x-pack/plugins/apm/readme.md
@@ -83,13 +83,13 @@ For debugging access Elasticsearch on http://localhost:9220` (elastic/changeme)
**Start server**
```
-node scripts/functional_tests_server --config x-pack/test/api_integration/config.js
+node scripts/functional_tests_server --config x-pack/test/api_integration/config.ts
```
**Run tests**
```
-node scripts/functional_test_runner --config x-pack/test/api_integration/config.js --grep='APM specs'
+node scripts/functional_test_runner --config x-pack/test/api_integration/config.ts --grep='APM specs'
```
APM tests are located in `x-pack/test/api_integration/apis/apm`.
diff --git a/x-pack/plugins/apm/server/lib/helpers/es_client.ts b/x-pack/plugins/apm/server/lib/helpers/es_client.ts
index c7a17197ca7787..892f8f0ddd1051 100644
--- a/x-pack/plugins/apm/server/lib/helpers/es_client.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/es_client.ts
@@ -19,6 +19,7 @@ import {
ESSearchRequest,
ESSearchResponse,
} from '../../../typings/elasticsearch';
+import { UI_SETTINGS } from '../../../../../../src/plugins/data/server';
import { OBSERVER_VERSION_MAJOR } from '../../../common/elasticsearch_fieldnames';
import { pickKeys } from '../../../common/utils/pick_keys';
import { APMRequestHandlerContext } from '../../routes/typings';
@@ -95,7 +96,7 @@ async function getParamsForSearchRequest(
savedObjectsClient: context.core.savedObjects.client,
config: context.config,
}),
- uiSettings.client.get('search:includeFrozen'),
+ uiSettings.client.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN),
]);
// Get indices for legacy data filter (only those which apply)
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/elements/metric/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/elements/metric/index.ts
index 7256657903aab0..14409ae166a840 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/elements/metric/index.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/elements/metric/index.ts
@@ -7,6 +7,7 @@
import { openSans } from '../../../common/lib/fonts';
import { ElementFactory } from '../../../types';
import { SetupInitializer } from '../../plugin';
+import { UI_SETTINGS } from '../../../../../../src/plugins/data/public';
export const metricElementInitializer: SetupInitializer = (core, setup) => {
return () => ({
@@ -23,7 +24,7 @@ export const metricElementInitializer: SetupInitializer = (core,
| metric "Countries"
metricFont={font size=48 family="${openSans.value}" color="#000000" align="center" lHeight=48}
labelFont={font size=14 family="${openSans.value}" color="#000000" align="center"}
- metricFormat="${core.uiSettings.get('format:number:defaultPattern')}"
+ metricFormat="${core.uiSettings.get(UI_SETTINGS.FORMAT_NUMBER_DEFAULT_PATTERN)}"
| render`,
});
};
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx
index 2dd116d5ada082..ad368a912cd8cc 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx
+++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx
@@ -11,12 +11,10 @@ import { StartDeps } from '../../plugin';
import {
IEmbeddable,
EmbeddableFactory,
- EmbeddablePanel,
EmbeddableFactoryNotFoundError,
} from '../../../../../../src/plugins/embeddable/public';
import { EmbeddableExpression } from '../../expression_types/embeddable';
import { RendererStrings } from '../../../i18n';
-import { getSavedObjectFinder } from '../../../../../../src/plugins/saved_objects/public';
import { embeddableInputToExpression } from './embeddable_input_to_expression';
import { EmbeddableInput } from '../../expression_types';
import { RendererHandlers } from '../../../types';
@@ -38,17 +36,7 @@ const renderEmbeddableFactory = (core: CoreStart, plugins: StartDeps) => {
style={{ width: domNode.offsetWidth, height: domNode.offsetHeight, cursor: 'auto' }}
>
-
+
);
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/time_filter/index.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/time_filter/index.tsx
index ef5bfb70d4b3db..25278adcf45297 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/time_filter/index.tsx
+++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/time_filter/index.tsx
@@ -7,6 +7,7 @@
import ReactDOM from 'react-dom';
import React from 'react';
import { toExpression } from '@kbn/interpreter/common';
+import { UI_SETTINGS } from '../../../../../../src/plugins/data/public';
import { syncFilterExpression } from '../../../public/lib/sync_filter_expression';
import { RendererStrings } from '../../../i18n';
import { TimeFilter } from './components';
@@ -20,7 +21,7 @@ const { timeFilter: strings } = RendererStrings;
export const timeFilterFactory: StartInitializer> = (core, plugins) => {
const { uiSettings } = core;
- const customQuickRanges = (uiSettings.get('timepicker:quickRanges') || []).map(
+ const customQuickRanges = (uiSettings.get(UI_SETTINGS.TIMEPICKER_QUICK_RANGES) || []).map(
({ from, to, display }: { from: string; to: string; display: string }) => ({
start: from,
end: to,
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts
index 4025d4deaf997f..5a3e3904f4f234 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts
@@ -11,6 +11,7 @@ import { templateFromReactComponent } from '../../../../public/lib/template_from
import { ArgumentFactory } from '../../../../types/arguments';
import { ArgumentStrings } from '../../../../i18n';
import { SetupInitializer } from '../../../plugin';
+import { UI_SETTINGS } from '../../../../../../../src/plugins/data/public';
const { NumberFormat: strings } = ArgumentStrings;
@@ -19,11 +20,11 @@ export const numberFormatInitializer: SetupInitializer {
const formatMap = {
- NUMBER: core.uiSettings.get('format:number:defaultPattern'),
- PERCENT: core.uiSettings.get('format:percent:defaultPattern'),
- CURRENCY: core.uiSettings.get('format:currency:defaultPattern'),
+ NUMBER: core.uiSettings.get(UI_SETTINGS.FORMAT_NUMBER_DEFAULT_PATTERN),
+ PERCENT: core.uiSettings.get(UI_SETTINGS.FORMAT_PERCENT_DEFAULT_PATTERN),
+ CURRENCY: core.uiSettings.get(UI_SETTINGS.FORMAT_CURRENCY_DEFAULT_PATTERN),
DURATION: '00:00:00',
- BYTES: core.uiSettings.get('format:bytes:defaultPattern'),
+ BYTES: core.uiSettings.get(UI_SETTINGS.FORMAT_BYTES_DEFAULT_PATTERN),
};
const numberFormats = [
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/metric.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/metric.ts
index 93912b7b0517f5..11bee46088576e 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/metric.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/metric.ts
@@ -7,6 +7,7 @@
import { openSans } from '../../../common/lib/fonts';
import { ViewStrings } from '../../../i18n';
import { SetupInitializer } from '../../plugin';
+import { UI_SETTINGS } from '../../../../../../src/plugins/data/public';
const { Metric: strings } = ViewStrings;
@@ -22,7 +23,7 @@ export const metricInitializer: SetupInitializer = (core, plugin) => {
displayName: strings.getMetricFormatDisplayName(),
help: strings.getMetricFormatHelp(),
argType: 'numberFormat',
- default: `"${core.uiSettings.get('format:number:defaultPattern')}"`,
+ default: `"${core.uiSettings.get(UI_SETTINGS.FORMAT_NUMBER_DEFAULT_PATTERN)}"`,
},
{
name: '_',
diff --git a/x-pack/plugins/dashboard_enhanced/kibana.json b/x-pack/plugins/dashboard_enhanced/kibana.json
index f416ca97f71107..37211ea5371797 100644
--- a/x-pack/plugins/dashboard_enhanced/kibana.json
+++ b/x-pack/plugins/dashboard_enhanced/kibana.json
@@ -3,6 +3,6 @@
"version": "kibana",
"server": false,
"ui": true,
- "requiredPlugins": ["data", "advancedUiActions", "drilldowns", "embeddable", "dashboard", "share"],
+ "requiredPlugins": ["data", "uiActionsEnhanced", "drilldowns", "embeddable", "dashboard", "share"],
"configPath": ["xpack", "dashboardEnhanced"]
}
diff --git a/x-pack/plugins/dashboard_enhanced/public/plugin.ts b/x-pack/plugins/dashboard_enhanced/public/plugin.ts
index c258a4148f84a0..413f5a7afe3564 100644
--- a/x-pack/plugins/dashboard_enhanced/public/plugin.ts
+++ b/x-pack/plugins/dashboard_enhanced/public/plugin.ts
@@ -9,19 +9,19 @@ import { SharePluginStart, SharePluginSetup } from '../../../../src/plugins/shar
import { EmbeddableSetup, EmbeddableStart } from '../../../../src/plugins/embeddable/public';
import { DashboardDrilldownsService } from './services';
import { DataPublicPluginStart } from '../../../../src/plugins/data/public';
-import { AdvancedUiActionsSetup, AdvancedUiActionsStart } from '../../advanced_ui_actions/public';
+import { AdvancedUiActionsSetup, AdvancedUiActionsStart } from '../../ui_actions_enhanced/public';
import { DrilldownsSetup, DrilldownsStart } from '../../drilldowns/public';
import { DashboardStart } from '../../../../src/plugins/dashboard/public';
export interface SetupDependencies {
- advancedUiActions: AdvancedUiActionsSetup;
+ uiActionsEnhanced: AdvancedUiActionsSetup;
drilldowns: DrilldownsSetup;
embeddable: EmbeddableSetup;
share: SharePluginSetup;
}
export interface StartDependencies {
- advancedUiActions: AdvancedUiActionsStart;
+ uiActionsEnhanced: AdvancedUiActionsStart;
data: DataPublicPluginStart;
drilldowns: DrilldownsStart;
embeddable: EmbeddableStart;
diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.test.tsx
index 555acf1fca5ffa..309e6cbf53a3db 100644
--- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.test.tsx
+++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.test.tsx
@@ -8,7 +8,7 @@ import { FlyoutEditDrilldownAction, FlyoutEditDrilldownParams } from './flyout_e
import { coreMock } from '../../../../../../../../src/core/public/mocks';
import { drilldownsPluginMock } from '../../../../../../drilldowns/public/mocks';
import { ViewMode } from '../../../../../../../../src/plugins/embeddable/public';
-import { uiActionsEnhancedPluginMock } from '../../../../../../advanced_ui_actions/public/mocks';
+import { uiActionsEnhancedPluginMock } from '../../../../../../ui_actions_enhanced/public/mocks';
import { EnhancedEmbeddable } from '../../../../../../embeddable_enhanced/public';
import { MockEmbeddable, enhanceEmbeddable } from '../test_helpers';
diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.test.tsx
index ec3a78e97eae4a..9a4ecb2d4bfb0a 100644
--- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.test.tsx
+++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.test.tsx
@@ -8,7 +8,7 @@ import React from 'react';
import { render, cleanup, act } from '@testing-library/react/pure';
import { MenuItem } from './menu_item';
import { createStateContainer } from '../../../../../../../../src/plugins/kibana_utils/public';
-import { UiActionsEnhancedDynamicActionManager as DynamicActionManager } from '../../../../../../advanced_ui_actions/public';
+import { UiActionsEnhancedDynamicActionManager as DynamicActionManager } from '../../../../../../ui_actions_enhanced/public';
import { EnhancedEmbeddable } from '../../../../../../embeddable_enhanced/public';
import '@testing-library/jest-dom';
diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/test_helpers.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/test_helpers.ts
index cccacf701a9ada..e831f87baa11c1 100644
--- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/test_helpers.ts
+++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/test_helpers.ts
@@ -10,9 +10,9 @@ import {
UiActionsEnhancedMemoryActionStorage as MemoryActionStorage,
UiActionsEnhancedDynamicActionManager as DynamicActionManager,
AdvancedUiActionsStart,
-} from '../../../../../advanced_ui_actions/public';
+} from '../../../../../ui_actions_enhanced/public';
import { TriggerContextMapping } from '../../../../../../../src/plugins/ui_actions/public';
-import { uiActionsEnhancedPluginMock } from '../../../../../advanced_ui_actions/public/mocks';
+import { uiActionsEnhancedPluginMock } from '../../../../../ui_actions_enhanced/public/mocks';
export class MockEmbeddable extends Embeddable {
public rootType = 'dashboard';
diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts
index f5926cd6961c2d..4325e3309b898a 100644
--- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts
+++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts
@@ -41,7 +41,7 @@ export class DashboardDrilldownsService {
setupDrilldowns(
core: CoreSetup,
- { advancedUiActions: uiActions }: SetupDependencies
+ { uiActionsEnhanced: uiActions }: SetupDependencies
) {
const start = createStartServicesGetter(core.getStartServices);
const getDashboardUrlGenerator = () => {
diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx
index c94d19d28e6da5..6ce7dccd3a3ec8 100644
--- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx
+++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx
@@ -101,13 +101,13 @@ describe('.execute() & getHref', () => {
},
},
plugins: {
- advancedUiActions: {},
+ uiActionsEnhanced: {},
data: {
actions: dataPluginActions,
},
},
self: {},
- })) as unknown) as StartServicesGetter>,
+ })) as unknown) as StartServicesGetter>,
getDashboardUrlGenerator: () =>
new UrlGeneratorsService().setup(coreMock.createSetup()).registerUrlGenerator(
createDashboardUrlGenerator(() =>
diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx
index 7ff84a75dd52ca..26a69132cffb17 100644
--- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx
+++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx
@@ -10,7 +10,7 @@ import { DashboardUrlGenerator } from '../../../../../../../src/plugins/dashboar
import { ActionContext, Config } from './types';
import { CollectConfigContainer } from './components';
import { DASHBOARD_TO_DASHBOARD_DRILLDOWN } from './constants';
-import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../../advanced_ui_actions/public';
+import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../../ui_actions_enhanced/public';
import { txtGoToDashboard } from './i18n';
import { esFilters } from '../../../../../../../src/plugins/data/public';
import { VisualizeEmbeddableContract } from '../../../../../../../src/plugins/visualizations/public';
@@ -22,7 +22,7 @@ import { StartServicesGetter } from '../../../../../../../src/plugins/kibana_uti
import { StartDependencies } from '../../../plugin';
export interface Params {
- start: StartServicesGetter>;
+ start: StartServicesGetter>;
getDashboardUrlGenerator: () => DashboardUrlGenerator;
}
diff --git a/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts
index c493e8ce86781b..3a511c7b5a1767 100644
--- a/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts
+++ b/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts
@@ -11,6 +11,7 @@ import {
ISearchContext,
ISearch,
getEsPreference,
+ UI_SETTINGS,
} from '../../../../../src/plugins/data/public';
import { IEnhancedEsSearchRequest, EnhancedSearchParams } from '../../common';
import { ASYNC_SEARCH_STRATEGY } from './async_search_strategy';
@@ -27,7 +28,7 @@ export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider {
const params: EnhancedSearchParams = {
- ignoreThrottled: !context.core.uiSettings.get('search:includeFrozen'),
+ ignoreThrottled: !context.core.uiSettings.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN),
preference: getEsPreference(context.core.uiSettings),
...request.params,
};
diff --git a/x-pack/plugins/drilldowns/kibana.json b/x-pack/plugins/drilldowns/kibana.json
index 678c054aa322c4..1614f94b488fdb 100644
--- a/x-pack/plugins/drilldowns/kibana.json
+++ b/x-pack/plugins/drilldowns/kibana.json
@@ -3,6 +3,6 @@
"version": "kibana",
"server": false,
"ui": true,
- "requiredPlugins": ["uiActions", "embeddable", "advancedUiActions"],
+ "requiredPlugins": ["uiActions", "embeddable", "uiActionsEnhanced"],
"configPath": ["xpack", "drilldowns"]
}
diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx
index a186feec339247..5fde4fc79e4333 100644
--- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx
+++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx
@@ -12,13 +12,13 @@ import {
dashboardFactory,
urlFactory,
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-} from '../../../../advanced_ui_actions/public/components/action_wizard/test_data';
+} from '../../../../ui_actions_enhanced/public/components/action_wizard/test_data';
import { Storage } from '../../../../../../src/plugins/kibana_utils/public';
import { StubBrowserStorage } from '../../../../../../src/test_utils/public/stub_browser_storage';
import { mockDynamicActionManager } from './test_data';
const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({
- advancedUiActions: {
+ uiActionsEnhanced: {
getActionFactories() {
return [dashboardFactory, urlFactory];
},
diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx
index 0f7f0cb22760b8..32cbec795d0924 100644
--- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx
+++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx
@@ -11,7 +11,7 @@ import { createFlyoutManageDrilldowns } from './connected_flyout_manage_drilldow
import {
dashboardFactory,
urlFactory,
-} from '../../../../advanced_ui_actions/public/components/action_wizard/test_data';
+} from '../../../../ui_actions_enhanced/public/components/action_wizard/test_data';
import { StubBrowserStorage } from '../../../../../../src/test_utils/public/stub_browser_storage';
import { Storage } from '../../../../../../src/plugins/kibana_utils/public';
import { mockDynamicActionManager } from './test_data';
@@ -24,7 +24,7 @@ import { toastDrilldownsCRUDError } from './i18n';
const storage = new Storage(new StubBrowserStorage());
const notifications = coreMock.createStart().notifications;
const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({
- advancedUiActions: {
+ uiActionsEnhanced: {
getActionFactories() {
return [dashboardFactory, urlFactory];
},
diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx
index 3c9d2d2a86fb11..45cf7365ebd917 100644
--- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx
+++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx
@@ -7,12 +7,12 @@
import React, { useEffect, useState } from 'react';
import useMountedState from 'react-use/lib/useMountedState';
import {
- AdvancedUiActionsActionFactory as ActionFactory,
+ UiActionsEnhancedActionFactory as ActionFactory,
AdvancedUiActionsStart,
UiActionsEnhancedDynamicActionManager as DynamicActionManager,
UiActionsEnhancedSerializedAction,
UiActionsEnhancedSerializedEvent,
-} from '../../../../advanced_ui_actions/public';
+} from '../../../../ui_actions_enhanced/public';
import { NotificationsStart } from '../../../../../../src/core/public';
import { DrilldownWizardConfig, FlyoutDrilldownWizard } from '../flyout_drilldown_wizard';
import { FlyoutListManageDrilldowns } from '../flyout_list_manage_drilldowns';
@@ -48,17 +48,17 @@ enum Routes {
}
export function createFlyoutManageDrilldowns({
- advancedUiActions,
+ uiActionsEnhanced,
storage,
notifications,
}: {
- advancedUiActions: AdvancedUiActionsStart;
+ uiActionsEnhanced: AdvancedUiActionsStart;
storage: IStorageWrapper;
notifications: NotificationsStart;
}) {
// fine to assume this is static,
// because all action factories should be registered in setup phase
- const allActionFactories = advancedUiActions.getActionFactories();
+ const allActionFactories = uiActionsEnhanced.getActionFactories();
const allActionFactoriesById = allActionFactories.reduce((acc, next) => {
acc[next.id] = next;
return acc;
diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts
index c9cb0b0eb1cb38..d585fa0692e8c1 100644
--- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts
+++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts
@@ -9,7 +9,7 @@ import {
UiActionsEnhancedDynamicActionManager as DynamicActionManager,
UiActionsEnhancedDynamicActionManagerState as DynamicActionManagerState,
UiActionsEnhancedSerializedAction,
-} from '../../../../advanced_ui_actions/public';
+} from '../../../../ui_actions_enhanced/public';
import { TriggerContextMapping } from '../../../../../../src/plugins/ui_actions/public';
import { createStateContainer } from '../../../../../../src/plugins/kibana_utils/common';
diff --git a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx
index add8b748afee94..be048bf920602a 100644
--- a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx
+++ b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx
@@ -14,8 +14,8 @@ import {
dashboardFactory,
urlFactory,
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-} from '../../../../advanced_ui_actions/public/components/action_wizard/test_data';
-import { AdvancedUiActionsActionFactory as ActionFactory } from '../../../../advanced_ui_actions/public/';
+} from '../../../../ui_actions_enhanced/public/components/action_wizard/test_data';
+import { UiActionsEnhancedActionFactory as ActionFactory } from '../../../../ui_actions_enhanced/public/';
storiesOf('components/FlyoutDrilldownWizard', module)
.add('default', () => {
diff --git a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx
index 84c1a04a71d156..87f886817517f7 100644
--- a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx
+++ b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx
@@ -16,7 +16,7 @@ import {
txtEditDrilldownTitle,
} from './i18n';
import { DrilldownHelloBar } from '../drilldown_hello_bar';
-import { AdvancedUiActionsActionFactory as ActionFactory } from '../../../../advanced_ui_actions/public';
+import { UiActionsEnhancedActionFactory as ActionFactory } from '../../../../ui_actions_enhanced/public';
export interface DrilldownWizardConfig {
name: string;
diff --git a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx
index 38168377b02bd7..1813851d728db6 100644
--- a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx
+++ b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx
@@ -8,9 +8,9 @@ import React from 'react';
import { EuiFieldText, EuiForm, EuiFormRow, EuiSpacer } from '@elastic/eui';
import { txtDrilldownAction, txtNameOfDrilldown, txtUntitledDrilldown } from './i18n';
import {
- AdvancedUiActionsActionFactory as ActionFactory,
+ UiActionsEnhancedActionFactory as ActionFactory,
ActionWizard,
-} from '../../../../advanced_ui_actions/public';
+} from '../../../../ui_actions_enhanced/public';
const noopFn = () => {};
diff --git a/x-pack/plugins/drilldowns/public/plugin.ts b/x-pack/plugins/drilldowns/public/plugin.ts
index 0108e04df9c99a..32176241c102fa 100644
--- a/x-pack/plugins/drilldowns/public/plugin.ts
+++ b/x-pack/plugins/drilldowns/public/plugin.ts
@@ -6,18 +6,18 @@
import { CoreStart, CoreSetup, Plugin } from 'src/core/public';
import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public';
-import { AdvancedUiActionsSetup, AdvancedUiActionsStart } from '../../advanced_ui_actions/public';
+import { AdvancedUiActionsSetup, AdvancedUiActionsStart } from '../../ui_actions_enhanced/public';
import { createFlyoutManageDrilldowns } from './components/connected_flyout_manage_drilldowns';
import { Storage } from '../../../../src/plugins/kibana_utils/public';
export interface SetupDependencies {
uiActions: UiActionsSetup;
- advancedUiActions: AdvancedUiActionsSetup;
+ uiActionsEnhanced: AdvancedUiActionsSetup;
}
export interface StartDependencies {
uiActions: UiActionsStart;
- advancedUiActions: AdvancedUiActionsStart;
+ uiActionsEnhanced: AdvancedUiActionsStart;
}
// eslint-disable-next-line
@@ -36,7 +36,7 @@ export class DrilldownsPlugin
public start(core: CoreStart, plugins: StartDependencies): StartContract {
return {
FlyoutManageDrilldowns: createFlyoutManageDrilldowns({
- advancedUiActions: plugins.advancedUiActions,
+ uiActionsEnhanced: plugins.uiActionsEnhanced,
storage: new Storage(localStorage),
notifications: core.notifications,
}),
diff --git a/x-pack/plugins/embeddable_enhanced/kibana.json b/x-pack/plugins/embeddable_enhanced/kibana.json
index 780a1d5d89870e..5663671de7bd9c 100644
--- a/x-pack/plugins/embeddable_enhanced/kibana.json
+++ b/x-pack/plugins/embeddable_enhanced/kibana.json
@@ -3,5 +3,5 @@
"version": "kibana",
"server": false,
"ui": true,
- "requiredPlugins": ["embeddable", "advancedUiActions"]
+ "requiredPlugins": ["embeddable", "uiActionsEnhanced"]
}
diff --git a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts
index f8b3a9dfb92d07..5c5d98d75295de 100644
--- a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts
+++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts
@@ -9,7 +9,7 @@ import {
EmbeddableActionStorage,
EmbeddableWithDynamicActionsInput,
} from './embeddable_action_storage';
-import { UiActionsEnhancedSerializedEvent } from '../../../advanced_ui_actions/public';
+import { UiActionsEnhancedSerializedEvent } from '../../../ui_actions_enhanced/public';
import { of } from '../../../../../src/plugins/kibana_utils/public';
class TestEmbeddable extends Embeddable {
diff --git a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts
index e93674ba650a70..fdc42585a80ce3 100644
--- a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts
+++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts
@@ -7,7 +7,7 @@
import {
UiActionsEnhancedAbstractActionStorage as AbstractActionStorage,
UiActionsEnhancedSerializedEvent as SerializedEvent,
-} from '../../../advanced_ui_actions/public';
+} from '../../../ui_actions_enhanced/public';
import {
EmbeddableInput,
EmbeddableOutput,
diff --git a/x-pack/plugins/embeddable_enhanced/public/plugin.ts b/x-pack/plugins/embeddable_enhanced/public/plugin.ts
index d26acb4459a717..e6413ac03aeaef 100644
--- a/x-pack/plugins/embeddable_enhanced/public/plugin.ts
+++ b/x-pack/plugins/embeddable_enhanced/public/plugin.ts
@@ -27,7 +27,7 @@ import {
UiActionsEnhancedDynamicActionManager as DynamicActionManager,
AdvancedUiActionsSetup,
AdvancedUiActionsStart,
-} from '../../advanced_ui_actions/public';
+} from '../../ui_actions_enhanced/public';
import { PanelNotificationsAction, ACTION_PANEL_NOTIFICATIONS } from './actions';
declare module '../../../../src/plugins/ui_actions/public' {
@@ -38,12 +38,12 @@ declare module '../../../../src/plugins/ui_actions/public' {
export interface SetupDependencies {
embeddable: EmbeddableSetup;
- advancedUiActions: AdvancedUiActionsSetup;
+ uiActionsEnhanced: AdvancedUiActionsSetup;
}
export interface StartDependencies {
embeddable: EmbeddableStart;
- advancedUiActions: AdvancedUiActionsStart;
+ uiActionsEnhanced: AdvancedUiActionsStart;
}
// eslint-disable-next-line
@@ -56,20 +56,20 @@ export class EmbeddableEnhancedPlugin
implements Plugin {
constructor(protected readonly context: PluginInitializerContext) {}
- private uiActions?: StartDependencies['advancedUiActions'];
+ private uiActions?: StartDependencies['uiActionsEnhanced'];
public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract {
this.setCustomEmbeddableFactoryProvider(plugins);
const panelNotificationAction = new PanelNotificationsAction();
- plugins.advancedUiActions.registerAction(panelNotificationAction);
- plugins.advancedUiActions.attachAction(PANEL_NOTIFICATION_TRIGGER, panelNotificationAction.id);
+ plugins.uiActionsEnhanced.registerAction(panelNotificationAction);
+ plugins.uiActionsEnhanced.attachAction(PANEL_NOTIFICATION_TRIGGER, panelNotificationAction.id);
return {};
}
public start(core: CoreStart, plugins: StartDependencies): StartContract {
- this.uiActions = plugins.advancedUiActions;
+ this.uiActions = plugins.uiActionsEnhanced;
return {};
}
diff --git a/x-pack/plugins/embeddable_enhanced/public/types.ts b/x-pack/plugins/embeddable_enhanced/public/types.ts
index 924605be332b21..4f5c316f2fc17c 100644
--- a/x-pack/plugins/embeddable_enhanced/public/types.ts
+++ b/x-pack/plugins/embeddable_enhanced/public/types.ts
@@ -5,7 +5,7 @@
*/
import { IEmbeddable } from '../../../../src/plugins/embeddable/public';
-import { UiActionsEnhancedDynamicActionManager as DynamicActionManager } from '../../advanced_ui_actions/public';
+import { UiActionsEnhancedDynamicActionManager as DynamicActionManager } from '../../ui_actions_enhanced/public';
export type EnhancedEmbeddable = E & {
enhancements: {
diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts
index 0144e573fc1462..ada86adf84cfd2 100644
--- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts
+++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts
@@ -51,7 +51,7 @@ describe('doesIlmPolicyExist', () => {
await clusterClientAdapter.doesIlmPolicyExist('foo');
expect(clusterClient.callAsInternalUser).toHaveBeenCalledWith('transport.request', {
method: 'GET',
- path: '_ilm/policy/foo',
+ path: '/_ilm/policy/foo',
});
});
@@ -78,7 +78,7 @@ describe('createIlmPolicy', () => {
await clusterClientAdapter.createIlmPolicy('foo', { args: true });
expect(clusterClient.callAsInternalUser).toHaveBeenCalledWith('transport.request', {
method: 'PUT',
- path: '_ilm/policy/foo',
+ path: '/_ilm/policy/foo',
body: { args: true },
});
});
diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts
index 7fd239ca493692..a036bfb74e4081 100644
--- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts
+++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts
@@ -41,7 +41,7 @@ export class ClusterClientAdapter {
public async doesIlmPolicyExist(policyName: string): Promise {
const request = {
method: 'GET',
- path: `_ilm/policy/${policyName}`,
+ path: `/_ilm/policy/${policyName}`,
};
try {
await this.callEs('transport.request', request);
@@ -55,7 +55,7 @@ export class ClusterClientAdapter {
public async createIlmPolicy(policyName: string, policy: unknown): Promise {
const request = {
method: 'PUT',
- path: `_ilm/policy/${policyName}`,
+ path: `/_ilm/policy/${policyName}`,
body: policy,
};
try {
diff --git a/x-pack/plugins/graph/server/routes/search.ts b/x-pack/plugins/graph/server/routes/search.ts
index ffca273d66c71a..645e6b520013fd 100644
--- a/x-pack/plugins/graph/server/routes/search.ts
+++ b/x-pack/plugins/graph/server/routes/search.ts
@@ -7,6 +7,7 @@
import { IRouter } from 'kibana/server';
import { schema } from '@kbn/config-schema';
import { LicenseState, verifyApiAccess } from '../lib/license_state';
+import { UI_SETTINGS } from '../../../../../src/plugins/data/server';
export function registerSearchRoute({
router,
@@ -41,7 +42,7 @@ export function registerSearchRoute({
response
) => {
verifyApiAccess(licenseState);
- const includeFrozen = await uiSettings.get('search:includeFrozen');
+ const includeFrozen = await uiSettings.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN);
try {
return response.ok({
body: {
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.js
index bfe1bbb04338c2..28bc8671f29e22 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.js
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.js
@@ -140,6 +140,41 @@ export const MinAgeInput = (props) => {
defaultMessage: 'hours from index creation',
}
);
+
+ minutesOptionLabel = i18n.translate(
+ 'xpack.indexLifecycleMgmt.editPolicy.creationMinutesOptionLabel',
+ {
+ defaultMessage: 'minutes from index creation',
+ }
+ );
+
+ secondsOptionLabel = i18n.translate(
+ 'xpack.indexLifecycleMgmt.editPolicy.creationSecondsOptionLabel',
+ {
+ defaultMessage: 'seconds from index creation',
+ }
+ );
+
+ millisecondsOptionLabel = i18n.translate(
+ 'xpack.indexLifecycleMgmt.editPolicy.creationMilliSecondsOptionLabel',
+ {
+ defaultMessage: 'milliseconds from index creation',
+ }
+ );
+
+ microsecondsOptionLabel = i18n.translate(
+ 'xpack.indexLifecycleMgmt.editPolicy.creationMicroSecondsOptionLabel',
+ {
+ defaultMessage: 'microseconds from index creation',
+ }
+ );
+
+ nanosecondsOptionLabel = i18n.translate(
+ 'xpack.indexLifecycleMgmt.editPolicy.creationNanoSecondsOptionLabel',
+ {
+ defaultMessage: 'nanoseconds from index creation',
+ }
+ );
}
return (
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/lifecycle.js b/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/lifecycle.js
index 789de0f528b1b7..03538fad9aa83a 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/lifecycle.js
+++ b/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/lifecycle.js
@@ -270,7 +270,9 @@ export const getLifecycle = (state) => {
if (phaseName === PHASE_DELETE) {
accum[phaseName].actions = {
...accum[phaseName].actions,
- delete: {},
+ delete: {
+ ...accum[phaseName].actions.delete,
+ },
};
}
}
diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts
index 2eb635e19be482..7bf3f96e2b2ef0 100644
--- a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts
+++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts
@@ -67,7 +67,7 @@ const warmPhaseSchema = schema.maybe(
actions: schema.object({
set_priority: setPrioritySchema,
unfollow: unfollowSchema,
- read_only: schema.maybe(schema.object({})), // Readonly has no options
+ readonly: schema.maybe(schema.object({})), // Readonly has no options
allocate: allocateSchema,
shrink: schema.maybe(
schema.object({
@@ -91,6 +91,11 @@ const coldPhaseSchema = schema.maybe(
unfollow: unfollowSchema,
allocate: allocateSchema,
freeze: schema.maybe(schema.object({})), // Freeze has no options
+ searchable_snapshot: schema.maybe(
+ schema.object({
+ snapshot_repository: schema.string(),
+ })
+ ),
}),
})
);
@@ -104,7 +109,11 @@ const deletePhaseSchema = schema.maybe(
policy: schema.string(),
})
),
- delete: schema.maybe(schema.object({})), // Delete has no options
+ delete: schema.maybe(
+ schema.object({
+ delete_searchable_snapshot: schema.maybe(schema.boolean()),
+ })
+ ),
}),
})
);
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts
index e5bce31ee6de13..da461609f0b836 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts
@@ -12,7 +12,7 @@ type HttpResponse = Record | any[];
// Register helpers to mock HTTP Requests
const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
const setLoadTemplatesResponse = (response: HttpResponse = []) => {
- server.respondWith('GET', `${API_BASE_PATH}/templates`, [
+ server.respondWith('GET', `${API_BASE_PATH}/index-templates`, [
200,
{ 'Content-Type': 'application/json' },
JSON.stringify(response),
@@ -28,7 +28,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
};
const setDeleteTemplateResponse = (response: HttpResponse = []) => {
- server.respondWith('DELETE', `${API_BASE_PATH}/templates`, [
+ server.respondWith('POST', `${API_BASE_PATH}/delete-index-templates`, [
200,
{ 'Content-Type': 'application/json' },
JSON.stringify(response),
@@ -39,7 +39,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
const status = error ? error.status || 400 : 200;
const body = error ? error.body : response;
- server.respondWith('GET', `${API_BASE_PATH}/templates/:id`, [
+ server.respondWith('GET', `${API_BASE_PATH}/index-templates/:id`, [
status,
{ 'Content-Type': 'application/json' },
JSON.stringify(body),
@@ -50,7 +50,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
const status = error ? error.body.status || 400 : 200;
const body = error ? JSON.stringify(error.body) : JSON.stringify(response);
- server.respondWith('PUT', `${API_BASE_PATH}/templates`, [
+ server.respondWith('POST', `${API_BASE_PATH}/index-templates`, [
status,
{ 'Content-Type': 'application/json' },
body,
@@ -61,7 +61,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
const status = error ? error.status || 400 : 200;
const body = error ? JSON.stringify(error.body) : JSON.stringify(response);
- server.respondWith('PUT', `${API_BASE_PATH}/templates/:name`, [
+ server.respondWith('PUT', `${API_BASE_PATH}/index-templates/:name`, [
status,
{ 'Content-Type': 'application/json' },
body,
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts
index 18e5edb5c2250e..8e7755a65af3cf 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts
@@ -16,6 +16,7 @@ export type TestSubjects =
| 'cell'
| 'closeDetailsButton'
| 'createTemplateButton'
+ | 'createLegacyTemplateButton'
| 'deleteSystemTemplateCallOut'
| 'deleteTemplateButton'
| 'deleteTemplatesConfirmation'
@@ -46,4 +47,7 @@ export type TestSubjects =
| 'templateDetails.title'
| 'templateList'
| 'templateTable'
- | 'templatesTab';
+ | 'templatesTab'
+ | 'legacyTemplateTable'
+ | 'viewButton'
+ | 'filterList.filterItem';
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts
index d195ce46c2f540..a7ac2ebf9bb024 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts
@@ -7,9 +7,17 @@
import { act } from 'react-dom/test-utils';
import { setupEnvironment, nextTick } from '../helpers';
-
import { HomeTestBed, setup } from './home.helpers';
+/**
+ * The below import is required to avoid a console error warn from the "brace" package
+ * console.warn ../node_modules/brace/index.js:3999
+ Could not load worker ReferenceError: Worker is not defined
+ at createWorker (//node_modules/brace/index.js:17992:5)
+ */
+import { stubWebWorker } from '../../../../../test_utils/stub_web_worker';
+stubWebWorker();
+
describe('', () => {
const { server, httpRequestsMockHelpers } = setupEnvironment();
let testBed: HomeTestBed;
@@ -62,7 +70,7 @@ describe('', () => {
expect(exists('indicesList')).toBe(true);
expect(exists('templateList')).toBe(false);
- httpRequestsMockHelpers.setLoadTemplatesResponse([]);
+ httpRequestsMockHelpers.setLoadTemplatesResponse({ templates: [], legacyTemplates: [] });
actions.selectHomeTab('templatesTab');
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts
index 5260dc64d0c91c..98bd3077670a75 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts
@@ -12,7 +12,6 @@ import {
TestBed,
TestBedConfig,
findTestSubject,
- nextTick,
} from '../../../../../test_utils';
// NOTE: We have to use the Home component instead of the TemplateList component because we depend
// upon react router to provide the name of the template to load in the detail panel.
@@ -45,6 +44,7 @@ export interface IndexTemplatesTabTestBed extends TestBed {
clickTemplateAt: (index: number) => void;
clickCloseDetailsButton: () => void;
clickActionMenu: (name: TemplateDeserialized['name']) => void;
+ toggleViewItem: (view: 'composable' | 'system') => void;
};
}
@@ -102,15 +102,14 @@ export const setup = async (): Promise => {
const clickTemplateAt = async (index: number) => {
const { component, table, router } = testBed;
- const { rows } = table.getMetaData('templateTable');
+ const { rows } = table.getMetaData('legacyTemplateTable');
const templateLink = findTestSubject(rows[index].reactWrapper, 'templateDetailsLink');
+ const { href } = templateLink.props();
await act(async () => {
- const { href } = templateLink.props();
router.navigateTo(href!);
- await nextTick();
- component.update();
});
+ component.update();
};
const clickCloseDetailsButton = () => {
@@ -119,6 +118,23 @@ export const setup = async (): Promise => {
find('closeDetailsButton').simulate('click');
};
+ const toggleViewItem = (view: 'composable' | 'system') => {
+ const { find, component } = testBed;
+ const views = ['composable', 'system'];
+
+ // First open the pop over
+ act(() => {
+ find('viewButton').simulate('click');
+ });
+ component.update();
+
+ // Then click on a filter item
+ act(() => {
+ find('filterList.filterItem').at(views.indexOf(view)).simulate('click');
+ });
+ component.update();
+ };
+
return {
...testBed,
findAction,
@@ -130,6 +146,7 @@ export const setup = async (): Promise => {
clickTemplateAt,
clickCloseDetailsButton,
clickActionMenu,
+ toggleViewItem,
},
};
};
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts
index c9a279e90d0e0a..8f6a8dddeb195e 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts
@@ -8,16 +8,18 @@ import { act } from 'react-dom/test-utils';
import * as fixtures from '../../../test/fixtures';
import { API_BASE_PATH } from '../../../common/constants';
-import { setupEnvironment, nextTick, getRandomString } from '../helpers';
+import { setupEnvironment, getRandomString } from '../helpers';
import { IndexTemplatesTabTestBed, setup } from './index_templates_tab.helpers';
const removeWhiteSpaceOnArrayValues = (array: any[]) =>
array.map((value) => {
- if (!value.trim) {
+ if (typeof value !== 'string') {
return value;
}
- return value.trim();
+
+ // Convert non breaking spaces ( ) to ordinary space
+ return value.trim().replace(/\s/g, ' ');
});
describe('Index Templates tab', () => {
@@ -31,13 +33,8 @@ describe('Index Templates tab', () => {
beforeEach(async () => {
httpRequestsMockHelpers.setLoadIndicesResponse([]);
- testBed = await setup();
-
await act(async () => {
- const { component } = testBed;
-
- await nextTick();
- component.update();
+ testBed = await setup();
});
});
@@ -45,14 +42,12 @@ describe('Index Templates tab', () => {
beforeEach(async () => {
const { actions, component } = testBed;
- httpRequestsMockHelpers.setLoadTemplatesResponse([]);
-
- actions.goToTemplatesList();
+ httpRequestsMockHelpers.setLoadTemplatesResponse({ templates: [], legacyTemplates: [] });
await act(async () => {
- await nextTick();
- component.update();
+ actions.goToTemplatesList();
});
+ component.update();
});
test('should display an empty prompt', async () => {
@@ -64,6 +59,9 @@ describe('Index Templates tab', () => {
});
describe('when there are index templates', () => {
+ // Add a default loadIndexTemplate response
+ httpRequestsMockHelpers.setLoadTemplateResponse(fixtures.getTemplate());
+
const template1 = fixtures.getTemplate({
name: `a${getRandomString()}`,
indexPatterns: ['template1Pattern1*', 'template1Pattern2'],
@@ -78,37 +76,87 @@ describe('Index Templates tab', () => {
},
},
});
+
const template2 = fixtures.getTemplate({
name: `b${getRandomString()}`,
indexPatterns: ['template2Pattern1*'],
});
+
const template3 = fixtures.getTemplate({
name: `.c${getRandomString()}`, // mock system template
indexPatterns: ['template3Pattern1*', 'template3Pattern2', 'template3Pattern3'],
});
+ const template4 = fixtures.getTemplate({
+ name: `a${getRandomString()}`,
+ indexPatterns: ['template4Pattern1*', 'template4Pattern2'],
+ template: {
+ settings: {
+ index: {
+ number_of_shards: '1',
+ lifecycle: {
+ name: 'my_ilm_policy',
+ },
+ },
+ },
+ },
+ isLegacy: true,
+ });
+
+ const template5 = fixtures.getTemplate({
+ name: `b${getRandomString()}`,
+ indexPatterns: ['template5Pattern1*'],
+ isLegacy: true,
+ });
+
+ const template6 = fixtures.getTemplate({
+ name: `.c${getRandomString()}`, // mock system template
+ indexPatterns: ['template6Pattern1*', 'template6Pattern2', 'template6Pattern3'],
+ isLegacy: true,
+ });
+
const templates = [template1, template2, template3];
+ const legacyTemplates = [template4, template5, template6];
beforeEach(async () => {
const { actions, component } = testBed;
- httpRequestsMockHelpers.setLoadTemplatesResponse(templates);
-
- actions.goToTemplatesList();
+ httpRequestsMockHelpers.setLoadTemplatesResponse({ templates, legacyTemplates });
await act(async () => {
- await nextTick();
- component.update();
+ actions.goToTemplatesList();
});
+ component.update();
});
test('should list them in the table', async () => {
const { table } = testBed;
const { tableCellsValues } = table.getMetaData('templateTable');
+ const { tableCellsValues: legacyTableCellsValues } = table.getMetaData('legacyTemplateTable');
+ // Test composable table content
tableCellsValues.forEach((row, i) => {
const template = templates[i];
+ const { name, indexPatterns, priority, ilmPolicy, composedOf } = template;
+
+ const ilmPolicyName = ilmPolicy && ilmPolicy.name ? ilmPolicy.name : '';
+ const composedOfString = composedOf ? composedOf.join(',') : '';
+ const priorityFormatted = priority ? priority.toString() : '';
+
+ expect(removeWhiteSpaceOnArrayValues(row)).toEqual([
+ name,
+ indexPatterns.join(', '),
+ ilmPolicyName,
+ composedOfString,
+ priorityFormatted,
+ 'M S A', // Mappings Settings Aliases badges
+ ]);
+ });
+
+ // Test legacy table content
+ legacyTableCellsValues.forEach((row, i) => {
+ const template = legacyTemplates[i];
const { name, indexPatterns, order, ilmPolicy } = template;
const ilmPolicyName = ilmPolicy && ilmPolicy.name ? ilmPolicy.name : '';
@@ -129,44 +177,40 @@ describe('Index Templates tab', () => {
});
test('should have a button to reload the index templates', async () => {
- const { component, exists, actions } = testBed;
+ const { exists, actions } = testBed;
const totalRequests = server.requests.length;
expect(exists('reloadButton')).toBe(true);
await act(async () => {
actions.clickReloadButton();
- await nextTick();
- component.update();
});
expect(server.requests.length).toBe(totalRequests + 1);
- expect(server.requests[server.requests.length - 1].url).toBe(`${API_BASE_PATH}/templates`);
+ expect(server.requests[server.requests.length - 1].url).toBe(
+ `${API_BASE_PATH}/index-templates`
+ );
});
test('should have a button to create a new template', () => {
const { exists } = testBed;
- expect(exists('createTemplateButton')).toBe(true);
+ expect(exists('createLegacyTemplateButton')).toBe(true);
});
test('should have a switch to view system templates', async () => {
- const { table, exists, component, form } = testBed;
- const { rows } = table.getMetaData('templateTable');
+ const { table, exists, actions } = testBed;
+ const { rows } = table.getMetaData('legacyTemplateTable');
expect(rows.length).toEqual(
- templates.filter((template) => !template.name.startsWith('.')).length
+ legacyTemplates.filter((template) => !template.name.startsWith('.')).length
);
- expect(exists('systemTemplatesSwitch')).toBe(true);
+ expect(exists('viewButton')).toBe(true);
- await act(async () => {
- form.toggleEuiSwitch('systemTemplatesSwitch');
- await nextTick();
- component.update();
- });
+ actions.toggleViewItem('system');
- const { rows: updatedRows } = table.getMetaData('templateTable');
- expect(updatedRows.length).toEqual(templates.length);
+ const { rows: updatedRows } = table.getMetaData('legacyTemplateTable');
+ expect(updatedRows.length).toEqual(legacyTemplates.length);
});
test('each row should have a link to the template details panel', async () => {
@@ -176,12 +220,12 @@ describe('Index Templates tab', () => {
expect(exists('templateList')).toBe(true);
expect(exists('templateDetails')).toBe(true);
- expect(find('templateDetails.title').text()).toBe(template1.name);
+ expect(find('templateDetails.title').text()).toBe(legacyTemplates[0].name);
});
test('template actions column should have an option to delete', () => {
const { actions, findAction } = testBed;
- const { name: templateName } = template1;
+ const [{ name: templateName }] = legacyTemplates;
actions.clickActionMenu(templateName);
@@ -192,7 +236,7 @@ describe('Index Templates tab', () => {
test('template actions column should have an option to clone', () => {
const { actions, findAction } = testBed;
- const { name: templateName } = template1;
+ const [{ name: templateName }] = legacyTemplates;
actions.clickActionMenu(templateName);
@@ -203,7 +247,7 @@ describe('Index Templates tab', () => {
test('template actions column should have an option to edit', () => {
const { actions, findAction } = testBed;
- const { name: templateName } = template1;
+ const [{ name: templateName }] = legacyTemplates;
actions.clickActionMenu(templateName);
@@ -215,7 +259,7 @@ describe('Index Templates tab', () => {
describe('delete index template', () => {
test('should show a confirmation when clicking the delete template button', async () => {
const { actions } = testBed;
- const { name: templateName } = template1;
+ const [{ name: templateName }] = legacyTemplates;
await actions.clickTemplateAction(templateName, 'delete');
@@ -231,32 +275,28 @@ describe('Index Templates tab', () => {
});
test('should show a warning message when attempting to delete a system template', async () => {
- const { component, form, actions } = testBed;
+ const { exists, actions } = testBed;
- await act(async () => {
- form.toggleEuiSwitch('systemTemplatesSwitch');
- await nextTick();
- component.update();
- });
+ actions.toggleViewItem('system');
- const { name: systemTemplateName } = template3;
+ const { name: systemTemplateName } = legacyTemplates[2];
await actions.clickTemplateAction(systemTemplateName, 'delete');
- expect(
- document.body.querySelector('[data-test-subj="deleteSystemTemplateCallOut"]')
- ).not.toBe(null);
+ expect(exists('deleteSystemTemplateCallOut')).toBe(true);
});
test('should send the correct HTTP request to delete an index template', async () => {
- const { component, actions, table } = testBed;
- const { rows } = table.getMetaData('templateTable');
+ const { actions, table } = testBed;
+ const { rows } = table.getMetaData('legacyTemplateTable');
const templateId = rows[0].columns[2].value;
- const {
- name: templateName,
- _kbnMeta: { formatVersion },
- } = template1;
+ const [
+ {
+ name: templateName,
+ _kbnMeta: { isLegacy },
+ },
+ ] = legacyTemplates;
await actions.clickTemplateAction(templateName, 'delete');
const modal = document.body.querySelector('[data-test-subj="deleteTemplatesConfirmation"]');
@@ -273,16 +313,14 @@ describe('Index Templates tab', () => {
await act(async () => {
confirmButton!.click();
- await nextTick();
- component.update();
});
const latestRequest = server.requests[server.requests.length - 1];
expect(latestRequest.method).toBe('POST');
- expect(latestRequest.url).toBe(`${API_BASE_PATH}/delete-templates`);
+ expect(latestRequest.url).toBe(`${API_BASE_PATH}/delete-index-templates`);
expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual({
- templates: [{ name: template1.name, formatVersion }],
+ templates: [{ name: legacyTemplates[0].name, isLegacy }],
});
});
});
@@ -292,6 +330,7 @@ describe('Index Templates tab', () => {
const template = fixtures.getTemplate({
name: `a${getRandomString()}`,
indexPatterns: ['template1Pattern1*', 'template1Pattern2'],
+ isLegacy: true,
});
httpRequestsMockHelpers.setLoadTemplateResponse(template);
@@ -316,7 +355,7 @@ describe('Index Templates tab', () => {
test('should set the correct title', async () => {
const { find } = testBed;
- const { name } = template1;
+ const [{ name }] = legacyTemplates;
expect(find('templateDetails.title').text()).toEqual(name);
});
@@ -327,12 +366,10 @@ describe('Index Templates tab', () => {
expect(exists('closeDetailsButton')).toBe(true);
expect(exists('summaryTab')).toBe(true);
- actions.clickCloseDetailsButton();
-
await act(async () => {
- await nextTick();
- component.update();
+ actions.clickCloseDetailsButton();
});
+ component.update();
expect(exists('summaryTab')).toBe(false);
});
@@ -372,6 +409,7 @@ describe('Index Templates tab', () => {
alias1: {},
},
},
+ isLegacy: true,
});
const { find, actions, exists } = testBed;
@@ -412,19 +450,15 @@ describe('Index Templates tab', () => {
const templateWithNoOptionalFields = fixtures.getTemplate({
name: `a${getRandomString()}`,
indexPatterns: ['template1Pattern1*', 'template1Pattern2'],
+ isLegacy: true,
});
- const { actions, find, exists, component } = testBed;
+ const { actions, find, exists } = testBed;
httpRequestsMockHelpers.setLoadTemplateResponse(templateWithNoOptionalFields);
await actions.clickTemplateAt(0);
- await act(async () => {
- await nextTick();
- component.update();
- });
-
expect(find('templateDetails.tab').length).toBe(4);
expect(exists('summaryTab')).toBe(true);
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts
index cf00e0f6d14e17..11c25ffbb590f5 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts
@@ -8,9 +8,17 @@ import { act } from 'react-dom/test-utils';
import { API_BASE_PATH } from '../../../common/constants';
import { setupEnvironment, nextTick } from '../helpers';
-
import { IndicesTestBed, setup } from './indices_tab.helpers';
+/**
+ * The below import is required to avoid a console error warn from the "brace" package
+ * console.warn ../node_modules/brace/index.js:3999
+ Could not load worker ReferenceError: Worker is not defined
+ at createWorker (//node_modules/brace/index.js:17992:5)
+ */
+import { stubWebWorker } from '../../../../../test_utils/stub_web_worker';
+stubWebWorker();
+
describe('', () => {
const { server, httpRequestsMockHelpers } = setupEnvironment();
let testBed: IndicesTestBed;
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx
index e0db9cd58ee238..6250ef0dc247d4 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx
+++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx
@@ -53,6 +53,7 @@ describe.skip('', () => {
template: {
mappings: MAPPINGS,
},
+ isLegacy: true,
});
beforeEach(async () => {
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx
index 95545b6c66f54b..50b35fc76721a0 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx
+++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx
@@ -7,7 +7,7 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
-import { DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT } from '../../../common';
+import { CREATE_LEGACY_TEMPLATE_BY_DEFAULT } from '../../../common';
import { setupEnvironment, nextTick } from '../helpers';
import {
@@ -344,7 +344,6 @@ describe.skip('', () => {
const latestRequest = server.requests[server.requests.length - 1];
const expected = {
- isManaged: false,
name: TEMPLATE_NAME,
indexPatterns: DEFAULT_INDEX_PATTERNS,
template: {
@@ -366,7 +365,8 @@ describe.skip('', () => {
aliases: ALIASES,
},
_kbnMeta: {
- formatVersion: DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT,
+ isLegacy: CREATE_LEGACY_TEMPLATE_BY_DEFAULT,
+ isManaged: false,
},
};
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx
index 6e935a5263301b..88067d479f7e7b 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx
+++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx
@@ -62,6 +62,7 @@ describe.skip('', () => {
const templateToEdit = fixtures.getTemplate({
name: 'index_template_without_mappings',
indexPatterns: ['indexPattern1'],
+ isLegacy: true,
});
beforeEach(async () => {
@@ -102,6 +103,7 @@ describe.skip('', () => {
template: {
mappings: MAPPING,
},
+ isLegacy: true,
});
beforeEach(async () => {
@@ -206,9 +208,9 @@ describe.skip('', () => {
settings: SETTINGS,
aliases: ALIASES,
},
- isManaged: false,
_kbnMeta: {
- formatVersion: templateToEdit._kbnMeta.formatVersion,
+ isManaged: false,
+ isLegacy: templateToEdit._kbnMeta.isLegacy,
},
};
diff --git a/x-pack/plugins/index_management/common/constants/index.ts b/x-pack/plugins/index_management/common/constants/index.ts
index 966e2e8e64838a..526b9fede2a679 100644
--- a/x-pack/plugins/index_management/common/constants/index.ts
+++ b/x-pack/plugins/index_management/common/constants/index.ts
@@ -9,7 +9,7 @@ export { BASE_PATH } from './base_path';
export { API_BASE_PATH } from './api_base_path';
export { INVALID_INDEX_PATTERN_CHARS, INVALID_TEMPLATE_NAME_CHARS } from './invalid_characters';
export * from './index_statuses';
-export { DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT } from './index_templates';
+export { CREATE_LEGACY_TEMPLATE_BY_DEFAULT } from './index_templates';
export {
UIM_APP_NAME,
diff --git a/x-pack/plugins/index_management/common/constants/index_templates.ts b/x-pack/plugins/index_management/common/constants/index_templates.ts
index 788e96ee895ed8..7696b3832c51e7 100644
--- a/x-pack/plugins/index_management/common/constants/index_templates.ts
+++ b/x-pack/plugins/index_management/common/constants/index_templates.ts
@@ -6,7 +6,7 @@
/**
* Up until the end of the 8.x release cycle we need to support both
- * V1 and V2 index template formats. This constant keeps track of whether
- * we create V1 or V2 index template format in the UI.
+ * legacy and composable index template formats. This constant keeps track of whether
+ * we create legacy index template format by default in the UI.
*/
-export const DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT = 1;
+export const CREATE_LEGACY_TEMPLATE_BY_DEFAULT = true;
diff --git a/x-pack/plugins/index_management/common/index.ts b/x-pack/plugins/index_management/common/index.ts
index 459eda7552c852..3792e322ae40be 100644
--- a/x-pack/plugins/index_management/common/index.ts
+++ b/x-pack/plugins/index_management/common/index.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { PLUGIN, API_BASE_PATH, DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT } from './constants';
+export { PLUGIN, API_BASE_PATH, CREATE_LEGACY_TEMPLATE_BY_DEFAULT } from './constants';
export { getTemplateParameter } from './lib';
diff --git a/x-pack/plugins/index_management/common/lib/index.ts b/x-pack/plugins/index_management/common/lib/index.ts
index 33f7fbe45182e1..16eb544c56a089 100644
--- a/x-pack/plugins/index_management/common/lib/index.ts
+++ b/x-pack/plugins/index_management/common/lib/index.ts
@@ -4,9 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
export {
+ deserializeLegacyTemplateList,
deserializeTemplateList,
- deserializeV1Template,
- serializeV1Template,
+ deserializeLegacyTemplate,
+ serializeLegacyTemplate,
} from './template_serialization';
export { getTemplateParameter } from './utils';
diff --git a/x-pack/plugins/index_management/common/lib/template_serialization.ts b/x-pack/plugins/index_management/common/lib/template_serialization.ts
index 33a83d1e9335b5..249881f668d9f6 100644
--- a/x-pack/plugins/index_management/common/lib/template_serialization.ts
+++ b/x-pack/plugins/index_management/common/lib/template_serialization.ts
@@ -5,60 +5,34 @@
*/
import {
TemplateDeserialized,
- TemplateV1Serialized,
- TemplateV2Serialized,
+ LegacyTemplateSerialized,
+ TemplateSerialized,
TemplateListItem,
} from '../types';
const hasEntries = (data: object = {}) => Object.entries(data).length > 0;
-export function serializeV1Template(template: TemplateDeserialized): TemplateV1Serialized {
- const {
- name,
- version,
- order,
- indexPatterns,
- template: { settings, aliases, mappings } = {} as TemplateDeserialized['template'],
- } = template;
+export function serializeTemplate(templateDeserialized: TemplateDeserialized): TemplateSerialized {
+ const { version, priority, indexPatterns, template, composedOf } = templateDeserialized;
- const serializedTemplate: TemplateV1Serialized = {
- name,
+ return {
version,
- order,
+ priority,
+ template,
index_patterns: indexPatterns,
- settings,
- aliases,
- mappings,
- };
-
- return serializedTemplate;
-}
-
-export function serializeV2Template(template: TemplateDeserialized): TemplateV2Serialized {
- const { aliases, mappings, settings, ...templateV1serialized } = serializeV1Template(template);
-
- return {
- ...templateV1serialized,
- template: {
- aliases,
- mappings,
- settings,
- },
- priority: template.priority,
- composed_of: template.composedOf,
+ composed_of: composedOf,
};
}
-export function deserializeV2Template(
- templateEs: TemplateV2Serialized,
+export function deserializeTemplate(
+ templateEs: TemplateSerialized & { name: string },
managedTemplatePrefix?: string
): TemplateDeserialized {
const {
name,
version,
- order,
index_patterns: indexPatterns,
- template,
+ template = {},
priority,
composed_of: composedOf,
} = templateEs;
@@ -67,49 +41,92 @@ export function deserializeV2Template(
const deserializedTemplate: TemplateDeserialized = {
name,
version,
- order,
+ priority,
indexPatterns: indexPatterns.sort(),
template,
- ilmPolicy: settings && settings.index && settings.index.lifecycle,
- isManaged: Boolean(managedTemplatePrefix && name.startsWith(managedTemplatePrefix)),
- priority,
+ ilmPolicy: settings?.index?.lifecycle,
composedOf,
_kbnMeta: {
- formatVersion: 2,
+ isManaged: Boolean(managedTemplatePrefix && name.startsWith(managedTemplatePrefix)),
},
};
return deserializedTemplate;
}
-export function deserializeV1Template(
- templateEs: TemplateV1Serialized,
+export function deserializeTemplateList(
+ indexTemplates: Array<{ name: string; index_template: TemplateSerialized }>,
+ managedTemplatePrefix?: string
+): TemplateListItem[] {
+ return indexTemplates.map(({ name, index_template: templateSerialized }) => {
+ const {
+ template: { mappings, settings, aliases },
+ ...deserializedTemplate
+ } = deserializeTemplate({ name, ...templateSerialized }, managedTemplatePrefix);
+
+ return {
+ ...deserializedTemplate,
+ hasSettings: hasEntries(settings),
+ hasAliases: hasEntries(aliases),
+ hasMappings: hasEntries(mappings),
+ };
+ });
+}
+
+/**
+ * ------------------------------------------
+ * --------- LEGACY INDEX TEMPLATES ---------
+ * ------------------------------------------
+ */
+
+export function serializeLegacyTemplate(template: TemplateDeserialized): LegacyTemplateSerialized {
+ const {
+ version,
+ order,
+ indexPatterns,
+ template: { settings, aliases, mappings },
+ } = template;
+
+ return {
+ version,
+ order,
+ index_patterns: indexPatterns,
+ settings,
+ aliases,
+ mappings,
+ };
+}
+
+export function deserializeLegacyTemplate(
+ templateEs: LegacyTemplateSerialized & { name: string },
managedTemplatePrefix?: string
): TemplateDeserialized {
const { settings, aliases, mappings, ...rest } = templateEs;
- const deserializedTemplateV2 = deserializeV2Template(
+ const deserializedTemplate = deserializeTemplate(
{ ...rest, template: { aliases, settings, mappings } },
managedTemplatePrefix
);
return {
- ...deserializedTemplateV2,
+ ...deserializedTemplate,
+ order: templateEs.order,
_kbnMeta: {
- formatVersion: 1,
+ ...deserializedTemplate._kbnMeta,
+ isLegacy: true,
},
};
}
-export function deserializeTemplateList(
- indexTemplatesByName: { [key: string]: Omit },
+export function deserializeLegacyTemplateList(
+ indexTemplatesByName: { [key: string]: LegacyTemplateSerialized },
managedTemplatePrefix?: string
): TemplateListItem[] {
return Object.entries(indexTemplatesByName).map(([name, templateSerialized]) => {
const {
template: { mappings, settings, aliases },
...deserializedTemplate
- } = deserializeV1Template({ name, ...templateSerialized }, managedTemplatePrefix);
+ } = deserializeLegacyTemplate({ name, ...templateSerialized }, managedTemplatePrefix);
return {
...deserializedTemplate,
diff --git a/x-pack/plugins/index_management/common/lib/utils.test.ts b/x-pack/plugins/index_management/common/lib/utils.test.ts
index 221d1b009cede0..056101061a82bb 100644
--- a/x-pack/plugins/index_management/common/lib/utils.test.ts
+++ b/x-pack/plugins/index_management/common/lib/utils.test.ts
@@ -3,12 +3,12 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { TemplateV1Serialized, TemplateV2Serialized } from '../types';
-import { getTemplateVersion } from './utils';
+import { LegacyTemplateSerialized, TemplateSerialized } from '../types';
+import { isLegacyTemplate } from './utils';
describe('utils', () => {
- describe('getTemplateVersion', () => {
- test('should detect v1 template', () => {
+ describe('isLegacyTemplate', () => {
+ test('should detect legacy template', () => {
const template = {
name: 'my_template',
index_patterns: ['logs*'],
@@ -16,10 +16,10 @@ describe('utils', () => {
properties: {},
},
};
- expect(getTemplateVersion(template as TemplateV1Serialized)).toBe(1);
+ expect(isLegacyTemplate(template as LegacyTemplateSerialized)).toBe(true);
});
- test('should detect v2 template', () => {
+ test('should detect composable template', () => {
const template = {
name: 'my_template',
index_patterns: ['logs*'],
@@ -29,7 +29,7 @@ describe('utils', () => {
},
},
};
- expect(getTemplateVersion(template as TemplateV2Serialized)).toBe(2);
+ expect(isLegacyTemplate(template as TemplateSerialized)).toBe(false);
});
});
});
diff --git a/x-pack/plugins/index_management/common/lib/utils.ts b/x-pack/plugins/index_management/common/lib/utils.ts
index eee35dc1ab4679..5a7db8ef50ab43 100644
--- a/x-pack/plugins/index_management/common/lib/utils.ts
+++ b/x-pack/plugins/index_management/common/lib/utils.ts
@@ -4,26 +4,24 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { TemplateDeserialized, TemplateV1Serialized, TemplateV2Serialized } from '../types';
+import { TemplateDeserialized, LegacyTemplateSerialized, TemplateSerialized } from '../types';
/**
- * Helper to get the format version of an index template.
- * v1 will be supported up until 9.x but marked as deprecated from 7.8
- * v2 will be supported from 7.8
+ * Helper to know if a template has the legacy format or not
+ * legacy format will be supported up until 9.x but marked as deprecated from 7.8
+ * new (composable) format is supported from 7.8
*/
-export const getTemplateVersion = (
- template: TemplateDeserialized | TemplateV1Serialized | TemplateV2Serialized
-): 1 | 2 => {
- return {}.hasOwnProperty.call(template, 'template') ? 2 : 1;
+export const isLegacyTemplate = (
+ template: TemplateDeserialized | LegacyTemplateSerialized | TemplateSerialized
+): boolean => {
+ return {}.hasOwnProperty.call(template, 'template') ? false : true;
};
export const getTemplateParameter = (
- template: TemplateV1Serialized | TemplateV2Serialized,
+ template: LegacyTemplateSerialized | TemplateSerialized,
setting: 'aliases' | 'settings' | 'mappings'
) => {
- const formatVersion = getTemplateVersion(template);
-
- return formatVersion === 1
- ? (template as TemplateV1Serialized)[setting]
- : (template as TemplateV2Serialized).template[setting];
+ return isLegacyTemplate(template)
+ ? (template as LegacyTemplateSerialized)[setting]
+ : (template as TemplateSerialized).template[setting];
};
diff --git a/x-pack/plugins/index_management/common/types/templates.ts b/x-pack/plugins/index_management/common/types/templates.ts
index c37088982f207f..f113aa44d058f8 100644
--- a/x-pack/plugins/index_management/common/types/templates.ts
+++ b/x-pack/plugins/index_management/common/types/templates.ts
@@ -8,28 +8,45 @@ import { IndexSettings } from './indices';
import { Aliases } from './aliases';
import { Mappings } from './mappings';
-// Template serialized (from Elasticsearch)
-interface TemplateBaseSerialized {
- name: string;
+/**
+ * Index template format from Elasticsearch
+ */
+export interface TemplateSerialized {
index_patterns: string[];
+ template: {
+ settings?: IndexSettings;
+ aliases?: Aliases;
+ mappings?: Mappings;
+ };
+ composed_of?: string[];
version?: number;
- order?: number;
-}
-
-export interface TemplateV1Serialized extends TemplateBaseSerialized {
- settings?: IndexSettings;
- aliases?: Aliases;
- mappings?: Mappings;
+ priority?: number;
}
-export interface TemplateV2Serialized extends TemplateBaseSerialized {
+/**
+ * TemplateDeserialized is the format the UI will be working with,
+ * regardless if we are loading the new format (composable) index template,
+ * or the legacy one. Serialization is done server side.
+ */
+export interface TemplateDeserialized {
+ name: string;
+ indexPatterns: string[];
template: {
settings?: IndexSettings;
aliases?: Aliases;
mappings?: Mappings;
};
+ composedOf?: string[]; // Used on composable index template
+ version?: number;
priority?: number;
- composed_of?: string[];
+ order?: number; // Used on legacy index template
+ ilmPolicy?: {
+ name: string;
+ };
+ _kbnMeta: {
+ isManaged: boolean;
+ isLegacy?: boolean;
+ };
}
/**
@@ -42,42 +59,30 @@ export interface TemplateListItem {
indexPatterns: string[];
version?: number;
order?: number;
+ priority?: number;
hasSettings: boolean;
hasAliases: boolean;
hasMappings: boolean;
ilmPolicy?: {
name: string;
};
- isManaged: boolean;
_kbnMeta: {
- formatVersion: IndexTemplateFormatVersion;
+ isManaged: boolean;
+ isLegacy?: boolean;
};
}
/**
- * TemplateDeserialized falls back to index template V2 format
- * The UI will only be dealing with this interface, conversion from and to V1 format
- * is done server side.
+ * ------------------------------------------
+ * --------- LEGACY INDEX TEMPLATES ---------
+ * ------------------------------------------
*/
-export interface TemplateDeserialized {
- name: string;
- indexPatterns: string[];
- isManaged: boolean;
- template: {
- settings?: IndexSettings;
- aliases?: Aliases;
- mappings?: Mappings;
- };
- _kbnMeta: {
- formatVersion: IndexTemplateFormatVersion;
- };
+
+export interface LegacyTemplateSerialized {
+ index_patterns: string[];
version?: number;
- priority?: number;
+ settings?: IndexSettings;
+ aliases?: Aliases;
+ mappings?: Mappings;
order?: number;
- ilmPolicy?: {
- name: string;
- };
- composedOf?: string[];
}
-
-export type IndexTemplateFormatVersion = 1 | 2;
diff --git a/x-pack/plugins/index_management/public/application/components/template_delete_modal.tsx b/x-pack/plugins/index_management/public/application/components/template_delete_modal.tsx
index a87412ef929504..06babb0db3bd1d 100644
--- a/x-pack/plugins/index_management/public/application/components/template_delete_modal.tsx
+++ b/x-pack/plugins/index_management/public/application/components/template_delete_modal.tsx
@@ -9,7 +9,6 @@ import { EuiConfirmModal, EuiOverlayMask, EuiCallOut, EuiCheckbox, EuiBadge } fr
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-import { IndexTemplateFormatVersion } from '../../../common';
import { deleteTemplates } from '../services/api';
import { notificationService } from '../services/notification';
@@ -17,7 +16,7 @@ export const TemplateDeleteModal = ({
templatesToDelete,
callback,
}: {
- templatesToDelete: Array<{ name: string; formatVersion: IndexTemplateFormatVersion }>;
+ templatesToDelete: Array<{ name: string; isLegacy?: boolean }>;
callback: (data?: { hasDeletedTemplates: boolean }) => void;
}) => {
const [isDeleteConfirmed, setIsDeleteConfirmed] = useState(false);
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_review.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_review.tsx
index 7b266034bc336f..387887239aaf0b 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_review.tsx
+++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_review.tsx
@@ -23,8 +23,8 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { serializers } from '../../../../shared_imports';
import {
- serializeV1Template,
- serializeV2Template,
+ serializeLegacyTemplate,
+ serializeTemplate,
} from '../../../../../common/lib/template_serialization';
import { TemplateDeserialized, getTemplateParameter } from '../../../../../common';
import { StepProps } from '../types';
@@ -60,16 +60,20 @@ export const StepReview: React.FunctionComponent = ({ template, updat
indexPatterns,
version,
order,
- _kbnMeta: { formatVersion },
+ _kbnMeta: { isLegacy },
} = template!;
- const serializedTemplate =
- formatVersion === 1
- ? serializeV1Template(stripEmptyFields(template!) as TemplateDeserialized)
- : serializeV2Template(stripEmptyFields(template!) as TemplateDeserialized);
-
- // Name not included in ES request body
- delete serializedTemplate.name;
+ const serializedTemplate = isLegacy
+ ? serializeLegacyTemplate(
+ stripEmptyFields(template!, {
+ types: ['string'],
+ }) as TemplateDeserialized
+ )
+ : serializeTemplate(
+ stripEmptyFields(template!, {
+ types: ['string'],
+ }) as TemplateDeserialized
+ );
const serializedMappings = getTemplateParameter(serializedTemplate, 'mappings');
const serializedSettings = getTemplateParameter(serializedTemplate, 'settings');
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx
index 0cdfaae70f1513..52e26e6d3e8959 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx
+++ b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx
@@ -15,7 +15,7 @@ import {
} from '@elastic/eui';
import { serializers } from '../../../shared_imports';
-import { TemplateDeserialized, DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT } from '../../../../common';
+import { TemplateDeserialized, CREATE_LEGACY_TEMPLATE_BY_DEFAULT } from '../../../../common';
import { TemplateSteps } from './template_steps';
import { StepAliases, StepLogistics, StepMappings, StepSettings, StepReview } from './steps';
import { StepProps, DataGetterFunc } from './types';
@@ -51,9 +51,9 @@ export const TemplateForm: React.FunctionComponent = ({
name: '',
indexPatterns: [],
template: {},
- isManaged: false,
_kbnMeta: {
- formatVersion: DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT,
+ isManaged: false,
+ isLegacy: CREATE_LEGACY_TEMPLATE_BY_DEFAULT,
},
},
onSave,
@@ -246,7 +246,9 @@ export const TemplateForm: React.FunctionComponent = ({
iconType="check"
onClick={onSave.bind(
null,
- stripEmptyFields(template.current!) as TemplateDeserialized
+ stripEmptyFields(template.current!, {
+ types: ['string'],
+ }) as TemplateDeserialized
)}
data-test-subj="submitButton"
isLoading={isSaving}
diff --git a/x-pack/plugins/index_management/public/application/lib/index_templates.ts b/x-pack/plugins/index_management/public/application/lib/index_templates.ts
index 7129e536287c11..08102ae93cc0e7 100644
--- a/x-pack/plugins/index_management/public/application/lib/index_templates.ts
+++ b/x-pack/plugins/index_management/public/application/lib/index_templates.ts
@@ -6,12 +6,12 @@
import { parse } from 'query-string';
import { Location } from 'history';
-export const getFormatVersionFromQueryparams = (location: Location): 1 | 2 | undefined => {
- const { v: version } = parse(location.search.substring(1));
+export const getIsLegacyFromQueryParams = (location: Location): boolean => {
+ const { legacy } = parse(location.search.substring(1));
- if (!Boolean(version) || typeof version !== 'string') {
- return undefined;
+ if (!Boolean(legacy) || typeof legacy !== 'string') {
+ return false;
}
- return +version as 1 | 2;
+ return legacy === 'true';
};
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/components/filter_list_button.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/components/filter_list_button.tsx
new file mode 100644
index 00000000000000..1c95cca3fead66
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/components/filter_list_button.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 React, { useState } from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiFilterButton, EuiPopover, EuiFilterSelectItem } from '@elastic/eui';
+
+interface Filter {
+ name: string;
+ checked: 'on' | 'off';
+}
+
+interface Props {
+ filters: Filters;
+ onChange(filters: Filters): void;
+}
+
+export type Filters = {
+ [key in T]: Filter;
+};
+
+export function FilterListButton({ onChange, filters }: Props) {
+ const [isPopoverOpen, setIsPopoverOpen] = useState(false);
+
+ const activeFilters = Object.values(filters).filter((v) => (v as Filter).checked === 'on');
+
+ const onButtonClick = () => {
+ setIsPopoverOpen(!isPopoverOpen);
+ };
+
+ const closePopover = () => {
+ setIsPopoverOpen(false);
+ };
+
+ const toggleFilter = (filter: T) => {
+ const previousValue = filters[filter].checked;
+ onChange({
+ ...filters,
+ [filter]: {
+ ...filters[filter],
+ checked: previousValue === 'on' ? 'off' : 'on',
+ },
+ });
+ };
+
+ const button = (
+ 0}
+ numActiveFilters={activeFilters.length}
+ data-test-subj="viewButton"
+ >
+
+
+ );
+
+ return (
+
+
+ {Object.entries(filters).map(([filter, item], index) => (
+ toggleFilter(filter as T)}
+ data-test-subj="filterItem"
+ >
+ {(item as Filter).name}
+
+ ))}
+
+
+ );
+}
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/components/index.ts b/x-pack/plugins/index_management/public/application/sections/home/template_list/components/index.ts
new file mode 100644
index 00000000000000..dcaba319bb21a8
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/components/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export * from './filter_list_button';
+export * from './template_content_indicator';
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/components/template_content_indicator.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/components/template_content_indicator.tsx
new file mode 100644
index 00000000000000..78e33d7940bd4e
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/components/template_content_indicator.tsx
@@ -0,0 +1,51 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiBadge, EuiToolTip } from '@elastic/eui';
+
+interface Props {
+ mappings: boolean;
+ settings: boolean;
+ aliases: boolean;
+}
+
+const texts = {
+ settings: i18n.translate('xpack.idxMgmt.templateContentIndicator.indexSettingsTooltipLabel', {
+ defaultMessage: 'Index settings',
+ }),
+ mappings: i18n.translate('xpack.idxMgmt.templateContentIndicator.mappingsTooltipLabel', {
+ defaultMessage: 'Mappings',
+ }),
+ aliases: i18n.translate('xpack.idxMgmt.templateContentIndicator.aliasesTooltipLabel', {
+ defaultMessage: 'Aliases',
+ }),
+};
+
+export const TemplateContentIndicator = ({ mappings, settings, aliases }: Props) => {
+ const getColor = (flag: boolean) => (flag ? 'primary' : 'hollow');
+
+ return (
+ <>
+
+ <>
+ M
+
+ >
+
+
+ <>
+ S
+
+ >
+
+
+ A
+
+ >
+ );
+};
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_details/index.ts b/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_details/index.ts
new file mode 100644
index 00000000000000..519120b559e7ba
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_details/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { LegacyTemplateDetails } from './template_details';
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_details/template_details.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_details/template_details.tsx
new file mode 100644
index 00000000000000..ec2956973d4f6b
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_details/template_details.tsx
@@ -0,0 +1,327 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { Fragment, useState } from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { i18n } from '@kbn/i18n';
+import {
+ EuiCallOut,
+ EuiFlyout,
+ EuiFlyoutHeader,
+ EuiTitle,
+ EuiFlyoutBody,
+ EuiFlyoutFooter,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiButtonEmpty,
+ EuiTab,
+ EuiTabs,
+ EuiSpacer,
+ EuiPopover,
+ EuiButton,
+ EuiContextMenu,
+} from '@elastic/eui';
+import {
+ UIM_TEMPLATE_DETAIL_PANEL_MAPPINGS_TAB,
+ UIM_TEMPLATE_DETAIL_PANEL_SUMMARY_TAB,
+ UIM_TEMPLATE_DETAIL_PANEL_SETTINGS_TAB,
+ UIM_TEMPLATE_DETAIL_PANEL_ALIASES_TAB,
+} from '../../../../../../../common/constants';
+import { TemplateDeserialized } from '../../../../../../../common';
+import {
+ TemplateDeleteModal,
+ SectionLoading,
+ SectionError,
+ Error,
+} from '../../../../../components';
+import { useLoadIndexTemplate } from '../../../../../services/api';
+import { decodePath } from '../../../../../services/routing';
+import { SendRequestResponse } from '../../../../../../shared_imports';
+import { useServices } from '../../../../../app_context';
+import { TabSummary, TabMappings, TabSettings, TabAliases } from '../../template_details/tabs';
+
+interface Props {
+ template: { name: string; isLegacy?: boolean };
+ onClose: () => void;
+ editTemplate: (name: string, isLegacy?: boolean) => void;
+ cloneTemplate: (name: string, isLegacy?: boolean) => void;
+ reload: () => Promise;
+}
+
+const SUMMARY_TAB_ID = 'summary';
+const MAPPINGS_TAB_ID = 'mappings';
+const ALIASES_TAB_ID = 'aliases';
+const SETTINGS_TAB_ID = 'settings';
+
+const TABS = [
+ {
+ id: SUMMARY_TAB_ID,
+ name: i18n.translate('xpack.idxMgmt.legacyTemplateDetails.summaryTabTitle', {
+ defaultMessage: 'Summary',
+ }),
+ },
+ {
+ id: SETTINGS_TAB_ID,
+ name: i18n.translate('xpack.idxMgmt.legacyTemplateDetails.settingsTabTitle', {
+ defaultMessage: 'Settings',
+ }),
+ },
+ {
+ id: MAPPINGS_TAB_ID,
+ name: i18n.translate('xpack.idxMgmt.legacyTemplateDetails.mappingsTabTitle', {
+ defaultMessage: 'Mappings',
+ }),
+ },
+ {
+ id: ALIASES_TAB_ID,
+ name: i18n.translate('xpack.idxMgmt.legacyTemplateDetails.aliasesTabTitle', {
+ defaultMessage: 'Aliases',
+ }),
+ },
+];
+
+const tabToComponentMap: {
+ [key: string]: React.FunctionComponent<{ templateDetails: TemplateDeserialized }>;
+} = {
+ [SUMMARY_TAB_ID]: TabSummary,
+ [SETTINGS_TAB_ID]: TabSettings,
+ [MAPPINGS_TAB_ID]: TabMappings,
+ [ALIASES_TAB_ID]: TabAliases,
+};
+
+const tabToUiMetricMap: { [key: string]: string } = {
+ [SUMMARY_TAB_ID]: UIM_TEMPLATE_DETAIL_PANEL_SUMMARY_TAB,
+ [SETTINGS_TAB_ID]: UIM_TEMPLATE_DETAIL_PANEL_SETTINGS_TAB,
+ [MAPPINGS_TAB_ID]: UIM_TEMPLATE_DETAIL_PANEL_MAPPINGS_TAB,
+ [ALIASES_TAB_ID]: UIM_TEMPLATE_DETAIL_PANEL_ALIASES_TAB,
+};
+
+export const LegacyTemplateDetails: React.FunctionComponent = ({
+ template: { name: templateName, isLegacy },
+ onClose,
+ editTemplate,
+ cloneTemplate,
+ reload,
+}) => {
+ const { uiMetricService } = useServices();
+ const decodedTemplateName = decodePath(templateName);
+ const { error, data: templateDetails, isLoading } = useLoadIndexTemplate(
+ decodedTemplateName,
+ isLegacy
+ );
+ const isManaged = templateDetails?._kbnMeta.isManaged ?? false;
+ const [templateToDelete, setTemplateToDelete] = useState<
+ Array<{ name: string; isLegacy?: boolean }>
+ >([]);
+ const [activeTab, setActiveTab] = useState(SUMMARY_TAB_ID);
+ const [isPopoverOpen, setIsPopOverOpen] = useState(false);
+
+ let content;
+
+ if (isLoading) {
+ content = (
+
+
+
+ );
+ } else if (error) {
+ content = (
+
+ }
+ error={error as Error}
+ data-test-subj="sectionError"
+ />
+ );
+ } else if (templateDetails) {
+ const Content = tabToComponentMap[activeTab];
+ const managedTemplateCallout = isManaged ? (
+
+
+ }
+ color="primary"
+ size="s"
+ >
+
+
+
+
+ ) : null;
+
+ content = (
+
+ {managedTemplateCallout}
+
+
+ {TABS.map((tab) => (
+ {
+ uiMetricService.trackMetric('click', tabToUiMetricMap[tab.id]);
+ setActiveTab(tab.id);
+ }}
+ isSelected={tab.id === activeTab}
+ key={tab.id}
+ data-test-subj="tab"
+ >
+ {tab.name}
+
+ ))}
+
+
+
+
+
+
+ );
+ }
+
+ return (
+
+ {templateToDelete && templateToDelete.length > 0 ? (
+ {
+ if (data && data.hasDeletedTemplates) {
+ reload();
+ } else {
+ setTemplateToDelete([]);
+ }
+ onClose();
+ }}
+ templatesToDelete={templateToDelete}
+ />
+ ) : null}
+
+
+
+
+
+ {decodedTemplateName}
+
+
+
+
+ {content}
+
+
+
+
+
+
+
+
+ {templateDetails && (
+
+ {/* Manage templates context menu */}
+ setIsPopOverOpen((prev) => !prev)}
+ >
+
+
+ }
+ isOpen={isPopoverOpen}
+ closePopover={() => setIsPopOverOpen(false)}
+ panelPaddingSize="none"
+ withTitle
+ anchorPosition="rightUp"
+ repositionOnScroll
+ >
+ editTemplate(templateName, isLegacy),
+ disabled: isManaged,
+ },
+ {
+ name: i18n.translate(
+ 'xpack.idxMgmt.legacyTemplateDetails.cloneButtonLabel',
+ {
+ defaultMessage: 'Clone',
+ }
+ ),
+ icon: 'copy',
+ onClick: () => cloneTemplate(templateName, isLegacy),
+ },
+ {
+ name: i18n.translate(
+ 'xpack.idxMgmt.legacyTemplateDetails.deleteButtonLabel',
+ {
+ defaultMessage: 'Delete',
+ }
+ ),
+ icon: 'trash',
+ onClick: () =>
+ setTemplateToDelete([{ name: decodedTemplateName, isLegacy }]),
+ disabled: isManaged,
+ },
+ ],
+ },
+ ]}
+ />
+
+
+ )}
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_table/index.ts b/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_table/index.ts
new file mode 100644
index 00000000000000..a8499df45ce273
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_table/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { LegacyTemplateTable } from './template_table';
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_table/template_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_table/template_table.tsx
new file mode 100644
index 00000000000000..92fedd5d68f002
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_table/template_table.tsx
@@ -0,0 +1,304 @@
+/*
+ * 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, { useState, Fragment } from 'react';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiInMemoryTable, EuiIcon, EuiButton, EuiLink, EuiBasicTableColumn } from '@elastic/eui';
+import { ScopedHistory } from 'kibana/public';
+import { reactRouterNavigate } from '../../../../../../../../../../src/plugins/kibana_react/public';
+import { TemplateListItem } from '../../../../../../../common';
+import { UIM_TEMPLATE_SHOW_DETAILS_CLICK } from '../../../../../../../common/constants';
+import { TemplateDeleteModal } from '../../../../../components';
+import { useServices } from '../../../../../app_context';
+import { SendRequestResponse } from '../../../../../../shared_imports';
+
+interface Props {
+ templates: TemplateListItem[];
+ reload: () => Promise;
+ editTemplate: (name: string, isLegacy?: boolean) => void;
+ cloneTemplate: (name: string, isLegacy?: boolean) => void;
+ history: ScopedHistory;
+}
+
+export const LegacyTemplateTable: React.FunctionComponent = ({
+ templates,
+ reload,
+ editTemplate,
+ cloneTemplate,
+ history,
+}) => {
+ const { uiMetricService } = useServices();
+ const [selection, setSelection] = useState([]);
+ const [templatesToDelete, setTemplatesToDelete] = useState<
+ Array<{ name: string; isLegacy?: boolean }>
+ >([]);
+
+ const columns: Array> = [
+ {
+ field: 'name',
+ name: i18n.translate('xpack.idxMgmt.templateList.legacyTable.nameColumnTitle', {
+ defaultMessage: 'Name',
+ }),
+ truncateText: true,
+ sortable: true,
+ render: (name: TemplateListItem['name'], item: TemplateListItem) => {
+ return (
+ /* eslint-disable-next-line @elastic/eui/href-or-on-click */
+ uiMetricService.trackMetric('click', UIM_TEMPLATE_SHOW_DETAILS_CLICK)
+ )}
+ data-test-subj="templateDetailsLink"
+ >
+ {name}
+
+ );
+ },
+ },
+ {
+ field: 'indexPatterns',
+ name: i18n.translate('xpack.idxMgmt.templateList.legacyTable.indexPatternsColumnTitle', {
+ defaultMessage: 'Index patterns',
+ }),
+ truncateText: true,
+ sortable: true,
+ render: (indexPatterns: string[]) => {indexPatterns.join(', ')},
+ },
+ {
+ field: 'ilmPolicy',
+ name: i18n.translate('xpack.idxMgmt.templateList.legacyTable.ilmPolicyColumnTitle', {
+ defaultMessage: 'ILM policy',
+ }),
+ truncateText: true,
+ sortable: true,
+ render: (ilmPolicy: { name: string }) =>
+ ilmPolicy && ilmPolicy.name ? (
+
+ {ilmPolicy.name}
+
+ ) : null,
+ },
+ {
+ field: 'order',
+ name: i18n.translate('xpack.idxMgmt.templateList.legacyTable.orderColumnTitle', {
+ defaultMessage: 'Order',
+ }),
+ truncateText: true,
+ sortable: true,
+ },
+ {
+ field: 'hasMappings',
+ name: i18n.translate('xpack.idxMgmt.templateList.legacyTable.mappingsColumnTitle', {
+ defaultMessage: 'Mappings',
+ }),
+ truncateText: true,
+ sortable: true,
+ render: (hasMappings: boolean) => (hasMappings ? : null),
+ },
+ {
+ field: 'hasSettings',
+ name: i18n.translate('xpack.idxMgmt.templateList.legacyTable.settingsColumnTitle', {
+ defaultMessage: 'Settings',
+ }),
+ truncateText: true,
+ sortable: true,
+ render: (hasSettings: boolean) => (hasSettings ? : null),
+ },
+ {
+ field: 'hasAliases',
+ name: i18n.translate('xpack.idxMgmt.templateList.legacyTable.aliasesColumnTitle', {
+ defaultMessage: 'Aliases',
+ }),
+ truncateText: true,
+ sortable: true,
+ render: (hasAliases: boolean) => (hasAliases ? : null),
+ },
+ {
+ name: i18n.translate('xpack.idxMgmt.templateList.legacyTable.actionColumnTitle', {
+ defaultMessage: 'Actions',
+ }),
+ actions: [
+ {
+ name: i18n.translate('xpack.idxMgmt.templateList.legacyTable.actionEditText', {
+ defaultMessage: 'Edit',
+ }),
+ isPrimary: true,
+ description: i18n.translate(
+ 'xpack.idxMgmt.templateList.legacyTable.actionEditDecription',
+ {
+ defaultMessage: 'Edit this template',
+ }
+ ),
+ icon: 'pencil',
+ type: 'icon',
+ onClick: ({ name, _kbnMeta: { isLegacy } }: TemplateListItem) => {
+ editTemplate(name, isLegacy);
+ },
+ enabled: ({ _kbnMeta: { isManaged } }: TemplateListItem) => !isManaged,
+ },
+ {
+ type: 'icon',
+ name: i18n.translate('xpack.idxMgmt.templateList.legacyTable.actionCloneTitle', {
+ defaultMessage: 'Clone',
+ }),
+ description: i18n.translate(
+ 'xpack.idxMgmt.templateList.legacyTable.actionCloneDescription',
+ {
+ defaultMessage: 'Clone this template',
+ }
+ ),
+ icon: 'copy',
+ onClick: ({ name, _kbnMeta: { isLegacy } }: TemplateListItem) => {
+ cloneTemplate(name, isLegacy);
+ },
+ },
+ {
+ name: i18n.translate('xpack.idxMgmt.templateList.legacyTable.actionDeleteText', {
+ defaultMessage: 'Delete',
+ }),
+ description: i18n.translate(
+ 'xpack.idxMgmt.templateList.legacyTable.actionDeleteDecription',
+ {
+ defaultMessage: 'Delete this template',
+ }
+ ),
+ icon: 'trash',
+ color: 'danger',
+ type: 'icon',
+ onClick: ({ name, _kbnMeta: { isLegacy } }: TemplateListItem) => {
+ setTemplatesToDelete([{ name, isLegacy }]);
+ },
+ isPrimary: true,
+ enabled: ({ _kbnMeta: { isManaged } }: TemplateListItem) => !isManaged,
+ },
+ ],
+ },
+ ];
+
+ const pagination = {
+ initialPageSize: 20,
+ pageSizeOptions: [10, 20, 50],
+ };
+
+ const sorting = {
+ sort: {
+ field: 'name',
+ direction: 'asc',
+ },
+ } as const;
+
+ const selectionConfig = {
+ onSelectionChange: setSelection,
+ selectable: ({ _kbnMeta: { isManaged } }: TemplateListItem) => !isManaged,
+ selectableMessage: (selectable: boolean) => {
+ if (!selectable) {
+ return i18n.translate(
+ 'xpack.idxMgmt.templateList.legacyTable.deleteManagedTemplateTooltip',
+ {
+ defaultMessage: 'You cannot delete a managed template.',
+ }
+ );
+ }
+ return '';
+ },
+ };
+
+ const searchConfig = {
+ box: {
+ incremental: true,
+ },
+ toolsLeft:
+ selection.length > 0 ? (
+
+ setTemplatesToDelete(
+ selection.map(({ name, _kbnMeta: { isLegacy } }: TemplateListItem) => ({
+ name,
+ isLegacy,
+ }))
+ )
+ }
+ color="danger"
+ >
+
+
+ ) : undefined,
+ toolsRight: [
+
+
+ ,
+ ],
+ };
+
+ return (
+
+ {templatesToDelete && templatesToDelete.length > 0 ? (
+ {
+ if (data && data.hasDeletedTemplates) {
+ reload();
+ } else {
+ setTemplatesToDelete([]);
+ }
+ }}
+ templatesToDelete={templatesToDelete}
+ />
+ ) : null}
+ ({
+ 'data-test-subj': 'row',
+ })}
+ cellProps={() => ({
+ 'data-test-subj': 'cell',
+ })}
+ data-test-subj="legacyTemplateTable"
+ message={
+
+ }
+ />
+
+ );
+};
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/template_details.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/template_details.tsx
index ed403276af564f..9f51f114176fbd 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/template_details.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/template_details.tsx
@@ -4,313 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { Fragment, useState } from 'react';
-import { FormattedMessage } from '@kbn/i18n/react';
-import { i18n } from '@kbn/i18n';
-import {
- EuiCallOut,
- EuiFlyout,
- EuiFlyoutHeader,
- EuiTitle,
- EuiFlyoutBody,
- EuiFlyoutFooter,
- EuiFlexGroup,
- EuiFlexItem,
- EuiButtonEmpty,
- EuiTab,
- EuiTabs,
- EuiSpacer,
- EuiPopover,
- EuiButton,
- EuiContextMenu,
-} from '@elastic/eui';
-import {
- UIM_TEMPLATE_DETAIL_PANEL_MAPPINGS_TAB,
- UIM_TEMPLATE_DETAIL_PANEL_SUMMARY_TAB,
- UIM_TEMPLATE_DETAIL_PANEL_SETTINGS_TAB,
- UIM_TEMPLATE_DETAIL_PANEL_ALIASES_TAB,
-} from '../../../../../../common/constants';
-import { TemplateDeserialized, IndexTemplateFormatVersion } from '../../../../../../common';
-import { TemplateDeleteModal, SectionLoading, SectionError, Error } from '../../../../components';
-import { useLoadIndexTemplate } from '../../../../services/api';
-import { decodePath } from '../../../../services/routing';
-import { SendRequestResponse } from '../../../../../shared_imports';
-import { useServices } from '../../../../app_context';
-import { TabSummary, TabMappings, TabSettings, TabAliases } from './tabs';
+import React from 'react';
-interface Props {
- template: { name: string; formatVersion: IndexTemplateFormatVersion };
- onClose: () => void;
- editTemplate: (name: string, formatVersion: IndexTemplateFormatVersion) => void;
- cloneTemplate: (name: string, formatVersion: IndexTemplateFormatVersion) => void;
- reload: () => Promise;
-}
-
-const SUMMARY_TAB_ID = 'summary';
-const MAPPINGS_TAB_ID = 'mappings';
-const ALIASES_TAB_ID = 'aliases';
-const SETTINGS_TAB_ID = 'settings';
-
-const TABS = [
- {
- id: SUMMARY_TAB_ID,
- name: i18n.translate('xpack.idxMgmt.templateDetails.summaryTabTitle', {
- defaultMessage: 'Summary',
- }),
- },
- {
- id: SETTINGS_TAB_ID,
- name: i18n.translate('xpack.idxMgmt.templateDetails.settingsTabTitle', {
- defaultMessage: 'Settings',
- }),
- },
- {
- id: MAPPINGS_TAB_ID,
- name: i18n.translate('xpack.idxMgmt.templateDetails.mappingsTabTitle', {
- defaultMessage: 'Mappings',
- }),
- },
- {
- id: ALIASES_TAB_ID,
- name: i18n.translate('xpack.idxMgmt.templateDetails.aliasesTabTitle', {
- defaultMessage: 'Aliases',
- }),
- },
-];
-
-const tabToComponentMap: {
- [key: string]: React.FunctionComponent<{ templateDetails: TemplateDeserialized }>;
-} = {
- [SUMMARY_TAB_ID]: TabSummary,
- [SETTINGS_TAB_ID]: TabSettings,
- [MAPPINGS_TAB_ID]: TabMappings,
- [ALIASES_TAB_ID]: TabAliases,
-};
-
-const tabToUiMetricMap: { [key: string]: string } = {
- [SUMMARY_TAB_ID]: UIM_TEMPLATE_DETAIL_PANEL_SUMMARY_TAB,
- [SETTINGS_TAB_ID]: UIM_TEMPLATE_DETAIL_PANEL_SETTINGS_TAB,
- [MAPPINGS_TAB_ID]: UIM_TEMPLATE_DETAIL_PANEL_MAPPINGS_TAB,
- [ALIASES_TAB_ID]: UIM_TEMPLATE_DETAIL_PANEL_ALIASES_TAB,
-};
-
-export const TemplateDetails: React.FunctionComponent = ({
- template: { name: templateName, formatVersion },
- onClose,
- editTemplate,
- cloneTemplate,
- reload,
-}) => {
- const { uiMetricService } = useServices();
- const decodedTemplateName = decodePath(templateName);
- const { error, data: templateDetails, isLoading } = useLoadIndexTemplate(
- decodedTemplateName,
- formatVersion
- );
- const isManaged = templateDetails?.isManaged;
- const [templateToDelete, setTemplateToDelete] = useState<
- Array<{ name: string; formatVersion: IndexTemplateFormatVersion }>
- >([]);
- const [activeTab, setActiveTab] = useState(SUMMARY_TAB_ID);
- const [isPopoverOpen, setIsPopOverOpen] = useState(false);
-
- let content;
-
- if (isLoading) {
- content = (
-
-
-
- );
- } else if (error) {
- content = (
-
- }
- error={error as Error}
- data-test-subj="sectionError"
- />
- );
- } else if (templateDetails) {
- const Content = tabToComponentMap[activeTab];
- const managedTemplateCallout = isManaged ? (
-
-
- }
- color="primary"
- size="s"
- >
-
-
-
-
- ) : null;
-
- content = (
-
- {managedTemplateCallout}
-
-
- {TABS.map((tab) => (
- {
- uiMetricService.trackMetric('click', tabToUiMetricMap[tab.id]);
- setActiveTab(tab.id);
- }}
- isSelected={tab.id === activeTab}
- key={tab.id}
- data-test-subj="tab"
- >
- {tab.name}
-
- ))}
-
-
-
-
-
-
- );
- }
-
- return (
-
- {templateToDelete && templateToDelete.length > 0 ? (
- {
- if (data && data.hasDeletedTemplates) {
- reload();
- } else {
- setTemplateToDelete([]);
- }
- onClose();
- }}
- templatesToDelete={templateToDelete}
- />
- ) : null}
-
-
-
-
-
- {decodedTemplateName}
-
-
-
-
- {content}
-
-
-
-
-
-
-
-
- {templateDetails && (
-
- {/* Manage templates context menu */}
- setIsPopOverOpen((prev) => !prev)}
- >
-
-
- }
- isOpen={isPopoverOpen}
- closePopover={() => setIsPopOverOpen(false)}
- panelPaddingSize="none"
- withTitle
- anchorPosition="rightUp"
- repositionOnScroll
- >
- editTemplate(templateName, formatVersion),
- disabled: isManaged,
- },
- {
- name: i18n.translate('xpack.idxMgmt.templateDetails.cloneButtonLabel', {
- defaultMessage: 'Clone',
- }),
- icon: 'copy',
- onClick: () => cloneTemplate(templateName, formatVersion),
- },
- {
- name: i18n.translate(
- 'xpack.idxMgmt.templateDetails.deleteButtonLabel',
- {
- defaultMessage: 'Delete',
- }
- ),
- icon: 'trash',
- onClick: () =>
- setTemplateToDelete([{ name: decodedTemplateName, formatVersion }]),
- disabled: isManaged,
- },
- ],
- },
- ]}
- />
-
-
- )}
-
-
-
-
- );
+export const TemplateDetails: React.FunctionComponent = () => {
+ // TODO new (V2) templatte details
+ return null;
};
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx
index db0833ea032336..fc3d5125e30667 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx
@@ -7,19 +7,20 @@
import React, { Fragment, useState, useEffect, useMemo } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { FormattedMessage } from '@kbn/i18n/react';
+import { i18n } from '@kbn/i18n';
import { ScopedHistory } from 'kibana/public';
import {
EuiEmptyPrompt,
EuiSpacer,
EuiTitle,
EuiText,
- EuiSwitch,
EuiFlexItem,
EuiFlexGroup,
+ EuiButton,
} from '@elastic/eui';
import { UIM_TEMPLATE_LIST_LOAD } from '../../../../../common/constants';
-import { IndexTemplateFormatVersion } from '../../../../../common';
+import { TemplateListItem } from '../../../../../common';
import { SectionError, SectionLoading, Error } from '../../../components';
import { useLoadIndexTemplates } from '../../../services/api';
import { useServices } from '../../../app_context';
@@ -28,14 +29,20 @@ import {
getTemplateListLink,
getTemplateCloneLink,
} from '../../../services/routing';
-import { getFormatVersionFromQueryparams } from '../../../lib/index_templates';
+import { getIsLegacyFromQueryParams } from '../../../lib/index_templates';
import { TemplateTable } from './template_table';
-import { TemplateDetails } from './template_details';
+import { LegacyTemplateTable } from './legacy_templates/template_table';
+import { LegacyTemplateDetails } from './legacy_templates/template_details';
+import { FilterListButton, Filters } from './components';
+type FilterName = 'composable' | 'system';
interface MatchParams {
templateName?: string;
}
+const stripOutSystemTemplates = (templates: TemplateListItem[]): TemplateListItem[] =>
+ templates.filter((template) => !template.name.startsWith('.'));
+
export const TemplateList: React.FunctionComponent> = ({
match: {
params: { templateName },
@@ -44,122 +51,188 @@ export const TemplateList: React.FunctionComponent {
const { uiMetricService } = useServices();
- const { error, isLoading, data: templates, sendRequest: reload } = useLoadIndexTemplates();
- const queryParamsFormatVersion = getFormatVersionFromQueryparams(location);
+ const { error, isLoading, data: allTemplates, sendRequest: reload } = useLoadIndexTemplates();
- let content;
+ const [filters, setFilters] = useState>({
+ composable: {
+ name: i18n.translate('xpack.idxMgmt.indexTemplatesList.viewComposableTemplateLabel', {
+ defaultMessage: 'Composable templates',
+ }),
+ checked: 'on',
+ },
+ system: {
+ name: i18n.translate('xpack.idxMgmt.indexTemplatesList.viewSystemTemplateLabel', {
+ defaultMessage: 'System templates',
+ }),
+ checked: 'off',
+ },
+ });
- const [showSystemTemplates, setShowSystemTemplates] = useState(false);
+ const filteredTemplates = useMemo(() => {
+ if (!allTemplates) {
+ return { templates: [], legacyTemplates: [] };
+ }
- // Filter out system index templates
- const filteredTemplates = useMemo(
- () => (templates ? templates.filter((template) => !template.name.startsWith('.')) : []),
- [templates]
- );
+ return filters.system.checked === 'on'
+ ? allTemplates
+ : {
+ templates: stripOutSystemTemplates(allTemplates.templates),
+ legacyTemplates: stripOutSystemTemplates(allTemplates.legacyTemplates),
+ };
+ }, [allTemplates, filters.system.checked]);
+
+ const showComposableTemplateTable = filters.composable.checked === 'on';
+
+ const selectedTemplate = Boolean(templateName)
+ ? {
+ name: templateName!,
+ isLegacy: getIsLegacyFromQueryParams(location),
+ }
+ : null;
+
+ const isLegacyTemplateDetailsVisible = selectedTemplate !== null && selectedTemplate.isLegacy;
+ const hasTemplates =
+ allTemplates && (allTemplates.legacyTemplates.length > 0 || allTemplates.templates.length > 0);
const closeTemplateDetails = () => {
history.push(getTemplateListLink());
};
- const editTemplate = (name: string, formatVersion: IndexTemplateFormatVersion) => {
- history.push(getTemplateEditLink(name, formatVersion));
+ const editTemplate = (name: string, isLegacy?: boolean) => {
+ history.push(getTemplateEditLink(name, isLegacy));
};
- const cloneTemplate = (name: string, formatVersion: IndexTemplateFormatVersion) => {
- history.push(getTemplateCloneLink(name, formatVersion));
+ const cloneTemplate = (name: string, isLegacy?: boolean) => {
+ history.push(getTemplateCloneLink(name, isLegacy));
};
- // Track component loaded
- useEffect(() => {
- uiMetricService.trackMetric('loaded', UIM_TEMPLATE_LIST_LOAD);
- }, [uiMetricService]);
+ const renderHeader = () => (
+
+
+
+
+
+
+
+
+
+ filters={filters} onChange={setFilters} />
+
+
+
+
+
+
+
+ );
- if (isLoading) {
- content = (
-
-
-
- );
- } else if (error) {
- content = (
-
+ showComposableTemplateTable ? (
+ <>
+
+
+ >
+ ) : null;
+
+ const renderLegacyTemplatesTable = () => (
+ <>
+
+
+
- }
- error={error as Error}
+
+
+
+
- );
- } else if (Array.isArray(templates) && templates.length === 0) {
- content = (
-
+ >
+ );
+
+ const renderContent = () => {
+ if (isLoading) {
+ return (
+
+
+
+ );
+ } else if (error) {
+ return (
+
-
- }
- data-test-subj="emptyPrompt"
- />
- );
- } else if (Array.isArray(templates) && templates.length > 0) {
- content = (
-
-
-
-
-
-
-
-
-
-
- setShowSystemTemplates(event.target.checked)}
- label={
-
- }
- />
-
-
-
-
+ );
+ } else if (!hasTemplates) {
+ return (
+
+
+
+ }
+ data-test-subj="emptyPrompt"
/>
-
- );
- }
+ );
+ } else {
+ return (
+
+ {/* Header */}
+ {renderHeader()}
+
+ {/* Composable index templates table */}
+ {renderTemplatesTable()}
+
+ {/* Legacy index templates table */}
+ {renderLegacyTemplatesTable()}
+
+ );
+ }
+ };
+
+ // Track component loaded
+ useEffect(() => {
+ uiMetricService.trackMetric('loaded', UIM_TEMPLATE_LIST_LOAD);
+ }, [uiMetricService]);
return (
- {content}
- {templateName && queryParamsFormatVersion !== undefined && (
-
Promise;
- editTemplate: (name: string, formatVersion: IndexTemplateFormatVersion) => void;
- cloneTemplate: (name: string, formatVersion: IndexTemplateFormatVersion) => void;
- history: ScopedHistory;
}
-export const TemplateTable: React.FunctionComponent = ({
- templates,
- reload,
- editTemplate,
- cloneTemplate,
- history,
-}) => {
- const { uiMetricService } = useServices();
- const [selection, setSelection] = useState([]);
+export const TemplateTable: React.FunctionComponent = ({ templates, reload }) => {
const [templatesToDelete, setTemplatesToDelete] = useState<
- Array<{ name: string; formatVersion: IndexTemplateFormatVersion }>
+ Array<{ name: string; isLegacy?: boolean }>
>([]);
const columns: Array> = [
@@ -45,24 +31,6 @@ export const TemplateTable: React.FunctionComponent = ({
}),
truncateText: true,
sortable: true,
- render: (name: TemplateListItem['name'], item: TemplateListItem) => {
- return (
- /* eslint-disable-next-line @elastic/eui/href-or-on-click */
- uiMetricService.trackMetric('click', UIM_TEMPLATE_SHOW_DETAILS_CLICK)
- )}
- data-test-subj="templateDetailsLink"
- >
- {name}
-
- );
- },
},
{
field: 'indexPatterns',
@@ -95,90 +63,36 @@ export const TemplateTable: React.FunctionComponent = ({
) : null,
},
{
- field: 'order',
- name: i18n.translate('xpack.idxMgmt.templateList.table.orderColumnTitle', {
- defaultMessage: 'Order',
+ field: 'composedOf',
+ name: i18n.translate('xpack.idxMgmt.templateList.table.componentsColumnTitle', {
+ defaultMessage: 'Components',
}),
truncateText: true,
sortable: true,
+ render: (composedOf: string[] = []) => {composedOf.join(', ')},
},
{
- field: 'hasMappings',
- name: i18n.translate('xpack.idxMgmt.templateList.table.mappingsColumnTitle', {
- defaultMessage: 'Mappings',
+ field: 'priority',
+ name: i18n.translate('xpack.idxMgmt.templateList.table.priorityColumnTitle', {
+ defaultMessage: 'Priority',
}),
truncateText: true,
sortable: true,
- render: (hasMappings: boolean) => (hasMappings ? : null),
},
{
- field: 'hasSettings',
- name: i18n.translate('xpack.idxMgmt.templateList.table.settingsColumnTitle', {
- defaultMessage: 'Settings',
- }),
- truncateText: true,
- sortable: true,
- render: (hasSettings: boolean) => (hasSettings ? : null),
- },
- {
- field: 'hasAliases',
- name: i18n.translate('xpack.idxMgmt.templateList.table.aliasesColumnTitle', {
- defaultMessage: 'Aliases',
+ field: 'hasMappings',
+ name: i18n.translate('xpack.idxMgmt.templateList.table.overridesColumnTitle', {
+ defaultMessage: 'Overrides',
}),
truncateText: true,
- sortable: true,
- render: (hasAliases: boolean) => (hasAliases ? : null),
- },
- {
- name: i18n.translate('xpack.idxMgmt.templateList.table.actionColumnTitle', {
- defaultMessage: 'Actions',
- }),
- actions: [
- {
- name: i18n.translate('xpack.idxMgmt.templateList.table.actionEditText', {
- defaultMessage: 'Edit',
- }),
- isPrimary: true,
- description: i18n.translate('xpack.idxMgmt.templateList.table.actionEditDecription', {
- defaultMessage: 'Edit this template',
- }),
- icon: 'pencil',
- type: 'icon',
- onClick: ({ name, _kbnMeta: { formatVersion } }: TemplateListItem) => {
- editTemplate(name, formatVersion);
- },
- enabled: ({ isManaged }: TemplateListItem) => !isManaged,
- },
- {
- type: 'icon',
- name: i18n.translate('xpack.idxMgmt.templateList.table.actionCloneTitle', {
- defaultMessage: 'Clone',
- }),
- description: i18n.translate('xpack.idxMgmt.templateList.table.actionCloneDescription', {
- defaultMessage: 'Clone this template',
- }),
- icon: 'copy',
- onClick: ({ name, _kbnMeta: { formatVersion } }: TemplateListItem) => {
- cloneTemplate(name, formatVersion);
- },
- },
- {
- name: i18n.translate('xpack.idxMgmt.templateList.table.actionDeleteText', {
- defaultMessage: 'Delete',
- }),
- description: i18n.translate('xpack.idxMgmt.templateList.table.actionDeleteDecription', {
- defaultMessage: 'Delete this template',
- }),
- icon: 'trash',
- color: 'danger',
- type: 'icon',
- onClick: ({ name, _kbnMeta: { formatVersion } }: TemplateListItem) => {
- setTemplatesToDelete([{ name, formatVersion }]);
- },
- isPrimary: true,
- enabled: ({ isManaged }: TemplateListItem) => !isManaged,
- },
- ],
+ sortable: false,
+ render: (_, item) => (
+
+ ),
},
];
@@ -194,70 +108,10 @@ export const TemplateTable: React.FunctionComponent = ({
},
} as const;
- const selectionConfig = {
- onSelectionChange: setSelection,
- selectable: ({ isManaged }: TemplateListItem) => !isManaged,
- selectableMessage: (selectable: boolean) => {
- if (!selectable) {
- return i18n.translate('xpack.idxMgmt.templateList.table.deleteManagedTemplateTooltip', {
- defaultMessage: 'You cannot delete a managed template.',
- });
- }
- return '';
- },
- };
-
const searchConfig = {
box: {
incremental: true,
},
- toolsLeft:
- selection.length > 0 ? (
-
- setTemplatesToDelete(
- selection.map(({ name, _kbnMeta: { formatVersion } }: TemplateListItem) => ({
- name,
- formatVersion,
- }))
- )
- }
- color="danger"
- >
-
-
- ) : undefined,
- toolsRight: [
-
-
- ,
-
-
- ,
- ],
};
return (
@@ -280,8 +134,7 @@ export const TemplateTable: React.FunctionComponent = ({
columns={columns}
search={searchConfig}
sorting={sorting}
- isSelectable={true}
- selection={selectionConfig}
+ isSelectable={false}
pagination={pagination}
rowProps={() => ({
'data-test-subj': 'row',
diff --git a/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx b/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx
index b69e441feb176d..8bdd230f899524 100644
--- a/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx
@@ -8,12 +8,12 @@ import { RouteComponentProps } from 'react-router-dom';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle } from '@elastic/eui';
-import { TemplateDeserialized, DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT } from '../../../../common';
+import { TemplateDeserialized } from '../../../../common';
import { TemplateForm, SectionLoading, SectionError, Error } from '../../components';
import { breadcrumbService } from '../../services/breadcrumbs';
import { decodePath, getTemplateDetailsLink } from '../../services/routing';
import { saveTemplate, useLoadIndexTemplate } from '../../services/api';
-import { getFormatVersionFromQueryparams } from '../../lib/index_templates';
+import { getIsLegacyFromQueryParams } from '../../lib/index_templates';
interface MatchParams {
name: string;
@@ -27,14 +27,13 @@ export const TemplateClone: React.FunctionComponent {
const decodedTemplateName = decodePath(name);
- const formatVersion =
- getFormatVersionFromQueryparams(location) ?? DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT;
+ const isLegacy = getIsLegacyFromQueryParams(location);
const [isSaving, setIsSaving] = useState(false);
const [saveError, setSaveError] = useState(null);
const { error: templateToCloneError, data: templateToClone, isLoading } = useLoadIndexTemplate(
decodedTemplateName,
- formatVersion
+ isLegacy
);
const onSave = async (template: TemplateDeserialized) => {
@@ -52,7 +51,7 @@ export const TemplateClone: React.FunctionComponent {
diff --git a/x-pack/plugins/index_management/public/application/sections/template_create/template_create.tsx b/x-pack/plugins/index_management/public/application/sections/template_create/template_create.tsx
index 27341685f3dc01..f567b9835d53df 100644
--- a/x-pack/plugins/index_management/public/application/sections/template_create/template_create.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/template_create/template_create.tsx
@@ -33,7 +33,7 @@ export const TemplateCreate: React.FunctionComponent = ({ h
return;
}
- history.push(getTemplateDetailsLink(name, template._kbnMeta.formatVersion));
+ history.push(getTemplateDetailsLink(name, template._kbnMeta.isLegacy));
};
const clearSaveError = () => {
diff --git a/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx b/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx
index 9ad26d0af802d8..d3e539989bc96c 100644
--- a/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx
@@ -8,12 +8,12 @@ import { RouteComponentProps } from 'react-router-dom';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiPageBody, EuiPageContent, EuiTitle, EuiSpacer, EuiCallOut } from '@elastic/eui';
-import { TemplateDeserialized, DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT } from '../../../../common';
+import { TemplateDeserialized } from '../../../../common';
import { breadcrumbService } from '../../services/breadcrumbs';
import { useLoadIndexTemplate, updateTemplate } from '../../services/api';
import { decodePath, getTemplateDetailsLink } from '../../services/routing';
import { SectionLoading, SectionError, TemplateForm, Error } from '../../components';
-import { getFormatVersionFromQueryparams } from '../../lib/index_templates';
+import { getIsLegacyFromQueryParams } from '../../lib/index_templates';
interface MatchParams {
name: string;
@@ -27,16 +27,12 @@ export const TemplateEdit: React.FunctionComponent {
const decodedTemplateName = decodePath(name);
- const formatVersion =
- getFormatVersionFromQueryparams(location) ?? DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT;
+ const isLegacy = getIsLegacyFromQueryParams(location);
const [isSaving, setIsSaving] = useState(false);
const [saveError, setSaveError] = useState(null);
- const { error, data: template, isLoading } = useLoadIndexTemplate(
- decodedTemplateName,
- formatVersion
- );
+ const { error, data: template, isLoading } = useLoadIndexTemplate(decodedTemplateName, isLegacy);
useEffect(() => {
breadcrumbService.setBreadcrumbs('templateEdit');
@@ -55,7 +51,7 @@ export const TemplateEdit: React.FunctionComponent {
@@ -87,7 +83,10 @@ export const TemplateEdit: React.FunctionComponent
);
} else if (template) {
- const { name: templateName, isManaged } = template;
+ const {
+ name: templateName,
+ _kbnMeta: { isManaged },
+ } = template;
const isSystemTemplate = templateName && templateName.startsWith('.');
if (isManaged) {
diff --git a/x-pack/plugins/index_management/public/application/services/api.ts b/x-pack/plugins/index_management/public/application/services/api.ts
index 181707b3661b4f..3961942b83ea38 100644
--- a/x-pack/plugins/index_management/public/application/services/api.ts
+++ b/x-pack/plugins/index_management/public/application/services/api.ts
@@ -37,11 +37,7 @@ import { TAB_SETTINGS, TAB_MAPPING, TAB_STATS } from '../constants';
import { useRequest, sendRequest } from './use_request';
import { httpService } from './http';
import { UiMetricService } from './ui_metric';
-import {
- TemplateDeserialized,
- TemplateListItem,
- IndexTemplateFormatVersion,
-} from '../../../common';
+import { TemplateDeserialized, TemplateListItem } from '../../../common';
import { IndexMgmtMetricsType } from '../../types';
// Temporary hack to provide the uiMetricService instance to this file.
@@ -214,17 +210,15 @@ export async function loadIndexData(type: string, indexName: string) {
}
export function useLoadIndexTemplates() {
- return useRequest({
- path: `${API_BASE_PATH}/templates`,
+ return useRequest<{ templates: TemplateListItem[]; legacyTemplates: TemplateListItem[] }>({
+ path: `${API_BASE_PATH}/index-templates`,
method: 'get',
});
}
-export async function deleteTemplates(
- templates: Array<{ name: string; formatVersion: IndexTemplateFormatVersion }>
-) {
+export async function deleteTemplates(templates: Array<{ name: string; isLegacy?: boolean }>) {
const result = sendRequest({
- path: `${API_BASE_PATH}/delete-templates`,
+ path: `${API_BASE_PATH}/delete-index-templates`,
method: 'post',
body: { templates },
});
@@ -236,23 +230,20 @@ export async function deleteTemplates(
return result;
}
-export function useLoadIndexTemplate(
- name: TemplateDeserialized['name'],
- formatVersion: IndexTemplateFormatVersion
-) {
+export function useLoadIndexTemplate(name: TemplateDeserialized['name'], isLegacy?: boolean) {
return useRequest({
- path: `${API_BASE_PATH}/templates/${encodeURIComponent(name)}`,
+ path: `${API_BASE_PATH}/index-templates/${encodeURIComponent(name)}`,
method: 'get',
query: {
- v: formatVersion,
+ legacy: isLegacy,
},
});
}
export async function saveTemplate(template: TemplateDeserialized, isClone?: boolean) {
const result = await sendRequest({
- path: `${API_BASE_PATH}/templates`,
- method: 'put',
+ path: `${API_BASE_PATH}/index-templates`,
+ method: 'post',
body: JSON.stringify(template),
});
@@ -266,7 +257,7 @@ export async function saveTemplate(template: TemplateDeserialized, isClone?: boo
export async function updateTemplate(template: TemplateDeserialized) {
const { name } = template;
const result = await sendRequest({
- path: `${API_BASE_PATH}/templates/${encodeURIComponent(name)}`,
+ path: `${API_BASE_PATH}/index-templates/${encodeURIComponent(name)}`,
method: 'put',
body: JSON.stringify(template),
});
diff --git a/x-pack/plugins/index_management/public/application/services/routing.ts b/x-pack/plugins/index_management/public/application/services/routing.ts
index fe118b1181082e..a999c58f5bb429 100644
--- a/x-pack/plugins/index_management/public/application/services/routing.ts
+++ b/x-pack/plugins/index_management/public/application/services/routing.ts
@@ -3,33 +3,29 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { IndexTemplateFormatVersion } from '../../../common';
-export const getTemplateListLink = () => {
- return `/templates`;
-};
+export const getTemplateListLink = () => `/templates`;
// Need to add some additonal encoding/decoding logic to work with React Router
// For background, see: https://github.com/ReactTraining/history/issues/505
-export const getTemplateDetailsLink = (
- name: string,
- formatVersion: IndexTemplateFormatVersion,
- withHash = false
-) => {
- const baseUrl = `/templates/${encodeURIComponent(encodeURIComponent(name))}?v=${formatVersion}`;
- const url = withHash ? `#${baseUrl}` : baseUrl;
+export const getTemplateDetailsLink = (name: string, isLegacy?: boolean, withHash = false) => {
+ const baseUrl = `/templates/${encodeURIComponent(encodeURIComponent(name))}`;
+ let url = withHash ? `#${baseUrl}` : baseUrl;
+ if (isLegacy) {
+ url = `${url}?legacy=${isLegacy}`;
+ }
return encodeURI(url);
};
-export const getTemplateEditLink = (name: string, formatVersion: IndexTemplateFormatVersion) => {
+export const getTemplateEditLink = (name: string, isLegacy?: boolean) => {
return encodeURI(
- `/edit_template/${encodeURIComponent(encodeURIComponent(name))}?v=${formatVersion}`
+ `/edit_template/${encodeURIComponent(encodeURIComponent(name))}?legacy=${isLegacy === true}`
);
};
-export const getTemplateCloneLink = (name: string, formatVersion: IndexTemplateFormatVersion) => {
+export const getTemplateCloneLink = (name: string, isLegacy?: boolean) => {
return encodeURI(
- `/clone_template/${encodeURIComponent(encodeURIComponent(name))}?v=${formatVersion}`
+ `/clone_template/${encodeURIComponent(encodeURIComponent(name))}?legacy=${isLegacy === true}`
);
};
diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts
index 1409fa8af2cebd..26e74847e3e05f 100644
--- a/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts
+++ b/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts
@@ -7,7 +7,7 @@
import { i18n } from '@kbn/i18n';
import { TemplateDeserialized } from '../../../../common';
-import { serializeV1Template } from '../../../../common/lib';
+import { serializeLegacyTemplate } from '../../../../common/lib';
import { RouteDependencies } from '../../../types';
import { addBasePath } from '../index';
import { templateSchema } from './validate_schemas';
@@ -15,35 +15,26 @@ import { templateSchema } from './validate_schemas';
const bodySchema = templateSchema;
export function registerCreateRoute({ router, license, lib }: RouteDependencies) {
- router.put(
- { path: addBasePath('/templates'), validate: { body: bodySchema } },
+ router.post(
+ { path: addBasePath('/index-templates'), validate: { body: bodySchema } },
license.guardApiRoute(async (ctx, req, res) => {
const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client;
const template = req.body as TemplateDeserialized;
const {
- _kbnMeta: { formatVersion },
+ _kbnMeta: { isLegacy },
} = template;
- if (formatVersion !== 1) {
- return res.badRequest({ body: 'Only index template version 1 can be created.' });
+ if (!isLegacy) {
+ return res.badRequest({ body: 'Only legacy index templates can be created.' });
}
- // For now we format to V1 index templates.
- // When the V2 API is ready we will only create V2 template format.
- const serializedTemplate = serializeV1Template(template);
-
- const {
- name,
- order,
- index_patterns,
- version,
- settings,
- mappings,
- aliases,
- } = serializedTemplate;
+ const serializedTemplate = serializeLegacyTemplate(template);
+ const { order, index_patterns, version, settings, mappings, aliases } = serializedTemplate;
// Check that template with the same name doesn't already exist
- const templateExists = await callAsCurrentUser('indices.existsTemplate', { name });
+ const templateExists = await callAsCurrentUser('indices.existsTemplate', {
+ name: template.name,
+ });
if (templateExists) {
return res.conflict({
@@ -51,7 +42,7 @@ export function registerCreateRoute({ router, license, lib }: RouteDependencies)
i18n.translate('xpack.idxMgmt.createRoute.duplicateTemplateIdErrorMessage', {
defaultMessage: "There is already a template with name '{name}'.",
values: {
- name,
+ name: template.name,
},
})
),
@@ -61,7 +52,7 @@ export function registerCreateRoute({ router, license, lib }: RouteDependencies)
try {
// Otherwise create new index template
const response = await callAsCurrentUser('indices.putTemplate', {
- name,
+ name: template.name,
order,
body: {
index_patterns,
diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_delete_route.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_delete_route.ts
index 3dc31482b4947d..b5cc00ad6d8cc2 100644
--- a/x-pack/plugins/index_management/server/routes/api/templates/register_delete_route.ts
+++ b/x-pack/plugins/index_management/server/routes/api/templates/register_delete_route.ts
@@ -16,7 +16,7 @@ const bodySchema = schema.object({
templates: schema.arrayOf(
schema.object({
name: schema.string(),
- formatVersion: schema.oneOf([schema.literal(1), schema.literal(2)]),
+ isLegacy: schema.maybe(schema.boolean()),
})
),
});
@@ -24,7 +24,7 @@ const bodySchema = schema.object({
export function registerDeleteRoute({ router, license }: RouteDependencies) {
router.post(
{
- path: addBasePath('/delete-templates'),
+ path: addBasePath('/delete-index-templates'),
validate: { body: bodySchema },
},
license.guardApiRoute(async (ctx, req, res) => {
@@ -35,10 +35,10 @@ export function registerDeleteRoute({ router, license }: RouteDependencies) {
};
await Promise.all(
- templates.map(async ({ name, formatVersion }) => {
+ templates.map(async ({ name, isLegacy }) => {
try {
- if (formatVersion !== 1) {
- return res.badRequest({ body: 'Only index template version 1 can be deleted.' });
+ if (!isLegacy) {
+ return res.badRequest({ body: 'Only legacy index template can be deleted.' });
}
await ctx.core.elasticsearch.legacy.client.callAsCurrentUser('indices.deleteTemplate', {
diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts
index b18a8d88d3a4a9..12ec005258a62f 100644
--- a/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts
+++ b/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts
@@ -5,20 +5,38 @@
*/
import { schema, TypeOf } from '@kbn/config-schema';
-import { deserializeV1Template, deserializeTemplateList } from '../../../../common/lib';
+import {
+ deserializeLegacyTemplate,
+ deserializeLegacyTemplateList,
+ deserializeTemplateList,
+} from '../../../../common/lib';
import { getManagedTemplatePrefix } from '../../../lib/get_managed_templates';
import { RouteDependencies } from '../../../types';
import { addBasePath } from '../index';
export function registerGetAllRoute({ router, license }: RouteDependencies) {
router.get(
- { path: addBasePath('/templates'), validate: false },
+ { path: addBasePath('/index-templates'), validate: false },
license.guardApiRoute(async (ctx, req, res) => {
const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client;
const managedTemplatePrefix = await getManagedTemplatePrefix(callAsCurrentUser);
- const indexTemplatesByName = await callAsCurrentUser('indices.getTemplate');
- const body = deserializeTemplateList(indexTemplatesByName, managedTemplatePrefix);
+ const _legacyTemplates = await callAsCurrentUser('indices.getTemplate');
+ const { index_templates: _templates } = await callAsCurrentUser('transport.request', {
+ path: '_index_template',
+ method: 'GET',
+ });
+
+ const legacyTemplates = deserializeLegacyTemplateList(
+ _legacyTemplates,
+ managedTemplatePrefix
+ );
+ const templates = deserializeTemplateList(_templates, managedTemplatePrefix);
+
+ const body = {
+ templates,
+ legacyTemplates,
+ };
return res.ok({ body });
})
@@ -31,22 +49,22 @@ const paramsSchema = schema.object({
// Require the template format version (V1 or V2) to be provided as Query param
const querySchema = schema.object({
- v: schema.oneOf([schema.literal('1'), schema.literal('2')]),
+ legacy: schema.maybe(schema.boolean()),
});
export function registerGetOneRoute({ router, license, lib }: RouteDependencies) {
router.get(
{
- path: addBasePath('/templates/{name}'),
+ path: addBasePath('/index-templates/{name}'),
validate: { params: paramsSchema, query: querySchema },
},
license.guardApiRoute(async (ctx, req, res) => {
- const { name } = req.params as typeof paramsSchema.type;
+ const { name } = req.params as TypeOf;
const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client;
- const { v: version } = req.query as TypeOf;
+ const { legacy } = req.query as TypeOf;
- if (version !== '1') {
+ if (!legacy) {
return res.badRequest({ body: 'Only index template version 1 can be fetched.' });
}
@@ -56,7 +74,7 @@ export function registerGetOneRoute({ router, license, lib }: RouteDependencies)
if (indexTemplateByName[name]) {
return res.ok({
- body: deserializeV1Template(
+ body: deserializeLegacyTemplate(
{ ...indexTemplateByName[name], name },
managedTemplatePrefix
),
diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts
index 81d7aa1b4978c1..5b2a0d8722e46a 100644
--- a/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts
+++ b/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts
@@ -6,7 +6,7 @@
import { schema } from '@kbn/config-schema';
import { TemplateDeserialized } from '../../../../common';
-import { serializeV1Template } from '../../../../common/lib';
+import { serializeLegacyTemplate } from '../../../../common/lib';
import { RouteDependencies } from '../../../types';
import { addBasePath } from '../index';
import { templateSchema } from './validate_schemas';
@@ -19,7 +19,7 @@ const paramsSchema = schema.object({
export function registerUpdateRoute({ router, license, lib }: RouteDependencies) {
router.put(
{
- path: addBasePath('/templates/{name}'),
+ path: addBasePath('/index-templates/{name}'),
validate: { body: bodySchema, params: paramsSchema },
},
license.guardApiRoute(async (ctx, req, res) => {
@@ -27,14 +27,14 @@ export function registerUpdateRoute({ router, license, lib }: RouteDependencies)
const { name } = req.params as typeof paramsSchema.type;
const template = req.body as TemplateDeserialized;
const {
- _kbnMeta: { formatVersion },
+ _kbnMeta: { isLegacy },
} = template;
- if (formatVersion !== 1) {
- return res.badRequest({ body: 'Only index template version 1 can be edited.' });
+ if (!isLegacy) {
+ return res.badRequest({ body: 'Only legacy index template can be edited.' });
}
- const serializedTemplate = serializeV1Template(template);
+ const serializedTemplate = serializeLegacyTemplate(template);
const { order, index_patterns, version, settings, mappings, aliases } = serializedTemplate;
diff --git a/x-pack/plugins/index_management/server/routes/api/templates/validate_schemas.ts b/x-pack/plugins/index_management/server/routes/api/templates/validate_schemas.ts
index 491a686f811779..6ab28e90211237 100644
--- a/x-pack/plugins/index_management/server/routes/api/templates/validate_schemas.ts
+++ b/x-pack/plugins/index_management/server/routes/api/templates/validate_schemas.ts
@@ -24,8 +24,8 @@ export const templateSchema = schema.object({
rollover_alias: schema.maybe(schema.string()),
})
),
- isManaged: schema.maybe(schema.boolean()),
_kbnMeta: schema.object({
- formatVersion: schema.oneOf([schema.literal(1), schema.literal(2)]),
+ isManaged: schema.maybe(schema.boolean()),
+ isLegacy: schema.maybe(schema.boolean()),
}),
});
diff --git a/x-pack/plugins/index_management/test/fixtures/template.ts b/x-pack/plugins/index_management/test/fixtures/template.ts
index 055c32d5cd5e4a..e2e93bfb365d4d 100644
--- a/x-pack/plugins/index_management/test/fixtures/template.ts
+++ b/x-pack/plugins/index_management/test/fixtures/template.ts
@@ -5,7 +5,7 @@
*/
import { getRandomString, getRandomNumber } from '../../../../test_utils';
-import { TemplateDeserialized, DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT } from '../../common';
+import { TemplateDeserialized } from '../../common';
export const getTemplate = ({
name = getRandomString(),
@@ -14,10 +14,11 @@ export const getTemplate = ({
indexPatterns = [],
template: { settings, aliases, mappings } = {},
isManaged = false,
- templateFormatVersion = DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT,
+ isLegacy = false,
}: Partial<
TemplateDeserialized & {
- templateFormatVersion?: 1 | 2;
+ isLegacy?: boolean;
+ isManaged: boolean;
}
> = {}): TemplateDeserialized => ({
name,
@@ -29,8 +30,8 @@ export const getTemplate = ({
mappings,
settings,
},
- isManaged,
_kbnMeta: {
- formatVersion: templateFormatVersion,
+ isManaged,
+ isLegacy,
},
});
diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx
index f4c7332a88e1d2..db5cfb1416dec3 100644
--- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx
+++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx
@@ -288,6 +288,7 @@ export const Expressions: React.FC = (props) => {
/>
+
{
- setLegendOptions((previous) => ({ ...previous, steps: parseInt(e.target.value, 10) }));
+ const steps = parseInt(e.target.value, 10);
+ setLegendOptions((previous) => ({ ...previous, steps }));
},
[setLegendOptions]
);
const handlePaletteChange = useCallback(
(e) => {
- setLegendOptions((previous) => ({ ...previous, palette: e.target.value }));
+ const palette = e.target.value;
+ setLegendOptions((previous) => ({ ...previous, palette }));
},
[setLegendOptions]
);
diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/time_controls.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/time_controls.tsx
index ef6486eac0fdb1..afee0c0498187f 100644
--- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/time_controls.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/time_controls.tsx
@@ -6,6 +6,7 @@
import { EuiSuperDatePicker, OnRefreshChangeProps, OnTimeChangeProps } from '@elastic/eui';
import React, { useCallback } from 'react';
+import { UI_SETTINGS } from '../../../../../../../../src/plugins/data/public';
import { euiStyled } from '../../../../../../observability/public';
import { MetricsTimeInput } from '../hooks/use_metrics_time';
import { useKibanaUiSetting } from '../../../../utils/use_kibana_ui_setting';
@@ -22,7 +23,7 @@ interface MetricsTimeControlsProps {
}
export const MetricsTimeControls = (props: MetricsTimeControlsProps) => {
- const [timepickerQuickRanges] = useKibanaUiSetting('timepicker:quickRanges');
+ const [timepickerQuickRanges] = useKibanaUiSetting(UI_SETTINGS.TIMEPICKER_QUICK_RANGES);
const {
onChangeTimeRange,
onRefresh,
diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/toolbar.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/toolbar.tsx
index 7ad1d943a98969..1471efbd21e18a 100644
--- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/toolbar.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/toolbar.tsx
@@ -7,7 +7,7 @@
import { EuiFlexGroup, EuiFlexItem, EuiSuperDatePicker, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
-import { IIndexPattern } from 'src/plugins/data/public';
+import { IIndexPattern, UI_SETTINGS } from '../../../../../../../../src/plugins/data/public';
import {
MetricsExplorerMetric,
MetricsExplorerAggregation,
@@ -61,7 +61,7 @@ export const MetricsExplorerToolbar = ({
onViewStateChange,
}: Props) => {
const isDefaultOptions = options.aggregation === 'avg' && options.metrics.length === 0;
- const [timepickerQuickRanges] = useKibanaUiSetting('timepicker:quickRanges');
+ const [timepickerQuickRanges] = useKibanaUiSetting(UI_SETTINGS.TIMEPICKER_QUICK_RANGES);
const commonlyUsedRanges = mapKibanaQuickRangesToDatePickerRanges(timepickerQuickRanges);
return (
diff --git a/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts
index 7c0c535795638f..79c276a1e58f7e 100644
--- a/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts
+++ b/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts
@@ -32,7 +32,7 @@ import {
} from '../../../../../../../src/core/server';
import { RequestHandler } from '../../../../../../../src/core/server';
import { InfraConfig } from '../../../plugin';
-import { IndexPatternsFetcher } from '../../../../../../../src/plugins/data/server';
+import { IndexPatternsFetcher, UI_SETTINGS } from '../../../../../../../src/plugins/data/server';
export class KibanaFramework {
public router: IRouter;
@@ -197,10 +197,10 @@ export class KibanaFramework {
) {
const { elasticsearch, uiSettings } = requestContext.core;
- const includeFrozen = await uiSettings.client.get('search:includeFrozen');
+ const includeFrozen = await uiSettings.client.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN);
if (endpoint === 'msearch') {
const maxConcurrentShardRequests = await uiSettings.client.get(
- 'courier:maxConcurrentShardRequests'
+ UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS
);
if (maxConcurrentShardRequests > 0) {
params = { ...params, max_concurrent_shard_requests: maxConcurrentShardRequests };
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts
index 233a34a67d1ec9..a282a742d614c9 100644
--- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts
@@ -336,6 +336,9 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs, alertId: s
group,
alertState: stateToAlertMessage[nextState],
reason,
+ value: mapToConditionsLookup(alertResults, (result) => result[group].currentValue),
+ threshold: mapToConditionsLookup(criteria, (c) => c.threshold),
+ metric: mapToConditionsLookup(criteria, (c) => c.metric),
});
}
@@ -352,3 +355,14 @@ export const FIRED_ACTIONS = {
defaultMessage: 'Fired',
}),
};
+
+const mapToConditionsLookup = (
+ list: any[],
+ mapFn: (value: any, index: number, array: any[]) => unknown
+) =>
+ list
+ .map(mapFn)
+ .reduce(
+ (result: Record, value, i) => ({ ...result, [`condition${i}`]: value }),
+ {}
+ );
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts
index 8b3903f2ee3be9..2c98a568d16de6 100644
--- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts
@@ -55,6 +55,30 @@ export function registerMetricThresholdAlertType(libs: InfraBackendLibs) {
}
);
+ const valueActionVariableDescription = i18n.translate(
+ 'xpack.infra.metrics.alerting.threshold.alerting.valueActionVariableDescription',
+ {
+ defaultMessage:
+ 'The value of the metric in the specified condition. Usage: (ctx.value.condition0, ctx.value.condition1, etc...).',
+ }
+ );
+
+ const metricActionVariableDescription = i18n.translate(
+ 'xpack.infra.metrics.alerting.threshold.alerting.metricActionVariableDescription',
+ {
+ defaultMessage:
+ 'The metric name in the specified condition. Usage: (ctx.metric.condition0, ctx.metric.condition1, etc...).',
+ }
+ );
+
+ const thresholdActionVariableDescription = i18n.translate(
+ 'xpack.infra.metrics.alerting.threshold.alerting.thresholdActionVariableDescription',
+ {
+ defaultMessage:
+ 'The threshold value of the metric for the specified condition. Usage: (ctx.threshold.condition0, ctx.threshold.condition1, etc...).',
+ }
+ );
+
return {
id: METRIC_THRESHOLD_ALERT_TYPE_ID,
name: 'Metric threshold',
@@ -82,6 +106,9 @@ export function registerMetricThresholdAlertType(libs: InfraBackendLibs) {
{ name: 'group', description: groupActionVariableDescription },
{ name: 'alertState', description: alertStateActionVariableDescription },
{ name: 'reason', description: reasonActionVariableDescription },
+ { name: 'value', description: valueActionVariableDescription },
+ { name: 'metric', description: metricActionVariableDescription },
+ { name: 'threshold', description: thresholdActionVariableDescription },
],
},
producer: 'metrics',
diff --git a/x-pack/plugins/ingest_manager/README.md b/x-pack/plugins/ingest_manager/README.md
index f0c2466b25b047..50c42544b8bdc7 100644
--- a/x-pack/plugins/ingest_manager/README.md
+++ b/x-pack/plugins/ingest_manager/README.md
@@ -52,12 +52,12 @@ This plugin follows the `common`, `server`, `public` structure from the [Archite
1. In one terminal, change to the `x-pack` directory and start the test server with
```
- node scripts/functional_tests_server.js --config test/api_integration/config.js
+ node scripts/functional_tests_server.js --config test/api_integration/config.ts
```
1. in a second terminal, run the tests from the Kibana root directory with
```
- node scripts/functional_test_runner.js --config x-pack/test/api_integration/config.js
+ node scripts/functional_test_runner.js --config x-pack/test/api_integration/config.ts
```
#### EPM
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.test.ts
index 5e83a976bd7a4a..635dce93f00276 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.test.ts
+++ b/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.test.ts
@@ -83,4 +83,22 @@ foo: bar
custom: { foo: 'bar' },
});
});
+
+ it('should support optional yaml values at root level', () => {
+ const streamTemplate = `
+input: logs
+{{custom}}
+ `;
+ const vars = {
+ custom: {
+ type: 'yaml',
+ value: null,
+ },
+ };
+
+ const output = createStream(vars, streamTemplate);
+ expect(output).toEqual({
+ input: 'logs',
+ });
+ });
});
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.ts b/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.ts
index 61f2f95fe20a9f..0bcb2464f8d742 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.ts
+++ b/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.ts
@@ -94,7 +94,10 @@ function replaceRootLevelYamlVariables(yamlVariables: { [k: string]: any }, yaml
let patchedTemplate = yamlTemplate;
Object.entries(yamlVariables).forEach(([key, val]) => {
- patchedTemplate = patchedTemplate.replace(new RegExp(`^"${key}"`, 'gm'), safeDump(val));
+ patchedTemplate = patchedTemplate.replace(
+ new RegExp(`^"${key}"`, 'gm'),
+ val ? safeDump(val) : ''
+ );
});
return patchedTemplate;
diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx
index f1a2edd2d554f7..33f4f46681a283 100644
--- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx
+++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx
@@ -19,6 +19,7 @@ import {
FilterManager,
IFieldType,
IIndexPattern,
+ UI_SETTINGS,
} from '../../../../../src/plugins/data/public';
import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks';
const dataStartMock = dataPluginMock.createStartContract();
@@ -183,7 +184,7 @@ describe('Lens App', () => {
jest.fn((type) => {
if (type === 'timepicker:timeDefaults') {
return { from: 'now-7d', to: 'now' };
- } else if (type === 'search:queryLanguage') {
+ } else if (type === UI_SETTINGS.SEARCH_QUERY_LANGUAGE) {
return 'kuery';
} else {
return [];
diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx
index ffa59a6fb6bc98..1349d33983fcd0 100644
--- a/x-pack/plugins/lens/public/app_plugin/app.tsx
+++ b/x-pack/plugins/lens/public/app_plugin/app.tsx
@@ -27,6 +27,7 @@ import {
IndexPattern as IndexPatternInstance,
IndexPatternsContract,
SavedQuery,
+ UI_SETTINGS,
} from '../../../../../src/plugins/data/public';
interface State {
@@ -76,7 +77,8 @@ export function App({
onAppLeave: AppMountParameters['onAppLeave'];
}) {
const language =
- storage.get('kibana.userQueryLanguage') || core.uiSettings.get('search:queryLanguage');
+ storage.get('kibana.userQueryLanguage') ||
+ core.uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE);
const [state, setState] = useState(() => {
const currentRange = data.query.timefilter.timefilter.getTime();
@@ -413,7 +415,7 @@ export function App({
query: '',
language:
storage.get('kibana.userQueryLanguage') ||
- core.uiSettings.get('search:queryLanguage'),
+ core.uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE),
},
}));
}}
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx
index e665e8b8dd326a..defc142d4976ea 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx
@@ -12,6 +12,7 @@ import { EuiSwitch, EuiSwitchEvent } from '@elastic/eui';
import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from 'kibana/public';
import { coreMock } from 'src/core/public/mocks';
import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
+import { UI_SETTINGS } from '../../../../../../../src/plugins/data/public';
import {
dataPluginMock,
getCalculateAutoTimeExpression,
@@ -23,7 +24,7 @@ const dataStart = dataPluginMock.createStartContract();
dataStart.search.aggs.calculateAutoTimeExpression = getCalculateAutoTimeExpression({
...coreMock.createStart().uiSettings,
get: (path: string) => {
- if (path === 'histogram:maxBars') {
+ if (path === UI_SETTINGS.HISTOGRAM_MAX_BARS) {
return 10;
}
},
diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts
index f9a577e001c64e..3000c9321b3b9e 100644
--- a/x-pack/plugins/lens/public/plugin.ts
+++ b/x-pack/plugins/lens/public/plugin.ts
@@ -6,7 +6,7 @@
import { AppMountParameters, CoreSetup, CoreStart } from 'kibana/public';
import { DataPublicPluginSetup, DataPublicPluginStart } from 'src/plugins/data/public';
-import { EmbeddableSetup, EmbeddableStart } from 'src/plugins/embeddable/public';
+import { EmbeddableSetup } from 'src/plugins/embeddable/public';
import { ExpressionsSetup, ExpressionsStart } from 'src/plugins/expressions/public';
import { VisualizationsSetup } from 'src/plugins/visualizations/public';
import { NavigationPublicPluginStart } from 'src/plugins/navigation/public';
@@ -38,7 +38,6 @@ export interface LensPluginSetupDependencies {
export interface LensPluginStartDependencies {
data: DataPublicPluginStart;
- embeddable: EmbeddableStart;
expressions: ExpressionsStart;
navigation: NavigationPublicPluginStart;
uiActions: UiActionsStart;
diff --git a/x-pack/plugins/lens/public/xy_visualization/index.ts b/x-pack/plugins/lens/public/xy_visualization/index.ts
index 23cf9e7ff818fc..cd25cb57295115 100644
--- a/x-pack/plugins/lens/public/xy_visualization/index.ts
+++ b/x-pack/plugins/lens/public/xy_visualization/index.ts
@@ -8,6 +8,7 @@ import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist
import { CoreSetup, IUiSettingsClient } from 'kibana/public';
import moment from 'moment-timezone';
import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public';
+import { UI_SETTINGS } from '../../../../../src/plugins/data/public';
import { xyVisualization } from './xy_visualization';
import { xyChart, getXyChartRenderer } from './xy_expression';
import { legendConfig, xConfig, layerConfig } from './types';
@@ -47,7 +48,7 @@ export class XyVisualization {
? EUI_CHARTS_THEME_DARK.theme
: EUI_CHARTS_THEME_LIGHT.theme,
timeZone: getTimeZone(core.uiSettings),
- histogramBarTarget: core.uiSettings.get('histogram:barTarget'),
+ histogramBarTarget: core.uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET),
})
);
diff --git a/x-pack/plugins/lens/readme.md b/x-pack/plugins/lens/readme.md
index 60b4266edadb3d..70d7f16b0f7fc9 100644
--- a/x-pack/plugins/lens/readme.md
+++ b/x-pack/plugins/lens/readme.md
@@ -11,4 +11,4 @@ Run all tests from the `x-pack` root directory
- You may want to comment out all imports except for Lens in the config file.
- API Functional tests:
- Run `node scripts/functional_tests_server`
- - Run `node ../scripts/functional_test_runner.js --config ./test/api_integration/config.js --grep=Lens`
+ - Run `node ../scripts/functional_test_runner.js --config ./test/api_integration/config.ts --grep=Lens`
diff --git a/x-pack/plugins/licensing/server/index.ts b/x-pack/plugins/licensing/server/index.ts
index 76e65afc595c4c..ba577660d865cc 100644
--- a/x-pack/plugins/licensing/server/index.ts
+++ b/x-pack/plugins/licensing/server/index.ts
@@ -10,6 +10,7 @@ import { LicensingPlugin } from './plugin';
export const plugin = (context: PluginInitializerContext) => new LicensingPlugin(context);
export * from '../common/types';
+export { FeatureUsageServiceSetup, FeatureUsageServiceStart } from './services';
export * from './types';
export { config } from './licensing_config';
export { CheckLicense, wrapRouteWithLicenseCheck } from './wrap_route_with_license_check';
diff --git a/x-pack/plugins/maps/public/angular/get_initial_query.js b/x-pack/plugins/maps/public/angular/get_initial_query.js
index 4f61142413671d..84f431cf2b3b63 100644
--- a/x-pack/plugins/maps/public/angular/get_initial_query.js
+++ b/x-pack/plugins/maps/public/angular/get_initial_query.js
@@ -5,6 +5,7 @@
*/
import { getUiSettings } from '../kibana_services';
+import { UI_SETTINGS } from '../../../../../src/plugins/data/public';
export function getInitialQuery({ mapStateJSON, appState = {}, userQueryLanguage }) {
const settings = getUiSettings();
@@ -22,6 +23,6 @@ export function getInitialQuery({ mapStateJSON, appState = {}, userQueryLanguage
return {
query: '',
- language: userQueryLanguage || settings.get('search:queryLanguage'),
+ language: userQueryLanguage || settings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE),
};
}
diff --git a/x-pack/plugins/maps/public/angular/get_initial_refresh_config.js b/x-pack/plugins/maps/public/angular/get_initial_refresh_config.js
index f13e435cd1d5cf..17a50c6c5f6852 100644
--- a/x-pack/plugins/maps/public/angular/get_initial_refresh_config.js
+++ b/x-pack/plugins/maps/public/angular/get_initial_refresh_config.js
@@ -5,6 +5,7 @@
*/
import { getUiSettings } from '../kibana_services';
+import { UI_SETTINGS } from '../../../../../src/plugins/data/public';
export function getInitialRefreshConfig({ mapStateJSON, globalState = {} }) {
const uiSettings = getUiSettings();
@@ -16,7 +17,7 @@ export function getInitialRefreshConfig({ mapStateJSON, globalState = {} }) {
}
}
- const defaultRefreshConfig = uiSettings.get('timepicker:refreshIntervalDefaults');
+ const defaultRefreshConfig = uiSettings.get(UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS);
const refreshInterval = { ...defaultRefreshConfig, ...globalState.refreshInterval };
return {
isPaused: refreshInterval.pause,
diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js b/x-pack/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js
index 75b6b5d66f1de6..45c7507160e980 100644
--- a/x-pack/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js
+++ b/x-pack/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js
@@ -20,6 +20,7 @@ import {
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
+import { UI_SETTINGS } from '../../../../../../../src/plugins/data/public';
import { getIndexPatternService, getUiSettings, getData } from '../../../kibana_services';
import { GlobalFilterCheckbox } from '../../../components/global_filter_checkbox';
@@ -101,7 +102,7 @@ export class FilterEditor extends Component {
query={
layerQuery
? layerQuery
- : { language: uiSettings.get('search:queryLanguage'), query: '' }
+ : { language: uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE), query: '' }
}
onQuerySubmit={this._onQueryChange}
indexPatterns={this.state.indexPatterns}
diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/where_expression.js b/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/where_expression.js
index d87761d3dabe8c..8fdb71de2dfed4 100644
--- a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/where_expression.js
+++ b/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/where_expression.js
@@ -8,6 +8,7 @@ import React, { Component } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiButton, EuiPopover, EuiExpression, EuiFormHelpText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
+import { UI_SETTINGS } from '../../../../../../../../src/plugins/data/public';
import { getUiSettings, getData } from '../../../../kibana_services';
export class WhereExpression extends Component {
@@ -79,7 +80,7 @@ export class WhereExpression extends Component {
query={
whereQuery
? whereQuery
- : { language: getUiSettings().get('search:queryLanguage'), query: '' }
+ : { language: getUiSettings().get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE), query: '' }
}
onQuerySubmit={this._onQueryChange}
indexPatterns={[indexPattern]}
diff --git a/x-pack/plugins/ml/public/application/components/job_selector/custom_selection_table/custom_selection_table.js b/x-pack/plugins/ml/public/application/components/custom_selection_table/custom_selection_table.js
similarity index 84%
rename from x-pack/plugins/ml/public/application/components/job_selector/custom_selection_table/custom_selection_table.js
rename to x-pack/plugins/ml/public/application/components/custom_selection_table/custom_selection_table.js
index af282d53273d1e..c86b716b2f49b9 100644
--- a/x-pack/plugins/ml/public/application/components/job_selector/custom_selection_table/custom_selection_table.js
+++ b/x-pack/plugins/ml/public/application/components/custom_selection_table/custom_selection_table.js
@@ -29,7 +29,7 @@ import {
import { Pager } from '@elastic/eui/lib/services';
import { i18n } from '@kbn/i18n';
-const JOBS_PER_PAGE = 20;
+const ITEMS_PER_PAGE = 20;
function getError(error) {
if (error !== null) {
@@ -43,15 +43,18 @@ function getError(error) {
}
export function CustomSelectionTable({
+ checkboxDisabledCheck,
columns,
filterDefaultFields,
filters,
items,
+ itemsPerPage = ITEMS_PER_PAGE,
onTableChange,
+ radioDisabledCheck,
selectedIds,
singleSelection,
sortableProperties,
- timeseriesOnly,
+ tableItemId = 'id',
}) {
const [itemIdToSelectedMap, setItemIdToSelectedMap] = useState(getCurrentlySelectedItemIdsMap());
const [currentItems, setCurrentItems] = useState(items);
@@ -59,7 +62,7 @@ export function CustomSelectionTable({
const [sortedColumn, setSortedColumn] = useState('');
const [pager, setPager] = useState();
const [pagerSettings, setPagerSettings] = useState({
- itemsPerPage: JOBS_PER_PAGE,
+ itemsPerPage: itemsPerPage,
firstItemIndex: 0,
lastItemIndex: 1,
});
@@ -77,9 +80,9 @@ export function CustomSelectionTable({
}, [selectedIds]); // eslint-disable-line
useEffect(() => {
- const tablePager = new Pager(currentItems.length, JOBS_PER_PAGE);
+ const tablePager = new Pager(currentItems.length, itemsPerPage);
setPagerSettings({
- itemsPerPage: JOBS_PER_PAGE,
+ itemsPerPage: itemsPerPage,
firstItemIndex: tablePager.getFirstItemIndex(),
lastItemIndex: tablePager.getLastItemIndex(),
});
@@ -100,7 +103,7 @@ export function CustomSelectionTable({
function handleTableChange({ isSelected, itemId }) {
const selectedMapIds = Object.getOwnPropertyNames(itemIdToSelectedMap);
- const currentItemIds = currentItems.map((item) => item.id);
+ const currentItemIds = currentItems.map((item) => item[tableItemId]);
let currentSelected = selectedMapIds.filter(
(id) => itemIdToSelectedMap[id] === true && id !== itemId
@@ -124,11 +127,11 @@ export function CustomSelectionTable({
onTableChange(currentSelected);
}
- function handleChangeItemsPerPage(itemsPerPage) {
- pager.setItemsPerPage(itemsPerPage);
+ function handleChangeItemsPerPage(numItemsPerPage) {
+ pager.setItemsPerPage(numItemsPerPage);
setPagerSettings({
...pagerSettings,
- itemsPerPage,
+ itemsPerPage: numItemsPerPage,
firstItemIndex: pager.getFirstItemIndex(),
lastItemIndex: pager.getLastItemIndex(),
});
@@ -161,7 +164,9 @@ export function CustomSelectionTable({
}
function areAllItemsSelected() {
- const indexOfUnselectedItem = currentItems.findIndex((item) => !isItemSelected(item.id));
+ const indexOfUnselectedItem = currentItems.findIndex(
+ (item) => !isItemSelected(item[tableItemId])
+ );
return indexOfUnselectedItem === -1;
}
@@ -199,7 +204,7 @@ export function CustomSelectionTable({
function toggleAll() {
const allSelected = areAllItemsSelected() || itemIdToSelectedMap.all === true;
const newItemIdToSelectedMap = {};
- currentItems.forEach((item) => (newItemIdToSelectedMap[item.id] = !allSelected));
+ currentItems.forEach((item) => (newItemIdToSelectedMap[item[tableItemId]] = !allSelected));
setItemIdToSelectedMap(newItemIdToSelectedMap);
handleTableChange({ isSelected: !allSelected, itemId: 'all' });
}
@@ -255,20 +260,23 @@ export function CustomSelectionTable({
{!singleSelection && (
toggleItem(item.id)}
+ disabled={
+ checkboxDisabledCheck !== undefined ? checkboxDisabledCheck(item) : undefined
+ }
+ id={`${item[tableItemId]}-checkbox`}
+ data-test-subj={`${item[tableItemId]}-checkbox`}
+ checked={isItemSelected(item[tableItemId])}
+ onChange={() => toggleItem(item[tableItemId])}
type="inList"
/>
)}
{singleSelection && (
toggleItem(item.id)}
- disabled={timeseriesOnly && item.isSingleMetricViewerJob === false}
+ id={item[tableItemId]}
+ data-test-subj={`${item[tableItemId]}-radio-button`}
+ checked={isItemSelected(item[tableItemId])}
+ onChange={() => toggleItem(item[tableItemId])}
+ disabled={radioDisabledCheck !== undefined ? radioDisabledCheck(item) : undefined}
/>
)}
@@ -299,11 +307,11 @@ export function CustomSelectionTable({
return (
{cells}
@@ -331,7 +339,7 @@ export function CustomSelectionTable({
-
+
{renderSelectAll(true)}
-
+
{renderHeaderCells()}
{renderRows()}
@@ -368,7 +376,7 @@ export function CustomSelectionTable({
handlePageChange(pageIndex)}
@@ -379,13 +387,16 @@ export function CustomSelectionTable({
}
CustomSelectionTable.propTypes = {
+ checkboxDisabledCheck: PropTypes.func,
columns: PropTypes.array.isRequired,
filterDefaultFields: PropTypes.array,
filters: PropTypes.array,
items: PropTypes.array.isRequired,
+ itemsPerPage: PropTypes.number,
onTableChange: PropTypes.func.isRequired,
+ radioDisabledCheck: PropTypes.func,
selectedId: PropTypes.array,
singleSelection: PropTypes.bool,
sortableProperties: PropTypes.object,
- timeseriesOnly: PropTypes.bool,
+ tableItemId: PropTypes.string,
};
diff --git a/x-pack/plugins/ml/public/application/components/job_selector/custom_selection_table/index.js b/x-pack/plugins/ml/public/application/components/custom_selection_table/index.js
similarity index 100%
rename from x-pack/plugins/ml/public/application/components/job_selector/custom_selection_table/index.js
rename to x-pack/plugins/ml/public/application/components/custom_selection_table/index.js
diff --git a/x-pack/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.js b/x-pack/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.js
index 4eeef560dd6d1f..7b104ea372ae56 100644
--- a/x-pack/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.js
+++ b/x-pack/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.js
@@ -6,7 +6,7 @@
import React, { Fragment, useState, useEffect } from 'react';
import { PropTypes } from 'prop-types';
-import { CustomSelectionTable } from '../custom_selection_table';
+import { CustomSelectionTable } from '../../custom_selection_table';
import { JobSelectorBadge } from '../job_selector_badge';
import { TimeRangeBar } from '../timerange_bar';
@@ -107,7 +107,7 @@ export function JobSelectorTable({
id: 'checkbox',
isCheckbox: true,
textOnly: false,
- width: '24px',
+ width: '32px',
},
{
label: 'job ID',
@@ -157,6 +157,9 @@ export function JobSelectorTable({
filterDefaultFields={!singleSelection ? JOB_FILTER_FIELDS : undefined}
items={jobs}
onTableChange={(selectionFromTable) => onSelection({ selectionFromTable })}
+ radioDisabledCheck={(item) => {
+ return timeseriesOnly && item.isSingleMetricViewerJob === false;
+ }}
selectedIds={selectedIds}
singleSelection={singleSelection}
sortableProperties={sortableProperties}
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/_index.scss b/x-pack/plugins/ml/public/application/data_frame_analytics/_index.scss
index 89a0018f90401a..5508c021d33139 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/_index.scss
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/_index.scss
@@ -2,3 +2,4 @@
@import 'pages/analytics_management/components/analytics_list/index';
@import 'pages/analytics_management/components/create_analytics_form/index';
@import 'pages/analytics_management/components/create_analytics_flyout/index';
+@import 'pages/analytics_management/components/create_analytics_button/index';
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts
index 7633e07e8f3dca..0b4e6d27b96e5c 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts
@@ -24,6 +24,21 @@ export enum ANALYSIS_CONFIG_TYPE {
CLASSIFICATION = 'classification',
}
+export enum ANALYSIS_ADVANCED_FIELDS {
+ FEATURE_INFLUENCE_THRESHOLD = 'feature_influence_threshold',
+ GAMMA = 'gamma',
+ LAMBDA = 'lambda',
+ MAX_TREES = 'max_trees',
+ NUM_TOP_FEATURE_IMPORTANCE_VALUES = 'num_top_feature_importance_values',
+}
+
+export enum OUTLIER_ANALYSIS_METHOD {
+ LOF = 'lof',
+ LDOF = 'ldof',
+ DISTANCE_KTH_NN = 'distance_kth_nn',
+ DISTANCE_KNN = 'distance_knn',
+}
+
interface OutlierAnalysis {
[key: string]: {};
outlier_detection: {};
@@ -263,11 +278,13 @@ export const isClassificationAnalysis = (arg: any): arg is ClassificationAnalysi
};
export const isResultsSearchBoolQuery = (arg: any): arg is ResultsSearchBoolQuery => {
+ if (arg === undefined) return false;
const keys = Object.keys(arg);
return keys.length === 1 && keys[0] === 'bool';
};
export const isQueryStringQuery = (arg: any): arg is QueryStringQuery => {
+ if (arg === undefined) return false;
const keys = Object.keys(arg);
return keys.length === 1 && keys[0] === 'query_string';
};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts
index 400902c152c9e6..58343e26153ccf 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts
@@ -17,6 +17,7 @@ export {
IndexPattern,
REFRESH_ANALYTICS_LIST_STATE,
ANALYSIS_CONFIG_TYPE,
+ OUTLIER_ANALYSIS_METHOD,
RegressionEvaluateResponse,
getValuesFromResponse,
loadEvalData,
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step.tsx
new file mode 100644
index 00000000000000..f957dcab2e87ea
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step.tsx
@@ -0,0 +1,32 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC } from 'react';
+import { EuiForm } from '@elastic/eui';
+
+import { CreateAnalyticsStepProps } from '../../../analytics_management/hooks/use_create_analytics_form';
+import { AdvancedStepForm } from './advanced_step_form';
+import { AdvancedStepDetails } from './advanced_step_details';
+import { ANALYTICS_STEPS } from '../../page';
+
+export const AdvancedStep: FC = ({
+ actions,
+ state,
+ step,
+ setCurrentStep,
+ stepActivated,
+}) => {
+ return (
+
+ {step === ANALYTICS_STEPS.ADVANCED && (
+
+ )}
+ {step !== ANALYTICS_STEPS.ADVANCED && stepActivated === true && (
+
+ )}
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_details.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_details.tsx
new file mode 100644
index 00000000000000..a9c8b6d4040ad1
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_details.tsx
@@ -0,0 +1,274 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC, Fragment } from 'react';
+import { i18n } from '@kbn/i18n';
+import {
+ EuiButtonEmpty,
+ EuiDescriptionList,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiSpacer,
+ EuiTitle,
+} from '@elastic/eui';
+import {
+ UNSET_CONFIG_ITEM,
+ State,
+} from '../../../analytics_management/hooks/use_create_analytics_form/state';
+import { ANALYSIS_CONFIG_TYPE } from '../../../../common/analytics';
+import { ANALYTICS_STEPS } from '../../page';
+
+function getStringValue(value: number | undefined) {
+ return value !== undefined ? `${value}` : UNSET_CONFIG_ITEM;
+}
+
+export interface ListItems {
+ title: string;
+ description: string | JSX.Element;
+}
+
+export const AdvancedStepDetails: FC<{ setCurrentStep: any; state: State }> = ({
+ setCurrentStep,
+ state,
+}) => {
+ const { form, isJobCreated } = state;
+ const {
+ computeFeatureInfluence,
+ dependentVariable,
+ eta,
+ featureBagFraction,
+ featureInfluenceThreshold,
+ gamma,
+ jobType,
+ lambda,
+ method,
+ maxTrees,
+ modelMemoryLimit,
+ nNeighbors,
+ numTopClasses,
+ numTopFeatureImportanceValues,
+ outlierFraction,
+ predictionFieldName,
+ randomizeSeed,
+ standardizationEnabled,
+ } = form;
+
+ const isRegOrClassJob =
+ jobType === ANALYSIS_CONFIG_TYPE.REGRESSION || jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION;
+
+ const advancedFirstCol: ListItems[] = [];
+ const advancedSecondCol: ListItems[] = [];
+ const advancedThirdCol: ListItems[] = [];
+
+ const hyperFirstCol: ListItems[] = [];
+ const hyperSecondCol: ListItems[] = [];
+ const hyperThirdCol: ListItems[] = [];
+
+ if (jobType === ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION) {
+ advancedFirstCol.push({
+ title: i18n.translate(
+ 'xpack.ml.dataframe.analytics.create.configDetails.computeFeatureInfluence',
+ {
+ defaultMessage: 'Compute feature influence',
+ }
+ ),
+ description: computeFeatureInfluence,
+ });
+
+ advancedSecondCol.push({
+ title: i18n.translate(
+ 'xpack.ml.dataframe.analytics.create.configDetails.featureInfluenceThreshold',
+ {
+ defaultMessage: 'Feature influence threshold',
+ }
+ ),
+ description: getStringValue(featureInfluenceThreshold),
+ });
+
+ advancedThirdCol.push({
+ title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.modelMemoryLimit', {
+ defaultMessage: 'Model memory limit',
+ }),
+ description: `${modelMemoryLimit}`,
+ });
+
+ hyperFirstCol.push(
+ {
+ title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.nNeighbors', {
+ defaultMessage: 'N neighbors',
+ }),
+ description: getStringValue(nNeighbors),
+ },
+ {
+ title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.outlierFraction', {
+ defaultMessage: 'Outlier fraction',
+ }),
+ description: getStringValue(outlierFraction),
+ }
+ );
+
+ hyperSecondCol.push({
+ title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.method', {
+ defaultMessage: 'Method',
+ }),
+ description: method !== undefined ? method : UNSET_CONFIG_ITEM,
+ });
+
+ hyperThirdCol.push({
+ title: i18n.translate(
+ 'xpack.ml.dataframe.analytics.create.configDetails.standardizationEnabled',
+ {
+ defaultMessage: 'Standardization enabled',
+ }
+ ),
+ description: `${standardizationEnabled}`,
+ });
+ }
+
+ if (isRegOrClassJob) {
+ if (jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION) {
+ advancedFirstCol.push({
+ title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.numTopClasses', {
+ defaultMessage: 'Top classes',
+ }),
+ description: `${numTopClasses}`,
+ });
+ }
+
+ advancedFirstCol.push({
+ title: i18n.translate(
+ 'xpack.ml.dataframe.analytics.create.configDetails.numTopFeatureImportanceValues',
+ {
+ defaultMessage: 'Top feature importance values',
+ }
+ ),
+ description: `${numTopFeatureImportanceValues}`,
+ });
+
+ hyperFirstCol.push(
+ {
+ title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.lambdaFields', {
+ defaultMessage: 'Lambda',
+ }),
+ description: getStringValue(lambda),
+ },
+ {
+ title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.eta', {
+ defaultMessage: 'Eta',
+ }),
+ description: getStringValue(eta),
+ }
+ );
+
+ advancedSecondCol.push({
+ title: i18n.translate(
+ 'xpack.ml.dataframe.analytics.create.configDetails.predictionFieldName',
+ {
+ defaultMessage: 'Prediction field name',
+ }
+ ),
+ description: predictionFieldName ? predictionFieldName : `${dependentVariable}_prediction`,
+ });
+
+ hyperSecondCol.push(
+ {
+ title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.maxTreesFields', {
+ defaultMessage: 'Max trees',
+ }),
+ description: getStringValue(maxTrees),
+ },
+ {
+ title: i18n.translate(
+ 'xpack.ml.dataframe.analytics.create.configDetails.featureBagFraction',
+ {
+ defaultMessage: 'Feature bag fraction',
+ }
+ ),
+ description: getStringValue(featureBagFraction),
+ }
+ );
+
+ advancedThirdCol.push({
+ title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.modelMemoryLimit', {
+ defaultMessage: 'Model memory limit',
+ }),
+ description: `${modelMemoryLimit}`,
+ });
+
+ hyperThirdCol.push(
+ {
+ title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.gamma', {
+ defaultMessage: 'Gamma',
+ }),
+ description: getStringValue(gamma),
+ },
+ {
+ title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.randomizedSeed', {
+ defaultMessage: 'Randomized seed',
+ }),
+ description: getStringValue(randomizeSeed),
+ }
+ );
+ }
+
+ return (
+
+
+
+ {i18n.translate('xpack.ml.dataframe.analytics.create.advancedConfigDetailsTitle', {
+ defaultMessage: 'Advanced configuration',
+ })}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {i18n.translate('xpack.ml.dataframe.analytics.create.hyperParametersDetailsTitle', {
+ defaultMessage: 'Hyper parameters',
+ })}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {!isJobCreated && (
+ {
+ setCurrentStep(ANALYTICS_STEPS.ADVANCED);
+ }}
+ >
+ {i18n.translate('xpack.ml.dataframe.analytics.create.advancedDetails.editButtonText', {
+ defaultMessage: 'Edit',
+ })}
+
+ )}
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_form.tsx
new file mode 100644
index 00000000000000..8b137ac72361c7
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_form.tsx
@@ -0,0 +1,332 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC, Fragment, useMemo } from 'react';
+import {
+ EuiAccordion,
+ EuiFieldNumber,
+ EuiFieldText,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiFormRow,
+ EuiSelect,
+ EuiSpacer,
+ EuiTitle,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { HyperParameters } from './hyper_parameters';
+import { CreateAnalyticsStepProps } from '../../../analytics_management/hooks/use_create_analytics_form';
+import { getModelMemoryLimitErrors } from '../../../analytics_management/hooks/use_create_analytics_form/reducer';
+import {
+ ANALYSIS_CONFIG_TYPE,
+ NUM_TOP_FEATURE_IMPORTANCE_VALUES_MIN,
+} from '../../../../common/analytics';
+import { DEFAULT_MODEL_MEMORY_LIMIT } from '../../../analytics_management/hooks/use_create_analytics_form/state';
+import { ANALYTICS_STEPS } from '../../page';
+import { ContinueButton } from '../continue_button';
+import { OutlierHyperParameters } from './outlier_hyper_parameters';
+
+export function getNumberValue(value?: number) {
+ return value === undefined ? '' : +value;
+}
+
+export const AdvancedStepForm: FC = ({
+ actions,
+ state,
+ setCurrentStep,
+}) => {
+ const { setFormState } = actions;
+ const { form, isJobCreated } = state;
+ const {
+ computeFeatureInfluence,
+ featureInfluenceThreshold,
+ jobType,
+ modelMemoryLimit,
+ modelMemoryLimitValidationResult,
+ numTopClasses,
+ numTopFeatureImportanceValues,
+ numTopFeatureImportanceValuesValid,
+ predictionFieldName,
+ } = form;
+
+ const mmlErrors = useMemo(() => getModelMemoryLimitErrors(modelMemoryLimitValidationResult), [
+ modelMemoryLimitValidationResult,
+ ]);
+
+ const isRegOrClassJob =
+ jobType === ANALYSIS_CONFIG_TYPE.REGRESSION || jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION;
+
+ const mmlInvalid = modelMemoryLimitValidationResult !== null;
+
+ const outlierDetectionAdvancedConfig = (
+
+
+
+ {
+ setFormState({
+ computeFeatureInfluence: e.target.value,
+ });
+ }}
+ />
+
+
+
+
+
+ setFormState({
+ featureInfluenceThreshold: e.target.value === '' ? undefined : +e.target.value,
+ })
+ }
+ data-test-subj="mlAnalyticsCreateJobWizardFeatureInfluenceThresholdInput"
+ min={0}
+ max={1}
+ step={0.001}
+ value={getNumberValue(featureInfluenceThreshold)}
+ />
+
+
+
+ );
+
+ const regAndClassAdvancedConfig = (
+
+
+
+ {i18n.translate(
+ 'xpack.ml.dataframe.analytics.create.numTopFeatureImportanceValuesErrorText',
+ {
+ defaultMessage: 'Invalid maximum number of feature importance values.',
+ }
+ )}
+ ,
+ ]
+ : []),
+ ]}
+ >
+
+ setFormState({
+ numTopFeatureImportanceValues: e.target.value === '' ? undefined : +e.target.value,
+ })
+ }
+ step={1}
+ value={getNumberValue(numTopFeatureImportanceValues)}
+ />
+
+
+
+ _prediction.',
+ }
+ )}
+ >
+ setFormState({ predictionFieldName: e.target.value })}
+ data-test-subj="mlAnalyticsCreateJobWizardPredictionFieldNameInput"
+ />
+
+
+
+ );
+
+ return (
+
+
+
+ {i18n.translate('xpack.ml.dataframe.analytics.create.advancedConfigSectionTitle', {
+ defaultMessage: 'Advanced configuration',
+ })}
+
+
+
+ {jobType === ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION && outlierDetectionAdvancedConfig}
+ {isRegOrClassJob && regAndClassAdvancedConfig}
+ {jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION && (
+
+
+
+ setFormState({
+ numTopClasses: e.target.value === '' ? undefined : +e.target.value,
+ })
+ }
+ step={1}
+ value={getNumberValue(numTopClasses)}
+ />
+
+
+ )}
+
+
+ setFormState({ modelMemoryLimit: e.target.value })}
+ isInvalid={mmlInvalid}
+ data-test-subj="mlAnalyticsCreateJobWizardModelMemoryInput"
+ />
+
+
+
+
+
+
+ {i18n.translate('xpack.ml.dataframe.analytics.create.hyperParametersSectionTitle', {
+ defaultMessage: 'Hyper parameters',
+ })}
+
+
+ }
+ initialIsOpen={false}
+ data-test-subj="mlAnalyticsCreateJobWizardHyperParametersSection"
+ >
+
+ {jobType === ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION && (
+
+ )}
+ {isRegOrClassJob && }
+
+
+
+ {
+ setCurrentStep(ANALYTICS_STEPS.DETAILS);
+ }}
+ />
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/hyper_parameters.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/hyper_parameters.tsx
new file mode 100644
index 00000000000000..144a0621060032
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/hyper_parameters.tsx
@@ -0,0 +1,188 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC, Fragment } from 'react';
+import { EuiFieldNumber, EuiFlexItem, EuiFormRow } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { CreateAnalyticsFormProps } from '../../../analytics_management/hooks/use_create_analytics_form';
+import { getNumberValue } from './advanced_step_form';
+
+const MAX_TREES_LIMIT = 2000;
+
+export const HyperParameters: FC = ({ actions, state }) => {
+ const { setFormState } = actions;
+
+ const { eta, featureBagFraction, gamma, lambda, maxTrees, randomizeSeed } = state.form;
+
+ return (
+
+
+
+
+ setFormState({ lambda: e.target.value === '' ? undefined : +e.target.value })
+ }
+ step={0.001}
+ min={0}
+ value={getNumberValue(lambda)}
+ />
+
+
+
+
+
+ setFormState({ maxTrees: e.target.value === '' ? undefined : +e.target.value })
+ }
+ isInvalid={maxTrees !== undefined && !Number.isInteger(maxTrees)}
+ step={1}
+ min={1}
+ max={MAX_TREES_LIMIT}
+ value={getNumberValue(maxTrees)}
+ />
+
+
+
+
+
+ setFormState({ gamma: e.target.value === '' ? undefined : +e.target.value })
+ }
+ step={0.001}
+ min={0}
+ value={getNumberValue(gamma)}
+ />
+
+
+
+
+
+ setFormState({ eta: e.target.value === '' ? undefined : +e.target.value })
+ }
+ step={0.001}
+ min={0.001}
+ max={1}
+ value={getNumberValue(eta)}
+ />
+
+
+
+
+
+ setFormState({
+ featureBagFraction: e.target.value === '' ? undefined : +e.target.value,
+ })
+ }
+ isInvalid={
+ featureBagFraction !== undefined &&
+ (featureBagFraction > 1 || featureBagFraction <= 0)
+ }
+ step={0.001}
+ max={1}
+ value={getNumberValue(featureBagFraction)}
+ />
+
+
+
+
+
+ setFormState({ randomizeSeed: e.target.value === '' ? undefined : +e.target.value })
+ }
+ isInvalid={randomizeSeed !== undefined && typeof randomizeSeed !== 'number'}
+ value={getNumberValue(randomizeSeed)}
+ step={1}
+ />
+
+
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/index.ts
new file mode 100644
index 00000000000000..6a19e55b533c10
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { AdvancedStep } from './advanced_step';
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/outlier_hyper_parameters.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/outlier_hyper_parameters.tsx
new file mode 100644
index 00000000000000..dfe7969d8a6d96
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/outlier_hyper_parameters.tsx
@@ -0,0 +1,153 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC, Fragment } from 'react';
+import { EuiFieldNumber, EuiFlexItem, EuiFormRow, EuiSelect } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { OUTLIER_ANALYSIS_METHOD } from '../../../../common/analytics';
+import { CreateAnalyticsFormProps } from '../../../analytics_management/hooks/use_create_analytics_form';
+import { getNumberValue } from './advanced_step_form';
+
+export const OutlierHyperParameters: FC = ({ actions, state }) => {
+ const { setFormState } = actions;
+
+ const { method, nNeighbors, outlierFraction, standardizationEnabled } = state.form;
+
+ return (
+
+
+
+ ({
+ value: outlierMethod,
+ text: outlierMethod,
+ }))}
+ value={method}
+ hasNoInitialSelection={true}
+ onChange={(e) => {
+ setFormState({ method: e.target.value });
+ }}
+ data-test-subj="mlAnalyticsCreateJobWizardMethodInput"
+ />
+
+
+
+
+
+ setFormState({ nNeighbors: e.target.value === '' ? undefined : +e.target.value })
+ }
+ step={1}
+ min={1}
+ value={getNumberValue(nNeighbors)}
+ />
+
+
+
+
+
+ setFormState({ outlierFraction: e.target.value === '' ? undefined : +e.target.value })
+ }
+ step={0.001}
+ min={0}
+ max={1}
+ value={getNumberValue(outlierFraction)}
+ />
+
+
+
+
+ {
+ setFormState({ standardizationEnabled: e.target.value });
+ }}
+ />
+
+
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/back_to_list_panel/back_to_list_panel.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/back_to_list_panel/back_to_list_panel.tsx
new file mode 100644
index 00000000000000..e437d27372a3eb
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/back_to_list_panel/back_to_list_panel.tsx
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC, Fragment } from 'react';
+import { EuiCard, EuiHorizontalRule, EuiIcon } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
+function redirectToAnalyticsManagementPage() {
+ window.location.href = '#/data_frame_analytics?';
+}
+
+export const BackToListPanel: FC = () => (
+
+
+ }
+ title={i18n.translate('xpack.ml.dataframe.analytics.create.analyticsListCardTitle', {
+ defaultMessage: 'Data Frame Analytics',
+ })}
+ description={i18n.translate(
+ 'xpack.ml.dataframe.analytics.create.analyticsListCardDescription',
+ {
+ defaultMessage: 'Return to the analytics management page.',
+ }
+ )}
+ onClick={redirectToAnalyticsManagementPage}
+ data-test-subj="analyticsWizardCardManagement"
+ />
+
+);
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/back_to_list_panel/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/back_to_list_panel/index.ts
new file mode 100644
index 00000000000000..4da65615628797
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/back_to_list_panel/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { BackToListPanel } from './back_to_list_panel';
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/analysis_fields_table.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/analysis_fields_table.tsx
new file mode 100644
index 00000000000000..ad540285e49f09
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/analysis_fields_table.tsx
@@ -0,0 +1,207 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC, Fragment, memo, useEffect, useState } from 'react';
+import { EuiCallOut, EuiFormRow, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui';
+// @ts-ignore no declaration
+import { LEFT_ALIGNMENT, CENTER_ALIGNMENT, SortableProperties } from '@elastic/eui/lib/services';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { FieldSelectionItem } from '../../../../common/analytics';
+// @ts-ignore could not find declaration file
+import { CustomSelectionTable } from '../../../../../components/custom_selection_table';
+
+const columns = [
+ {
+ id: 'checkbox',
+ isCheckbox: true,
+ textOnly: false,
+ width: '32px',
+ },
+ {
+ label: i18n.translate('xpack.ml.dataframe.analytics.create.analyticsTable.fieldNameColumn', {
+ defaultMessage: 'Field name',
+ }),
+ id: 'name',
+ isSortable: true,
+ alignment: LEFT_ALIGNMENT,
+ },
+ {
+ id: 'mapping_types',
+ label: i18n.translate('xpack.ml.dataframe.analytics.create.analyticsTable.mappingColumn', {
+ defaultMessage: 'Mapping',
+ }),
+ isSortable: false,
+ alignment: LEFT_ALIGNMENT,
+ },
+ {
+ label: i18n.translate('xpack.ml.dataframe.analytics.create.analyticsTable.isIncludedColumn', {
+ defaultMessage: 'Is included',
+ }),
+ id: 'is_included',
+ alignment: LEFT_ALIGNMENT,
+ isSortable: true,
+ // eslint-disable-next-line @typescript-eslint/camelcase
+ render: ({ is_included }: { is_included: boolean }) => (is_included ? 'Yes' : 'No'),
+ },
+ {
+ label: i18n.translate('xpack.ml.dataframe.analytics.create.analyticsTable.isRequiredColumn', {
+ defaultMessage: 'Is required',
+ }),
+ id: 'is_required',
+ alignment: LEFT_ALIGNMENT,
+ isSortable: true,
+ // eslint-disable-next-line @typescript-eslint/camelcase
+ render: ({ is_required }: { is_required: boolean }) => (is_required ? 'Yes' : 'No'),
+ },
+ {
+ label: i18n.translate('xpack.ml.dataframe.analytics.create.analyticsTable.reasonColumn', {
+ defaultMessage: 'Reason',
+ }),
+ id: 'reason',
+ alignment: LEFT_ALIGNMENT,
+ isSortable: false,
+ },
+];
+
+const checkboxDisabledCheck = (item: FieldSelectionItem) =>
+ (item.is_included === false && !item.reason?.includes('in excludes list')) ||
+ item.is_required === true;
+
+export const MemoizedAnalysisFieldsTable: FC<{
+ excludes: string[];
+ loadingItems: boolean;
+ setFormState: any;
+ tableItems: FieldSelectionItem[];
+}> = memo(
+ ({ excludes, loadingItems, setFormState, tableItems }) => {
+ const [sortableProperties, setSortableProperties] = useState();
+ const [currentSelection, setCurrentSelection] = useState([]);
+
+ useEffect(() => {
+ if (excludes.length > 0) {
+ setCurrentSelection(excludes);
+ }
+ }, []);
+
+ // Only set form state on unmount to prevent re-renders due to props changing if exludes was updated on each selection
+ useEffect(() => {
+ return () => {
+ setFormState({ excludes: currentSelection });
+ };
+ }, [currentSelection]);
+
+ useEffect(() => {
+ let sortablePropertyItems = [];
+ const defaultSortProperty = 'name';
+
+ sortablePropertyItems = [
+ {
+ name: 'name',
+ getValue: (item: any) => item.name.toLowerCase(),
+ isAscending: true,
+ },
+ {
+ name: 'is_included',
+ getValue: (item: any) => item.is_included,
+ isAscending: true,
+ },
+ {
+ name: 'is_required',
+ getValue: (item: any) => item.is_required,
+ isAscending: true,
+ },
+ ];
+ const sortableProps = new SortableProperties(sortablePropertyItems, defaultSortProperty);
+
+ setSortableProperties(sortableProps);
+ }, []);
+
+ const filters = [
+ {
+ type: 'field_value_selection',
+ field: 'is_included',
+ name: i18n.translate('xpack.ml.dataframe.analytics.create.excludedFilterLabel', {
+ defaultMessage: 'Is included',
+ }),
+ multiSelect: false,
+ options: [
+ {
+ value: true,
+ view: (
+
+ {i18n.translate('xpack.ml.dataframe.analytics.create.isIncludedOption', {
+ defaultMessage: 'Yes',
+ })}
+
+ ),
+ },
+ {
+ value: false,
+ view: (
+
+ {i18n.translate('xpack.ml.dataframe.analytics.create.isNotIncludedOption', {
+ defaultMessage: 'No',
+ })}
+
+ ),
+ },
+ ],
+ },
+ ];
+
+ return (
+
+
+
+
+ {tableItems.length === 0 && (
+
+
+
+ )}
+ {tableItems.length > 0 && (
+
+ {
+ setCurrentSelection(selection);
+ }}
+ selectedIds={currentSelection}
+ singleSelection={false}
+ sortableProperties={sortableProperties}
+ tableItemId={'name'}
+ />
+
+ )}
+
+
+ );
+ },
+ (prevProps, nextProps) => prevProps.tableItems.length === nextProps.tableItems.length
+);
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step.tsx
new file mode 100644
index 00000000000000..220910535aafe9
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step.tsx
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC } from 'react';
+import { EuiForm } from '@elastic/eui';
+
+import { CreateAnalyticsStepProps } from '../../../analytics_management/hooks/use_create_analytics_form';
+import { ConfigurationStepDetails } from './configuration_step_details';
+import { ConfigurationStepForm } from './configuration_step_form';
+import { ANALYTICS_STEPS } from '../../page';
+
+export const ConfigurationStep: FC = ({
+ actions,
+ state,
+ setCurrentStep,
+ step,
+ stepActivated,
+}) => {
+ return (
+
+ {step === ANALYTICS_STEPS.CONFIGURATION && (
+
+ )}
+ {step !== ANALYTICS_STEPS.CONFIGURATION && stepActivated === true && (
+
+ )}
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx
new file mode 100644
index 00000000000000..6603af9aa302ee
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx
@@ -0,0 +1,115 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC, Fragment } from 'react';
+import { i18n } from '@kbn/i18n';
+import {
+ EuiButtonEmpty,
+ EuiDescriptionList,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiSpacer,
+} from '@elastic/eui';
+import {
+ State,
+ UNSET_CONFIG_ITEM,
+} from '../../../analytics_management/hooks/use_create_analytics_form/state';
+import { ANALYSIS_CONFIG_TYPE } from '../../../../common/analytics';
+import { useMlContext } from '../../../../../contexts/ml';
+import { ANALYTICS_STEPS } from '../../page';
+
+interface Props {
+ setCurrentStep: React.Dispatch>;
+ state: State;
+}
+
+export const ConfigurationStepDetails: FC = ({ setCurrentStep, state }) => {
+ const mlContext = useMlContext();
+ const { currentIndexPattern } = mlContext;
+ const { form, isJobCreated } = state;
+ const { dependentVariable, excludes, jobConfigQueryString, jobType, trainingPercent } = form;
+
+ const isJobTypeWithDepVar =
+ jobType === ANALYSIS_CONFIG_TYPE.REGRESSION || jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION;
+
+ const detailsFirstCol = [
+ {
+ title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.sourceIndex', {
+ defaultMessage: 'Source index',
+ }),
+ description: currentIndexPattern.title || UNSET_CONFIG_ITEM,
+ },
+ {
+ title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.Query', {
+ defaultMessage: 'Query',
+ }),
+ description: jobConfigQueryString || UNSET_CONFIG_ITEM,
+ },
+ ];
+
+ const detailsSecondCol = [
+ {
+ title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.jobType', {
+ defaultMessage: 'Job type',
+ }),
+ description: jobType! as string,
+ },
+ ];
+
+ const detailsThirdCol = [
+ {
+ title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.excludedFields', {
+ defaultMessage: 'Excluded fields',
+ }),
+ description: excludes.length > 0 ? excludes.join(', ') : UNSET_CONFIG_ITEM,
+ },
+ ];
+
+ if (isJobTypeWithDepVar) {
+ detailsSecondCol.push({
+ title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.trainingPercent', {
+ defaultMessage: 'Training percent',
+ }),
+ description: `${trainingPercent}`,
+ });
+ detailsThirdCol.unshift({
+ title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.dependentVariable', {
+ defaultMessage: 'Dependent variable',
+ }),
+ description: dependentVariable,
+ });
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {!isJobCreated && (
+ {
+ setCurrentStep(ANALYTICS_STEPS.CONFIGURATION);
+ }}
+ >
+ {i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.editButtonText', {
+ defaultMessage: 'Edit',
+ })}
+
+ )}
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx
new file mode 100644
index 00000000000000..9446dfd4ed5250
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx
@@ -0,0 +1,449 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC, Fragment, useEffect, useRef } from 'react';
+import { EuiBadge, EuiComboBox, EuiFormRow, EuiRange, EuiSpacer, EuiText } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { debounce } from 'lodash';
+
+import { newJobCapsService } from '../../../../../services/new_job_capabilities_service';
+import { useMlContext } from '../../../../../contexts/ml';
+
+import {
+ DfAnalyticsExplainResponse,
+ FieldSelectionItem,
+ ANALYSIS_CONFIG_TYPE,
+ TRAINING_PERCENT_MIN,
+ TRAINING_PERCENT_MAX,
+} from '../../../../common/analytics';
+import { CreateAnalyticsStepProps } from '../../../analytics_management/hooks/use_create_analytics_form';
+import { Messages } from '../../../analytics_management/components/create_analytics_form/messages';
+import {
+ DEFAULT_MODEL_MEMORY_LIMIT,
+ getJobConfigFromFormState,
+ State,
+} from '../../../analytics_management/hooks/use_create_analytics_form/state';
+import { shouldAddAsDepVarOption } from '../../../analytics_management/components/create_analytics_form/form_options_validation';
+import { ml } from '../../../../../services/ml_api_service';
+import { getToastNotifications } from '../../../../../util/dependency_cache';
+
+import { ANALYTICS_STEPS } from '../../page';
+import { ContinueButton } from '../continue_button';
+import { JobType } from './job_type';
+import { SupportedFieldsMessage } from './supported_fields_message';
+import { MemoizedAnalysisFieldsTable } from './analysis_fields_table';
+import { DataGrid } from '../../../../../components/data_grid';
+import { useIndexData } from '../../hooks';
+import { ExplorationQueryBar } from '../../../analytics_exploration/components/exploration_query_bar';
+import { useSavedSearch } from './use_saved_search';
+
+const requiredFieldsErrorText = i18n.translate(
+ 'xpack.ml.dataframe.analytics.createWizard.requiredFieldsErrorMessage',
+ {
+ defaultMessage: 'At least one field must be included in the analysis.',
+ }
+);
+
+export const ConfigurationStepForm: FC = ({
+ actions,
+ state,
+ setCurrentStep,
+}) => {
+ const mlContext = useMlContext();
+ const { currentSavedSearch, currentIndexPattern } = mlContext;
+ const { savedSearchQuery, savedSearchQueryStr } = useSavedSearch();
+
+ const { initiateWizard, setEstimatedModelMemoryLimit, setFormState } = actions;
+ const { estimatedModelMemoryLimit, form, isJobCreated, requestMessages } = state;
+ const firstUpdate = useRef(true);
+ const {
+ dependentVariable,
+ dependentVariableFetchFail,
+ dependentVariableOptions,
+ excludes,
+ excludesTableItems,
+ fieldOptionsFetchFail,
+ jobConfigQuery,
+ jobConfigQueryString,
+ jobType,
+ loadingDepVarOptions,
+ loadingFieldOptions,
+ maxDistinctValuesError,
+ modelMemoryLimit,
+ previousJobType,
+ requiredFieldsError,
+ trainingPercent,
+ } = form;
+
+ const setJobConfigQuery = ({ query, queryString }: { query: any; queryString: string }) => {
+ setFormState({ jobConfigQuery: query, jobConfigQueryString: queryString });
+ };
+
+ const indexData = useIndexData(
+ currentIndexPattern,
+ savedSearchQuery !== undefined ? savedSearchQuery : jobConfigQuery
+ );
+
+ const indexPreviewProps = {
+ ...indexData,
+ dataTestSubj: 'mlAnalyticsCreationDataGrid',
+ toastNotifications: getToastNotifications(),
+ };
+
+ const isJobTypeWithDepVar =
+ jobType === ANALYSIS_CONFIG_TYPE.REGRESSION || jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION;
+
+ const dependentVariableEmpty = isJobTypeWithDepVar && dependentVariable === '';
+
+ const isStepInvalid =
+ dependentVariableEmpty ||
+ jobType === undefined ||
+ maxDistinctValuesError !== undefined ||
+ requiredFieldsError !== undefined;
+
+ const loadDepVarOptions = async (formState: State['form']) => {
+ setFormState({
+ loadingDepVarOptions: true,
+ maxDistinctValuesError: undefined,
+ });
+ try {
+ if (currentIndexPattern !== undefined) {
+ const formStateUpdate: {
+ loadingDepVarOptions: boolean;
+ dependentVariableFetchFail: boolean;
+ dependentVariableOptions: State['form']['dependentVariableOptions'];
+ dependentVariable?: State['form']['dependentVariable'];
+ } = {
+ loadingDepVarOptions: false,
+ dependentVariableFetchFail: false,
+ dependentVariableOptions: [] as State['form']['dependentVariableOptions'],
+ };
+
+ // Get fields and filter for supported types for job type
+ const { fields } = newJobCapsService;
+
+ let resetDependentVariable = true;
+ for (const field of fields) {
+ if (shouldAddAsDepVarOption(field, jobType)) {
+ formStateUpdate.dependentVariableOptions.push({
+ label: field.id,
+ });
+
+ if (formState.dependentVariable === field.id) {
+ resetDependentVariable = false;
+ }
+ }
+ }
+
+ if (resetDependentVariable) {
+ formStateUpdate.dependentVariable = '';
+ }
+
+ setFormState(formStateUpdate);
+ }
+ } catch (e) {
+ setFormState({ loadingDepVarOptions: false, dependentVariableFetchFail: true });
+ }
+ };
+
+ const debouncedGetExplainData = debounce(async () => {
+ const jobTypeChanged = previousJobType !== jobType;
+ const shouldUpdateModelMemoryLimit = !firstUpdate.current || !modelMemoryLimit;
+ const shouldUpdateEstimatedMml =
+ !firstUpdate.current || !modelMemoryLimit || estimatedModelMemoryLimit === '';
+
+ if (firstUpdate.current) {
+ firstUpdate.current = false;
+ }
+ // Reset if jobType changes (jobType requires dependent_variable to be set -
+ // which won't be the case if switching from outlier detection)
+ if (jobTypeChanged) {
+ setFormState({
+ loadingFieldOptions: true,
+ });
+ }
+
+ try {
+ const jobConfig = getJobConfigFromFormState(form);
+ delete jobConfig.dest;
+ delete jobConfig.model_memory_limit;
+ const resp: DfAnalyticsExplainResponse = await ml.dataFrameAnalytics.explainDataFrameAnalytics(
+ jobConfig
+ );
+ const expectedMemoryWithoutDisk = resp.memory_estimation?.expected_memory_without_disk;
+
+ if (shouldUpdateEstimatedMml) {
+ setEstimatedModelMemoryLimit(expectedMemoryWithoutDisk);
+ }
+
+ const fieldSelection: FieldSelectionItem[] | undefined = resp.field_selection;
+
+ let hasRequiredFields = false;
+ if (fieldSelection) {
+ for (let i = 0; i < fieldSelection.length; i++) {
+ const field = fieldSelection[i];
+ if (field.is_included === true && field.is_required === false) {
+ hasRequiredFields = true;
+ break;
+ }
+ }
+ }
+
+ // If job type has changed load analysis field options again
+ if (jobTypeChanged) {
+ setFormState({
+ ...(shouldUpdateModelMemoryLimit ? { modelMemoryLimit: expectedMemoryWithoutDisk } : {}),
+ excludesTableItems: fieldSelection ? fieldSelection : [],
+ loadingFieldOptions: false,
+ fieldOptionsFetchFail: false,
+ maxDistinctValuesError: undefined,
+ requiredFieldsError: !hasRequiredFields ? requiredFieldsErrorText : undefined,
+ });
+ } else {
+ setFormState({
+ ...(shouldUpdateModelMemoryLimit ? { modelMemoryLimit: expectedMemoryWithoutDisk } : {}),
+ requiredFieldsError: !hasRequiredFields ? requiredFieldsErrorText : undefined,
+ });
+ }
+ } catch (e) {
+ let errorMessage;
+ if (
+ jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION &&
+ e.body &&
+ e.body.message !== undefined &&
+ e.body.message.includes('status_exception') &&
+ (e.body.message.includes('must have at most') ||
+ e.body.message.includes('must have at least'))
+ ) {
+ errorMessage = e.body.message;
+ }
+ const fallbackModelMemoryLimit =
+ jobType !== undefined
+ ? DEFAULT_MODEL_MEMORY_LIMIT[jobType]
+ : DEFAULT_MODEL_MEMORY_LIMIT.outlier_detection;
+ setEstimatedModelMemoryLimit(fallbackModelMemoryLimit);
+ setFormState({
+ fieldOptionsFetchFail: true,
+ maxDistinctValuesError: errorMessage,
+ loadingFieldOptions: false,
+ ...(shouldUpdateModelMemoryLimit ? { modelMemoryLimit: fallbackModelMemoryLimit } : {}),
+ });
+ }
+ }, 300);
+
+ useEffect(() => {
+ initiateWizard();
+ }, []);
+
+ useEffect(() => {
+ setFormState({ sourceIndex: currentIndexPattern.title });
+ }, []);
+
+ useEffect(() => {
+ if (savedSearchQueryStr !== undefined) {
+ setFormState({ jobConfigQuery: savedSearchQuery, jobConfigQueryString: savedSearchQueryStr });
+ }
+ }, [JSON.stringify(savedSearchQuery), savedSearchQueryStr]);
+
+ useEffect(() => {
+ if (isJobTypeWithDepVar) {
+ loadDepVarOptions(form);
+ }
+ }, [jobType]);
+
+ useEffect(() => {
+ const hasBasicRequiredFields = jobType !== undefined;
+
+ const hasRequiredAnalysisFields =
+ (isJobTypeWithDepVar && dependentVariable !== '') ||
+ jobType === ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION;
+
+ if (hasBasicRequiredFields && hasRequiredAnalysisFields) {
+ debouncedGetExplainData();
+ }
+
+ return () => {
+ debouncedGetExplainData.cancel();
+ };
+ }, [jobType, dependentVariable, trainingPercent, JSON.stringify(excludes), jobConfigQueryString]);
+
+ return (
+
+
+
+
+ {savedSearchQuery === undefined && (
+
+
+
+ )}
+
+ {savedSearchQuery !== undefined && (
+
+ {i18n.translate('xpack.ml.dataframe.analytics.create.savedSearchLabel', {
+ defaultMessage: 'Saved search',
+ })}
+
+ )}
+
+ {savedSearchQuery !== undefined
+ ? currentSavedSearch?.attributes.title
+ : currentIndexPattern.title}
+
+
+ }
+ fullWidth
+ >
+
+
+ {isJobTypeWithDepVar && (
+
+
+ {i18n.translate(
+ 'xpack.ml.dataframe.analytics.create.dependentVariableOptionsFetchError',
+ {
+ defaultMessage:
+ 'There was a problem fetching fields. Please refresh the page and try again.',
+ }
+ )}
+ ,
+ ]
+ : []),
+ ...(fieldOptionsFetchFail === true && maxDistinctValuesError !== undefined
+ ? [
+
+ {i18n.translate(
+ 'xpack.ml.dataframe.analytics.create.dependentVariableMaxDistictValuesError',
+ {
+ defaultMessage: 'Invalid. {message}',
+ values: { message: maxDistinctValuesError },
+ }
+ )}
+ ,
+ ]
+ : []),
+ ]}
+ >
+
+ setFormState({
+ dependentVariable: selectedOptions[0].label || '',
+ })
+ }
+ isClearable={false}
+ isInvalid={dependentVariable === ''}
+ data-test-subj="mlAnalyticsCreateJobWizardDependentVariableSelect"
+ />
+
+
+ )}
+
+
+
+
+ {isJobTypeWithDepVar && (
+
+ setFormState({ trainingPercent: +e.target.value })}
+ data-test-subj="mlAnalyticsCreateJobWizardTrainingPercentSlider"
+ />
+
+ )}
+
+ {
+ setCurrentStep(ANALYTICS_STEPS.ADVANCED);
+ }}
+ />
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/index.ts
new file mode 100644
index 00000000000000..ba67a6f080d45c
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { ConfigurationStep } from './configuration_step';
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/job_type.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/job_type.tsx
new file mode 100644
index 00000000000000..f31c9cd28f65a4
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/job_type.tsx
@@ -0,0 +1,83 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { Fragment, FC } from 'react';
+import { i18n } from '@kbn/i18n';
+
+import { EuiFormRow, EuiSelect } from '@elastic/eui';
+import { ANALYSIS_CONFIG_TYPE } from '../../../../common';
+
+import { AnalyticsJobType } from '../../../analytics_management/hooks/use_create_analytics_form/state';
+
+interface Props {
+ type: AnalyticsJobType;
+ setFormState: React.Dispatch>;
+}
+
+export const JobType: FC = ({ type, setFormState }) => {
+ const outlierHelpText = i18n.translate(
+ 'xpack.ml.dataframe.analytics.create.outlierDetectionHelpText',
+ {
+ defaultMessage:
+ 'Outlier detection jobs require a source index that is mapped as a table-like data structure and analyze only numeric and boolean fields. Use the advanced editor to add custom options to the configuration.',
+ }
+ );
+
+ const regressionHelpText = i18n.translate(
+ 'xpack.ml.dataframe.analytics.create.outlierRegressionHelpText',
+ {
+ defaultMessage:
+ 'Regression jobs analyze only numeric fields. Use the advanced editor to apply custom options, such as the prediction field name.',
+ }
+ );
+
+ const classificationHelpText = i18n.translate(
+ 'xpack.ml.dataframe.analytics.create.classificationHelpText',
+ {
+ defaultMessage:
+ 'Classification jobs require a source index that is mapped as a table-like data structure and support fields that are numeric, boolean, text, keyword, or ip. Use the advanced editor to apply custom options, such as the prediction field name.',
+ }
+ );
+
+ const helpText = {
+ [ANALYSIS_CONFIG_TYPE.REGRESSION]: regressionHelpText,
+ [ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION]: outlierHelpText,
+ [ANALYSIS_CONFIG_TYPE.CLASSIFICATION]: classificationHelpText,
+ };
+
+ return (
+
+
+ ({
+ value: jobType,
+ text: jobType.replace(/_/g, ' '),
+ 'data-test-subj': `mlAnalyticsCreation-${jobType}-option`,
+ }))}
+ value={type}
+ hasNoInitialSelection={true}
+ onChange={(e) => {
+ const value = e.target.value as AnalyticsJobType;
+ setFormState({
+ previousJobType: type,
+ jobType: value,
+ excludes: [],
+ requiredFieldsError: undefined,
+ });
+ }}
+ data-test-subj="mlAnalyticsCreateJobWizardJobTypeSelect"
+ />
+
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/supported_fields_message.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/supported_fields_message.tsx
new file mode 100644
index 00000000000000..fe13cc1d6edfc5
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/supported_fields_message.tsx
@@ -0,0 +1,120 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC, Fragment, useState, useEffect } from 'react';
+import { EuiSpacer, EuiText } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+
+import { AnalyticsJobType } from '../../../analytics_management/hooks/use_create_analytics_form/state';
+import { ANALYSIS_CONFIG_TYPE } from '../../../../common/analytics';
+import { Field, EVENT_RATE_FIELD_ID } from '../../../../../../../common/types/fields';
+import { BASIC_NUMERICAL_TYPES, EXTENDED_NUMERICAL_TYPES } from '../../../../common/fields';
+import {
+ OMIT_FIELDS,
+ CATEGORICAL_TYPES,
+} from '../../../analytics_management/components/create_analytics_form/form_options_validation';
+import { ES_FIELD_TYPES } from '../../../../../../../../../../src/plugins/data/public';
+import { newJobCapsService } from '../../../../../services/new_job_capabilities_service';
+
+const containsClassificationFieldsCb = ({ name, type }: Field) =>
+ !OMIT_FIELDS.includes(name) &&
+ name !== EVENT_RATE_FIELD_ID &&
+ (BASIC_NUMERICAL_TYPES.has(type) ||
+ CATEGORICAL_TYPES.has(type) ||
+ type === ES_FIELD_TYPES.BOOLEAN);
+
+const containsRegressionFieldsCb = ({ name, type }: Field) =>
+ !OMIT_FIELDS.includes(name) &&
+ name !== EVENT_RATE_FIELD_ID &&
+ (BASIC_NUMERICAL_TYPES.has(type) || EXTENDED_NUMERICAL_TYPES.has(type));
+
+const containsOutlierFieldsCb = ({ name, type }: Field) =>
+ !OMIT_FIELDS.includes(name) && name !== EVENT_RATE_FIELD_ID && BASIC_NUMERICAL_TYPES.has(type);
+
+const callbacks: Record boolean> = {
+ [ANALYSIS_CONFIG_TYPE.CLASSIFICATION]: containsClassificationFieldsCb,
+ [ANALYSIS_CONFIG_TYPE.REGRESSION]: containsRegressionFieldsCb,
+ [ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION]: containsOutlierFieldsCb,
+};
+
+const messages: Record = {
+ [ANALYSIS_CONFIG_TYPE.CLASSIFICATION]: (
+
+ ),
+ [ANALYSIS_CONFIG_TYPE.REGRESSION]: (
+
+ ),
+ [ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION]: (
+
+ ),
+};
+
+interface Props {
+ jobType: AnalyticsJobType;
+}
+
+export const SupportedFieldsMessage: FC = ({ jobType }) => {
+ const [sourceIndexContainsSupportedFields, setSourceIndexContainsSupportedFields] = useState<
+ boolean
+ >(true);
+ const [sourceIndexFieldsCheckFailed, setSourceIndexFieldsCheckFailed] = useState(false);
+ const { fields } = newJobCapsService;
+
+ // Find out if index pattern contains supported fields for job type. Provides a hint in the form
+ // that job may not run correctly if no supported fields are found.
+ const validateFields = () => {
+ if (fields && jobType !== undefined) {
+ try {
+ const containsSupportedFields: boolean = fields.some(callbacks[jobType]);
+
+ setSourceIndexContainsSupportedFields(containsSupportedFields);
+ setSourceIndexFieldsCheckFailed(false);
+ } catch (e) {
+ setSourceIndexFieldsCheckFailed(true);
+ }
+ }
+ };
+
+ useEffect(() => {
+ if (jobType !== undefined) {
+ setSourceIndexContainsSupportedFields(true);
+ setSourceIndexFieldsCheckFailed(false);
+ validateFields();
+ }
+ }, [jobType]);
+
+ if (sourceIndexContainsSupportedFields === true) return null;
+
+ if (sourceIndexFieldsCheckFailed === true) {
+ return (
+
+
+
+
+ );
+ }
+
+ return (
+
+
+ {jobType !== undefined && messages[jobType]}
+
+
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/use_saved_search.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/use_saved_search.ts
new file mode 100644
index 00000000000000..856358538b26fa
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/use_saved_search.ts
@@ -0,0 +1,50 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { useState, useEffect } from 'react';
+import { useMlContext } from '../../../../../contexts/ml';
+import { esQuery, esKuery } from '../../../../../../../../../../src/plugins/data/public';
+import { SEARCH_QUERY_LANGUAGE } from '../../../../../../../common/constants/search';
+import { getQueryFromSavedSearch } from '../../../../../util/index_utils';
+
+export function useSavedSearch() {
+ const [savedSearchQuery, setSavedSearchQuery] = useState(undefined);
+ const [savedSearchQueryStr, setSavedSearchQueryStr] = useState(undefined);
+
+ const mlContext = useMlContext();
+ const { currentSavedSearch, currentIndexPattern, kibanaConfig } = mlContext;
+
+ const getQueryData = () => {
+ let qry;
+ let qryString;
+
+ if (currentSavedSearch !== null) {
+ const { query } = getQueryFromSavedSearch(currentSavedSearch);
+ const queryLanguage = query.language as SEARCH_QUERY_LANGUAGE;
+ qryString = query.query;
+
+ if (queryLanguage === SEARCH_QUERY_LANGUAGE.KUERY) {
+ const ast = esKuery.fromKueryExpression(qryString);
+ qry = esKuery.toElasticsearchQuery(ast, currentIndexPattern);
+ } else {
+ qry = esQuery.luceneStringToDsl(qryString);
+ esQuery.decorateQuery(qry, kibanaConfig.get('query:queryString:options'));
+ }
+
+ setSavedSearchQuery(qry);
+ setSavedSearchQueryStr(qryString);
+ }
+ };
+
+ useEffect(() => {
+ getQueryData();
+ }, []);
+
+ return {
+ savedSearchQuery,
+ savedSearchQueryStr,
+ };
+}
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/continue_button.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/continue_button.tsx
new file mode 100644
index 00000000000000..6e95a0a246573b
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/continue_button.tsx
@@ -0,0 +1,34 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC } from 'react';
+import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
+const continueButtonText = i18n.translate(
+ 'xpack.ml.dataframe.analytics.creation.continueButtonText',
+ {
+ defaultMessage: 'Continue',
+ }
+);
+
+export const ContinueButton: FC<{ isDisabled: boolean; onClick: any }> = ({
+ isDisabled,
+ onClick,
+}) => (
+
+
+
+ {continueButtonText}
+
+
+
+);
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/create_step.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/create_step.tsx
new file mode 100644
index 00000000000000..2dda5f5d819b78
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/create_step.tsx
@@ -0,0 +1,95 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC, Fragment, useState } from 'react';
+import {
+ EuiButton,
+ EuiCheckbox,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiFormRow,
+ EuiSpacer,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
+import { CreateAnalyticsFormProps } from '../../../analytics_management/hooks/use_create_analytics_form';
+import { Messages } from '../../../analytics_management/components/create_analytics_form/messages';
+import { ANALYTICS_STEPS } from '../../page';
+import { BackToListPanel } from '../back_to_list_panel';
+
+interface Props extends CreateAnalyticsFormProps {
+ step: ANALYTICS_STEPS;
+}
+
+export const CreateStep: FC = ({ actions, state, step }) => {
+ const { createAnalyticsJob, startAnalyticsJob } = actions;
+ const {
+ isAdvancedEditorValidJson,
+ isJobCreated,
+ isJobStarted,
+ isModalButtonDisabled,
+ isValid,
+ requestMessages,
+ } = state;
+
+ const [checked, setChecked] = useState(true);
+
+ if (step !== ANALYTICS_STEPS.CREATE) return null;
+
+ const handleCreation = async () => {
+ await createAnalyticsJob();
+
+ if (checked) {
+ startAnalyticsJob();
+ }
+ };
+
+ return (
+
+ {!isJobCreated && !isJobStarted && (
+
+
+
+ setChecked(e.target.checked)}
+ />
+
+
+
+
+ {i18n.translate('xpack.ml.dataframe.analytics.create.wizardCreateButton', {
+ defaultMessage: 'Create',
+ })}
+
+
+
+ )}
+
+
+ {isJobCreated === true && }
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/index.ts
new file mode 100644
index 00000000000000..01c8e4bff934dc
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { CreateStep } from './create_step';
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step.tsx
new file mode 100644
index 00000000000000..a40813ed2fc3ec
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step.tsx
@@ -0,0 +1,32 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC } from 'react';
+import { EuiForm } from '@elastic/eui';
+
+import { CreateAnalyticsStepProps } from '../../../analytics_management/hooks/use_create_analytics_form';
+import { DetailsStepDetails } from './details_step_details';
+import { DetailsStepForm } from './details_step_form';
+import { ANALYTICS_STEPS } from '../../page';
+
+export const DetailsStep: FC = ({
+ actions,
+ state,
+ setCurrentStep,
+ step,
+ stepActivated,
+}) => {
+ return (
+
+ {step === ANALYTICS_STEPS.DETAILS && (
+
+ )}
+ {step !== ANALYTICS_STEPS.DETAILS && stepActivated === true && (
+
+ )}
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_details.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_details.tsx
new file mode 100644
index 00000000000000..a4d86b48006e8a
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_details.tsx
@@ -0,0 +1,87 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC, Fragment } from 'react';
+import { i18n } from '@kbn/i18n';
+import {
+ EuiButtonEmpty,
+ EuiDescriptionList,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiSpacer,
+} from '@elastic/eui';
+import { State } from '../../../analytics_management/hooks/use_create_analytics_form/state';
+import { ANALYTICS_STEPS } from '../../page';
+
+export interface ListItems {
+ title: string;
+ description: string | JSX.Element;
+}
+
+export const DetailsStepDetails: FC<{ setCurrentStep: any; state: State }> = ({
+ setCurrentStep,
+ state,
+}) => {
+ const { form, isJobCreated } = state;
+ const { description, jobId, destinationIndex } = form;
+
+ const detailsFirstCol: ListItems[] = [
+ {
+ title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.jobId', {
+ defaultMessage: 'Job ID',
+ }),
+ description: jobId,
+ },
+ ];
+
+ const detailsSecondCol: ListItems[] = [
+ {
+ title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.jobDescription', {
+ defaultMessage: 'Job description',
+ }),
+ description,
+ },
+ ];
+
+ const detailsThirdCol: ListItems[] = [
+ {
+ title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.destIndex', {
+ defaultMessage: 'Destination index',
+ }),
+ description: destinationIndex || '',
+ },
+ ];
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {!isJobCreated && (
+ {
+ setCurrentStep(ANALYTICS_STEPS.DETAILS);
+ }}
+ >
+ {i18n.translate('xpack.ml.dataframe.analytics.create.detailsDetails.editButtonText', {
+ defaultMessage: 'Edit',
+ })}
+
+ )}
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_form.tsx
new file mode 100644
index 00000000000000..67f8472e7ad146
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_form.tsx
@@ -0,0 +1,221 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC, Fragment, useRef } from 'react';
+import { EuiFieldText, EuiFormRow, EuiLink, EuiSpacer, EuiSwitch, EuiTextArea } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
+import { useMlKibana } from '../../../../../contexts/kibana';
+import { CreateAnalyticsStepProps } from '../../../analytics_management/hooks/use_create_analytics_form';
+import { JOB_ID_MAX_LENGTH } from '../../../../../../../common/constants/validation';
+import { ContinueButton } from '../continue_button';
+import { ANALYTICS_STEPS } from '../../page';
+
+export const DetailsStepForm: FC = ({
+ actions,
+ state,
+ setCurrentStep,
+}) => {
+ const {
+ services: { docLinks },
+ } = useMlKibana();
+ const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks;
+
+ const { setFormState } = actions;
+ const { form, isJobCreated } = state;
+ const {
+ createIndexPattern,
+ description,
+ destinationIndex,
+ destinationIndexNameEmpty,
+ destinationIndexNameExists,
+ destinationIndexNameValid,
+ destinationIndexPatternTitleExists,
+ jobId,
+ jobIdEmpty,
+ jobIdExists,
+ jobIdInvalidMaxLength,
+ jobIdValid,
+ } = form;
+ const forceInput = useRef(null);
+
+ const isStepInvalid =
+ jobIdEmpty === true ||
+ jobIdExists === true ||
+ jobIdValid === false ||
+ destinationIndexNameEmpty === true ||
+ destinationIndexNameValid === false ||
+ (destinationIndexPatternTitleExists === true && createIndexPattern === true);
+
+ return (
+
+
+ {
+ if (input) {
+ forceInput.current = input;
+ }
+ }}
+ disabled={isJobCreated}
+ placeholder={i18n.translate('xpack.ml.dataframe.analytics.create.jobIdPlaceholder', {
+ defaultMessage: 'Job ID',
+ })}
+ value={jobId}
+ onChange={(e) => setFormState({ jobId: e.target.value })}
+ aria-label={i18n.translate('xpack.ml.dataframe.analytics.create.jobIdInputAriaLabel', {
+ defaultMessage: 'Choose a unique analytics job ID.',
+ })}
+ isInvalid={(!jobIdEmpty && !jobIdValid) || jobIdExists || jobIdEmpty}
+ data-test-subj="mlAnalyticsCreateJobFlyoutJobIdInput"
+ />
+
+
+ {
+ const value = e.target.value;
+ setFormState({ description: value });
+ }}
+ data-test-subj="mlDFAnalyticsJobCreationJobDescription"
+ />
+
+
+ {i18n.translate('xpack.ml.dataframe.analytics.create.destinationIndexInvalidError', {
+ defaultMessage: 'Invalid destination index name.',
+ })}
+
+
+ {i18n.translate(
+ 'xpack.ml.dataframe.stepDetailsForm.destinationIndexInvalidErrorLink',
+ {
+ defaultMessage: 'Learn more about index name limitations.',
+ }
+ )}
+
+ ,
+ ]
+ }
+ >
+ setFormState({ destinationIndex: e.target.value })}
+ aria-label={i18n.translate(
+ 'xpack.ml.dataframe.analytics.create.destinationIndexInputAriaLabel',
+ {
+ defaultMessage: 'Choose a unique destination index name.',
+ }
+ )}
+ isInvalid={!destinationIndexNameEmpty && !destinationIndexNameValid}
+ data-test-subj="mlAnalyticsCreateJobFlyoutDestinationIndexInput"
+ />
+
+
+ setFormState({ createIndexPattern: !createIndexPattern })}
+ data-test-subj="mlAnalyticsCreateJobWizardCreateIndexPatternSwitch"
+ />
+
+
+ {
+ setCurrentStep(ANALYTICS_STEPS.CREATE);
+ }}
+ />
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/index.ts
new file mode 100644
index 00000000000000..6cadd87d97e272
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { DetailsStep } from './details_step';
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/index.ts
new file mode 100644
index 00000000000000..efd7c0634bed44
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/index.ts
@@ -0,0 +1,10 @@
+/*
+ * 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 { ConfigurationStep } from './configuration_step/index';
+export { AdvancedStep } from './advanced_step/index';
+export { DetailsStep } from './details_step/index';
+export { CreateStep } from './create_step/index';
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/index.ts
new file mode 100644
index 00000000000000..5199fa1b6e4c6d
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { useIndexData } from './use_index_data';
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts
new file mode 100644
index 00000000000000..e8f25584201e37
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts
@@ -0,0 +1,103 @@
+/*
+ * 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 { useEffect } from 'react';
+
+import { IndexPattern } from '../../../../../../../../../src/plugins/data/public';
+import {
+ getDataGridSchemaFromKibanaFieldType,
+ getFieldsFromKibanaIndexPattern,
+ useDataGrid,
+ useRenderCellValue,
+ EsSorting,
+ SearchResponse7,
+ UseIndexDataReturnType,
+} from '../../../../components/data_grid';
+import { getErrorMessage } from '../../../../../../common/util/errors';
+import { INDEX_STATUS } from '../../../common/analytics';
+import { ml } from '../../../../services/ml_api_service';
+
+type IndexSearchResponse = SearchResponse7;
+
+export const useIndexData = (indexPattern: IndexPattern, query: any): UseIndexDataReturnType => {
+ const indexPatternFields = getFieldsFromKibanaIndexPattern(indexPattern);
+
+ // EuiDataGrid State
+ const columns = [
+ ...indexPatternFields.map((id) => {
+ const field = indexPattern.fields.getByName(id);
+ const schema = getDataGridSchemaFromKibanaFieldType(field);
+ return { id, schema };
+ }),
+ ];
+
+ const dataGrid = useDataGrid(columns);
+
+ const {
+ pagination,
+ resetPagination,
+ setErrorMessage,
+ setRowCount,
+ setStatus,
+ setTableItems,
+ sortingColumns,
+ tableItems,
+ } = dataGrid;
+
+ useEffect(() => {
+ resetPagination();
+ // custom comparison
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [JSON.stringify(query)]);
+
+ const getIndexData = async function () {
+ setErrorMessage('');
+ setStatus(INDEX_STATUS.LOADING);
+
+ const sort: EsSorting = sortingColumns.reduce((s, column) => {
+ s[column.id] = { order: column.direction };
+ return s;
+ }, {} as EsSorting);
+
+ const esSearchRequest = {
+ index: indexPattern.title,
+ body: {
+ // Instead of using the default query (`*`), fall back to a more efficient `match_all` query.
+ query, // isDefaultQuery(query) ? matchAllQuery : query,
+ from: pagination.pageIndex * pagination.pageSize,
+ size: pagination.pageSize,
+ ...(Object.keys(sort).length > 0 ? { sort } : {}),
+ },
+ };
+
+ try {
+ const resp: IndexSearchResponse = await ml.esSearch(esSearchRequest);
+
+ const docs = resp.hits.hits.map((d) => d._source);
+
+ setRowCount(resp.hits.total.value);
+ setTableItems(docs);
+ setStatus(INDEX_STATUS.LOADED);
+ } catch (e) {
+ setErrorMessage(getErrorMessage(e));
+ setStatus(INDEX_STATUS.ERROR);
+ }
+ };
+
+ useEffect(() => {
+ getIndexData();
+ // custom comparison
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [indexPattern.title, JSON.stringify([query, pagination, sortingColumns])]);
+
+ const renderCellValue = useRenderCellValue(indexPattern, pagination, tableItems);
+
+ return {
+ ...dataGrid,
+ columns,
+ renderCellValue,
+ };
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/index.ts
new file mode 100644
index 00000000000000..7e2d651439ae30
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { Page } from './page';
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx
new file mode 100644
index 00000000000000..def862b859162c
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx
@@ -0,0 +1,191 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC, useEffect, useState } from 'react';
+import {
+ EuiButtonEmpty,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiFormRow,
+ EuiPage,
+ EuiPageBody,
+ EuiPageContent,
+ EuiSpacer,
+ EuiSteps,
+ EuiStepStatus,
+ EuiText,
+ EuiTitle,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { useMlContext } from '../../../contexts/ml';
+import { newJobCapsService } from '../../../services/new_job_capabilities_service';
+import { useCreateAnalyticsForm } from '../analytics_management/hooks/use_create_analytics_form';
+import { CreateAnalyticsAdvancedEditor } from '../analytics_management/components/create_analytics_advanced_editor';
+import { AdvancedStep, ConfigurationStep, CreateStep, DetailsStep } from './components';
+
+export enum ANALYTICS_STEPS {
+ CONFIGURATION,
+ ADVANCED,
+ DETAILS,
+ CREATE,
+}
+
+export const Page: FC = () => {
+ const [currentStep, setCurrentStep] = useState(ANALYTICS_STEPS.CONFIGURATION);
+ const [activatedSteps, setActivatedSteps] = useState([true, false, false, false]);
+
+ const mlContext = useMlContext();
+ const { currentIndexPattern } = mlContext;
+
+ const createAnalyticsForm = useCreateAnalyticsForm();
+ const { isAdvancedEditorEnabled } = createAnalyticsForm.state;
+ const { jobType } = createAnalyticsForm.state.form;
+ const { switchToAdvancedEditor } = createAnalyticsForm.actions;
+
+ useEffect(() => {
+ if (activatedSteps[currentStep] === false) {
+ activatedSteps.splice(currentStep, 1, true);
+ setActivatedSteps(activatedSteps);
+ }
+ }, [currentStep]);
+
+ useEffect(() => {
+ if (currentIndexPattern) {
+ (async function () {
+ await newJobCapsService.initializeFromIndexPattern(currentIndexPattern, false, false);
+ })();
+ }
+ }, []);
+
+ const analyticsWizardSteps = [
+ {
+ title: i18n.translate('xpack.ml.dataframe.analytics.creation.configurationStepTitle', {
+ defaultMessage: 'Configuration',
+ }),
+ children: (
+
+ ),
+ status:
+ currentStep >= ANALYTICS_STEPS.CONFIGURATION ? undefined : ('incomplete' as EuiStepStatus),
+ },
+ {
+ title: i18n.translate('xpack.ml.dataframe.analytics.creation.advancedStepTitle', {
+ defaultMessage: 'Additional options',
+ }),
+ children: (
+
+ ),
+ status: currentStep >= ANALYTICS_STEPS.ADVANCED ? undefined : ('incomplete' as EuiStepStatus),
+ 'data-test-subj': 'mlAnalyticsCreateJobWizardAdvancedStep',
+ },
+ {
+ title: i18n.translate('xpack.ml.dataframe.analytics.creation.detailsStepTitle', {
+ defaultMessage: 'Job details',
+ }),
+ children: (
+
+ ),
+ status: currentStep >= ANALYTICS_STEPS.DETAILS ? undefined : ('incomplete' as EuiStepStatus),
+ 'data-test-subj': 'mlAnalyticsCreateJobWizardDetailsStep',
+ },
+ {
+ title: i18n.translate('xpack.ml.dataframe.analytics.creation.createStepTitle', {
+ defaultMessage: 'Create',
+ }),
+ children: ,
+ status: currentStep >= ANALYTICS_STEPS.CREATE ? undefined : ('incomplete' as EuiStepStatus),
+ 'data-test-subj': 'mlAnalyticsCreateJobWizardCreateStep',
+ },
+ ];
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {isAdvancedEditorEnabled === false && (
+
+
+
+
+ {i18n.translate(
+ 'xpack.ml.dataframe.analytics.create.switchToJsonEditorSwitch',
+ {
+ defaultMessage: 'Switch to json editor',
+ }
+ )}
+
+
+
+
+ )}
+
+
+ {isAdvancedEditorEnabled === true && (
+
+ )}
+ {isAdvancedEditorEnabled === false && (
+
+ )}
+
+
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx
index f95e6a93058ba2..8c158c1ca14a0c 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { Dispatch, FC, SetStateAction, useState } from 'react';
+import React, { Dispatch, FC, SetStateAction, useEffect, useState } from 'react';
import { EuiCode, EuiInputPopover } from '@elastic/eui';
@@ -30,11 +30,15 @@ interface ErrorMessage {
interface ExplorationQueryBarProps {
indexPattern: IIndexPattern;
setSearchQuery: Dispatch>;
+ includeQueryString?: boolean;
+ defaultQueryString?: string;
}
export const ExplorationQueryBar: FC = ({
indexPattern,
setSearchQuery,
+ includeQueryString = false,
+ defaultQueryString,
}) => {
// The internal state of the input query bar updated on every key stroke.
const [searchInput, setSearchInput] = useState({
@@ -44,20 +48,34 @@ export const ExplorationQueryBar: FC = ({
const [errorMessage, setErrorMessage] = useState(undefined);
+ useEffect(() => {
+ if (defaultQueryString !== undefined) {
+ setSearchInput({ query: defaultQueryString, language: SEARCH_QUERY_LANGUAGE.KUERY });
+ }
+ }, []);
+
const searchChangeHandler = (query: Query) => setSearchInput(query);
const searchSubmitHandler = (query: Query) => {
try {
switch (query.language) {
case SEARCH_QUERY_LANGUAGE.KUERY:
+ const convertedKQuery = esKuery.toElasticsearchQuery(
+ esKuery.fromKueryExpression(query.query as string),
+ indexPattern
+ );
setSearchQuery(
- esKuery.toElasticsearchQuery(
- esKuery.fromKueryExpression(query.query as string),
- indexPattern
- )
+ includeQueryString
+ ? { queryString: query.query, query: convertedKQuery }
+ : convertedKQuery
);
return;
case SEARCH_QUERY_LANGUAGE.LUCENE:
- setSearchQuery(esQuery.luceneStringToDsl(query.query as string));
+ const convertedLQuery = esQuery.luceneStringToDsl(query.query as string);
+ setSearchQuery(
+ includeQueryString
+ ? { queryString: query.query, query: convertedLQuery }
+ : convertedLQuery
+ );
return;
}
} catch (e) {
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx
index 72514c91ff58b3..295a3988e1b58e 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx
@@ -7,7 +7,7 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
-import { DeepReadonly } from '../../../../../../../common/types/common';
+// import { DeepReadonly } from '../../../../../../../common/types/common';
import {
checkPermission,
@@ -21,7 +21,7 @@ import {
isClassificationAnalysis,
} from '../../../../common/analytics';
import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form';
-import { CloneAction } from './action_clone';
+// import { CloneAction } from './action_clone';
import { getResultsUrl, isDataFrameAnalyticsRunning, DataFrameAnalyticsListRow } from './common';
import { stopAnalytics } from '../../services/analytics_service';
@@ -106,10 +106,10 @@ export const getActions = (createAnalyticsForm: CreateAnalyticsFormProps) => {
return ;
},
},
- {
- render: (item: DeepReadonly) => {
- return ;
- },
- },
+ // {
+ // render: (item: DeepReadonly) => {
+ // return ;
+ // },
+ // },
];
};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx
index 4a99c042b108b7..bb012a21908597 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx
@@ -53,6 +53,7 @@ import { CreateAnalyticsButton } from '../create_analytics_button';
import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form';
import { CreateAnalyticsFlyoutWrapper } from '../create_analytics_flyout_wrapper';
import { getSelectedJobIdFromUrl } from '../../../../../jobs/jobs_list/components/utils';
+import { SourceSelection } from '../source_selection';
function getItemIdToExpandedRowMap(
itemIds: DataFrameAnalyticsId[],
@@ -90,6 +91,7 @@ export const DataFrameAnalyticsList: FC = ({
createAnalyticsForm,
}) => {
const [isInitialized, setIsInitialized] = useState(false);
+ const [isSourceIndexModalVisible, setIsSourceIndexModalVisible] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [filterActive, setFilterActive] = useState(false);
@@ -271,7 +273,7 @@ export const DataFrameAnalyticsList: FC = ({
!isManagementTable && createAnalyticsForm
? [
setIsSourceIndexModalVisible(true)}
isDisabled={disabled}
data-test-subj="mlAnalyticsCreateFirstButton"
>
@@ -287,6 +289,9 @@ export const DataFrameAnalyticsList: FC = ({
{!isManagementTable && createAnalyticsForm && (
)}
+ {isSourceIndexModalVisible === true && (
+ setIsSourceIndexModalVisible(false)} />
+ )}
);
}
@@ -402,7 +407,10 @@ export const DataFrameAnalyticsList: FC = ({
{!isManagementTable && createAnalyticsForm && (
-
+
)}
@@ -435,6 +443,9 @@ export const DataFrameAnalyticsList: FC = ({
{!isManagementTable && createAnalyticsForm?.state.isModalVisible && (
)}
+ {isSourceIndexModalVisible === true && (
+ setIsSourceIndexModalVisible(false)} />
+ )}
);
};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx
index 48eb95948bb12b..17b905cab135bb 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx
@@ -23,11 +23,14 @@ import { XJsonMode } from '../../../../../../../shared_imports';
const xJsonMode = new XJsonMode();
import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form';
+import { CreateStep } from '../../../analytics_creation/components/create_step';
+import { ANALYTICS_STEPS } from '../../../analytics_creation/page';
-export const CreateAnalyticsAdvancedEditor: FC = ({ actions, state }) => {
+export const CreateAnalyticsAdvancedEditor: FC = (props) => {
+ const { actions, state } = props;
const { setAdvancedEditorRawString, setFormState } = actions;
- const { advancedEditorMessages, advancedEditorRawString, isJobCreated, requestMessages } = state;
+ const { advancedEditorMessages, advancedEditorRawString, isJobCreated } = state;
const {
createIndexPattern,
@@ -56,120 +59,105 @@ export const CreateAnalyticsAdvancedEditor: FC = ({ ac
return (
- {requestMessages.map((requestMessage, i) => (
+
+ {
+ if (input) {
+ forceInput.current = input;
+ }
+ }}
+ disabled={isJobCreated}
+ placeholder="analytics job ID"
+ value={jobId}
+ onChange={(e) => setFormState({ jobId: e.target.value })}
+ aria-label={i18n.translate(
+ 'xpack.ml.dataframe.analytics.create.advancedEditor.jobIdInputAriaLabel',
+ {
+ defaultMessage: 'Choose a unique analytics job ID.',
+ }
+ )}
+ isInvalid={(!jobIdEmpty && !jobIdValid) || jobIdExists}
+ />
+
+
+
+
+
+
+ {advancedEditorMessages.map((advancedEditorMessage, i) => (
- {requestMessage.error !== undefined ? {requestMessage.error}
: null}
+ {advancedEditorMessage.message !== '' && advancedEditorMessage.error !== undefined ? (
+ {advancedEditorMessage.error}
+ ) : null}
-
+
))}
{!isJobCreated && (
-
- {
- if (input) {
- forceInput.current = input;
- }
- }}
- disabled={isJobCreated}
- placeholder="analytics job ID"
- value={jobId}
- onChange={(e) => setFormState({ jobId: e.target.value })}
- aria-label={i18n.translate(
- 'xpack.ml.dataframe.analytics.create.advancedEditor.jobIdInputAriaLabel',
- {
- defaultMessage: 'Choose a unique analytics job ID.',
- }
- )}
- isInvalid={(!jobIdEmpty && !jobIdValid) || jobIdExists}
- />
-
-
-
-
-
-
- {advancedEditorMessages.map((advancedEditorMessage, i) => (
-
-
- {advancedEditorMessage.message !== '' &&
- advancedEditorMessage.error !== undefined ? (
- {advancedEditorMessage.error}
- ) : null}
-
-
-
- ))}
= ({ ac
)}
+
+
);
};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/_index.scss b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/_index.scss
new file mode 100644
index 00000000000000..14ff9de7ded4d4
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/_index.scss
@@ -0,0 +1,4 @@
+.dataFrameAnalyticsCreateSearchDialog {
+ width: $euiSizeL * 30;
+ min-height: $euiSizeL * 25;
+}
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.test.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.test.tsx
index 10565fb4d7a934..8e9b09ef41fa83 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.test.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.test.tsx
@@ -35,7 +35,9 @@ describe('Data Frame Analytics: ', () => {
test('Minimal initialization', () => {
const { getLastHookValue } = getMountedHook();
const props = getLastHookValue();
- const wrapper = mount();
+ const wrapper = mount(
+
+ );
expect(wrapper.find('EuiButton').text()).toBe('Create analytics job');
});
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.tsx
index 952bd48468b0af..595a1cf73e189e 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.tsx
@@ -10,15 +10,26 @@ import { i18n } from '@kbn/i18n';
import { createPermissionFailureMessage } from '../../../../../capabilities/check_capabilities';
import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form';
-export const CreateAnalyticsButton: FC = (props) => {
- const { disabled } = props.state;
- const { openModal } = props.actions;
+interface Props extends CreateAnalyticsFormProps {
+ setIsSourceIndexModalVisible: React.Dispatch>;
+}
+
+export const CreateAnalyticsButton: FC = ({
+ state,
+ actions,
+ setIsSourceIndexModalVisible,
+}) => {
+ const { disabled } = state;
+
+ const handleClick = () => {
+ setIsSourceIndexModalVisible(true);
+ };
const button = (
= ({ messages }) => {
{messages.map((requestMessage, i) => (
void;
+}
+
+export const SourceSelection: FC = ({ onClose }) => {
+ const { uiSettings, savedObjects } = useMlKibana().services;
+
+ const onSearchSelected = (id: string, type: string) => {
+ window.location.href = `ml#/data_frame_analytics/new_job?${
+ type === 'index-pattern' ? 'index' : 'savedSearchId'
+ }=${encodeURIComponent(id)}`;
+ };
+
+ return (
+ <>
+
+
+
+
+ {' '}
+ /{' '}
+
+
+
+
+ 'search',
+ name: i18n.translate(
+ 'xpack.ml.dataFrame.analytics.create.searchSelection.savedObjectType.search',
+ {
+ defaultMessage: 'Saved search',
+ }
+ ),
+ },
+ {
+ type: 'index-pattern',
+ getIconForSavedObject: () => 'indexPatternApp',
+ name: i18n.translate(
+ 'xpack.ml.dataFrame.analytics.create.searchSelection.savedObjectType.indexPattern',
+ {
+ defaultMessage: 'Index pattern',
+ }
+ ),
+ },
+ ]}
+ fixedPageSize={fixedPageSize}
+ uiSettings={uiSettings}
+ savedObjects={savedObjects}
+ />
+
+
+
+ >
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/actions.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/actions.ts
index 66e4103f5bb41d..c42e03b584a568 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/actions.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/actions.ts
@@ -72,6 +72,7 @@ export interface ActionDispatchers {
closeModal: () => void;
createAnalyticsJob: () => void;
openModal: () => Promise;
+ initiateWizard: () => Promise;
resetAdvancedEditorMessages: () => void;
setAdvancedEditorRawString: (payload: State['advancedEditorRawString']) => void;
setFormState: (payload: Partial) => void;
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/index.ts
index e5e56c084f7b99..8ec415bc2abb46 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/index.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/index.ts
@@ -5,4 +5,9 @@
*/
export { DEFAULT_NUM_TOP_FEATURE_IMPORTANCE_VALUES } from './state';
-export { useCreateAnalyticsForm, CreateAnalyticsFormProps } from './use_create_analytics_form';
+export {
+ AnalyticsCreationStep,
+ useCreateAnalyticsForm,
+ CreateAnalyticsFormProps,
+ CreateAnalyticsStepProps,
+} from './use_create_analytics_form';
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts
index 4ff7deab34f26a..387ce89ee41204 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts
@@ -11,6 +11,7 @@ import { mlNodesAvailable } from '../../../../../ml_nodes_check';
import { newJobCapsService } from '../../../../../services/new_job_capabilities_service';
import {
+ FieldSelectionItem,
isClassificationAnalysis,
isRegressionAnalysis,
DataFrameAnalyticsId,
@@ -26,7 +27,8 @@ export enum DEFAULT_MODEL_MEMORY_LIMIT {
classification = '100mb',
}
-export const DEFAULT_NUM_TOP_FEATURE_IMPORTANCE_VALUES = 2;
+export const DEFAULT_NUM_TOP_FEATURE_IMPORTANCE_VALUES = 0;
+export const UNSET_CONFIG_ITEM = '--';
export type EsIndexName = string;
export type DependentVariable = string;
@@ -47,6 +49,7 @@ export interface State {
advancedEditorMessages: FormMessage[];
advancedEditorRawString: string;
form: {
+ computeFeatureInfluence: string;
createIndexPattern: boolean;
dependentVariable: DependentVariable;
dependentVariableFetchFail: boolean;
@@ -57,31 +60,47 @@ export interface State {
destinationIndexNameEmpty: boolean;
destinationIndexNameValid: boolean;
destinationIndexPatternTitleExists: boolean;
+ eta: undefined | number;
excludes: string[];
+ excludesTableItems: FieldSelectionItem[];
excludesOptions: EuiComboBoxOptionOption[];
+ featureBagFraction: undefined | number;
+ featureInfluenceThreshold: undefined | number;
fieldOptionsFetchFail: boolean;
+ gamma: undefined | number;
jobId: DataFrameAnalyticsId;
jobIdExists: boolean;
jobIdEmpty: boolean;
jobIdInvalidMaxLength: boolean;
jobIdValid: boolean;
jobType: AnalyticsJobType;
+ jobConfigQuery: any;
+ jobConfigQueryString: string | undefined;
+ lambda: number | undefined;
loadingDepVarOptions: boolean;
loadingFieldOptions: boolean;
maxDistinctValuesError: string | undefined;
+ maxTrees: undefined | number;
+ method: undefined | string;
modelMemoryLimit: string | undefined;
modelMemoryLimitUnitValid: boolean;
modelMemoryLimitValidationResult: any;
+ nNeighbors: undefined | number;
numTopFeatureImportanceValues: number | undefined;
numTopFeatureImportanceValuesValid: boolean;
+ numTopClasses: number;
+ outlierFraction: undefined | number;
+ predictionFieldName: undefined | string;
previousJobType: null | AnalyticsJobType;
previousSourceIndex: EsIndexName | undefined;
requiredFieldsError: string | undefined;
+ randomizeSeed: undefined | number;
sourceIndex: EsIndexName;
sourceIndexNameEmpty: boolean;
sourceIndexNameValid: boolean;
sourceIndexContainsNumericalFields: boolean;
sourceIndexFieldsCheckFailed: boolean;
+ standardizationEnabled: undefined | string;
trainingPercent: number;
};
disabled: boolean;
@@ -105,7 +124,8 @@ export const getInitialState = (): State => ({
advancedEditorMessages: [],
advancedEditorRawString: '',
form: {
- createIndexPattern: false,
+ computeFeatureInfluence: 'true',
+ createIndexPattern: true,
dependentVariable: '',
dependentVariableFetchFail: false,
dependentVariableOptions: [],
@@ -115,8 +135,13 @@ export const getInitialState = (): State => ({
destinationIndexNameEmpty: true,
destinationIndexNameValid: false,
destinationIndexPatternTitleExists: false,
+ eta: undefined,
excludes: [],
+ featureBagFraction: undefined,
+ featureInfluenceThreshold: undefined,
fieldOptionsFetchFail: false,
+ gamma: undefined,
+ excludesTableItems: [],
excludesOptions: [],
jobId: '',
jobIdExists: false,
@@ -124,22 +149,33 @@ export const getInitialState = (): State => ({
jobIdInvalidMaxLength: false,
jobIdValid: false,
jobType: undefined,
+ jobConfigQuery: { match_all: {} },
+ jobConfigQueryString: undefined,
+ lambda: undefined,
loadingDepVarOptions: false,
loadingFieldOptions: false,
maxDistinctValuesError: undefined,
+ maxTrees: undefined,
+ method: undefined,
modelMemoryLimit: undefined,
modelMemoryLimitUnitValid: true,
modelMemoryLimitValidationResult: null,
+ nNeighbors: undefined,
numTopFeatureImportanceValues: DEFAULT_NUM_TOP_FEATURE_IMPORTANCE_VALUES,
numTopFeatureImportanceValuesValid: true,
+ numTopClasses: 2,
+ outlierFraction: undefined,
+ predictionFieldName: undefined,
previousJobType: null,
previousSourceIndex: undefined,
requiredFieldsError: undefined,
+ randomizeSeed: undefined,
sourceIndex: '',
sourceIndexNameEmpty: true,
sourceIndexNameValid: false,
sourceIndexContainsNumericalFields: true,
sourceIndexFieldsCheckFailed: false,
+ standardizationEnabled: 'true',
trainingPercent: 80,
},
jobConfig: {},
@@ -222,6 +258,7 @@ export const getJobConfigFromFormState = (
index: formState.sourceIndex.includes(',')
? formState.sourceIndex.split(',').map((d) => d.trim())
: formState.sourceIndex,
+ query: formState.jobConfigQuery,
},
dest: {
index: formState.destinationIndex,
@@ -239,15 +276,55 @@ export const getJobConfigFromFormState = (
formState.jobType === ANALYSIS_CONFIG_TYPE.REGRESSION ||
formState.jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION
) {
- jobConfig.analysis = {
- [formState.jobType]: {
- dependent_variable: formState.dependentVariable,
- num_top_feature_importance_values: formState.numTopFeatureImportanceValues,
- training_percent: formState.trainingPercent,
+ let analysis = {
+ dependent_variable: formState.dependentVariable,
+ num_top_feature_importance_values: formState.numTopFeatureImportanceValues,
+ training_percent: formState.trainingPercent,
+ };
+
+ analysis = Object.assign(
+ analysis,
+ formState.predictionFieldName && { prediction_field_name: formState.predictionFieldName },
+ formState.eta && { eta: formState.eta },
+ formState.featureBagFraction && {
+ feature_bag_fraction: formState.featureBagFraction,
},
+ formState.gamma && { gamma: formState.gamma },
+ formState.lambda && { lambda: formState.lambda },
+ formState.maxTrees && { max_trees: formState.maxTrees },
+ formState.randomizeSeed && { randomize_seed: formState.randomizeSeed }
+ );
+
+ jobConfig.analysis = {
+ [formState.jobType]: analysis,
};
}
+ if (
+ formState.jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION &&
+ jobConfig?.analysis?.classification !== undefined &&
+ formState.numTopClasses !== undefined
+ ) {
+ // @ts-ignore
+ jobConfig.analysis.classification.num_top_classes = formState.numTopClasses;
+ }
+
+ if (formState.jobType === ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION) {
+ const analysis = Object.assign(
+ {},
+ formState.method && { method: formState.method },
+ formState.nNeighbors && {
+ n_neighbors: formState.nNeighbors,
+ },
+ formState.outlierFraction && { outlier_fraction: formState.outlierFraction },
+ formState.standardizationEnabled && {
+ standardization_enabled: formState.standardizationEnabled,
+ }
+ );
+ // @ts-ignore
+ jobConfig.analysis.outlier_detection = analysis;
+ }
+
return jobConfig;
};
@@ -279,6 +356,11 @@ export function getCloneFormStateFromJobConfig(
resultState.dependentVariable = analysisConfig.dependent_variable;
resultState.numTopFeatureImportanceValues = analysisConfig.num_top_feature_importance_values;
resultState.trainingPercent = analysisConfig.training_percent;
+
+ if (isClassificationAnalysis(analyticsJobConfig.analysis)) {
+ // @ts-ignore
+ resultState.numTopClasses = analysisConfig.num_top_classes;
+ }
}
return resultState;
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts
index 1ec767d014a2e3..c4cbe149f88bc1 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts
@@ -36,11 +36,24 @@ import {
getCloneFormStateFromJobConfig,
} from './state';
+import { ANALYTICS_STEPS } from '../../../analytics_creation/page';
+
+export interface AnalyticsCreationStep {
+ number: ANALYTICS_STEPS;
+ completed: boolean;
+}
+
export interface CreateAnalyticsFormProps {
actions: ActionDispatchers;
state: State;
}
+export interface CreateAnalyticsStepProps extends CreateAnalyticsFormProps {
+ setCurrentStep: React.Dispatch>;
+ step?: ANALYTICS_STEPS;
+ stepActivated?: boolean;
+}
+
export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => {
const mlContext = useMlContext();
const [state, dispatch] = useReducer(reducer, getInitialState());
@@ -261,8 +274,12 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => {
dispatch({ type: ACTION.OPEN_MODAL });
};
+ const initiateWizard = async () => {
+ await mlContext.indexPatterns.clearCache();
+ await prepareFormValidation();
+ };
+
const startAnalyticsJob = async () => {
- setIsModalButtonDisabled(true);
try {
const response = await ml.dataFrameAnalytics.startDataFrameAnalytics(jobId);
if (response.acknowledged !== true) {
@@ -278,7 +295,6 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => {
),
});
setIsJobStarted(true);
- setIsModalButtonDisabled(false);
refresh();
} catch (e) {
addRequestMessage({
@@ -290,7 +306,6 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => {
}
),
});
- setIsModalButtonDisabled(false);
}
};
@@ -331,6 +346,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => {
closeModal,
createAnalyticsJob,
openModal,
+ initiateWizard,
resetAdvancedEditorMessages,
setAdvancedEditorRawString,
setFormState,
diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx
index d68c0342ac857d..97b4043c9fd644 100644
--- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx
+++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx
@@ -27,6 +27,7 @@ import {
Query,
esQuery,
esKuery,
+ UI_SETTINGS,
} from '../../../../../../../src/plugins/data/public';
import { SavedSearchSavedObject } from '../../../../common/types/kibana';
import { NavigationMenu } from '../../components/navigation_menu';
@@ -254,7 +255,7 @@ export const Page: FC = () => {
qry = esKuery.toElasticsearchQuery(ast, currentIndexPattern);
} else {
qry = esQuery.luceneStringToDsl(qryString);
- esQuery.decorateQuery(qry, kibanaConfig.get('query:queryString:options'));
+ esQuery.decorateQuery(qry, kibanaConfig.get(UI_SETTINGS.QUERY_STRING_OPTIONS));
}
return {
diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_creation.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_creation.tsx
new file mode 100644
index 00000000000000..68af9a2a49cab7
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_creation.tsx
@@ -0,0 +1,41 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC } from 'react';
+import { i18n } from '@kbn/i18n';
+import { parse } from 'query-string';
+import { MlRoute, PageLoader, PageProps } from '../../router';
+import { useResolver } from '../../use_resolver';
+import { basicResolvers } from '../../resolvers';
+import { Page } from '../../../data_frame_analytics/pages/analytics_creation';
+import { ML_BREADCRUMB } from '../../breadcrumbs';
+
+const breadcrumbs = [
+ ML_BREADCRUMB,
+ {
+ text: i18n.translate('xpack.ml.dataFrameAnalyticsBreadcrumbs.dataFrameManagementLabel', {
+ defaultMessage: 'Data Frame Analytics',
+ }),
+ href: '#/data_frame_analytics',
+ },
+];
+
+export const analyticsJobsCreationRoute: MlRoute = {
+ path: '/data_frame_analytics/new_job',
+ render: (props, deps) => ,
+ breadcrumbs,
+};
+
+const PageWrapper: FC = ({ location, deps }) => {
+ const { index, savedSearchId }: Record = parse(location.search, { sort: false });
+ const { context } = useResolver(index, savedSearchId, deps.config, basicResolvers(deps));
+
+ return (
+
+
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/index.ts b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/index.ts
index 552c15a408b65b..9b6bcc25c8c7eb 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/index.ts
+++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/index.ts
@@ -6,3 +6,4 @@
export * from './analytics_jobs_list';
export * from './analytics_job_exploration';
+export * from './analytics_job_creation';
diff --git a/x-pack/plugins/ml/public/application/services/explorer_service.ts b/x-pack/plugins/ml/public/application/services/explorer_service.ts
index efcec8cf2b9549..717ed3ba64c378 100644
--- a/x-pack/plugins/ml/public/application/services/explorer_service.ts
+++ b/x-pack/plugins/ml/public/application/services/explorer_service.ts
@@ -6,7 +6,11 @@
import { IUiSettingsClient } from 'kibana/public';
import { i18n } from '@kbn/i18n';
-import { TimefilterContract, TimeRange } from '../../../../../../src/plugins/data/public';
+import {
+ TimefilterContract,
+ TimeRange,
+ UI_SETTINGS,
+} from '../../../../../../src/plugins/data/public';
import { getBoundsRoundedToInterval, TimeBuckets, TimeRangeBounds } from '../util/time_buckets';
import { ExplorerJob, OverallSwimlaneData, SwimlaneData } from '../explorer/explorer_utils';
import { VIEW_BY_JOB_LABEL } from '../explorer/explorer_constants';
@@ -25,8 +29,8 @@ export class ExplorerService {
private mlResultsService: MlResultsService
) {
this.timeBuckets = new TimeBuckets({
- 'histogram:maxBars': uiSettings.get('histogram:maxBars'),
- 'histogram:barTarget': uiSettings.get('histogram:barTarget'),
+ 'histogram:maxBars': uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS),
+ 'histogram:barTarget': uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET),
dateFormat: uiSettings.get('dateFormat'),
'dateFormat:scaled': uiSettings.get('dateFormat:scaled'),
});
diff --git a/x-pack/plugins/ml/public/application/util/time_buckets.d.ts b/x-pack/plugins/ml/public/application/util/time_buckets.d.ts
index 9c5d663a6e4ab1..91661ea3fd3fab 100644
--- a/x-pack/plugins/ml/public/application/util/time_buckets.d.ts
+++ b/x-pack/plugins/ml/public/application/util/time_buckets.d.ts
@@ -5,6 +5,7 @@
*/
import { Moment } from 'moment';
+import { UI_SETTINGS } from '../../../../../../src/plugins/data/public';
export interface TimeRangeBounds {
min?: Moment;
diff --git a/x-pack/plugins/ml/public/application/util/time_buckets.js b/x-pack/plugins/ml/public/application/util/time_buckets.js
index 2b23eca1ab5c0f..1915a4ce6516bc 100644
--- a/x-pack/plugins/ml/public/application/util/time_buckets.js
+++ b/x-pack/plugins/ml/public/application/util/time_buckets.js
@@ -11,7 +11,7 @@ import dateMath from '@elastic/datemath';
import { timeBucketsCalcAutoIntervalProvider } from './calc_auto_interval';
import { parseInterval } from '../../../common/util/parse_interval';
import { getFieldFormats, getUiSettings } from './dependency_cache';
-import { FIELD_FORMAT_IDS } from '../../../../../../src/plugins/data/public';
+import { FIELD_FORMAT_IDS, UI_SETTINGS } from '../../../../../../src/plugins/data/public';
const unitsDesc = dateMath.unitsDesc;
const largeMax = unitsDesc.indexOf('w'); // Multiple units of week or longer converted to days for ES intervals.
@@ -21,8 +21,8 @@ const calcAuto = timeBucketsCalcAutoIntervalProvider();
export function getTimeBucketsFromCache() {
const uiSettings = getUiSettings();
return new TimeBuckets({
- 'histogram:maxBars': uiSettings.get('histogram:maxBars'),
- 'histogram:barTarget': uiSettings.get('histogram:barTarget'),
+ [UI_SETTINGS.HISTOGRAM_MAX_BARS]: uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS),
+ [UI_SETTINGS.HISTOGRAM_BAR_TARGET]: uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET),
dateFormat: uiSettings.get('dateFormat'),
'dateFormat:scaled': uiSettings.get('dateFormat:scaled'),
});
@@ -35,8 +35,8 @@ export function getTimeBucketsFromCache() {
*/
export function TimeBuckets(timeBucketsConfig) {
this._timeBucketsConfig = timeBucketsConfig;
- this.barTarget = this._timeBucketsConfig['histogram:barTarget'];
- this.maxBars = this._timeBucketsConfig['histogram:maxBars'];
+ this.barTarget = this._timeBucketsConfig[UI_SETTINGS.HISTOGRAM_BAR_TARGET];
+ this.maxBars = this._timeBucketsConfig[UI_SETTINGS.HISTOGRAM_MAX_BARS];
}
/**
diff --git a/x-pack/plugins/ml/public/application/util/time_buckets.test.js b/x-pack/plugins/ml/public/application/util/time_buckets.test.js
index baecf7df906412..250c7255f5b99f 100644
--- a/x-pack/plugins/ml/public/application/util/time_buckets.test.js
+++ b/x-pack/plugins/ml/public/application/util/time_buckets.test.js
@@ -5,6 +5,7 @@
*/
import moment from 'moment';
+import { UI_SETTINGS } from '../../../../../../src/plugins/data/public';
import { TimeBuckets, getBoundsRoundedToInterval, calcEsInterval } from './time_buckets';
describe('ML - time buckets', () => {
@@ -13,8 +14,8 @@ describe('ML - time buckets', () => {
beforeEach(() => {
const timeBucketsConfig = {
- 'histogram:maxBars': 100,
- 'histogram:barTarget': 50,
+ [UI_SETTINGS.HISTOGRAM_MAX_BARS]: 100,
+ [UI_SETTINGS.HISTOGRAM_BAR_TARGET]: 50,
};
autoBuckets = new TimeBuckets(timeBucketsConfig);
diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/swimlane_input_resolver.ts b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/swimlane_input_resolver.ts
index e48ce9f9a1d92d..74a7b432de2673 100644
--- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/swimlane_input_resolver.ts
+++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/swimlane_input_resolver.ts
@@ -27,7 +27,7 @@ import { MlStartDependencies } from '../../plugin';
import { SWIMLANE_TYPE } from '../../application/explorer/explorer_constants';
import { Filter } from '../../../../../../src/plugins/data/common/es_query/filters';
import { Query } from '../../../../../../src/plugins/data/common/query';
-import { esKuery } from '../../../../../../src/plugins/data/public';
+import { esKuery, UI_SETTINGS } from '../../../../../../src/plugins/data/public';
import { ExplorerJob, OverallSwimlaneData } from '../../application/explorer/explorer_utils';
import { parseInterval } from '../../../common/util/parse_interval';
import { AnomalyDetectorService } from '../../application/services/anomaly_detector_service';
@@ -62,8 +62,8 @@ export function useSwimlaneInputResolver(
const timeBuckets = useMemo(() => {
return new TimeBuckets({
- 'histogram:maxBars': uiSettings.get('histogram:maxBars'),
- 'histogram:barTarget': uiSettings.get('histogram:barTarget'),
+ 'histogram:maxBars': uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS),
+ 'histogram:barTarget': uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET),
dateFormat: uiSettings.get('dateFormat'),
'dateFormat:scaled': uiSettings.get('dateFormat:scaled'),
});
diff --git a/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts b/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts
index 0b2469c103578c..e6b4e4ccf85823 100644
--- a/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts
+++ b/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts
@@ -47,6 +47,7 @@ export const dataAnalyticsExplainSchema = schema.object({
/** Source */
source: schema.object({
index: schema.string(),
+ query: schema.maybe(schema.any()),
}),
analysis: schema.any(),
analyzed_fields: schema.maybe(schema.any()),
diff --git a/x-pack/plugins/monitoring/public/plugin.ts b/x-pack/plugins/monitoring/public/plugin.ts
index 73133da5a006ce..24383028e558c9 100644
--- a/x-pack/plugins/monitoring/public/plugin.ts
+++ b/x-pack/plugins/monitoring/public/plugin.ts
@@ -17,6 +17,7 @@ import {
FeatureCatalogueCategory,
HomePublicPluginSetup,
} from '../../../../src/plugins/home/public';
+import { UI_SETTINGS } from '../../../../src/plugins/data/public';
import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public';
import { MonitoringPluginDependencies, MonitoringConfig } from './types';
import {
@@ -110,7 +111,7 @@ export class MonitoringPlugin
timefilter.setRefreshInterval(refreshInterval);
timefilter.setTime(time);
uiSettings.overrideLocalDefault(
- 'timepicker:refreshIntervalDefaults',
+ UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS,
JSON.stringify(refreshInterval)
);
uiSettings.overrideLocalDefault('timepicker:timeDefaults', JSON.stringify(time));
diff --git a/x-pack/plugins/monitoring/public/views/access_denied/index.html b/x-pack/plugins/monitoring/public/views/access_denied/index.html
index 63cd4440ecf8a2..24863559212f78 100644
--- a/x-pack/plugins/monitoring/public/views/access_denied/index.html
+++ b/x-pack/plugins/monitoring/public/views/access_denied/index.html
@@ -30,12 +30,13 @@
diff --git a/x-pack/plugins/monitoring/public/views/access_denied/index.js b/x-pack/plugins/monitoring/public/views/access_denied/index.js
index 856e59702963a1..f7a4d03a26452b 100644
--- a/x-pack/plugins/monitoring/public/views/access_denied/index.js
+++ b/x-pack/plugins/monitoring/public/views/access_denied/index.js
@@ -4,16 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { noop } from 'lodash';
+import { kbnBaseUrl } from '../../../../../../src/plugins/kibana_legacy/common/kbn_base_url';
import { uiRoutes } from '../../angular/helpers/routes';
-import { Legacy } from '../../legacy_shims';
import template from './index.html';
const tryPrivilege = ($http, kbnUrl) => {
return $http
.get('../api/monitoring/v1/check_access')
.then(() => kbnUrl.redirect('/home'))
- .catch(noop);
+ .catch(() => true);
};
uiRoutes.when('/access-denied', {
@@ -31,17 +30,13 @@ uiRoutes.when('/access-denied', {
},
},
controllerAs: 'accessDenied',
- controller($scope, $injector) {
- const $window = $injector.get('$window');
- const kbnBaseUrl = $injector.get('kbnBaseUrl');
+ controller: function ($scope, $injector) {
const $http = $injector.get('$http');
const kbnUrl = $injector.get('kbnUrl');
const $interval = $injector.get('$interval');
// The template's "Back to Kibana" button click handler
- this.goToKibana = () => {
- $window.location.href = Legacy.shims.getBasePath() + kbnBaseUrl;
- };
+ this.goToKibanaURL = kbnBaseUrl;
// keep trying to load data in the background
const accessPoller = $interval(() => tryPrivilege($http, kbnUrl), 5 * 1000); // every 5 seconds
diff --git a/x-pack/plugins/reporting/server/config/schema.ts b/x-pack/plugins/reporting/server/config/schema.ts
index dfabfa98f8cbf3..b1234a6ddf0b66 100644
--- a/x-pack/plugins/reporting/server/config/schema.ts
+++ b/x-pack/plugins/reporting/server/config/schema.ts
@@ -162,6 +162,7 @@ const PollSchema = schema.object({
});
export const ConfigSchema = schema.object({
+ enabled: schema.boolean({ defaultValue: true }),
kibanaServer: KibanaServerSchema,
queue: QueueSchema,
capture: CaptureSchema,
diff --git a/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.test.ts b/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.test.ts
index 6f227d00974ca1..ddcf94079ade42 100644
--- a/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.test.ts
+++ b/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.test.ts
@@ -8,7 +8,7 @@ import nodeCrypto from '@elastic/node-crypto';
// @ts-ignore
import Puid from 'puid';
import sinon from 'sinon';
-import { fieldFormats } from '../../../../../../../src/plugins/data/server';
+import { fieldFormats, UI_SETTINGS } from '../../../../../../../src/plugins/data/server';
import { CancellationToken } from '../../../../common';
import { CSV_BOM_CHARS } from '../../../../common/constants';
import { LevelLogger } from '../../../lib';
@@ -16,6 +16,10 @@ import { setFieldFormats } from '../../../services';
import { createMockReportingCore } from '../../../test_helpers';
import { JobDocPayloadDiscoverCsv } from '../types';
import { executeJobFactory } from './execute_job';
+import {
+ CSV_SEPARATOR_SETTING,
+ CSV_QUOTE_VALUES_SETTING,
+} from '../../../../../../../src/plugins/share/server';
const delay = (ms: number) => new Promise((resolve) => setTimeout(() => resolve(), ms));
@@ -94,13 +98,13 @@ describe('CSV Execute Job', function () {
.stub(clusterStub, 'callAsCurrentUser')
.resolves(defaultElasticsearchResponse);
- mockUiSettingsClient.get.withArgs('csv:separator').returns(',');
- mockUiSettingsClient.get.withArgs('csv:quoteValues').returns(true);
+ mockUiSettingsClient.get.withArgs(CSV_SEPARATOR_SETTING).returns(',');
+ mockUiSettingsClient.get.withArgs(CSV_QUOTE_VALUES_SETTING).returns(true);
setFieldFormats({
fieldFormatServiceFactory() {
const uiConfigMock = {};
- (uiConfigMock as any)['format:defaultTypeMap'] = {
+ (uiConfigMock as any)[UI_SETTINGS.FORMAT_DEFAULT_TYPE_MAP] = {
_default_: { id: 'string', params: {} },
};
@@ -748,7 +752,7 @@ describe('CSV Execute Job', function () {
});
it('should use custom uiSettings csv:separator for header', async function () {
- mockUiSettingsClient.get.withArgs('csv:separator').returns(';');
+ mockUiSettingsClient.get.withArgs(CSV_SEPARATOR_SETTING).returns(';');
const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
const jobParams = getJobDocPayload({
headers: encryptedHeaders,
@@ -760,7 +764,7 @@ describe('CSV Execute Job', function () {
});
it('should escape column headers if uiSettings csv:quoteValues is true', async function () {
- mockUiSettingsClient.get.withArgs('csv:quoteValues').returns(true);
+ mockUiSettingsClient.get.withArgs(CSV_QUOTE_VALUES_SETTING).returns(true);
const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
const jobParams = getJobDocPayload({
headers: encryptedHeaders,
@@ -772,7 +776,7 @@ describe('CSV Execute Job', function () {
});
it(`shouldn't escape column headers if uiSettings csv:quoteValues is false`, async function () {
- mockUiSettingsClient.get.withArgs('csv:quoteValues').returns(false);
+ mockUiSettingsClient.get.withArgs(CSV_QUOTE_VALUES_SETTING).returns(false);
const executeJob = await executeJobFactory(mockReportingCore, mockLogger);
const jobParams = getJobDocPayload({
headers: encryptedHeaders,
diff --git a/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.ts b/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.ts
index de5ddb2503b2f8..4b17cc669efe1b 100644
--- a/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.ts
+++ b/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.ts
@@ -7,6 +7,10 @@
import { i18n } from '@kbn/i18n';
import Hapi from 'hapi';
import { IUiSettingsClient, KibanaRequest } from '../../../../../../../src/core/server';
+import {
+ CSV_SEPARATOR_SETTING,
+ CSV_QUOTE_VALUES_SETTING,
+} from '../../../../../../../src/plugins/share/server';
import { ReportingCore } from '../../..';
import { CSV_BOM_CHARS, CSV_JOB_TYPE } from '../../../../common/constants';
import { getFieldFormats } from '../../../../server/services';
@@ -94,8 +98,8 @@ export const executeJobFactory: ExecuteJobFactory {
const [separator, quoteValues, timezone] = await Promise.all([
- client.get('csv:separator'),
- client.get('csv:quoteValues'),
+ client.get(CSV_SEPARATOR_SETTING),
+ client.get(CSV_QUOTE_VALUES_SETTING),
client.get('dateFormat:tz'),
]);
diff --git a/x-pack/plugins/reporting/server/export_types/csv/server/lib/field_format_map.test.ts b/x-pack/plugins/reporting/server/export_types/csv/server/lib/field_format_map.test.ts
index 0434da3d11fe1e..83aa23de676639 100644
--- a/x-pack/plugins/reporting/server/export_types/csv/server/lib/field_format_map.test.ts
+++ b/x-pack/plugins/reporting/server/export_types/csv/server/lib/field_format_map.test.ts
@@ -9,6 +9,7 @@ import expect from '@kbn/expect';
import {
fieldFormats,
FieldFormatsGetConfigFn,
+ UI_SETTINGS,
} from '../../../../../../../../src/plugins/data/server';
import { fieldFormatMapFactory } from './field_format_map';
@@ -28,10 +29,10 @@ describe('field format map', function () {
},
};
const configMock: Record = {};
- configMock['format:defaultTypeMap'] = {
+ configMock[UI_SETTINGS.FORMAT_DEFAULT_TYPE_MAP] = {
number: { id: 'number', params: {} },
};
- configMock['format:number:defaultPattern'] = '0,0.[000]';
+ configMock[UI_SETTINGS.FORMAT_NUMBER_DEFAULT_PATTERN] = '0,0.[000]';
const getConfig = ((key: string) => configMock[key]) as FieldFormatsGetConfigFn;
const testValue = '4000';
diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts
index a9e4366f4eda6b..3f997a703bef12 100644
--- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts
+++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts
@@ -16,7 +16,12 @@ import {
Filter,
IIndexPattern,
Query,
+ UI_SETTINGS,
} from '../../../../../../../../src/plugins/data/server';
+import {
+ CSV_SEPARATOR_SETTING,
+ CSV_QUOTE_VALUES_SETTING,
+} from '../../../../../../../../src/plugins/share/server';
import { CancellationToken } from '../../../../../common';
import { LevelLogger } from '../../../../lib';
import { createGenerateCsv } from '../../../csv/server/lib/generate_csv';
@@ -32,9 +37,9 @@ import { getFilters } from './get_filters';
const getEsQueryConfig = async (config: IUiSettingsClient) => {
const configs = await Promise.all([
- config.get('query:allowLeadingWildcards'),
- config.get('query:queryString:options'),
- config.get('courier:ignoreFilterIfFieldNotInIndex'),
+ config.get(UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS),
+ config.get(UI_SETTINGS.QUERY_STRING_OPTIONS),
+ config.get(UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX),
]);
const [allowLeadingWildcards, queryStringOptions, ignoreFilterIfFieldNotInIndex] = configs;
return {
@@ -45,7 +50,10 @@ const getEsQueryConfig = async (config: IUiSettingsClient) => {
};
const getUiSettings = async (config: IUiSettingsClient) => {
- const configs = await Promise.all([config.get('csv:separator'), config.get('csv:quoteValues')]);
+ const configs = await Promise.all([
+ config.get(CSV_SEPARATOR_SETTING),
+ config.get(CSV_QUOTE_VALUES_SETTING),
+ ]);
const [separator, quoteValues] = configs;
return { separator, quoteValues };
};
diff --git a/x-pack/plugins/security/public/account_management/account_management_app.ts b/x-pack/plugins/security/public/account_management/account_management_app.ts
index 41567a04fe0302..0bb7785259c0ea 100644
--- a/x-pack/plugins/security/public/account_management/account_management_app.ts
+++ b/x-pack/plugins/security/public/account_management/account_management_app.ts
@@ -5,7 +5,12 @@
*/
import { i18n } from '@kbn/i18n';
-import { StartServicesAccessor, ApplicationSetup, AppMountParameters } from 'src/core/public';
+import {
+ ApplicationSetup,
+ AppMountParameters,
+ AppNavLinkStatus,
+ StartServicesAccessor,
+} from '../../../../../src/core/public';
import { AuthenticationServiceSetup } from '../authentication';
interface CreateDeps {
@@ -23,8 +28,7 @@ export const accountManagementApp = Object.freeze({
application.register({
id: this.id,
title,
- // TODO: switch to proper enum once https://github.com/elastic/kibana/issues/58327 is resolved.
- navLinkStatus: 3,
+ navLinkStatus: AppNavLinkStatus.hidden,
appRoute: '/security/account',
async mount({ element }: AppMountParameters) {
const [
diff --git a/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.scss b/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.scss
index 6784052ef4337e..344cde9c7825ce 100644
--- a/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.scss
+++ b/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.scss
@@ -23,9 +23,10 @@
}
&:focus {
+ @include euiFocusRing;
+
border-color: transparent;
border-radius: $euiBorderRadius;
- @include euiFocusRing;
.secLoginCard__title {
text-decoration: underline;
diff --git a/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_privileges.ts b/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_privileges.ts
index 98110a83103aa0..6821c163d817dd 100644
--- a/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_privileges.ts
+++ b/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_privileges.ts
@@ -11,13 +11,15 @@ import { Feature } from '../../../../../features/public';
import { KibanaPrivileges } from '../model';
import { SecurityLicenseFeatures } from '../../..';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { featuresPluginMock } from '../../../../../features/server/mocks';
+
export const createRawKibanaPrivileges = (
features: Feature[],
{ allowSubFeaturePrivileges = true } = {}
) => {
- const featuresService = {
- getFeatures: () => features,
- };
+ const featuresService = featuresPluginMock.createSetup();
+ featuresService.getFeatures.mockReturnValue(features);
const licensingService = {
getFeatures: () => ({ allowSubFeaturePrivileges } as SecurityLicenseFeatures),
diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx
index afb8b6ec5dbe0c..43387d913e6fc5 100644
--- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx
+++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx
@@ -163,7 +163,12 @@ function getProps({
const { http, docLinks, notifications } = coreMock.createStart();
http.get.mockImplementation(async (path: any) => {
if (path === '/api/spaces/space') {
- return buildSpaces();
+ if (spacesEnabled) {
+ return buildSpaces();
+ }
+
+ const notFoundError = { response: { status: 404 } };
+ throw notFoundError;
}
});
@@ -181,7 +186,6 @@ function getProps({
notifications,
docLinks: new DocumentationLinksService(docLinks),
fatalErrors,
- spacesEnabled,
uiCapabilities: buildUICapabilities(canManageSpaces),
history: (scopedHistoryMock.create() as unknown) as ScopedHistory,
};
diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx
index 77f4455d813c6b..15888733ec4247 100644
--- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx
+++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx
@@ -80,7 +80,6 @@ interface Props {
docLinks: DocumentationLinksService;
http: HttpStart;
license: SecurityLicense;
- spacesEnabled: boolean;
uiCapabilities: Capabilities;
notifications: NotificationsStart;
fatalErrors: FatalErrorsSetup;
@@ -225,14 +224,21 @@ function useRole(
return [role, setRole] as [Role | null, typeof setRole];
}
-function useSpaces(http: HttpStart, fatalErrors: FatalErrorsSetup, spacesEnabled: boolean) {
- const [spaces, setSpaces] = useState(null);
+function useSpaces(http: HttpStart, fatalErrors: FatalErrorsSetup) {
+ const [spaces, setSpaces] = useState<{ enabled: boolean; list: Space[] } | null>(null);
useEffect(() => {
- (spacesEnabled ? http.get('/api/spaces/space') : Promise.resolve([])).then(
- (fetchedSpaces) => setSpaces(fetchedSpaces),
- (err) => fatalErrors.add(err)
+ http.get('/api/spaces/space').then(
+ (fetchedSpaces) => setSpaces({ enabled: true, list: fetchedSpaces }),
+ (err: IHttpFetchError) => {
+ // Spaces plugin can be disabled and hence this endpoint can be unavailable.
+ if (err.response?.status === 404) {
+ setSpaces({ enabled: false, list: [] });
+ } else {
+ fatalErrors.add(err);
+ }
+ }
);
- }, [http, fatalErrors, spacesEnabled]);
+ }, [http, fatalErrors]);
return spaces;
}
@@ -278,7 +284,6 @@ export const EditRolePage: FunctionComponent = ({
roleName,
action,
fatalErrors,
- spacesEnabled,
license,
docLinks,
uiCapabilities,
@@ -295,7 +300,7 @@ export const EditRolePage: FunctionComponent = ({
const runAsUsers = useRunAsUsers(userAPIClient, fatalErrors);
const indexPatternsTitles = useIndexPatternsTitles(indexPatterns, fatalErrors, notifications);
const privileges = usePrivileges(privilegesAPIClient, fatalErrors);
- const spaces = useSpaces(http, fatalErrors, spacesEnabled);
+ const spaces = useSpaces(http, fatalErrors);
const features = useFeatures(getFeatures, fatalErrors);
const [role, setRole] = useRole(
rolesAPIClient,
@@ -434,8 +439,8 @@ export const EditRolePage: FunctionComponent = ({
= ({
setFormError(null);
try {
- await rolesAPIClient.saveRole({ role, spacesEnabled });
+ await rolesAPIClient.saveRole({ role, spacesEnabled: spaces.enabled });
} catch (error) {
notifications.toasts.addDanger(get(error, 'data.message'));
return;
@@ -554,7 +559,7 @@ export const EditRolePage: FunctionComponent = ({
backToRoleList();
};
- const description = spacesEnabled ? (
+ const description = spaces.enabled ? (
(),
+ getFeatureUsageService: jest
+ .fn()
+ .mockReturnValue(securityFeatureUsageServiceMock.createStartContract()),
};
}
@@ -1451,6 +1455,9 @@ describe('Authenticator', () => {
);
expect(mockSessionStorage.set).not.toHaveBeenCalled();
+ expect(
+ mockOptions.getFeatureUsageService().recordPreAccessAgreementUsage
+ ).not.toHaveBeenCalled();
});
it('fails if cannot retrieve user session', async () => {
@@ -1463,6 +1470,9 @@ describe('Authenticator', () => {
);
expect(mockSessionStorage.set).not.toHaveBeenCalled();
+ expect(
+ mockOptions.getFeatureUsageService().recordPreAccessAgreementUsage
+ ).not.toHaveBeenCalled();
});
it('fails if license doesn allow access agreement acknowledgement', async () => {
@@ -1477,6 +1487,9 @@ describe('Authenticator', () => {
);
expect(mockSessionStorage.set).not.toHaveBeenCalled();
+ expect(
+ mockOptions.getFeatureUsageService().recordPreAccessAgreementUsage
+ ).not.toHaveBeenCalled();
});
it('properly acknowledges access agreement for the authenticated user', async () => {
@@ -1493,6 +1506,10 @@ describe('Authenticator', () => {
type: 'basic',
name: 'basic1',
});
+
+ expect(
+ mockOptions.getFeatureUsageService().recordPreAccessAgreementUsage
+ ).toHaveBeenCalledTimes(1);
});
});
});
diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts
index 98342a8494e383..ac5c2a72b9667c 100644
--- a/x-pack/plugins/security/server/authentication/authenticator.ts
+++ b/x-pack/plugins/security/server/authentication/authenticator.ts
@@ -38,6 +38,7 @@ import { DeauthenticationResult } from './deauthentication_result';
import { Tokens } from './tokens';
import { canRedirectRequest } from './can_redirect_request';
import { HTTPAuthorizationHeader } from './http_authentication';
+import { SecurityFeatureUsageServiceStart } from '../feature_usage';
/**
* The shape of the session that is actually stored in the cookie.
@@ -94,6 +95,7 @@ export interface ProviderLoginAttempt {
export interface AuthenticatorOptions {
auditLogger: SecurityAuditLogger;
+ getFeatureUsageService: () => SecurityFeatureUsageServiceStart;
getCurrentUser: (request: KibanaRequest) => AuthenticatedUser | null;
config: Pick;
basePath: HttpServiceSetup['basePath'];
@@ -502,6 +504,8 @@ export class Authenticator {
currentUser.username,
existingSession.provider
);
+
+ this.options.getFeatureUsageService().recordPreAccessAgreementUsage();
}
/**
diff --git a/x-pack/plugins/security/server/authentication/index.test.ts b/x-pack/plugins/security/server/authentication/index.test.ts
index 1c1e0ed781f18e..c7323509c00d68 100644
--- a/x-pack/plugins/security/server/authentication/index.test.ts
+++ b/x-pack/plugins/security/server/authentication/index.test.ts
@@ -42,6 +42,8 @@ import {
} from './api_keys';
import { SecurityLicense } from '../../common/licensing';
import { SecurityAuditLogger } from '../audit';
+import { SecurityFeatureUsageServiceStart } from '../feature_usage';
+import { securityFeatureUsageServiceMock } from '../feature_usage/index.mock';
describe('setupAuthentication()', () => {
let mockSetupAuthenticationParams: {
@@ -51,6 +53,7 @@ describe('setupAuthentication()', () => {
http: jest.Mocked;
clusterClient: jest.Mocked;
license: jest.Mocked;
+ getFeatureUsageService: () => jest.Mocked;
};
let mockScopedClusterClient: jest.Mocked>;
beforeEach(() => {
@@ -69,6 +72,9 @@ describe('setupAuthentication()', () => {
clusterClient: elasticsearchServiceMock.createClusterClient(),
license: licenseMock.create(),
loggers: loggingServiceMock.create(),
+ getFeatureUsageService: jest
+ .fn()
+ .mockReturnValue(securityFeatureUsageServiceMock.createStartContract()),
};
mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
diff --git a/x-pack/plugins/security/server/authentication/index.ts b/x-pack/plugins/security/server/authentication/index.ts
index 779b852195b028..ec48c727a57398 100644
--- a/x-pack/plugins/security/server/authentication/index.ts
+++ b/x-pack/plugins/security/server/authentication/index.ts
@@ -17,6 +17,7 @@ import { ConfigType } from '../config';
import { getErrorStatusCode } from '../errors';
import { Authenticator, ProviderSession } from './authenticator';
import { APIKeys, CreateAPIKeyParams, InvalidateAPIKeyParams } from './api_keys';
+import { SecurityFeatureUsageServiceStart } from '../feature_usage';
export { canRedirectRequest } from './can_redirect_request';
export { Authenticator, ProviderLoginAttempt } from './authenticator';
@@ -37,6 +38,7 @@ export {
interface SetupAuthenticationParams {
auditLogger: SecurityAuditLogger;
+ getFeatureUsageService: () => SecurityFeatureUsageServiceStart;
http: CoreSetup['http'];
clusterClient: IClusterClient;
config: ConfigType;
@@ -48,6 +50,7 @@ export type Authentication = UnwrapPromise {
diff --git a/x-pack/plugins/security/server/authorization/app_authorization.ts b/x-pack/plugins/security/server/authorization/app_authorization.ts
index aead8cb07897c4..1036997ca821d1 100644
--- a/x-pack/plugins/security/server/authorization/app_authorization.ts
+++ b/x-pack/plugins/security/server/authorization/app_authorization.ts
@@ -4,13 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { CoreSetup, Logger } from '../../../../../src/core/server';
-import { FeaturesService } from '../plugin';
-import { Authorization } from '.';
+import { HttpServiceSetup, Logger } from '../../../../../src/core/server';
+import { PluginSetupContract as FeaturesPluginSetup } from '../../../features/server';
+import { AuthorizationServiceSetup } from '.';
class ProtectedApplications {
private applications: Set | null = null;
- constructor(private readonly featuresService: FeaturesService) {}
+ constructor(private readonly featuresService: FeaturesPluginSetup) {}
public shouldProtect(appId: string) {
// Currently, once we get the list of features we essentially "lock" additional
@@ -30,14 +30,14 @@ class ProtectedApplications {
}
export function initAppAuthorization(
- http: CoreSetup['http'],
+ http: HttpServiceSetup,
{
actions,
checkPrivilegesDynamicallyWithRequest,
mode,
- }: Pick,
+ }: Pick,
logger: Logger,
- featuresService: FeaturesService
+ featuresService: FeaturesPluginSetup
) {
const protectedApplications = new ProtectedApplications(featuresService);
diff --git a/x-pack/plugins/security/server/authorization/authorization_service.test.ts b/x-pack/plugins/security/server/authorization/authorization_service.test.ts
new file mode 100644
index 00000000000000..978c985cfe820d
--- /dev/null
+++ b/x-pack/plugins/security/server/authorization/authorization_service.test.ts
@@ -0,0 +1,263 @@
+/*
+ * 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 {
+ mockAuthorizationModeFactory,
+ mockCheckPrivilegesDynamicallyWithRequestFactory,
+ mockCheckPrivilegesWithRequestFactory,
+ mockCheckSavedObjectsPrivilegesWithRequestFactory,
+ mockPrivilegesFactory,
+ mockRegisterPrivilegesWithCluster,
+} from './service.test.mocks';
+
+import { BehaviorSubject } from 'rxjs';
+import { CoreStatus, ServiceStatusLevels } from '../../../../../src/core/server';
+import { checkPrivilegesWithRequestFactory } from './check_privileges';
+import { checkPrivilegesDynamicallyWithRequestFactory } from './check_privileges_dynamically';
+import { checkSavedObjectsPrivilegesWithRequestFactory } from './check_saved_objects_privileges';
+import { authorizationModeFactory } from './mode';
+import { privilegesFactory } from './privileges';
+import { AuthorizationService } from '.';
+
+import {
+ coreMock,
+ elasticsearchServiceMock,
+ loggingServiceMock,
+} from '../../../../../src/core/server/mocks';
+import { featuresPluginMock } from '../../../features/server/mocks';
+import { licenseMock } from '../../common/licensing/index.mock';
+import { SecurityLicense, SecurityLicenseFeatures } from '../../common/licensing';
+import { nextTick } from 'test_utils/enzyme_helpers';
+
+const kibanaIndexName = '.a-kibana-index';
+const application = `kibana-${kibanaIndexName}`;
+const mockCheckPrivilegesWithRequest = Symbol();
+const mockCheckPrivilegesDynamicallyWithRequest = Symbol();
+const mockCheckSavedObjectsPrivilegesWithRequest = Symbol();
+const mockPrivilegesService = Symbol();
+const mockAuthorizationMode = Symbol();
+beforeEach(() => {
+ mockCheckPrivilegesWithRequestFactory.mockReturnValue(mockCheckPrivilegesWithRequest);
+ mockCheckPrivilegesDynamicallyWithRequestFactory.mockReturnValue(
+ mockCheckPrivilegesDynamicallyWithRequest
+ );
+ mockCheckSavedObjectsPrivilegesWithRequestFactory.mockReturnValue(
+ mockCheckSavedObjectsPrivilegesWithRequest
+ );
+ mockPrivilegesFactory.mockReturnValue(mockPrivilegesService);
+ mockAuthorizationModeFactory.mockReturnValue(mockAuthorizationMode);
+});
+
+afterEach(() => {
+ mockRegisterPrivilegesWithCluster.mockClear();
+});
+
+it(`#setup returns exposed services`, () => {
+ const mockClusterClient = elasticsearchServiceMock.createClusterClient();
+ const mockGetSpacesService = jest
+ .fn()
+ .mockReturnValue({ getSpaceId: jest.fn(), namespaceToSpaceId: jest.fn() });
+ const mockFeaturesSetup = featuresPluginMock.createSetup();
+ const mockLicense = licenseMock.create();
+ const mockCoreSetup = coreMock.createSetup();
+
+ const authorizationService = new AuthorizationService();
+ const authz = authorizationService.setup({
+ http: mockCoreSetup.http,
+ capabilities: mockCoreSetup.capabilities,
+ status: mockCoreSetup.status,
+ clusterClient: mockClusterClient,
+ license: mockLicense,
+ loggers: loggingServiceMock.create(),
+ kibanaIndexName,
+ packageVersion: 'some-version',
+ features: mockFeaturesSetup,
+ getSpacesService: mockGetSpacesService,
+ });
+
+ expect(authz.actions.version).toBe('version:some-version');
+ expect(authz.applicationName).toBe(application);
+
+ expect(authz.checkPrivilegesWithRequest).toBe(mockCheckPrivilegesWithRequest);
+ expect(checkPrivilegesWithRequestFactory).toHaveBeenCalledWith(
+ authz.actions,
+ mockClusterClient,
+ authz.applicationName
+ );
+
+ expect(authz.checkPrivilegesDynamicallyWithRequest).toBe(
+ mockCheckPrivilegesDynamicallyWithRequest
+ );
+ expect(checkPrivilegesDynamicallyWithRequestFactory).toHaveBeenCalledWith(
+ mockCheckPrivilegesWithRequest,
+ mockGetSpacesService
+ );
+
+ expect(authz.checkSavedObjectsPrivilegesWithRequest).toBe(
+ mockCheckSavedObjectsPrivilegesWithRequest
+ );
+ expect(checkSavedObjectsPrivilegesWithRequestFactory).toHaveBeenCalledWith(
+ mockCheckPrivilegesWithRequest,
+ mockGetSpacesService
+ );
+
+ expect(authz.privileges).toBe(mockPrivilegesService);
+ expect(privilegesFactory).toHaveBeenCalledWith(authz.actions, mockFeaturesSetup, mockLicense);
+
+ expect(authz.mode).toBe(mockAuthorizationMode);
+ expect(authorizationModeFactory).toHaveBeenCalledWith(mockLicense);
+
+ expect(mockCoreSetup.capabilities.registerSwitcher).toHaveBeenCalledTimes(1);
+ expect(mockCoreSetup.capabilities.registerSwitcher).toHaveBeenCalledWith(expect.any(Function));
+});
+
+describe('#start', () => {
+ let statusSubject: BehaviorSubject;
+ let licenseSubject: BehaviorSubject;
+ let mockLicense: jest.Mocked;
+ beforeEach(() => {
+ const mockClusterClient = elasticsearchServiceMock.createClusterClient();
+
+ licenseSubject = new BehaviorSubject(({} as unknown) as SecurityLicenseFeatures);
+ mockLicense = licenseMock.create();
+ mockLicense.isEnabled.mockReturnValue(false);
+ mockLicense.features$ = licenseSubject;
+
+ statusSubject = new BehaviorSubject({
+ elasticsearch: { level: ServiceStatusLevels.unavailable, summary: 'Service is NOT working' },
+ savedObjects: { level: ServiceStatusLevels.unavailable, summary: 'Service is NOT working' },
+ });
+ const mockCoreSetup = coreMock.createSetup();
+ mockCoreSetup.status.core$ = statusSubject;
+
+ const authorizationService = new AuthorizationService();
+ authorizationService.setup({
+ http: mockCoreSetup.http,
+ capabilities: mockCoreSetup.capabilities,
+ status: mockCoreSetup.status,
+ clusterClient: mockClusterClient,
+ license: mockLicense,
+ loggers: loggingServiceMock.create(),
+ kibanaIndexName,
+ packageVersion: 'some-version',
+ features: featuresPluginMock.createSetup(),
+ getSpacesService: jest
+ .fn()
+ .mockReturnValue({ getSpaceId: jest.fn(), namespaceToSpaceId: jest.fn() }),
+ });
+
+ const featuresStart = featuresPluginMock.createStart();
+ featuresStart.getFeatures.mockReturnValue([]);
+
+ authorizationService.start({ clusterClient: mockClusterClient, features: featuresStart });
+
+ // ES and license aren't available yet.
+ expect(mockRegisterPrivilegesWithCluster).not.toHaveBeenCalled();
+ });
+
+ it('registers cluster privileges', async () => {
+ // ES is available now, but not license.
+ statusSubject.next({
+ elasticsearch: { level: ServiceStatusLevels.available, summary: 'Service is working' },
+ savedObjects: { level: ServiceStatusLevels.unavailable, summary: 'Service is NOT working' },
+ });
+ expect(mockRegisterPrivilegesWithCluster).not.toHaveBeenCalled();
+
+ // Both ES and license are available now.
+ mockLicense.isEnabled.mockReturnValue(true);
+ licenseSubject.next(({} as unknown) as SecurityLicenseFeatures);
+ expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(1);
+
+ await nextTick();
+
+ // New changes still trigger privileges re-registration.
+ licenseSubject.next(({} as unknown) as SecurityLicenseFeatures);
+ expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(2);
+ });
+
+ it('schedules retries if fails to register cluster privileges', async () => {
+ jest.useFakeTimers();
+
+ mockRegisterPrivilegesWithCluster.mockRejectedValue(new Error('Some error'));
+
+ // Both ES and license are available.
+ mockLicense.isEnabled.mockReturnValue(true);
+ statusSubject.next({
+ elasticsearch: { level: ServiceStatusLevels.available, summary: 'Service is working' },
+ savedObjects: { level: ServiceStatusLevels.unavailable, summary: 'Service is NOT working' },
+ });
+ expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(1);
+
+ // Next retry isn't performed immediately, retry happens only after a timeout.
+ await nextTick();
+ expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(1);
+ jest.advanceTimersByTime(100);
+ expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(2);
+
+ // Delay between consequent retries is increasing.
+ await nextTick();
+ jest.advanceTimersByTime(100);
+ expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(2);
+ await nextTick();
+ jest.advanceTimersByTime(100);
+ expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(3);
+
+ // When call finally succeeds retries aren't scheduled anymore.
+ mockRegisterPrivilegesWithCluster.mockResolvedValue(undefined);
+ await nextTick();
+ jest.runAllTimers();
+ expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(4);
+ await nextTick();
+ jest.runAllTimers();
+ expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(4);
+
+ // New changes still trigger privileges re-registration.
+ licenseSubject.next(({} as unknown) as SecurityLicenseFeatures);
+ expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(5);
+ });
+});
+
+it('#stop unsubscribes from license and ES updates.', () => {
+ const mockClusterClient = elasticsearchServiceMock.createClusterClient();
+
+ const licenseSubject = new BehaviorSubject(({} as unknown) as SecurityLicenseFeatures);
+ const mockLicense = licenseMock.create();
+ mockLicense.isEnabled.mockReturnValue(false);
+ mockLicense.features$ = licenseSubject;
+
+ const mockCoreSetup = coreMock.createSetup();
+ mockCoreSetup.status.core$ = new BehaviorSubject({
+ elasticsearch: { level: ServiceStatusLevels.available, summary: 'Service is working' },
+ savedObjects: { level: ServiceStatusLevels.available, summary: 'Service is working' },
+ });
+
+ const authorizationService = new AuthorizationService();
+ authorizationService.setup({
+ http: mockCoreSetup.http,
+ capabilities: mockCoreSetup.capabilities,
+ status: mockCoreSetup.status,
+ clusterClient: mockClusterClient,
+ license: mockLicense,
+ loggers: loggingServiceMock.create(),
+ kibanaIndexName,
+ packageVersion: 'some-version',
+ features: featuresPluginMock.createSetup(),
+ getSpacesService: jest
+ .fn()
+ .mockReturnValue({ getSpaceId: jest.fn(), namespaceToSpaceId: jest.fn() }),
+ });
+
+ const featuresStart = featuresPluginMock.createStart();
+ featuresStart.getFeatures.mockReturnValue([]);
+ authorizationService.start({ clusterClient: mockClusterClient, features: featuresStart });
+
+ authorizationService.stop();
+
+ // After stop we don't register privileges even if all requirements are met.
+ mockLicense.isEnabled.mockReturnValue(true);
+ licenseSubject.next(({} as unknown) as SecurityLicenseFeatures);
+ expect(mockRegisterPrivilegesWithCluster).not.toHaveBeenCalled();
+});
diff --git a/x-pack/plugins/security/server/authorization/authorization_service.ts b/x-pack/plugins/security/server/authorization/authorization_service.ts
new file mode 100644
index 00000000000000..989784a1436d26
--- /dev/null
+++ b/x-pack/plugins/security/server/authorization/authorization_service.ts
@@ -0,0 +1,221 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { combineLatest, BehaviorSubject, Subscription } from 'rxjs';
+import { distinctUntilChanged, filter } from 'rxjs/operators';
+import { UICapabilities } from 'ui/capabilities';
+import {
+ LoggerFactory,
+ KibanaRequest,
+ IClusterClient,
+ ServiceStatusLevels,
+ Logger,
+ StatusServiceSetup,
+ HttpServiceSetup,
+ CapabilitiesSetup,
+} from '../../../../../src/core/server';
+
+import {
+ PluginSetupContract as FeaturesPluginSetup,
+ PluginStartContract as FeaturesPluginStart,
+} from '../../../features/server';
+
+import { SpacesService } from '../plugin';
+import { Actions } from './actions';
+import { CheckPrivilegesWithRequest, checkPrivilegesWithRequestFactory } from './check_privileges';
+import {
+ CheckPrivilegesDynamicallyWithRequest,
+ checkPrivilegesDynamicallyWithRequestFactory,
+} from './check_privileges_dynamically';
+import {
+ CheckSavedObjectsPrivilegesWithRequest,
+ checkSavedObjectsPrivilegesWithRequestFactory,
+} from './check_saved_objects_privileges';
+import { AuthorizationMode, authorizationModeFactory } from './mode';
+import { privilegesFactory, PrivilegesService } from './privileges';
+import { initAppAuthorization } from './app_authorization';
+import { initAPIAuthorization } from './api_authorization';
+import { disableUICapabilitiesFactory } from './disable_ui_capabilities';
+import { validateFeaturePrivileges } from './validate_feature_privileges';
+import { validateReservedPrivileges } from './validate_reserved_privileges';
+import { registerPrivilegesWithCluster } from './register_privileges_with_cluster';
+import { APPLICATION_PREFIX } from '../../common/constants';
+import { SecurityLicense } from '../../common/licensing';
+
+export { Actions } from './actions';
+export { CheckSavedObjectsPrivileges } from './check_saved_objects_privileges';
+export { featurePrivilegeIterator } from './privileges';
+
+interface AuthorizationServiceSetupParams {
+ packageVersion: string;
+ http: HttpServiceSetup;
+ status: StatusServiceSetup;
+ capabilities: CapabilitiesSetup;
+ clusterClient: IClusterClient;
+ license: SecurityLicense;
+ loggers: LoggerFactory;
+ features: FeaturesPluginSetup;
+ kibanaIndexName: string;
+ getSpacesService(): SpacesService | undefined;
+}
+
+interface AuthorizationServiceStartParams {
+ features: FeaturesPluginStart;
+ clusterClient: IClusterClient;
+}
+
+export interface AuthorizationServiceSetup {
+ actions: Actions;
+ checkPrivilegesWithRequest: CheckPrivilegesWithRequest;
+ checkPrivilegesDynamicallyWithRequest: CheckPrivilegesDynamicallyWithRequest;
+ checkSavedObjectsPrivilegesWithRequest: CheckSavedObjectsPrivilegesWithRequest;
+ applicationName: string;
+ mode: AuthorizationMode;
+ privileges: PrivilegesService;
+}
+
+export class AuthorizationService {
+ private logger!: Logger;
+ private license!: SecurityLicense;
+ private status!: StatusServiceSetup;
+ private applicationName!: string;
+ private privileges!: PrivilegesService;
+
+ private statusSubscription?: Subscription;
+
+ setup({
+ http,
+ capabilities,
+ status,
+ packageVersion,
+ clusterClient,
+ license,
+ loggers,
+ features,
+ kibanaIndexName,
+ getSpacesService,
+ }: AuthorizationServiceSetupParams): AuthorizationServiceSetup {
+ this.logger = loggers.get('authorization');
+ this.license = license;
+ this.status = status;
+ this.applicationName = `${APPLICATION_PREFIX}${kibanaIndexName}`;
+
+ const mode = authorizationModeFactory(license);
+ const actions = new Actions(packageVersion);
+ this.privileges = privilegesFactory(actions, features, license);
+
+ const checkPrivilegesWithRequest = checkPrivilegesWithRequestFactory(
+ actions,
+ clusterClient,
+ this.applicationName
+ );
+
+ const authz = {
+ actions,
+ applicationName: this.applicationName,
+ mode,
+ privileges: this.privileges,
+ checkPrivilegesWithRequest,
+ checkPrivilegesDynamicallyWithRequest: checkPrivilegesDynamicallyWithRequestFactory(
+ checkPrivilegesWithRequest,
+ getSpacesService
+ ),
+ checkSavedObjectsPrivilegesWithRequest: checkSavedObjectsPrivilegesWithRequestFactory(
+ checkPrivilegesWithRequest,
+ getSpacesService
+ ),
+ };
+
+ capabilities.registerSwitcher(
+ async (request: KibanaRequest, uiCapabilities: UICapabilities) => {
+ // If we have a license which doesn't enable security, or we're a legacy user we shouldn't
+ // disable any ui capabilities
+ if (!mode.useRbacForRequest(request)) {
+ return uiCapabilities;
+ }
+
+ const disableUICapabilities = disableUICapabilitiesFactory(
+ request,
+ features.getFeatures(),
+ this.logger,
+ authz
+ );
+
+ if (!request.auth.isAuthenticated) {
+ return disableUICapabilities.all(uiCapabilities);
+ }
+
+ return await disableUICapabilities.usingPrivileges(uiCapabilities);
+ }
+ );
+
+ initAPIAuthorization(http, authz, loggers.get('api-authorization'));
+ initAppAuthorization(http, authz, loggers.get('app-authorization'), features);
+
+ return authz;
+ }
+
+ start({ clusterClient, features }: AuthorizationServiceStartParams) {
+ const allFeatures = features.getFeatures();
+ validateFeaturePrivileges(allFeatures);
+ validateReservedPrivileges(allFeatures);
+
+ this.registerPrivileges(clusterClient);
+ }
+
+ stop() {
+ if (this.statusSubscription !== undefined) {
+ this.statusSubscription.unsubscribe();
+ this.statusSubscription = undefined;
+ }
+ }
+
+ private registerPrivileges(clusterClient: IClusterClient) {
+ const RETRY_SCALE_DURATION = 100;
+ const RETRY_TIMEOUT_MAX = 10000;
+ const retries$ = new BehaviorSubject(0);
+ let retryTimeout: NodeJS.Timeout;
+
+ // Register cluster privileges once Elasticsearch is available and Security plugin is enabled.
+ this.statusSubscription = combineLatest([
+ this.status.core$,
+ this.license.features$,
+ retries$.asObservable().pipe(
+ // We shouldn't emit new value if retry counter is reset. This comparator isn't called for
+ // the initial value.
+ distinctUntilChanged((prev, curr) => prev === curr || curr === 0)
+ ),
+ ])
+ .pipe(
+ filter(
+ ([status]) =>
+ this.license.isEnabled() && status.elasticsearch.level === ServiceStatusLevels.available
+ )
+ )
+ .subscribe(async () => {
+ // If status or license change occurred before retry timeout we should cancel it.
+ if (retryTimeout) {
+ clearTimeout(retryTimeout);
+ }
+
+ try {
+ await registerPrivilegesWithCluster(
+ this.logger,
+ this.privileges,
+ this.applicationName,
+ clusterClient
+ );
+ retries$.next(0);
+ } catch (err) {
+ const retriesElapsed = retries$.getValue() + 1;
+ retryTimeout = setTimeout(
+ () => retries$.next(retriesElapsed),
+ Math.min(retriesElapsed * RETRY_SCALE_DURATION, RETRY_TIMEOUT_MAX)
+ );
+ }
+ });
+ }
+}
diff --git a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts
index 72937c15756ac9..183ad9169a1233 100644
--- a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts
+++ b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts
@@ -10,13 +10,13 @@ import { KibanaRequest, Logger } from '../../../../../src/core/server';
import { Feature } from '../../../features/server';
import { CheckPrivilegesResponse } from './check_privileges';
-import { Authorization } from './index';
+import { AuthorizationServiceSetup } from '.';
export function disableUICapabilitiesFactory(
request: KibanaRequest,
features: Feature[],
logger: Logger,
- authz: Authorization
+ authz: AuthorizationServiceSetup
) {
const featureNavLinkIds = features
.map((feature) => feature.navLinkId)
diff --git a/x-pack/plugins/security/server/authorization/index.test.ts b/x-pack/plugins/security/server/authorization/index.test.ts
deleted file mode 100644
index 32520534547649..00000000000000
--- a/x-pack/plugins/security/server/authorization/index.test.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import {
- mockAuthorizationModeFactory,
- mockCheckPrivilegesDynamicallyWithRequestFactory,
- mockCheckPrivilegesWithRequestFactory,
- mockCheckSavedObjectsPrivilegesWithRequestFactory,
- mockPrivilegesFactory,
-} from './service.test.mocks';
-
-import { checkPrivilegesWithRequestFactory } from './check_privileges';
-import { checkPrivilegesDynamicallyWithRequestFactory } from './check_privileges_dynamically';
-import { checkSavedObjectsPrivilegesWithRequestFactory } from './check_saved_objects_privileges';
-import { authorizationModeFactory } from './mode';
-import { privilegesFactory } from './privileges';
-import { setupAuthorization } from '.';
-
-import {
- coreMock,
- elasticsearchServiceMock,
- loggingServiceMock,
-} from '../../../../../src/core/server/mocks';
-import { licenseMock } from '../../common/licensing/index.mock';
-
-test(`returns exposed services`, () => {
- const kibanaIndexName = '.a-kibana-index';
- const application = `kibana-${kibanaIndexName}`;
-
- const mockCheckPrivilegesWithRequest = Symbol();
- mockCheckPrivilegesWithRequestFactory.mockReturnValue(mockCheckPrivilegesWithRequest);
-
- const mockCheckPrivilegesDynamicallyWithRequest = Symbol();
- mockCheckPrivilegesDynamicallyWithRequestFactory.mockReturnValue(
- mockCheckPrivilegesDynamicallyWithRequest
- );
-
- const mockCheckSavedObjectsPrivilegesWithRequest = Symbol();
- mockCheckSavedObjectsPrivilegesWithRequestFactory.mockReturnValue(
- mockCheckSavedObjectsPrivilegesWithRequest
- );
-
- const mockPrivilegesService = Symbol();
- mockPrivilegesFactory.mockReturnValue(mockPrivilegesService);
- const mockAuthorizationMode = Symbol();
- mockAuthorizationModeFactory.mockReturnValue(mockAuthorizationMode);
-
- const mockClusterClient = elasticsearchServiceMock.createClusterClient();
- const mockGetSpacesService = jest
- .fn()
- .mockReturnValue({ getSpaceId: jest.fn(), namespaceToSpaceId: jest.fn() });
- const mockFeaturesService = { getFeatures: () => [] };
- const mockLicense = licenseMock.create();
-
- const authz = setupAuthorization({
- http: coreMock.createSetup().http,
- clusterClient: mockClusterClient,
- license: mockLicense,
- loggers: loggingServiceMock.create(),
- kibanaIndexName,
- packageVersion: 'some-version',
- featuresService: mockFeaturesService,
- getSpacesService: mockGetSpacesService,
- });
-
- expect(authz.actions.version).toBe('version:some-version');
- expect(authz.applicationName).toBe(application);
-
- expect(authz.checkPrivilegesWithRequest).toBe(mockCheckPrivilegesWithRequest);
- expect(checkPrivilegesWithRequestFactory).toHaveBeenCalledWith(
- authz.actions,
- mockClusterClient,
- authz.applicationName
- );
-
- expect(authz.checkPrivilegesDynamicallyWithRequest).toBe(
- mockCheckPrivilegesDynamicallyWithRequest
- );
- expect(checkPrivilegesDynamicallyWithRequestFactory).toHaveBeenCalledWith(
- mockCheckPrivilegesWithRequest,
- mockGetSpacesService
- );
-
- expect(authz.checkSavedObjectsPrivilegesWithRequest).toBe(
- mockCheckSavedObjectsPrivilegesWithRequest
- );
- expect(checkSavedObjectsPrivilegesWithRequestFactory).toHaveBeenCalledWith(
- mockCheckPrivilegesWithRequest,
- mockGetSpacesService
- );
-
- expect(authz.privileges).toBe(mockPrivilegesService);
- expect(privilegesFactory).toHaveBeenCalledWith(authz.actions, mockFeaturesService, mockLicense);
-
- expect(authz.mode).toBe(mockAuthorizationMode);
- expect(authorizationModeFactory).toHaveBeenCalledWith(mockLicense);
-});
diff --git a/x-pack/plugins/security/server/authorization/index.ts b/x-pack/plugins/security/server/authorization/index.ts
index cf970a561b93f9..d5c1323354f86e 100644
--- a/x-pack/plugins/security/server/authorization/index.ts
+++ b/x-pack/plugins/security/server/authorization/index.ts
@@ -4,134 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { UICapabilities } from 'ui/capabilities';
-import {
- CoreSetup,
- LoggerFactory,
- KibanaRequest,
- IClusterClient,
-} from '../../../../../src/core/server';
-
-import { FeaturesService, SpacesService } from '../plugin';
-import { Actions } from './actions';
-import { CheckPrivilegesWithRequest, checkPrivilegesWithRequestFactory } from './check_privileges';
-import {
- CheckPrivilegesDynamicallyWithRequest,
- checkPrivilegesDynamicallyWithRequestFactory,
-} from './check_privileges_dynamically';
-import {
- CheckSavedObjectsPrivilegesWithRequest,
- checkSavedObjectsPrivilegesWithRequestFactory,
-} from './check_saved_objects_privileges';
-import { AuthorizationMode, authorizationModeFactory } from './mode';
-import { privilegesFactory, PrivilegesService } from './privileges';
-import { initAppAuthorization } from './app_authorization';
-import { initAPIAuthorization } from './api_authorization';
-import { disableUICapabilitiesFactory } from './disable_ui_capabilities';
-import { validateFeaturePrivileges } from './validate_feature_privileges';
-import { validateReservedPrivileges } from './validate_reserved_privileges';
-import { registerPrivilegesWithCluster } from './register_privileges_with_cluster';
-import { APPLICATION_PREFIX } from '../../common/constants';
-import { SecurityLicense } from '../../common/licensing';
-
export { Actions } from './actions';
+export { AuthorizationService, AuthorizationServiceSetup } from './authorization_service';
export { CheckSavedObjectsPrivileges } from './check_saved_objects_privileges';
export { featurePrivilegeIterator } from './privileges';
-
-interface SetupAuthorizationParams {
- packageVersion: string;
- http: CoreSetup['http'];
- clusterClient: IClusterClient;
- license: SecurityLicense;
- loggers: LoggerFactory;
- featuresService: FeaturesService;
- kibanaIndexName: string;
- getSpacesService(): SpacesService | undefined;
-}
-
-export interface Authorization {
- actions: Actions;
- checkPrivilegesWithRequest: CheckPrivilegesWithRequest;
- checkPrivilegesDynamicallyWithRequest: CheckPrivilegesDynamicallyWithRequest;
- checkSavedObjectsPrivilegesWithRequest: CheckSavedObjectsPrivilegesWithRequest;
- applicationName: string;
- mode: AuthorizationMode;
- privileges: PrivilegesService;
- disableUnauthorizedCapabilities: (
- request: KibanaRequest,
- capabilities: UICapabilities
- ) => Promise;
- registerPrivilegesWithCluster: () => Promise;
-}
-
-export function setupAuthorization({
- http,
- packageVersion,
- clusterClient,
- license,
- loggers,
- featuresService,
- kibanaIndexName,
- getSpacesService,
-}: SetupAuthorizationParams): Authorization {
- const actions = new Actions(packageVersion);
- const mode = authorizationModeFactory(license);
- const applicationName = `${APPLICATION_PREFIX}${kibanaIndexName}`;
- const checkPrivilegesWithRequest = checkPrivilegesWithRequestFactory(
- actions,
- clusterClient,
- applicationName
- );
- const privileges = privilegesFactory(actions, featuresService, license);
- const logger = loggers.get('authorization');
-
- const authz = {
- actions,
- applicationName,
- checkPrivilegesWithRequest,
- checkPrivilegesDynamicallyWithRequest: checkPrivilegesDynamicallyWithRequestFactory(
- checkPrivilegesWithRequest,
- getSpacesService
- ),
- checkSavedObjectsPrivilegesWithRequest: checkSavedObjectsPrivilegesWithRequestFactory(
- checkPrivilegesWithRequest,
- getSpacesService
- ),
- mode,
- privileges,
-
- async disableUnauthorizedCapabilities(request: KibanaRequest, capabilities: UICapabilities) {
- // If we have a license which doesn't enable security, or we're a legacy user we shouldn't
- // disable any ui capabilities
- if (!mode.useRbacForRequest(request)) {
- return capabilities;
- }
-
- const disableUICapabilities = disableUICapabilitiesFactory(
- request,
- featuresService.getFeatures(),
- logger,
- authz
- );
-
- if (!request.auth.isAuthenticated) {
- return disableUICapabilities.all(capabilities);
- }
-
- return await disableUICapabilities.usingPrivileges(capabilities);
- },
-
- registerPrivilegesWithCluster: async () => {
- const features = featuresService.getFeatures();
- validateFeaturePrivileges(features);
- validateReservedPrivileges(features);
-
- await registerPrivilegesWithCluster(logger, privileges, applicationName, clusterClient);
- },
- };
-
- initAPIAuthorization(http, authz, loggers.get('api-authorization'));
- initAppAuthorization(http, authz, loggers.get('app-authorization'), featuresService);
-
- return authz;
-}
diff --git a/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts b/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts
index b023c12d35b79a..06f064a379fe6e 100644
--- a/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts
+++ b/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts
@@ -8,6 +8,8 @@ import { Feature } from '../../../../features/server';
import { Actions } from '../actions';
import { privilegesFactory } from './privileges';
+import { featuresPluginMock } from '../../../../features/server/mocks';
+
const actions = new Actions('1.0.0-zeta1');
describe('features', () => {
@@ -42,7 +44,9 @@ describe('features', () => {
}),
];
- const mockFeaturesService = { getFeatures: jest.fn().mockReturnValue(features) };
+ const mockFeaturesService = featuresPluginMock.createSetup();
+ mockFeaturesService.getFeatures.mockReturnValue(features);
+
const mockLicenseService = {
getFeatures: jest.fn().mockReturnValue({ allowSubFeaturePrivileges: true }),
};
diff --git a/x-pack/plugins/security/server/authorization/privileges/privileges.ts b/x-pack/plugins/security/server/authorization/privileges/privileges.ts
index f3b2881e79ece5..5a15290a7f1a29 100644
--- a/x-pack/plugins/security/server/authorization/privileges/privileges.ts
+++ b/x-pack/plugins/security/server/authorization/privileges/privileges.ts
@@ -6,11 +6,10 @@
import { uniq } from 'lodash';
import { SecurityLicense } from '../../../common/licensing';
-import { Feature } from '../../../../features/server';
+import { Feature, PluginSetupContract as FeaturesPluginSetup } from '../../../../features/server';
import { RawKibanaPrivileges } from '../../../common/model';
import { Actions } from '../actions';
import { featurePrivilegeBuilderFactory } from './feature_privilege_builder';
-import { FeaturesService } from '../../plugin';
import {
featurePrivilegeIterator,
subFeaturePrivilegeIterator,
@@ -22,7 +21,7 @@ export interface PrivilegesService {
export function privilegesFactory(
actions: Actions,
- featuresService: FeaturesService,
+ featuresService: FeaturesPluginSetup,
licenseService: Pick
) {
const featurePrivilegeBuilder = featurePrivilegeBuilderFactory(actions);
diff --git a/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts b/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts
index fff4345c72409f..e21203e60b887e 100644
--- a/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts
+++ b/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts
@@ -49,7 +49,7 @@ const registerPrivilegesWithClusterTest = (
});
for (const deletedPrivilege of deletedPrivileges) {
expect(mockLogger.debug).toHaveBeenCalledWith(
- `Deleting Kibana Privilege ${deletedPrivilege} from Elasticearch for ${application}`
+ `Deleting Kibana Privilege ${deletedPrivilege} from Elasticsearch for ${application}`
);
expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith(
'shield.deletePrivilege',
@@ -82,7 +82,7 @@ const registerPrivilegesWithClusterTest = (
`Registering Kibana Privileges with Elasticsearch for ${application}`
);
expect(mockLogger.debug).toHaveBeenCalledWith(
- `Kibana Privileges already registered with Elasticearch for ${application}`
+ `Kibana Privileges already registered with Elasticsearch for ${application}`
);
};
};
diff --git a/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.ts b/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.ts
index 22e7830d20e28e..8e54794494a908 100644
--- a/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.ts
+++ b/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.ts
@@ -61,14 +61,14 @@ export async function registerPrivilegesWithCluster(
privilege: application,
});
if (arePrivilegesEqual(existingPrivileges, expectedPrivileges)) {
- logger.debug(`Kibana Privileges already registered with Elasticearch for ${application}`);
+ logger.debug(`Kibana Privileges already registered with Elasticsearch for ${application}`);
return;
}
const privilegesToDelete = getPrivilegesToDelete(existingPrivileges, expectedPrivileges);
for (const privilegeToDelete of privilegesToDelete) {
logger.debug(
- `Deleting Kibana Privilege ${privilegeToDelete} from Elasticearch for ${application}`
+ `Deleting Kibana Privilege ${privilegeToDelete} from Elasticsearch for ${application}`
);
try {
await clusterClient.callAsInternalUser('shield.deletePrivilege', {
diff --git a/x-pack/plugins/security/server/authorization/service.test.mocks.ts b/x-pack/plugins/security/server/authorization/service.test.mocks.ts
index 5cd2eac20094dd..d73adde66a4907 100644
--- a/x-pack/plugins/security/server/authorization/service.test.mocks.ts
+++ b/x-pack/plugins/security/server/authorization/service.test.mocks.ts
@@ -28,3 +28,8 @@ export const mockAuthorizationModeFactory = jest.fn();
jest.mock('./mode', () => ({
authorizationModeFactory: mockAuthorizationModeFactory,
}));
+
+export const mockRegisterPrivilegesWithCluster = jest.fn();
+jest.mock('./register_privileges_with_cluster', () => ({
+ registerPrivilegesWithCluster: mockRegisterPrivilegesWithCluster,
+}));
diff --git a/x-pack/plugins/security/server/feature_usage/feature_usage_service.test.ts b/x-pack/plugins/security/server/feature_usage/feature_usage_service.test.ts
new file mode 100644
index 00000000000000..46796fa73ef26f
--- /dev/null
+++ b/x-pack/plugins/security/server/feature_usage/feature_usage_service.test.ts
@@ -0,0 +1,42 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { SecurityFeatureUsageService } from './feature_usage_service';
+
+describe('#setup', () => {
+ it('registers all known security features', () => {
+ const featureUsage = { register: jest.fn() };
+ const securityFeatureUsage = new SecurityFeatureUsageService();
+ securityFeatureUsage.setup({ featureUsage });
+ expect(featureUsage.register).toHaveBeenCalledTimes(2);
+ expect(featureUsage.register.mock.calls.map((c) => c[0])).toMatchInlineSnapshot(`
+ Array [
+ "Subfeature privileges",
+ "Pre-access agreement",
+ ]
+ `);
+ });
+});
+
+describe('start contract', () => {
+ it('notifies when sub-feature privileges are in use', () => {
+ const featureUsage = { notifyUsage: jest.fn(), getLastUsages: jest.fn() };
+ const securityFeatureUsage = new SecurityFeatureUsageService();
+ const startContract = securityFeatureUsage.start({ featureUsage });
+ startContract.recordSubFeaturePrivilegeUsage();
+ expect(featureUsage.notifyUsage).toHaveBeenCalledTimes(1);
+ expect(featureUsage.notifyUsage).toHaveBeenCalledWith('Subfeature privileges');
+ });
+
+ it('notifies when pre-access agreement is used', () => {
+ const featureUsage = { notifyUsage: jest.fn(), getLastUsages: jest.fn() };
+ const securityFeatureUsage = new SecurityFeatureUsageService();
+ const startContract = securityFeatureUsage.start({ featureUsage });
+ startContract.recordPreAccessAgreementUsage();
+ expect(featureUsage.notifyUsage).toHaveBeenCalledTimes(1);
+ expect(featureUsage.notifyUsage).toHaveBeenCalledWith('Pre-access agreement');
+ });
+});
diff --git a/x-pack/plugins/security/server/feature_usage/feature_usage_service.ts b/x-pack/plugins/security/server/feature_usage/feature_usage_service.ts
new file mode 100644
index 00000000000000..1bc1e664981bf6
--- /dev/null
+++ b/x-pack/plugins/security/server/feature_usage/feature_usage_service.ts
@@ -0,0 +1,38 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { FeatureUsageServiceSetup, FeatureUsageServiceStart } from '../../../licensing/server';
+
+interface SetupDeps {
+ featureUsage: FeatureUsageServiceSetup;
+}
+
+interface StartDeps {
+ featureUsage: FeatureUsageServiceStart;
+}
+
+export interface SecurityFeatureUsageServiceStart {
+ recordPreAccessAgreementUsage: () => void;
+ recordSubFeaturePrivilegeUsage: () => void;
+}
+
+export class SecurityFeatureUsageService {
+ public setup({ featureUsage }: SetupDeps) {
+ featureUsage.register('Subfeature privileges', 'gold');
+ featureUsage.register('Pre-access agreement', 'gold');
+ }
+
+ public start({ featureUsage }: StartDeps): SecurityFeatureUsageServiceStart {
+ return {
+ recordPreAccessAgreementUsage() {
+ featureUsage.notifyUsage('Pre-access agreement');
+ },
+ recordSubFeaturePrivilegeUsage() {
+ featureUsage.notifyUsage('Subfeature privileges');
+ },
+ };
+ }
+}
diff --git a/x-pack/plugins/security/server/feature_usage/index.mock.ts b/x-pack/plugins/security/server/feature_usage/index.mock.ts
new file mode 100644
index 00000000000000..6ed42145abd766
--- /dev/null
+++ b/x-pack/plugins/security/server/feature_usage/index.mock.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { SecurityFeatureUsageServiceStart } from './feature_usage_service';
+
+export const securityFeatureUsageServiceMock = {
+ createStartContract() {
+ return {
+ recordPreAccessAgreementUsage: jest.fn(),
+ recordSubFeaturePrivilegeUsage: jest.fn(),
+ } as jest.Mocked;
+ },
+};
diff --git a/x-pack/plugins/security/server/feature_usage/index.ts b/x-pack/plugins/security/server/feature_usage/index.ts
new file mode 100644
index 00000000000000..a3e1f35ee3824e
--- /dev/null
+++ b/x-pack/plugins/security/server/feature_usage/index.ts
@@ -0,0 +1,10 @@
+/*
+ * 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 {
+ SecurityFeatureUsageService,
+ SecurityFeatureUsageServiceStart,
+} from './feature_usage_service';
diff --git a/x-pack/plugins/security/server/mocks.ts b/x-pack/plugins/security/server/mocks.ts
index 72a946d6c51557..c2d99433b03466 100644
--- a/x-pack/plugins/security/server/mocks.ts
+++ b/x-pack/plugins/security/server/mocks.ts
@@ -4,8 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { SecurityPluginSetup } from './plugin';
-
import { authenticationMock } from './authentication/index.mock';
import { authorizationMock } from './authorization/index.mock';
import { licenseMock } from '../common/licensing/index.mock';
@@ -23,7 +21,6 @@ function createSetupMock() {
},
registerSpacesService: jest.fn(),
license: licenseMock.create(),
- __legacyCompat: {} as SecurityPluginSetup['__legacyCompat'],
};
}
diff --git a/x-pack/plugins/security/server/plugin.test.ts b/x-pack/plugins/security/server/plugin.test.ts
index 3e30ff9447f3e0..e01c608e5f3065 100644
--- a/x-pack/plugins/security/server/plugin.test.ts
+++ b/x-pack/plugins/security/server/plugin.test.ts
@@ -41,16 +41,15 @@ describe('Security Plugin', () => {
mockClusterClient = elasticsearchServiceMock.createCustomClusterClient();
mockCoreSetup.elasticsearch.legacy.createClient.mockReturnValue(mockClusterClient);
- mockDependencies = { licensing: { license$: of({}) } } as PluginSetupDependencies;
+ mockDependencies = ({
+ licensing: { license$: of({}), featureUsage: { register: jest.fn() } },
+ } as unknown) as PluginSetupDependencies;
});
describe('setup()', () => {
it('exposes proper contract', async () => {
await expect(plugin.setup(mockCoreSetup, mockDependencies)).resolves.toMatchInlineSnapshot(`
Object {
- "__legacyCompat": Object {
- "registerPrivilegesWithCluster": [Function],
- },
"audit": Object {
"getLogger": [Function],
},
diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts
index bdda0be9b15a76..c8f47aaae7b5d8 100644
--- a/x-pack/plugins/security/server/plugin.ts
+++ b/x-pack/plugins/security/server/plugin.ts
@@ -8,32 +8,35 @@ import { combineLatest } from 'rxjs';
import { first, map } from 'rxjs/operators';
import { TypeOf } from '@kbn/config-schema';
import {
+ deepFreeze,
ICustomClusterClient,
CoreSetup,
+ CoreStart,
Logger,
PluginInitializerContext,
} from '../../../../src/core/server';
-import { deepFreeze } from '../../../../src/core/server';
import { SpacesPluginSetup } from '../../spaces/server';
-import { PluginSetupContract as FeaturesSetupContract } from '../../features/server';
-import { LicensingPluginSetup } from '../../licensing/server';
+import {
+ PluginSetupContract as FeaturesPluginSetup,
+ PluginStartContract as FeaturesPluginStart,
+} from '../../features/server';
+import { LicensingPluginSetup, LicensingPluginStart } from '../../licensing/server';
import { Authentication, setupAuthentication } from './authentication';
-import { Authorization, setupAuthorization } from './authorization';
+import { AuthorizationService, AuthorizationServiceSetup } from './authorization';
import { ConfigSchema, createConfig } from './config';
import { defineRoutes } from './routes';
import { SecurityLicenseService, SecurityLicense } from '../common/licensing';
import { setupSavedObjects } from './saved_objects';
import { AuditService, SecurityAuditLogger, AuditServiceSetup } from './audit';
import { elasticsearchClientPlugin } from './elasticsearch_client_plugin';
+import { SecurityFeatureUsageService, SecurityFeatureUsageServiceStart } from './feature_usage';
export type SpacesService = Pick<
SpacesPluginSetup['spacesService'],
'getSpaceId' | 'namespaceToSpaceId'
>;
-export type FeaturesService = Pick;
-
/**
* Describes public Security plugin contract returned at the `setup` stage.
*/
@@ -48,7 +51,7 @@ export interface SecurityPluginSetup {
| 'grantAPIKeyAsInternalUser'
| 'invalidateAPIKeyAsInternalUser'
>;
- authz: Pick;
+ authz: Pick;
license: SecurityLicense;
audit: Pick;
@@ -61,17 +64,18 @@ export interface SecurityPluginSetup {
* @param service Spaces service exposed by the Spaces plugin.
*/
registerSpacesService: (service: SpacesService) => void;
-
- __legacyCompat: {
- registerPrivilegesWithCluster: () => void;
- };
}
export interface PluginSetupDependencies {
- features: FeaturesService;
+ features: FeaturesPluginSetup;
licensing: LicensingPluginSetup;
}
+export interface PluginStartDependencies {
+ features: FeaturesPluginStart;
+ licensing: LicensingPluginStart;
+}
+
/**
* Represents Security Plugin instance that will be managed by the Kibana plugin system.
*/
@@ -80,7 +84,18 @@ export class Plugin {
private clusterClient?: ICustomClusterClient;
private spacesService?: SpacesService | symbol = Symbol('not accessed');
private securityLicenseService?: SecurityLicenseService;
+
+ private readonly featureUsageService = new SecurityFeatureUsageService();
+ private featureUsageServiceStart?: SecurityFeatureUsageServiceStart;
+ private readonly getFeatureUsageService = () => {
+ if (!this.featureUsageServiceStart) {
+ throw new Error(`featureUsageServiceStart is not registered!`);
+ }
+ return this.featureUsageServiceStart;
+ };
+
private readonly auditService = new AuditService(this.initializerContext.logger.get('audit'));
+ private readonly authorizationService = new AuthorizationService();
private readonly getSpacesService = () => {
// Changing property value from Symbol to undefined denotes the fact that property was accessed.
@@ -95,7 +110,10 @@ export class Plugin {
this.logger = this.initializerContext.logger.get();
}
- public async setup(core: CoreSetup, { features, licensing }: PluginSetupDependencies) {
+ public async setup(
+ core: CoreSetup,
+ { features, licensing }: PluginSetupDependencies
+ ) {
const [config, legacyConfig] = await combineLatest([
this.initializerContext.config.create>().pipe(
map((rawConfig) =>
@@ -118,11 +136,14 @@ export class Plugin {
license$: licensing.license$,
});
+ this.featureUsageService.setup({ featureUsage: licensing.featureUsage });
+
const audit = this.auditService.setup({ license, config: config.audit });
const auditLogger = new SecurityAuditLogger(audit.getLogger());
const authc = await setupAuthentication({
auditLogger,
+ getFeatureUsageService: this.getFeatureUsageService,
http: core.http,
clusterClient: this.clusterClient,
config,
@@ -130,15 +151,17 @@ export class Plugin {
loggers: this.initializerContext.logger,
});
- const authz = await setupAuthorization({
+ const authz = this.authorizationService.setup({
http: core.http,
+ capabilities: core.capabilities,
+ status: core.status,
clusterClient: this.clusterClient,
license,
loggers: this.initializerContext.logger,
kibanaIndexName: legacyConfig.kibana.index,
packageVersion: this.initializerContext.env.packageInfo.version,
getSpacesService: this.getSpacesService,
- featuresService: features,
+ features,
});
setupSavedObjects({
@@ -148,8 +171,6 @@ export class Plugin {
getSpacesService: this.getSpacesService,
});
- core.capabilities.registerSwitcher(authz.disableUnauthorizedCapabilities);
-
defineRoutes({
router: core.http.createRouter(),
basePath: core.http.basePath,
@@ -160,6 +181,11 @@ export class Plugin {
authc,
authz,
license,
+ getFeatures: () =>
+ core
+ .getStartServices()
+ .then(([, { features: featuresStart }]) => featuresStart.getFeatures()),
+ getFeatureUsageService: this.getFeatureUsageService,
});
return deepFreeze({
@@ -192,15 +218,15 @@ export class Plugin {
this.spacesService = service;
},
-
- __legacyCompat: {
- registerPrivilegesWithCluster: async () => await authz.registerPrivilegesWithCluster(),
- },
});
}
- public start() {
+ public start(core: CoreStart, { features, licensing }: PluginStartDependencies) {
this.logger.debug('Starting plugin');
+ this.featureUsageServiceStart = this.featureUsageService.start({
+ featureUsage: licensing.featureUsage,
+ });
+ this.authorizationService.start({ features, clusterClient: this.clusterClient! });
}
public stop() {
@@ -216,7 +242,11 @@ export class Plugin {
this.securityLicenseService = undefined;
}
+ if (this.featureUsageServiceStart) {
+ this.featureUsageServiceStart = undefined;
+ }
this.auditService.stop();
+ this.authorizationService.stop();
}
private wasSpacesServiceAccessed() {
diff --git a/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts
index d7710bf669ce1e..bec60fa149bcf9 100644
--- a/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts
+++ b/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts
@@ -15,6 +15,8 @@ import {
httpServerMock,
} from '../../../../../../../src/core/server/mocks';
import { routeDefinitionParamsMock } from '../../index.mock';
+import { Feature } from '../../../../../features/server';
+import { securityFeatureUsageServiceMock } from '../../../feature_usage/index.mock';
const application = 'kibana-.kibana';
const privilegeMap = {
@@ -47,7 +49,12 @@ interface TestOptions {
licenseCheckResult?: LicenseCheck;
apiResponses?: Array<() => Promise>;
payload?: Record;
- asserts: { statusCode: number; result?: Record; apiArguments?: unknown[][] };
+ asserts: {
+ statusCode: number;
+ result?: Record;
+ apiArguments?: unknown[][];
+ recordSubFeaturePrivilegeUsage?: boolean;
+ };
}
const putRoleTest = (
@@ -71,6 +78,47 @@ const putRoleTest = (
mockScopedClusterClient.callAsCurrentUser.mockImplementationOnce(apiResponse);
}
+ mockRouteDefinitionParams.getFeatureUsageService.mockReturnValue(
+ securityFeatureUsageServiceMock.createStartContract()
+ );
+
+ mockRouteDefinitionParams.getFeatures.mockResolvedValue([
+ new Feature({
+ id: 'feature_1',
+ name: 'feature 1',
+ app: [],
+ privileges: {
+ all: {
+ ui: [],
+ savedObject: { all: [], read: [] },
+ },
+ read: {
+ ui: [],
+ savedObject: { all: [], read: [] },
+ },
+ },
+ subFeatures: [
+ {
+ name: 'sub feature 1',
+ privilegeGroups: [
+ {
+ groupType: 'independent',
+ privileges: [
+ {
+ id: 'sub_feature_privilege_1',
+ name: 'first sub-feature privilege',
+ includeIn: 'none',
+ ui: [],
+ savedObject: { all: [], read: [] },
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ }),
+ ]);
+
definePutRolesRoutes(mockRouteDefinitionParams);
const [[{ validate }, handler]] = mockRouteDefinitionParams.router.put.mock.calls;
@@ -99,6 +147,16 @@ const putRoleTest = (
expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled();
}
expect(mockContext.licensing.license.check).toHaveBeenCalledWith('security', 'basic');
+
+ if (asserts.recordSubFeaturePrivilegeUsage) {
+ expect(
+ mockRouteDefinitionParams.getFeatureUsageService().recordSubFeaturePrivilegeUsage
+ ).toHaveBeenCalledTimes(1);
+ } else {
+ expect(
+ mockRouteDefinitionParams.getFeatureUsageService().recordSubFeaturePrivilegeUsage
+ ).not.toHaveBeenCalled();
+ }
});
};
@@ -598,5 +656,131 @@ describe('PUT role', () => {
result: undefined,
},
});
+
+ putRoleTest(`notifies when sub-feature privileges are included`, {
+ name: 'foo-role',
+ payload: {
+ kibana: [
+ {
+ spaces: ['*'],
+ feature: {
+ feature_1: ['sub_feature_privilege_1'],
+ },
+ },
+ ],
+ },
+ apiResponses: [async () => ({}), async () => {}],
+ asserts: {
+ recordSubFeaturePrivilegeUsage: true,
+ apiArguments: [
+ ['shield.getRole', { name: 'foo-role', ignore: [404] }],
+ [
+ 'shield.putRole',
+ {
+ name: 'foo-role',
+ body: {
+ cluster: [],
+ indices: [],
+ run_as: [],
+ applications: [
+ {
+ application: 'kibana-.kibana',
+ privileges: ['feature_feature_1.sub_feature_privilege_1'],
+ resources: ['*'],
+ },
+ ],
+ metadata: undefined,
+ },
+ },
+ ],
+ ],
+ statusCode: 204,
+ result: undefined,
+ },
+ });
+
+ putRoleTest(`does not record sub-feature privilege usage for unknown privileges`, {
+ name: 'foo-role',
+ payload: {
+ kibana: [
+ {
+ spaces: ['*'],
+ feature: {
+ feature_1: ['unknown_sub_feature_privilege_1'],
+ },
+ },
+ ],
+ },
+ apiResponses: [async () => ({}), async () => {}],
+ asserts: {
+ recordSubFeaturePrivilegeUsage: false,
+ apiArguments: [
+ ['shield.getRole', { name: 'foo-role', ignore: [404] }],
+ [
+ 'shield.putRole',
+ {
+ name: 'foo-role',
+ body: {
+ cluster: [],
+ indices: [],
+ run_as: [],
+ applications: [
+ {
+ application: 'kibana-.kibana',
+ privileges: ['feature_feature_1.unknown_sub_feature_privilege_1'],
+ resources: ['*'],
+ },
+ ],
+ metadata: undefined,
+ },
+ },
+ ],
+ ],
+ statusCode: 204,
+ result: undefined,
+ },
+ });
+
+ putRoleTest(`does not record sub-feature privilege usage for unknown features`, {
+ name: 'foo-role',
+ payload: {
+ kibana: [
+ {
+ spaces: ['*'],
+ feature: {
+ unknown_feature: ['sub_feature_privilege_1'],
+ },
+ },
+ ],
+ },
+ apiResponses: [async () => ({}), async () => {}],
+ asserts: {
+ recordSubFeaturePrivilegeUsage: false,
+ apiArguments: [
+ ['shield.getRole', { name: 'foo-role', ignore: [404] }],
+ [
+ 'shield.putRole',
+ {
+ name: 'foo-role',
+ body: {
+ cluster: [],
+ indices: [],
+ run_as: [],
+ applications: [
+ {
+ application: 'kibana-.kibana',
+ privileges: ['feature_unknown_feature.sub_feature_privilege_1'],
+ resources: ['*'],
+ },
+ ],
+ metadata: undefined,
+ },
+ },
+ ],
+ ],
+ statusCode: 204,
+ result: undefined,
+ },
+ });
});
});
diff --git a/x-pack/plugins/security/server/routes/authorization/roles/put.ts b/x-pack/plugins/security/server/routes/authorization/roles/put.ts
index 5db83375afa965..d83cf92bcaa0d4 100644
--- a/x-pack/plugins/security/server/routes/authorization/roles/put.ts
+++ b/x-pack/plugins/security/server/routes/authorization/roles/put.ts
@@ -4,7 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { schema } from '@kbn/config-schema';
+import { schema, TypeOf } from '@kbn/config-schema';
+import { Feature } from '../../../../../features/common';
import { RouteDefinitionParams } from '../../index';
import { createLicensedRouteHandler } from '../../licensed_route_handler';
import { wrapIntoCustomErrorResponse } from '../../../errors';
@@ -14,7 +15,37 @@ import {
transformPutPayloadToElasticsearchRole,
} from './model';
-export function definePutRolesRoutes({ router, authz, clusterClient }: RouteDefinitionParams) {
+const roleGrantsSubFeaturePrivileges = (
+ features: Feature[],
+ role: TypeOf>
+) => {
+ if (!role.kibana) {
+ return false;
+ }
+
+ const subFeaturePrivileges = new Map(
+ features.map((feature) => [
+ feature.id,
+ feature.subFeatures.map((sf) => sf.privilegeGroups.map((pg) => pg.privileges)).flat(2),
+ ])
+ );
+
+ const hasAnySubFeaturePrivileges = role.kibana.some((kibanaPrivilege) =>
+ Object.entries(kibanaPrivilege.feature ?? {}).some(([featureId, privileges]) => {
+ return !!subFeaturePrivileges.get(featureId)?.some(({ id }) => privileges.includes(id));
+ })
+ );
+
+ return hasAnySubFeaturePrivileges;
+};
+
+export function definePutRolesRoutes({
+ router,
+ authz,
+ clusterClient,
+ getFeatures,
+ getFeatureUsageService,
+}: RouteDefinitionParams) {
router.put(
{
path: '/api/security/role/{name}',
@@ -46,9 +77,16 @@ export function definePutRolesRoutes({ router, authz, clusterClient }: RouteDefi
rawRoles[name] ? rawRoles[name].applications : []
);
- await clusterClient
- .asScoped(request)
- .callAsCurrentUser('shield.putRole', { name: request.params.name, body });
+ const [features] = await Promise.all([
+ getFeatures(),
+ clusterClient
+ .asScoped(request)
+ .callAsCurrentUser('shield.putRole', { name: request.params.name, body }),
+ ]);
+
+ if (roleGrantsSubFeaturePrivileges(features, request.body)) {
+ getFeatureUsageService().recordSubFeaturePrivilegeUsage();
+ }
return response.noContent();
} catch (error) {
diff --git a/x-pack/plugins/security/server/routes/index.mock.ts b/x-pack/plugins/security/server/routes/index.mock.ts
index b0c74b98ee19bf..1a93d6701e257d 100644
--- a/x-pack/plugins/security/server/routes/index.mock.ts
+++ b/x-pack/plugins/security/server/routes/index.mock.ts
@@ -29,5 +29,7 @@ export const routeDefinitionParamsMock = {
authz: authorizationMock.create(),
license: licenseMock.create(),
httpResources: httpResourcesMock.createRegistrar(),
+ getFeatures: jest.fn(),
+ getFeatureUsageService: jest.fn(),
}),
};
diff --git a/x-pack/plugins/security/server/routes/index.ts b/x-pack/plugins/security/server/routes/index.ts
index e43072b95c9067..5721a2699d15ce 100644
--- a/x-pack/plugins/security/server/routes/index.ts
+++ b/x-pack/plugins/security/server/routes/index.ts
@@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { Feature } from '../../../features/server';
import {
CoreSetup,
HttpResources,
@@ -13,7 +14,7 @@ import {
} from '../../../../../src/core/server';
import { SecurityLicense } from '../../common/licensing';
import { Authentication } from '../authentication';
-import { Authorization } from '../authorization';
+import { AuthorizationServiceSetup } from '../authorization';
import { ConfigType } from '../config';
import { defineAuthenticationRoutes } from './authentication';
@@ -23,6 +24,7 @@ import { defineIndicesRoutes } from './indices';
import { defineUsersRoutes } from './users';
import { defineRoleMappingRoutes } from './role_mapping';
import { defineViewRoutes } from './views';
+import { SecurityFeatureUsageServiceStart } from '../feature_usage';
/**
* Describes parameters used to define HTTP routes.
@@ -35,8 +37,10 @@ export interface RouteDefinitionParams {
clusterClient: IClusterClient;
config: ConfigType;
authc: Authentication;
- authz: Authorization;
+ authz: AuthorizationServiceSetup;
license: SecurityLicense;
+ getFeatures: () => Promise;
+ getFeatureUsageService: () => SecurityFeatureUsageServiceStart;
}
export function defineRoutes(params: RouteDefinitionParams) {
diff --git a/x-pack/plugins/security/server/saved_objects/index.ts b/x-pack/plugins/security/server/saved_objects/index.ts
index 29fbe3af21b951..6acfd06a0309bd 100644
--- a/x-pack/plugins/security/server/saved_objects/index.ts
+++ b/x-pack/plugins/security/server/saved_objects/index.ts
@@ -11,13 +11,16 @@ import {
SavedObjectsClient,
} from '../../../../../src/core/server';
import { SecureSavedObjectsClientWrapper } from './secure_saved_objects_client_wrapper';
-import { Authorization } from '../authorization';
+import { AuthorizationServiceSetup } from '../authorization';
import { SecurityAuditLogger } from '../audit';
import { SpacesService } from '../plugin';
interface SetupSavedObjectsParams {
auditLogger: SecurityAuditLogger;
- authz: Pick;
+ authz: Pick<
+ AuthorizationServiceSetup,
+ 'mode' | 'actions' | 'checkSavedObjectsPrivilegesWithRequest'
+ >;
savedObjects: CoreSetup['savedObjects'];
getSpacesService(): SpacesService | undefined;
}
diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts
index b5cd8f2dec0a3f..482794804685d9 100644
--- a/x-pack/plugins/security_solution/common/constants.ts
+++ b/x-pack/plugins/security_solution/common/constants.ts
@@ -8,6 +8,7 @@ export const APP_ID = 'securitySolution';
export const APP_NAME = 'Security';
export const APP_ICON = 'securityAnalyticsApp';
export const APP_PATH = `/app/security`;
+export const ADD_DATA_PATH = `/app/home#/tutorial_directory/security`;
export const DEFAULT_BYTES_FORMAT = 'format:bytes:defaultPattern';
export const DEFAULT_DATE_FORMAT = 'dateFormat';
export const DEFAULT_DATE_FORMAT_TZ = 'dateFormat:tz';
diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.test.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.test.ts
index 3fcb00d879583d..6c8c5e3f518082 100644
--- a/x-pack/plugins/security_solution/common/endpoint/generate_data.test.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.test.ts
@@ -3,7 +3,14 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { EndpointDocGenerator, Event, Tree, TreeNode } from './generate_data';
+import {
+ EndpointDocGenerator,
+ Event,
+ Tree,
+ TreeNode,
+ RelatedEventCategory,
+ ECSCategory,
+} from './generate_data';
interface Node {
events: Event[];
@@ -106,7 +113,11 @@ describe('data generator', () => {
generations,
percentTerminated: 100,
percentWithRelated: 100,
- relatedEvents: 4,
+ relatedEvents: [
+ { category: RelatedEventCategory.Driver, count: 1 },
+ { category: RelatedEventCategory.File, count: 2 },
+ { category: RelatedEventCategory.Network, count: 1 },
+ ],
});
});
@@ -117,6 +128,36 @@ describe('data generator', () => {
return (inRelated || inLifecycle) && event.process.entity_id === node.id;
};
+ it('has the right related events for each node', () => {
+ const checkRelatedEvents = (node: TreeNode) => {
+ expect(node.relatedEvents.length).toEqual(4);
+
+ const counts: Record = {};
+ for (const event of node.relatedEvents) {
+ if (Array.isArray(event.event.category)) {
+ for (const cat of event.event.category) {
+ counts[cat] = counts[cat] + 1 || 1;
+ }
+ } else {
+ counts[event.event.category] = counts[event.event.category] + 1 || 1;
+ }
+ }
+ expect(counts[ECSCategory.Driver]).toEqual(1);
+ expect(counts[ECSCategory.File]).toEqual(2);
+ expect(counts[ECSCategory.Network]).toEqual(1);
+ };
+
+ for (const node of tree.ancestry.values()) {
+ checkRelatedEvents(node);
+ }
+
+ for (const node of tree.children.values()) {
+ checkRelatedEvents(node);
+ }
+
+ checkRelatedEvents(tree.origin);
+ });
+
it('has the right number of ancestors', () => {
expect(tree.ancestry.size).toEqual(ancestors);
});
diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
index 57d41b65549073..b17a5aa28ac6a3 100644
--- a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
@@ -24,7 +24,7 @@ interface EventOptions {
entityID?: string;
parentEntityID?: string;
eventType?: string;
- eventCategory?: string;
+ eventCategory?: string | string[];
processName?: string;
}
@@ -75,21 +75,98 @@ const POLICIES: Array<{ name: string; id: string }> = [
const FILE_OPERATIONS: string[] = ['creation', 'open', 'rename', 'execution', 'deletion'];
interface EventInfo {
- category: string;
+ category: string | string[];
/**
* This denotes the `event.type` field for when an event is created, this can be `start` or `creation`
*/
creationType: string;
}
+/**
+ * The valid ecs categories.
+ */
+export enum ECSCategory {
+ Driver = 'driver',
+ File = 'file',
+ Network = 'network',
+ /**
+ * Registry has not been added to ecs yet.
+ */
+ Registry = 'registry',
+ Authentication = 'authentication',
+ Session = 'session',
+}
+
+/**
+ * High level categories for related events. These specify the type of related events that should be generated.
+ */
+export enum RelatedEventCategory {
+ /**
+ * The Random category allows the related event categories to be chosen randomly
+ */
+ Random = 'random',
+ Driver = 'driver',
+ File = 'file',
+ Network = 'network',
+ Registry = 'registry',
+ /**
+ * Security isn't an actual category but defines a type of related event to be created.
+ */
+ Security = 'security',
+}
+
+/**
+ * This map defines the relationship between a higher level event type defined by the RelatedEventCategory enums and
+ * the ECS categories that is should map to. This should only be used for tests that need to determine the exact
+ * ecs categories that were created based on the related event information passed to the generator.
+ */
+export const categoryMapping: Record = {
+ [RelatedEventCategory.Security]: [ECSCategory.Authentication, ECSCategory.Session],
+ [RelatedEventCategory.Driver]: ECSCategory.Driver,
+ [RelatedEventCategory.File]: ECSCategory.File,
+ [RelatedEventCategory.Network]: ECSCategory.Network,
+ [RelatedEventCategory.Registry]: ECSCategory.Registry,
+ /**
+ * Random is only used by the generator to indicate that it should randomly choose the event information when generating
+ * related events. It does not map to a specific ecs category.
+ */
+ [RelatedEventCategory.Random]: '',
+};
+
+/**
+ * The related event category and number of events that should be generated.
+ */
+export interface RelatedEventInfo {
+ category: RelatedEventCategory;
+ count: number;
+}
+
// These are from the v1 schemas and aren't all valid ECS event categories, still in flux
-const OTHER_EVENT_CATEGORIES: EventInfo[] = [
- { category: 'driver', creationType: 'start' },
- { category: 'file', creationType: 'creation' },
- { category: 'library', creationType: 'start' },
- { category: 'network', creationType: 'start' },
- { category: 'registry', creationType: 'creation' },
-];
+const OTHER_EVENT_CATEGORIES: Record<
+ Exclude,
+ EventInfo
+> = {
+ [RelatedEventCategory.Security]: {
+ category: categoryMapping[RelatedEventCategory.Security],
+ creationType: 'start',
+ },
+ [RelatedEventCategory.Driver]: {
+ category: categoryMapping[RelatedEventCategory.Driver],
+ creationType: 'start',
+ },
+ [RelatedEventCategory.File]: {
+ category: categoryMapping[RelatedEventCategory.File],
+ creationType: 'creation',
+ },
+ [RelatedEventCategory.Network]: {
+ category: categoryMapping[RelatedEventCategory.Network],
+ creationType: 'start',
+ },
+ [RelatedEventCategory.Registry]: {
+ category: categoryMapping[RelatedEventCategory.Registry],
+ creationType: 'creation',
+ },
+};
interface HostInfo {
elastic: {
@@ -164,7 +241,7 @@ export interface TreeOptions {
ancestors?: number;
generations?: number;
children?: number;
- relatedEvents?: number;
+ relatedEvents?: RelatedEventInfo[];
percentWithRelated?: number;
percentTerminated?: number;
alwaysGenMaxChildrenPerNode?: boolean;
@@ -487,7 +564,8 @@ export class EndpointDocGenerator {
* @param alertAncestors - number of ancestor generations to create relative to the alert
* @param childGenerations - number of child generations to create relative to the alert
* @param maxChildrenPerNode - maximum number of children for any given node in the tree
- * @param relatedEventsPerNode - number of related events (file, registry, etc) to create for each process event in the tree
+ * @param relatedEventsPerNode - can be an array of RelatedEventInfo objects describing the related events that should be generated for each process node
+ * or a number which defines the number of related events and will default to random categories
* @param percentNodesWithRelated - percent of nodes which should have related events
* @param percentTerminated - percent of nodes which will have process termination events
* @param alwaysGenMaxChildrenPerNode - flag to always return the max children per node instead of it being a random number of children
@@ -496,7 +574,7 @@ export class EndpointDocGenerator {
alertAncestors?: number,
childGenerations?: number,
maxChildrenPerNode?: number,
- relatedEventsPerNode?: number,
+ relatedEventsPerNode?: RelatedEventInfo[] | number,
percentNodesWithRelated?: number,
percentTerminated?: number,
alwaysGenMaxChildrenPerNode?: boolean
@@ -525,13 +603,14 @@ export class EndpointDocGenerator {
/**
* Creates an alert event and associated process ancestry. The alert event will always be the last event in the return array.
* @param alertAncestors - number of ancestor generations to create
- * @param relatedEventsPerNode - number of related events to add to each process node being created
+ * @param relatedEventsPerNode - can be an array of RelatedEventInfo objects describing the related events that should be generated for each process node
+ * or a number which defines the number of related events and will default to random categories
* @param pctWithRelated - percent of ancestors that will have related events
* @param pctWithTerminated - percent of ancestors that will have termination events
*/
public createAlertEventAncestry(
alertAncestors = 3,
- relatedEventsPerNode = 5,
+ relatedEventsPerNode: RelatedEventInfo[] | number = 5,
pctWithRelated = 30,
pctWithTerminated = 100
): Event[] {
@@ -611,7 +690,8 @@ export class EndpointDocGenerator {
* @param root - The process event to use as the root node of the tree
* @param generations - number of child generations to create. The root node is not counted as a generation.
* @param maxChildrenPerNode - maximum number of children for any given node in the tree
- * @param relatedEventsPerNode - number of related events (file, registry, etc) to create for each process event in the tree
+ * @param relatedEventsPerNode - can be an array of RelatedEventInfo objects describing the related events that should be generated for each process node
+ * or a number which defines the number of related events and will default to random categories
* @param percentNodesWithRelated - percent of nodes which should have related events
* @param percentChildrenTerminated - percent of nodes which will have process termination events
* @param alwaysGenMaxChildrenPerNode - flag to always return the max children per node instead of it being a random number of children
@@ -620,7 +700,7 @@ export class EndpointDocGenerator {
root: Event,
generations = 2,
maxChildrenPerNode = 2,
- relatedEventsPerNode = 3,
+ relatedEventsPerNode: RelatedEventInfo[] | number = 3,
percentNodesWithRelated = 100,
percentChildrenTerminated = 100,
alwaysGenMaxChildrenPerNode = false
@@ -686,25 +766,40 @@ export class EndpointDocGenerator {
/**
* Creates related events for a process event
* @param node - process event to relate events to by entityID
- * @param numRelatedEvents - number of related events to generate
+ * @param relatedEvents - can be an array of RelatedEventInfo objects describing the related events that should be generated for each process node
+ * or a number which defines the number of related events and will default to random categories
* @param processDuration - maximum number of seconds after process event that related event timestamp can be
*/
public *relatedEventsGenerator(
node: Event,
- numRelatedEvents = 10,
+ relatedEvents: RelatedEventInfo[] | number = 10,
processDuration: number = 6 * 3600
) {
- for (let i = 0; i < numRelatedEvents; i++) {
- const eventInfo = this.randomChoice(OTHER_EVENT_CATEGORIES);
-
- const ts = node['@timestamp'] + this.randomN(processDuration) * 1000;
- yield this.generateEvent({
- timestamp: ts,
- entityID: node.process.entity_id,
- parentEntityID: node.process.parent?.entity_id,
- eventCategory: eventInfo.category,
- eventType: eventInfo.creationType,
- });
+ let relatedEventsInfo: RelatedEventInfo[];
+ if (typeof relatedEvents === 'number') {
+ relatedEventsInfo = [{ category: RelatedEventCategory.Random, count: relatedEvents }];
+ } else {
+ relatedEventsInfo = relatedEvents;
+ }
+ for (const event of relatedEventsInfo) {
+ let eventInfo: EventInfo;
+
+ for (let i = 0; i < event.count; i++) {
+ if (event.category === RelatedEventCategory.Random) {
+ eventInfo = this.randomChoice(Object.values(OTHER_EVENT_CATEGORIES));
+ } else {
+ eventInfo = OTHER_EVENT_CATEGORIES[event.category];
+ }
+
+ const ts = node['@timestamp'] + this.randomN(processDuration) * 1000;
+ yield this.generateEvent({
+ timestamp: ts,
+ entityID: node.process.entity_id,
+ parentEntityID: node.process.parent?.entity_id,
+ eventCategory: eventInfo.category,
+ eventType: eventInfo.creationType,
+ });
+ }
}
}
@@ -834,7 +929,7 @@ export class EndpointDocGenerator {
status: HostPolicyResponseActionStatus.success,
},
{
- name: 'load_malware_mode',
+ name: 'load_malware_model',
message: 'Error deserializing EXE model; no valid malware model installed',
status: HostPolicyResponseActionStatus.success,
},
diff --git a/x-pack/plugins/security_solution/common/endpoint/types.ts b/x-pack/plugins/security_solution/common/endpoint/types.ts
index 45b5cf2526e12d..816f9b77115ec3 100644
--- a/x-pack/plugins/security_solution/common/endpoint/types.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/types.ts
@@ -41,14 +41,30 @@ type ImmutableMap = ReadonlyMap, Immutable>;
type ImmutableSet = ReadonlySet>;
type ImmutableObject = { readonly [K in keyof T]: Immutable };
+export interface EventStats {
+ /**
+ * The total number of related events (all events except process and alerts) that exist for a node.
+ */
+ total: number;
+ /**
+ * A mapping of ECS event.category to the number of related events are marked with that category
+ * For example:
+ * {
+ * network: 5,
+ * file: 2
+ * }
+ */
+ byCategory: Record;
+}
+
/**
* Statistical information for a node in a resolver tree.
*/
export interface ResolverNodeStats {
/**
- * The total number of related events (all events except process and alerts) that exist for a node.
+ * The stats for related events (excludes alerts and process events) for a particular node in the resolver tree.
*/
- totalEvents: number;
+ events: EventStats;
/**
* The total number of alerts that exist for a node.
*/
@@ -379,6 +395,7 @@ export interface LegacyEndpointEvent {
event?: {
action?: string;
type?: string;
+ category?: string | string[];
};
}
diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/helpers.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/helpers.test.tsx
index e39ee38d71da7a..b82d1c0a36ab28 100644
--- a/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/helpers.test.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/helpers.test.tsx
@@ -9,7 +9,11 @@ import { shallow } from 'enzyme';
import { EuiLoadingSpinner } from '@elastic/eui';
import { coreMock } from '../../../../../../../../src/core/public/mocks';
-import { esFilters, FilterManager } from '../../../../../../../../src/plugins/data/public';
+import {
+ esFilters,
+ FilterManager,
+ UI_SETTINGS,
+} from '../../../../../../../../src/plugins/data/public';
import { SeverityBadge } from '../severity_badge';
import * as i18n from './translations';
@@ -29,7 +33,7 @@ import { ListItems } from './types';
const setupMock = coreMock.createSetup();
const uiSettingsMock = (pinnedByDefault: boolean) => (key: string) => {
switch (key) {
- case 'filters:pinnedByDefault':
+ case UI_SETTINGS.FILTERS_PINNED_BY_DEFAULT:
return pinnedByDefault;
default:
throw new Error(`Unexpected uiSettings key in FilterManager mock: ${key}`);
diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/index.test.tsx
index 1f474630fd6e3a..b8f81f6d7e5f7a 100644
--- a/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/index.test.tsx
@@ -13,7 +13,12 @@ import {
getDescriptionItem,
} from '.';
-import { esFilters, Filter, FilterManager } from '../../../../../../../../src/plugins/data/public';
+import {
+ esFilters,
+ Filter,
+ FilterManager,
+ UI_SETTINGS,
+} from '../../../../../../../../src/plugins/data/public';
import {
mockAboutStepRule,
mockDefineStepRule,
@@ -33,7 +38,7 @@ describe('description_step', () => {
const setupMock = coreMock.createSetup();
const uiSettingsMock = (pinnedByDefault: boolean) => (key: string) => {
switch (key) {
- case 'filters:pinnedByDefault':
+ case UI_SETTINGS.FILTERS_PINNED_BY_DEFAULT:
return pinnedByDefault;
default:
throw new Error(`Unexpected uiSettings key in FilterManager mock: ${key}`);
diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/index.tsx
index ad71059984a8b0..778c6bd92bc731 100644
--- a/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/index.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/index.tsx
@@ -162,7 +162,6 @@ const StepRuleActionsComponent: FC = ({
{myStepData.throttle !== stepActionsDefaultValue.throttle ? (
<>
-
= ({
messageVariables: actionMessageParams,
}}
/>
-
>
) : (
= ({
component={GhostFormField}
/>
)}
+
diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine_empty_page.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine_empty_page.tsx
index 9632ddfeadc0d2..0c58f5620964ba 100644
--- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine_empty_page.tsx
+++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine_empty_page.tsx
@@ -9,12 +9,13 @@ import React from 'react';
import { useKibana } from '../../../common/lib/kibana';
import { EmptyPage } from '../../../common/components/empty_page';
import * as i18n from '../../../common/translations';
+import { ADD_DATA_PATH } from '../../../../common/constants';
export const DetectionEngineEmptyPage = React.memo(() => (
(
({ caseId, caseData, fetchCase, updateCase, userCanCrud }) => {
const basePath = window.location.origin + useBasePath();
- const caseLink = `${basePath}/app/siem#/case/${caseId}`;
+ const caseLink = `${basePath}/app/security#/case/${caseId}`;
const search = useGetUrlSearch(navTabs.case);
const [initLoadingData, setInitLoadingData] = useState(true);
const {
diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/translations.ts b/x-pack/plugins/security_solution/public/cases/components/case_view/translations.ts
index 9523e2485a6107..b8219ad52f5b0a 100644
--- a/x-pack/plugins/security_solution/public/cases/components/case_view/translations.ts
+++ b/x-pack/plugins/security_solution/public/cases/components/case_view/translations.ts
@@ -143,7 +143,7 @@ export const CASE_REFRESH = i18n.translate('xpack.securitySolution.case.caseView
export const EMAIL_SUBJECT = (caseTitle: string) =>
i18n.translate('xpack.securitySolution.case.caseView.emailSubject', {
values: { caseTitle },
- defaultMessage: 'SIEM Case - {caseTitle}',
+ defaultMessage: 'Security Case - {caseTitle}',
});
export const EMAIL_BODY = (caseUrl: string) =>
diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.test.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.test.tsx
index 43d5351a5dce3c..67963c7487826a 100644
--- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.test.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.test.tsx
@@ -52,7 +52,7 @@ describe('FieldMappingRow', () => {
test('it pass the corrects props to mapping row', () => {
const rows = wrapper.find(FieldMappingRow);
rows.forEach((row, index) => {
- expect(row.prop('siemField')).toEqual(mapping[index].source);
+ expect(row.prop('securitySolutionField')).toEqual(mapping[index].source);
expect(row.prop('selectedActionType')).toEqual(mapping[index].actionType);
expect(row.prop('selectedThirdParty')).toEqual(mapping[index].target);
});
@@ -68,7 +68,7 @@ describe('FieldMappingRow', () => {
const rows = newWrapper.find(FieldMappingRow);
rows.forEach((row, index) => {
- expect(row.prop('siemField')).toEqual(defaultMapping[index].source);
+ expect(row.prop('securitySolutionField')).toEqual(defaultMapping[index].source);
expect(row.prop('selectedActionType')).toEqual(defaultMapping[index].actionType);
expect(row.prop('selectedThirdParty')).toEqual(defaultMapping[index].target);
});
diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.tsx
index 3d515941fc2f33..415faa96eeeddc 100644
--- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.tsx
@@ -132,7 +132,7 @@ const FieldMappingComponent: React.FC = ({
key={`${item.source}`}
id={`${item.source}`}
disabled={disabled}
- siemField={item.source}
+ securitySolutionField={item.source}
thirdPartyOptions={getThirdPartyOptions(item.source, selectedConnector.fields)}
actionTypeOptions={actionTypeOptions}
onChangeActionType={onChangeActionType}
diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.test.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.test.tsx
index 3787a43ff2d28a..a2acd0e20b6adc 100644
--- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.test.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.test.tsx
@@ -51,7 +51,7 @@ describe('FieldMappingRow', () => {
const props: RowProps = {
id: 'title',
disabled: false,
- siemField: 'title',
+ securitySolutionField: 'title',
thirdPartyOptions,
actionTypeOptions,
onChangeActionType,
diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.tsx
index 922ea7222efce3..6e688b213f82c0 100644
--- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.tsx
@@ -20,7 +20,7 @@ import { AllThirdPartyFields } from '../../../common/lib/connectors/types';
export interface RowProps {
id: string;
disabled: boolean;
- siemField: CaseField;
+ securitySolutionField: CaseField;
thirdPartyOptions: Array>;
actionTypeOptions: Array>;
onChangeActionType: (caseField: CaseField, newActionType: ActionType) => void;
@@ -32,7 +32,7 @@ export interface RowProps {
const FieldMappingRowComponent: React.FC = ({
id,
disabled,
- siemField,
+ securitySolutionField,
thirdPartyOptions,
actionTypeOptions,
onChangeActionType,
@@ -40,13 +40,15 @@ const FieldMappingRowComponent: React.FC = ({
selectedActionType,
selectedThirdParty,
}) => {
- const siemFieldCapitalized = useMemo(() => capitalize(siemField), [siemField]);
+ const securitySolutionFieldCapitalized = useMemo(() => capitalize(securitySolutionField), [
+ securitySolutionField,
+ ]);
return (
- {siemFieldCapitalized}
+ {securitySolutionFieldCapitalized}
@@ -58,7 +60,7 @@ const FieldMappingRowComponent: React.FC = ({
disabled={disabled}
options={thirdPartyOptions}
valueOfSelected={selectedThirdParty}
- onChange={onChangeThirdParty.bind(null, siemField)}
+ onChange={onChangeThirdParty.bind(null, securitySolutionField)}
data-test-subj={`case-configure-third-party-select-${id}`}
/>
@@ -67,7 +69,7 @@ const FieldMappingRowComponent: React.FC = ({
disabled={disabled}
options={actionTypeOptions}
valueOfSelected={selectedActionType}
- onChange={onChangeActionType.bind(null, siemField)}
+ onChange={onChangeActionType.bind(null, securitySolutionField)}
data-test-subj={`case-configure-action-type-select-${id}`}
/>
diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/translations.ts b/x-pack/plugins/security_solution/public/cases/components/configure_cases/translations.ts
index c256c6dedb9189..9ef6ce2f3d4a93 100644
--- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/translations.ts
+++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/translations.ts
@@ -17,7 +17,7 @@ export const INCIDENT_MANAGEMENT_SYSTEM_DESC = i18n.translate(
'xpack.securitySolution.case.configureCases.incidentManagementSystemDesc',
{
defaultMessage:
- 'You may optionally connect SIEM cases to an external incident management system of your choosing. This will allow you to push case data as an incident in your chosen third-party system.',
+ 'You may optionally connect Security cases to an external incident management system of your choosing. This will allow you to push case data as an incident in your chosen third-party system.',
}
);
@@ -53,7 +53,7 @@ export const CASE_CLOSURE_OPTIONS_DESC = i18n.translate(
'xpack.securitySolution.case.configureCases.caseClosureOptionsDesc',
{
defaultMessage:
- 'Define how you wish SIEM cases to be closed. Automated case closures require an established connection to an external incident management system.',
+ 'Define how you wish Security cases to be closed. Automated case closures require an established connection to an external incident management system.',
}
);
@@ -67,21 +67,22 @@ export const CASE_CLOSURE_OPTIONS_LABEL = i18n.translate(
export const CASE_CLOSURE_OPTIONS_MANUAL = i18n.translate(
'xpack.securitySolution.case.configureCases.caseClosureOptionsManual',
{
- defaultMessage: 'Manually close SIEM cases',
+ defaultMessage: 'Manually close Security cases',
}
);
export const CASE_CLOSURE_OPTIONS_NEW_INCIDENT = i18n.translate(
'xpack.securitySolution.case.configureCases.caseClosureOptionsNewIncident',
{
- defaultMessage: 'Automatically close SIEM cases when pushing new incident to external system',
+ defaultMessage:
+ 'Automatically close Security cases when pushing new incident to external system',
}
);
export const CASE_CLOSURE_OPTIONS_CLOSED_INCIDENT = i18n.translate(
'xpack.securitySolution.case.configureCases.caseClosureOptionsClosedIncident',
{
- defaultMessage: 'Automatically close SIEM cases when incident is closed in external system',
+ defaultMessage: 'Automatically close Security cases when incident is closed in external system',
}
);
@@ -96,14 +97,14 @@ export const FIELD_MAPPING_DESC = i18n.translate(
'xpack.securitySolution.case.configureCases.fieldMappingDesc',
{
defaultMessage:
- 'Map SIEM case fields when pushing data to a third-party. Field mappings require an established connection to an external incident management system.',
+ 'Map Security case fields when pushing data to a third-party. Field mappings require an established connection to an external incident management system.',
}
);
export const FIELD_MAPPING_FIRST_COL = i18n.translate(
'xpack.securitySolution.case.configureCases.fieldMappingFirstCol',
{
- defaultMessage: 'SIEM case field',
+ defaultMessage: 'Security case field',
}
);
diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.test.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.test.tsx
index 8e07706d9c6ba1..ae9f1ec7469e41 100644
--- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.test.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.test.tsx
@@ -18,7 +18,7 @@ const onSaveContent = jest.fn();
const timelineId = '1e10f150-949b-11ea-b63c-2bc51864784c';
const defaultProps = {
- content: `A link to a timeline [timeline](http://localhost:5601/app/siem#/timelines?timeline=(id:'${timelineId}',isOpen:!t))`,
+ content: `A link to a timeline [timeline](http://localhost:5601/app/security#/timelines?timeline=(id:'${timelineId}',isOpen:!t))`,
id: 'markdown-id',
isEditable: false,
onChangeEditable,
diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/page_view.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/page_view.tsx
index 759274e3a4ffa2..6fe15310fc88e7 100644
--- a/x-pack/plugins/security_solution/public/common/components/endpoint/page_view.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/endpoint/page_view.tsx
@@ -149,7 +149,9 @@ export const PageView = memo(
)}
)}
- {tabs && {tabComponents}}
+ {tabComponents.length > 0 && (
+ {tabComponents}
+ )}
{bodyHeader && (
diff --git a/x-pack/plugins/security_solution/public/common/components/formatted_date/index.tsx b/x-pack/plugins/security_solution/public/common/components/formatted_date/index.tsx
index ac4a3533853f73..936bd26adff7f8 100644
--- a/x-pack/plugins/security_solution/public/common/components/formatted_date/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/formatted_date/index.tsx
@@ -75,14 +75,15 @@ PreferenceFormattedP1DTDate.displayName = 'PreferenceFormattedP1DTDate';
export const FormattedDate = React.memo<{
fieldName: string;
value?: string | number | null;
+ className?: string;
}>(
- ({ value, fieldName }): JSX.Element => {
+ ({ value, fieldName, className = '' }): JSX.Element => {
if (value == null) {
return getOrEmptyTagFromValue(value);
}
const maybeDate = getMaybeDate(value);
return maybeDate.isValid() ? (
-
+
) : (
diff --git a/x-pack/plugins/security_solution/public/common/components/header_global/index.tsx b/x-pack/plugins/security_solution/public/common/components/header_global/index.tsx
index 06a69d0612ee8c..c9085b5953817e 100644
--- a/x-pack/plugins/security_solution/public/common/components/header_global/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/header_global/index.tsx
@@ -18,6 +18,7 @@ import { MlPopover } from '../ml_popover/ml_popover';
import { SiemNavigation } from '../navigation';
import * as i18n from './translations';
import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source';
+import { ADD_DATA_PATH } from '../../../../common/constants';
const Wrapper = styled.header`
${({ theme }) => css`
@@ -86,7 +87,7 @@ export const HeaderGlobal = React.memo(({ hideDetectionEngine
{i18n.BUTTON_ADD_DATA}
diff --git a/x-pack/plugins/security_solution/public/common/components/localized_date_tooltip/index.tsx b/x-pack/plugins/security_solution/public/common/components/localized_date_tooltip/index.tsx
index 918ec70bd7407f..d8f8742cb43640 100644
--- a/x-pack/plugins/security_solution/public/common/components/localized_date_tooltip/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/localized_date_tooltip/index.tsx
@@ -13,9 +13,11 @@ export const LocalizedDateTooltip = React.memo<{
children: React.ReactNode;
date: Date;
fieldName?: string;
-}>(({ children, date, fieldName }) => (
+ className?: string;
+}>(({ children, date, fieldName, className = '' }) => (
{fieldName != null ? (
diff --git a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx
index 3c0189625ee291..d930136b3c0c46 100644
--- a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx
+++ b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx
@@ -18,7 +18,7 @@ import { createStore, State, substateMiddlewareFactory } from '../../store';
import { alertMiddlewareFactory } from '../../../endpoint_alerts/store/middleware';
import { AppRootProvider } from './app_root_provider';
import { managementMiddlewareFactory } from '../../../management/store/middleware';
-import { hostMiddlewareFactory } from '../../../endpoint_hosts/store/middleware';
+import { createKibanaContextProviderMock } from '../kibana_react';
import { SUB_PLUGINS_REDUCER, mockGlobalState } from '..';
type UiRender = (ui: React.ReactElement, options?: RenderOptions) => RenderResult;
@@ -57,10 +57,6 @@ export const createAppRootMockRenderer = (): AppContextTestRender => {
const depsStart = depsStartMock();
const middlewareSpy = createSpyMiddleware();
const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, apolloClientObservable, [
- substateMiddlewareFactory(
- (globalState) => globalState.hostList,
- hostMiddlewareFactory(coreStart, depsStart)
- ),
substateMiddlewareFactory(
(globalState) => globalState.alertList,
alertMiddlewareFactory(coreStart, depsStart)
@@ -68,11 +64,14 @@ export const createAppRootMockRenderer = (): AppContextTestRender => {
...managementMiddlewareFactory(coreStart, depsStart),
middlewareSpy.actionSpyMiddleware,
]);
+ const MockKibanaContextProvider = createKibanaContextProviderMock();
const AppWrapper: React.FC<{ children: React.ReactElement }> = ({ children }) => (
-
- {children}
-
+
+
+ {children}
+
+
);
const render: UiRender = (ui, options) => {
return reactRender(ui, {
diff --git a/x-pack/plugins/security_solution/public/common/mock/global_state.ts b/x-pack/plugins/security_solution/public/common/mock/global_state.ts
index 30dffa8dbf6bfd..4af39ade70d255 100644
--- a/x-pack/plugins/security_solution/public/common/mock/global_state.ts
+++ b/x-pack/plugins/security_solution/public/common/mock/global_state.ts
@@ -26,10 +26,8 @@ import {
import { networkModel } from '../../network/store';
import { TimelineType, TimelineStatus } from '../../../common/types/timeline';
import { initialAlertListState } from '../../endpoint_alerts/store/reducer';
-import { initialHostListState } from '../../endpoint_hosts/store/reducer';
import { mockManagementState } from '../../management/store/reducer';
import { AlertListState } from '../../../common/endpoint_alerts/types';
-import { HostState } from '../../endpoint_hosts/types';
import { ManagementState } from '../../management/types';
export const mockGlobalState: State = {
@@ -237,6 +235,5 @@ export const mockGlobalState: State = {
* they are cast to mutable versions here.
*/
alertList: initialAlertListState as AlertListState,
- hostList: initialHostListState as HostState,
management: mockManagementState as ManagementState,
};
diff --git a/x-pack/plugins/security_solution/public/common/mock/utils.ts b/x-pack/plugins/security_solution/public/common/mock/utils.ts
index 1ff5cb8e734ec3..c71a9ada75eee5 100644
--- a/x-pack/plugins/security_solution/public/common/mock/utils.ts
+++ b/x-pack/plugins/security_solution/public/common/mock/utils.ts
@@ -11,9 +11,7 @@ import { managementReducer } from '../../management/store/reducer';
import { ManagementPluginReducer } from '../../management';
import { SubPluginsInitReducer } from '../store';
import { EndpointAlertsPluginReducer } from '../../endpoint_alerts';
-import { EndpointHostsPluginReducer } from '../../endpoint_hosts';
import { alertListReducer } from '../../endpoint_alerts/store/reducer';
-import { hostListReducer } from '../../endpoint_hosts/store/reducer';
interface Global extends NodeJS.Global {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -30,7 +28,6 @@ export const SUB_PLUGINS_REDUCER: SubPluginsInitReducer = {
* These state's are wrapped in `Immutable`, but for compatibility with the overall app architecture,
* they are cast to mutable versions here.
*/
- hostList: hostListReducer as EndpointHostsPluginReducer['hostList'],
alertList: alertListReducer as EndpointAlertsPluginReducer['alertList'],
management: managementReducer as ManagementPluginReducer['management'],
};
diff --git a/x-pack/plugins/security_solution/public/common/store/actions.ts b/x-pack/plugins/security_solution/public/common/store/actions.ts
index 58e4e2f363e92c..453191ebafce6c 100644
--- a/x-pack/plugins/security_solution/public/common/store/actions.ts
+++ b/x-pack/plugins/security_solution/public/common/store/actions.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { HostAction } from '../../endpoint_hosts/store/action';
+import { HostAction } from '../../management/pages/endpoint_hosts/store/action';
import { AlertAction } from '../../endpoint_alerts/store/action';
import { PolicyListAction } from '../../management/pages/policy/store/policy_list';
import { PolicyDetailsAction } from '../../management/pages/policy/store/policy_details';
diff --git a/x-pack/plugins/security_solution/public/common/store/reducer.ts b/x-pack/plugins/security_solution/public/common/store/reducer.ts
index ba85fbef860d7a..6aa9c6c0593666 100644
--- a/x-pack/plugins/security_solution/public/common/store/reducer.ts
+++ b/x-pack/plugins/security_solution/public/common/store/reducer.ts
@@ -17,7 +17,6 @@ import { TimelinePluginReducer } from '../../timelines/store/timeline';
import { SecuritySubPlugins } from '../../app/types';
import { ManagementPluginReducer } from '../../management';
import { EndpointAlertsPluginReducer } from '../../endpoint_alerts';
-import { EndpointHostsPluginReducer } from '../../endpoint_hosts';
import { State } from './types';
import { AppAction } from './actions';
@@ -25,7 +24,6 @@ export type SubPluginsInitReducer = HostsPluginReducer &
NetworkPluginReducer &
TimelinePluginReducer &
EndpointAlertsPluginReducer &
- EndpointHostsPluginReducer &
ManagementPluginReducer;
/**
diff --git a/x-pack/plugins/security_solution/public/common/store/types.ts b/x-pack/plugins/security_solution/public/common/store/types.ts
index b9942979beb1e8..2b92451e30119b 100644
--- a/x-pack/plugins/security_solution/public/common/store/types.ts
+++ b/x-pack/plugins/security_solution/public/common/store/types.ts
@@ -17,7 +17,6 @@ import { DragAndDropState } from './drag_and_drop/reducer';
import { TimelinePluginState } from '../../timelines/store/timeline';
import { NetworkPluginState } from '../../network/store';
import { EndpointAlertsPluginState } from '../../endpoint_alerts';
-import { EndpointHostsPluginState } from '../../endpoint_hosts';
import { ManagementPluginState } from '../../management';
/**
@@ -31,7 +30,6 @@ export type State = CombinedState<
NetworkPluginState &
TimelinePluginState &
EndpointAlertsPluginState &
- EndpointHostsPluginState &
ManagementPluginState & {
app: AppState;
dragAndDrop: DragAndDropState;
diff --git a/x-pack/plugins/security_solution/public/endpoint_hosts/index.ts b/x-pack/plugins/security_solution/public/endpoint_hosts/index.ts
deleted file mode 100644
index bd1c5f96f8cdaa..00000000000000
--- a/x-pack/plugins/security_solution/public/endpoint_hosts/index.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { Reducer } from 'redux';
-import { SecuritySubPluginWithStore } from '../app/types';
-import { endpointHostsRoutes } from './routes';
-import { hostListReducer } from './store/reducer';
-import { HostState } from './types';
-import { hostMiddlewareFactory } from './store/middleware';
-import { CoreStart } from '../../../../../src/core/public';
-import { StartPlugins } from '../types';
-import { substateMiddlewareFactory } from '../common/store';
-import { AppAction } from '../common/store/actions';
-
-/**
- * Internally, our state is sometimes immutable, ignore that in our external
- * interface.
- */
-export interface EndpointHostsPluginState {
- hostList: HostState;
-}
-
-/**
- * Internally, we use `ImmutableReducer`, but we present a regular reducer
- * externally for compatibility w/ regular redux.
- */
-export interface EndpointHostsPluginReducer {
- hostList: Reducer;
-}
-
-export class EndpointHosts {
- public setup() {}
-
- public start(
- core: CoreStart,
- plugins: StartPlugins
- ): SecuritySubPluginWithStore<'hostList', HostState> {
- const { data, ingestManager } = plugins;
- const middleware = [
- substateMiddlewareFactory(
- (globalState) => globalState.hostList,
- hostMiddlewareFactory(core, { data, ingestManager })
- ),
- ];
- return {
- routes: endpointHostsRoutes(),
- store: {
- initialState: { hostList: undefined },
- /**
- * Cast the ImmutableReducer to a regular reducer for compatibility with
- * the subplugin architecture (which expects plain redux reducers.)
- */
- reducer: { hostList: hostListReducer } as EndpointHostsPluginReducer,
- middleware,
- },
- };
- }
-}
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts_empty_page.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts_empty_page.tsx
index 3ab0cb1f748d68..a01e249561e5c5 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/hosts_empty_page.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts_empty_page.tsx
@@ -9,6 +9,7 @@ import React from 'react';
import { EmptyPage } from '../../common/components/empty_page';
import { useKibana } from '../../common/lib/kibana';
import * as i18n from '../../common/translations';
+import { ADD_DATA_PATH } from '../../../common/constants';
export const HostsEmptyPage = React.memo(() => {
const { http, docLinks } = useKibana().services;
@@ -18,7 +19,7 @@ export const HostsEmptyPage = React.memo(() => {
= Exclude extends never ? T1 : never;
+type Exact = T extends Shape ? ExactKeys : never;
+
+/**
+ * Returns a string to be used in the URL as search query params.
+ * Ensures that when creating a URL query param string, that the given input strictly
+ * matches the expected interface (guards against possibly leaking internal state)
+ */
+const querystringStringify: (
+ params: Exact
+) => string = querystring.stringify;
+
+/** Make `selected_host` required */
+type EndpointDetailsUrlProps = Omit &
+ Required>;
+
+/**
+ * Input props for the `getManagementUrl()` method
+ */
export type GetManagementUrlProps = {
/**
* Exclude the URL prefix (everything to the left of where the router was mounted.
@@ -22,8 +45,8 @@ export type GetManagementUrlProps = {
*/
excludePrefix?: boolean;
} & (
- | { name: 'default' }
- | { name: 'endpointList' }
+ | ({ name: 'default' | 'endpointList' } & HostIndexUIQueryParams)
+ | ({ name: 'endpointDetails' | 'endpointPolicyResponse' } & EndpointDetailsUrlProps)
| { name: 'policyList' }
| { name: 'policyDetails'; policyId: string }
);
@@ -39,31 +62,47 @@ const URL_PREFIX = '#';
export const getManagementUrl = (props: GetManagementUrlProps): string => {
let url = props.excludePrefix ? '' : URL_PREFIX;
- switch (props.name) {
- case 'default':
- url += generatePath(MANAGEMENT_ROUTING_ROOT_PATH, {
- pageName: SiemPageName.management,
- });
- break;
- case 'endpointList':
+ if (props.name === 'default' || props.name === 'endpointList') {
+ const { name, excludePrefix, ...queryParams } = props;
+ const urlQueryParams = querystringStringify(
+ queryParams
+ );
+
+ if (name === 'endpointList') {
url += generatePath(MANAGEMENT_ROUTING_ENDPOINTS_PATH, {
pageName: SiemPageName.management,
tabName: ManagementSubTab.endpoints,
});
- break;
- case 'policyList':
- url += generatePath(MANAGEMENT_ROUTING_POLICIES_PATH, {
- pageName: SiemPageName.management,
- tabName: ManagementSubTab.policies,
- });
- break;
- case 'policyDetails':
- url += generatePath(MANAGEMENT_ROUTING_POLICY_DETAILS_PATH, {
+ } else {
+ url += generatePath(MANAGEMENT_ROUTING_ROOT_PATH, {
pageName: SiemPageName.management,
- tabName: ManagementSubTab.policies,
- policyId: props.policyId,
});
- break;
+ }
+
+ if (urlQueryParams) {
+ url += `?${urlQueryParams}`;
+ }
+ } else if (props.name === 'endpointDetails' || props.name === 'endpointPolicyResponse') {
+ const { name, excludePrefix, ...queryParams } = props;
+ queryParams.show = (props.name === 'endpointPolicyResponse'
+ ? 'policy_response'
+ : '') as HostIndexUIQueryParams['show'];
+
+ url += `${generatePath(MANAGEMENT_ROUTING_ENDPOINTS_PATH, {
+ pageName: SiemPageName.management,
+ tabName: ManagementSubTab.endpoints,
+ })}?${querystringStringify(queryParams)}`;
+ } else if (props.name === 'policyList') {
+ url += generatePath(MANAGEMENT_ROUTING_POLICIES_PATH, {
+ pageName: SiemPageName.management,
+ tabName: ManagementSubTab.policies,
+ });
+ } else if (props.name === 'policyDetails') {
+ url += generatePath(MANAGEMENT_ROUTING_POLICY_DETAILS_PATH, {
+ pageName: SiemPageName.management,
+ tabName: ManagementSubTab.policies,
+ policyId: props.policyId,
+ });
}
return url;
diff --git a/x-pack/plugins/security_solution/public/management/components/management_page_view.tsx b/x-pack/plugins/security_solution/public/management/components/management_page_view.tsx
index c6570da5cb5aab..5b140a53a363bf 100644
--- a/x-pack/plugins/security_solution/public/management/components/management_page_view.tsx
+++ b/x-pack/plugins/security_solution/public/management/components/management_page_view.tsx
@@ -13,7 +13,10 @@ import { getManagementUrl } from '..';
export const ManagementPageView = memo>((options) => {
const { tabName } = useParams<{ tabName: ManagementSubTab }>();
- const tabs = useMemo((): PageViewProps['tabs'] => {
+ const tabs = useMemo((): PageViewProps['tabs'] | undefined => {
+ if (options.viewType === 'details') {
+ return undefined;
+ }
return [
{
name: i18n.translate('xpack.securitySolution.managementTabs.endpoints', {
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/index.tsx
new file mode 100644
index 00000000000000..ff7f522b9bc528
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/index.tsx
@@ -0,0 +1,25 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { Switch, Route } from 'react-router-dom';
+import React, { memo } from 'react';
+import { HostList } from './view';
+import { MANAGEMENT_ROUTING_ENDPOINTS_PATH } from '../../common/constants';
+import { NotFoundPage } from '../../../app/404';
+
+/**
+ * Provides the routing container for the endpoints related views
+ */
+export const EndpointsContainer = memo(() => {
+ return (
+
+
+
+
+ );
+});
+
+EndpointsContainer.displayName = 'EndpointsContainer';
diff --git a/x-pack/plugins/security_solution/public/endpoint_hosts/routes.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/routes.tsx
similarity index 100%
rename from x-pack/plugins/security_solution/public/endpoint_hosts/routes.tsx
rename to x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/routes.tsx
diff --git a/x-pack/plugins/security_solution/public/endpoint_hosts/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts
similarity index 87%
rename from x-pack/plugins/security_solution/public/endpoint_hosts/store/action.ts
rename to x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts
index 9b38d7ce5a23ae..62a2d9e3205c26 100644
--- a/x-pack/plugins/security_solution/public/endpoint_hosts/store/action.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts
@@ -4,8 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { HostResultList, HostInfo, GetHostPolicyResponse } from '../../../common/endpoint/types';
-import { ServerApiError } from '../../common/types';
+import {
+ HostResultList,
+ HostInfo,
+ GetHostPolicyResponse,
+} from '../../../../../common/endpoint/types';
+import { ServerApiError } from '../../../../common/types';
interface ServerReturnedHostList {
type: 'serverReturnedHostList';
diff --git a/x-pack/plugins/security_solution/public/endpoint_hosts/store/host_pagination.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/host_pagination.test.ts
similarity index 89%
rename from x-pack/plugins/security_solution/public/endpoint_hosts/store/host_pagination.test.ts
rename to x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/host_pagination.test.ts
index 17feacb0a767a1..b8eaa39c777529 100644
--- a/x-pack/plugins/security_solution/public/endpoint_hosts/store/host_pagination.test.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/host_pagination.test.ts
@@ -8,10 +8,10 @@ import { CoreStart, HttpSetup } from 'kibana/public';
import { History, createBrowserHistory } from 'history';
import { applyMiddleware, Store, createStore } from 'redux';
-import { coreMock } from '../../../../../../src/core/public/mocks';
+import { coreMock } from '../../../../../../../../src/core/public/mocks';
-import { HostResultList, AppLocation } from '../../../common/endpoint/types';
-import { DepsStartMock, depsStartMock } from '../../common/mock/endpoint';
+import { HostResultList, AppLocation } from '../../../../../common/endpoint/types';
+import { DepsStartMock, depsStartMock } from '../../../../common/mock/endpoint';
import { hostMiddlewareFactory } from './middleware';
@@ -20,8 +20,11 @@ import { hostListReducer } from './reducer';
import { uiQueryParams } from './selectors';
import { mockHostResultList } from './mock_host_result_list';
import { HostState, HostIndexUIQueryParams } from '../types';
-import { MiddlewareActionSpyHelper, createSpyMiddleware } from '../../common/store/test_utils';
-import { urlFromQueryParams } from '../view/url_from_query_params';
+import {
+ MiddlewareActionSpyHelper,
+ createSpyMiddleware,
+} from '../../../../common/store/test_utils';
+import { getManagementUrl } from '../../..';
describe('host list pagination: ', () => {
let fakeCoreStart: jest.Mocked;
@@ -53,7 +56,9 @@ describe('host list pagination: ', () => {
queryParams = () => uiQueryParams(store.getState());
historyPush = (nextQueryParams: HostIndexUIQueryParams): void => {
- return history.push(urlFromQueryParams(nextQueryParams));
+ return history.push(
+ getManagementUrl({ name: 'endpointList', excludePrefix: true, ...nextQueryParams })
+ );
};
});
@@ -67,7 +72,7 @@ describe('host list pagination: ', () => {
type: 'userChangedUrl',
payload: {
...history.location,
- pathname: '/endpoint-hosts',
+ pathname: getManagementUrl({ name: 'endpointList', excludePrefix: true }),
},
});
await waitForAction('serverReturnedHostList');
diff --git a/x-pack/plugins/security_solution/public/endpoint_hosts/store/index.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts
similarity index 100%
rename from x-pack/plugins/security_solution/public/endpoint_hosts/store/index.test.ts
rename to x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts
diff --git a/x-pack/plugins/security_solution/public/endpoint_hosts/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts
similarity index 82%
rename from x-pack/plugins/security_solution/public/endpoint_hosts/store/middleware.test.ts
rename to x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts
index 0959a3438aad9a..a6cd2ca3afac45 100644
--- a/x-pack/plugins/security_solution/public/endpoint_hosts/store/middleware.test.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts
@@ -5,19 +5,23 @@
*/
import { CoreStart, HttpSetup } from 'kibana/public';
import { applyMiddleware, createStore, Store } from 'redux';
-import { coreMock } from '../../../../../../src/core/public/mocks';
+import { coreMock } from '../../../../../../../../src/core/public/mocks';
import { History, createBrowserHistory } from 'history';
-import { DepsStartMock, depsStartMock } from '../../common/mock/endpoint';
+import { DepsStartMock, depsStartMock } from '../../../../common/mock/endpoint';
-import { createSpyMiddleware, MiddlewareActionSpyHelper } from '../../common/store/test_utils';
-import { Immutable, HostResultList } from '../../../common/endpoint/types';
-import { AppAction } from '../../common/store/actions';
+import {
+ createSpyMiddleware,
+ MiddlewareActionSpyHelper,
+} from '../../../../common/store/test_utils';
+import { Immutable, HostResultList } from '../../../../../common/endpoint/types';
+import { AppAction } from '../../../../common/store/actions';
import { mockHostResultList } from './mock_host_result_list';
import { listData } from './selectors';
import { HostState } from '../types';
import { hostListReducer } from './reducer';
import { hostMiddlewareFactory } from './middleware';
+import { getManagementUrl } from '../../..';
describe('host list middleware', () => {
let fakeCoreStart: jest.Mocked;
@@ -56,7 +60,7 @@ describe('host list middleware', () => {
type: 'userChangedUrl',
payload: {
...history.location,
- pathname: '/endpoint-hosts',
+ pathname: getManagementUrl({ name: 'endpointList', excludePrefix: true }),
},
});
await waitForAction('serverReturnedHostList');
diff --git a/x-pack/plugins/security_solution/public/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts
similarity index 95%
rename from x-pack/plugins/security_solution/public/endpoint_hosts/store/middleware.ts
rename to x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts
index dd9ab19a702eaa..85667c9f9fc376 100644
--- a/x-pack/plugins/security_solution/public/endpoint_hosts/store/middleware.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts
@@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { HostResultList } from '../../../common/endpoint/types';
-import { ImmutableMiddlewareFactory } from '../../common/store';
+import { HostResultList } from '../../../../../common/endpoint/types';
+import { ImmutableMiddlewareFactory } from '../../../../common/store';
import { isOnHostPage, hasSelectedHost, uiQueryParams, listData } from './selectors';
import { HostState } from '../types';
@@ -37,7 +37,7 @@ export const hostMiddlewareFactory: ImmutableMiddlewareFactory