diff --git a/.eslintrc.js b/.eslintrc.js
index 3d6a5c262c453..af05af0f6e402 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -69,12 +69,6 @@ module.exports = {
'jsx-a11y/no-onchange': 'off',
},
},
- {
- files: ['src/legacy/core_plugins/data/**/*.{js,ts,tsx}'],
- rules: {
- 'react-hooks/exhaustive-deps': 'off',
- },
- },
{
files: ['src/legacy/core_plugins/expressions/**/*.{js,ts,tsx}'],
rules: {
diff --git a/.i18nrc.json b/.i18nrc.json
index 07878ed3c15fb..bffe99bf3654b 100644
--- a/.i18nrc.json
+++ b/.i18nrc.json
@@ -4,10 +4,7 @@
"console": "src/plugins/console",
"core": "src/core",
"dashboard": "src/plugins/dashboard",
- "data": [
- "src/legacy/core_plugins/data",
- "src/plugins/data"
- ],
+ "data": "src/plugins/data",
"embeddableApi": "src/plugins/embeddable",
"embeddableExamples": "examples/embeddable_examples",
"share": "src/plugins/share",
diff --git a/Jenkinsfile b/Jenkinsfile
index d43da6e0bee04..79d3c93006cb6 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -40,7 +40,12 @@ kibanaPipeline(timeoutMinutes: 135, checkPrChanges: true) {
'xpack-ciGroup9': kibanaPipeline.xpackCiGroupProcess(9),
'xpack-ciGroup10': kibanaPipeline.xpackCiGroupProcess(10),
'xpack-accessibility': kibanaPipeline.functionalTestProcess('xpack-accessibility', './test/scripts/jenkins_xpack_accessibility.sh'),
- 'xpack-siemCypress': kibanaPipeline.functionalTestProcess('xpack-siemCypress', './test/scripts/jenkins_siem_cypress.sh'),
+ 'xpack-siemCypress': { processNumber ->
+ whenChanged(['x-pack/legacy/plugins/siem/', 'x-pack/test/siem_cypress/']) {
+ kibanaPipeline.functionalTestProcess('xpack-siemCypress', './test/scripts/jenkins_siem_cypress.sh')(processNumber)
+ }
+ },
+
// 'xpack-visualRegression': kibanaPipeline.functionalTestProcess('xpack-visualRegression', './test/scripts/jenkins_xpack_visual_regression.sh'),
]),
])
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.enabled.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.enabled.md
new file mode 100644
index 0000000000000..2ef8c797f4054
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.enabled.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigOptions](./kibana-plugin-plugins-data-public.aggconfigoptions.md) > [enabled](./kibana-plugin-plugins-data-public.aggconfigoptions.enabled.md)
+
+## AggConfigOptions.enabled property
+
+Signature:
+
+```typescript
+enabled?: boolean;
+```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.id.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.id.md
new file mode 100644
index 0000000000000..8939854ab19ca
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.id.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigOptions](./kibana-plugin-plugins-data-public.aggconfigoptions.md) > [id](./kibana-plugin-plugins-data-public.aggconfigoptions.id.md)
+
+## AggConfigOptions.id property
+
+Signature:
+
+```typescript
+id?: string;
+```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.md
new file mode 100644
index 0000000000000..b841d9b04d6a7
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.md
@@ -0,0 +1,22 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigOptions](./kibana-plugin-plugins-data-public.aggconfigoptions.md)
+
+## AggConfigOptions interface
+
+Signature:
+
+```typescript
+export interface AggConfigOptions
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [enabled](./kibana-plugin-plugins-data-public.aggconfigoptions.enabled.md) | boolean
| |
+| [id](./kibana-plugin-plugins-data-public.aggconfigoptions.id.md) | string
| |
+| [params](./kibana-plugin-plugins-data-public.aggconfigoptions.params.md) | Record<string, any>
| |
+| [schema](./kibana-plugin-plugins-data-public.aggconfigoptions.schema.md) | string
| |
+| [type](./kibana-plugin-plugins-data-public.aggconfigoptions.type.md) | IAggType
| |
+
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.params.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.params.md
new file mode 100644
index 0000000000000..45219a837cc33
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.params.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigOptions](./kibana-plugin-plugins-data-public.aggconfigoptions.md) > [params](./kibana-plugin-plugins-data-public.aggconfigoptions.params.md)
+
+## AggConfigOptions.params property
+
+Signature:
+
+```typescript
+params?: Record;
+```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.schema.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.schema.md
new file mode 100644
index 0000000000000..b2b42eb2e5b4d
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.schema.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigOptions](./kibana-plugin-plugins-data-public.aggconfigoptions.md) > [schema](./kibana-plugin-plugins-data-public.aggconfigoptions.schema.md)
+
+## AggConfigOptions.schema property
+
+Signature:
+
+```typescript
+schema?: string;
+```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.type.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.type.md
new file mode 100644
index 0000000000000..866065ce52ba6
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.type.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigOptions](./kibana-plugin-plugins-data-public.aggconfigoptions.md) > [type](./kibana-plugin-plugins-data-public.aggconfigoptions.type.md)
+
+## AggConfigOptions.type property
+
+Signature:
+
+```typescript
+type: IAggType;
+```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.es_field_types.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.es_field_types.md
index e7341caf7b3cd..c5e01715534d1 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.es_field_types.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.es_field_types.md
@@ -30,6 +30,7 @@ export declare enum ES_FIELD_TYPES
| GEO\_POINT | "geo_point"
| |
| GEO\_SHAPE | "geo_shape"
| |
| HALF\_FLOAT | "half_float"
| |
+| HISTOGRAM | "histogram"
| |
| INTEGER | "integer"
| |
| IP | "ip"
| |
| KEYWORD | "keyword"
| |
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.kbn_field_types.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.kbn_field_types.md
index e5ae8ffbd2877..30c3aa946c1ce 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.kbn_field_types.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.kbn_field_types.md
@@ -23,6 +23,7 @@ export declare enum KBN_FIELD_TYPES
| DATE | "date"
| |
| GEO\_POINT | "geo_point"
| |
| GEO\_SHAPE | "geo_shape"
| |
+| HISTOGRAM | "histogram"
| |
| IP | "ip"
| |
| MURMUR3 | "murmur3"
| |
| NESTED | "nested"
| |
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md
index f8516ec476e88..ea77d6f39389b 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md
@@ -48,6 +48,7 @@
| Interface | Description |
| --- | --- |
+| [AggConfigOptions](./kibana-plugin-plugins-data-public.aggconfigoptions.md) | |
| [AggParamOption](./kibana-plugin-plugins-data-public.aggparamoption.md) | |
| [DataPublicPluginSetup](./kibana-plugin-plugins-data-public.datapublicpluginsetup.md) | |
| [DataPublicPluginStart](./kibana-plugin-plugins-data-public.datapublicpluginstart.md) | |
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.es_field_types.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.es_field_types.md
index 81a7cbca77c48..d071955f4f522 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.es_field_types.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.es_field_types.md
@@ -30,6 +30,7 @@ export declare enum ES_FIELD_TYPES
| GEO\_POINT | "geo_point"
| |
| GEO\_SHAPE | "geo_shape"
| |
| HALF\_FLOAT | "half_float"
| |
+| HISTOGRAM | "histogram"
| |
| INTEGER | "integer"
| |
| IP | "ip"
| |
| KEYWORD | "keyword"
| |
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.kbn_field_types.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.kbn_field_types.md
index 40b81d2f6ac4d..a0a64190497c8 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.kbn_field_types.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.kbn_field_types.md
@@ -23,6 +23,7 @@ export declare enum KBN_FIELD_TYPES
| DATE | "date"
| |
| GEO\_POINT | "geo_point"
| |
| GEO\_SHAPE | "geo_shape"
| |
+| HISTOGRAM | "histogram"
| |
| IP | "ip"
| |
| MURMUR3 | "murmur3"
| |
| NESTED | "nested"
| |
diff --git a/src/legacy/core_plugins/data/index.ts b/src/legacy/core_plugins/data/index.ts
deleted file mode 100644
index 10c8cf464b82d..0000000000000
--- a/src/legacy/core_plugins/data/index.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { resolve } from 'path';
-import { Legacy } from '../../../../kibana';
-
-// eslint-disable-next-line import/no-default-export
-export default function DataPlugin(kibana: any) {
- const config: Legacy.PluginSpecOptions = {
- id: 'data',
- require: ['elasticsearch'],
- publicDir: resolve(__dirname, 'public'),
- config: (Joi: any) => {
- return Joi.object({
- enabled: Joi.boolean().default(true),
- }).default();
- },
- init: (server: Legacy.Server) => ({}),
- uiExports: {
- injectDefaultVars: () => ({}),
- },
- };
-
- return new kibana.Plugin(config);
-}
diff --git a/src/legacy/core_plugins/data/package.json b/src/legacy/core_plugins/data/package.json
deleted file mode 100644
index 3f40374650ad7..0000000000000
--- a/src/legacy/core_plugins/data/package.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "data",
- "version": "kibana"
-}
diff --git a/src/legacy/core_plugins/data/public/legacy.ts b/src/legacy/core_plugins/data/public/legacy.ts
deleted file mode 100644
index 370b412127db8..0000000000000
--- a/src/legacy/core_plugins/data/public/legacy.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * New Platform Shim
- *
- * In this file, we import any legacy dependencies we have, and shim them into
- * our plugin by manually constructing the values that the new platform will
- * eventually be passing to the `setup` method of our plugin definition.
- *
- * The idea is that our `plugin.ts` can stay "pure" and not contain any legacy
- * world code. Then when it comes time to migrate to the new platform, we can
- * simply delete this shim file.
- *
- * We are also calling `setup` here and exporting our public contract so that
- * other legacy plugins are able to import from '../core_plugins/data/legacy'
- * and receive the response value of the `setup` contract, mimicking the
- * data that will eventually be injected by the new platform.
- */
-
-import { npSetup, npStart } from 'ui/new_platform';
-import { plugin } from '.';
-
-const dataPlugin = plugin();
-
-export const setup = dataPlugin.setup(npSetup.core);
-
-export const start = dataPlugin.start(npStart.core);
diff --git a/src/legacy/core_plugins/data/public/plugin.ts b/src/legacy/core_plugins/data/public/plugin.ts
deleted file mode 100644
index 76a3d92d20283..0000000000000
--- a/src/legacy/core_plugins/data/public/plugin.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { CoreSetup, CoreStart, Plugin } from 'kibana/public';
-
-/**
- * Interface for this plugin's returned `setup` contract.
- *
- * @public
- */
-export interface DataSetup {} // eslint-disable-line @typescript-eslint/no-empty-interface
-
-/**
- * Interface for this plugin's returned `start` contract.
- *
- * @public
- */
-export interface DataStart {} // eslint-disable-line @typescript-eslint/no-empty-interface
-
-/**
- * Data Plugin - public
- *
- * This is the entry point for the entire client-side public contract of the plugin.
- * If something is not explicitly exported here, you can safely assume it is private
- * to the plugin and not considered stable.
- *
- * All stateful contracts will be injected by the platform at runtime, and are defined
- * in the setup/start interfaces. The remaining items exported here are either types,
- * or static code.
- */
-
-export class DataPlugin implements Plugin {
- public setup(core: CoreSetup) {
- return {};
- }
-
- public start(core: CoreStart): DataStart {
- return {};
- }
-
- public stop() {}
-}
diff --git a/src/legacy/core_plugins/data/public/setup.ts b/src/legacy/core_plugins/data/public/setup.ts
deleted file mode 100644
index a99a2a4d06efe..0000000000000
--- a/src/legacy/core_plugins/data/public/setup.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { setup } from './legacy';
-
-// for backwards compatibility with 7.3
-export const data = setup;
diff --git a/src/legacy/core_plugins/input_control_vis/index.ts b/src/legacy/core_plugins/input_control_vis/index.ts
index 8f6178e26126b..d67472ac4b95f 100644
--- a/src/legacy/core_plugins/input_control_vis/index.ts
+++ b/src/legacy/core_plugins/input_control_vis/index.ts
@@ -25,7 +25,7 @@ import { LegacyPluginApi, LegacyPluginInitializer } from '../../../../src/legacy
const inputControlVisPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) =>
new Plugin({
id: 'input_control_vis',
- require: ['kibana', 'elasticsearch', 'visualizations', 'interpreter', 'data'],
+ require: ['kibana', 'elasticsearch', 'visualizations', 'interpreter'],
publicDir: resolve(__dirname, 'public'),
uiExports: {
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
diff --git a/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts b/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts
index dae6c9abb625e..023e6ebb7125c 100644
--- a/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts
+++ b/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts
@@ -22,7 +22,6 @@ import { i18n } from '@kbn/i18n';
import { createInputControlVisController } from './vis_controller';
import { getControlsTab } from './components/editor/controls_tab';
import { OptionsTab } from './components/editor/options_tab';
-import { Status } from '../../visualizations/public';
import { InputControlVisDependencies } from './plugin';
import { defaultFeedbackMessage } from '../../../../plugins/kibana_utils/common';
@@ -40,7 +39,6 @@ export function createInputControlVisTypeDefinition(deps: InputControlVisDepende
defaultMessage: 'Create interactive controls for easy dashboard manipulation.',
}),
stage: 'experimental',
- requiresUpdateStatus: [Status.PARAMS, Status.TIME],
feedbackMessage: defaultFeedbackMessage,
visualization: InputControlVisController,
visConfig: {
diff --git a/src/legacy/core_plugins/input_control_vis/public/vis_controller.tsx b/src/legacy/core_plugins/input_control_vis/public/vis_controller.tsx
index 624d000dd8d7a..c0ab235c1b9d1 100644
--- a/src/legacy/core_plugins/input_control_vis/public/vis_controller.tsx
+++ b/src/legacy/core_plugins/input_control_vis/public/vis_controller.tsx
@@ -54,7 +54,7 @@ export const createInputControlVisController = (deps: InputControlVisDependencie
.subscribe(this.queryBarUpdateHandler);
}
- async render(visData: any, visParams: VisParams, status: any) {
+ async render(visData: any, visParams: VisParams) {
this.visParams = visParams;
this.controls = [];
this.controls = await this.initControls();
diff --git a/src/legacy/core_plugins/kibana/public/.eslintrc.js b/src/legacy/core_plugins/kibana/public/.eslintrc.js
index e7171a5291d26..1153706eb8566 100644
--- a/src/legacy/core_plugins/kibana/public/.eslintrc.js
+++ b/src/legacy/core_plugins/kibana/public/.eslintrc.js
@@ -43,8 +43,6 @@ function buildRestrictedPaths(shimmedPlugins) {
'ui/**/*',
'src/legacy/ui/**/*',
'src/legacy/core_plugins/kibana/public/**/*',
- 'src/legacy/core_plugins/data/public/**/*',
- '!src/legacy/core_plugins/data/public/index.ts',
`!src/legacy/core_plugins/kibana/public/${shimmedPlugin}/**/*`,
],
allowSameFolder: false,
diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts
index cf76a9355e384..d369eb9679de6 100644
--- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts
@@ -52,9 +52,6 @@ export { angular };
export { wrapInI18nContext } from 'ui/i18n';
import { search } from '../../../../../plugins/data/public';
export const { getRequestInspectorStats, getResponseInspectorStats, tabifyAggResponse } = search;
-// @ts-ignore
-// @ts-ignore
-export { timezoneProvider } from 'ui/vis/lib/timezone';
export {
unhashUrl,
redirectWhenMissing,
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx
index 107c30ec5e688..f788347ac016c 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx
@@ -42,9 +42,10 @@ import {
} from '@elastic/charts';
import { i18n } from '@kbn/i18n';
+import { IUiSettingsClient } from 'kibana/public';
import { EuiChartThemeType } from '@elastic/eui/dist/eui_charts_theme';
import { Subscription } from 'rxjs';
-import { getServices, timezoneProvider } from '../../../kibana_services';
+import { getServices } from '../../../kibana_services';
export interface DiscoverHistogramProps {
chartData: any;
@@ -86,6 +87,16 @@ function getIntervalInMs(
}
}
+function getTimezone(uiSettings: IUiSettingsClient) {
+ if (uiSettings.isDefault('dateFormat:tz')) {
+ const detectedTimezone = moment.tz.guess();
+ if (detectedTimezone) return detectedTimezone;
+ else return moment().format('Z');
+ } else {
+ return uiSettings.get('dateFormat:tz', 'Browser');
+ }
+}
+
export function findMinInterval(
xValues: number[],
esValue: number,
@@ -193,7 +204,7 @@ export class DiscoverHistogram extends Component {
if (!$scope.vis) return;
- return $scope.vis.getAggConfig().onSearchRequestStart(searchSource, options);
+ return $scope.vis.data.aggs.onSearchRequestStart(searchSource, options);
});
$scope.searchSource.setField('aggs', function() {
if (!$scope.vis) return;
- return $scope.vis.getAggConfig().toDsl();
+ return $scope.vis.data.aggs.toDsl();
});
}
diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/breadcrumbs.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/breadcrumbs.ts
index c334172805b9f..b6a63d50b205b 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/breadcrumbs.ts
+++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/breadcrumbs.ts
@@ -69,7 +69,7 @@ export function getEditBreadcrumbs($route: any) {
return [
...getLandingBreadcrumbs(),
{
- text: $route.current.locals.savedVis.title,
+ text: $route.current.locals.resolved.savedVis.title,
},
];
}
diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.html b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.html
index 28baf21925cbe..0dcacd30fba4e 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.html
+++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.html
@@ -69,7 +69,8 @@
diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js
index 7d1c29fbf48da..c5325ca3108b4 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js
+++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js
@@ -22,6 +22,7 @@ import _ from 'lodash';
import { Subscription } from 'rxjs';
import { map } from 'rxjs/operators';
import { i18n } from '@kbn/i18n';
+import { EventEmitter } from 'events';
import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -84,6 +85,7 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
uiSettings,
I18nContext,
setActiveUrl,
+ visualizations,
} = getServices();
const {
@@ -98,27 +100,63 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
);
// Retrieve the resolved SavedVis instance.
- const savedVis = $route.current.locals.savedVis;
+ const { vis, savedVis, savedSearch, embeddableHandler } = $route.current.locals.resolved;
+ $scope.eventEmitter = new EventEmitter();
const _applyVis = () => {
$scope.$apply();
};
// This will trigger a digest cycle. This is needed when vis is updated from a global angular like in visualize_embeddable.js.
- savedVis.vis.on('apply', _applyVis);
+ $scope.eventEmitter.on('apply', _applyVis);
// vis is instance of src/legacy/ui/public/vis/vis.js.
// SearchSource is a promise-based stream of search results that can inherit from other search sources.
- const { vis, searchSource, savedSearch } = savedVis;
+ const searchSource = vis.data.searchSource;
$scope.vis = vis;
+ $scope.savedSearch = savedSearch;
const $appStatus = {
dirty: !savedVis.id,
};
- vis.on('dirtyStateChange', ({ isDirty }) => {
- vis.dirty = isDirty;
- $scope.$digest();
+ const defaultQuery = {
+ query: '',
+ language:
+ localStorage.get('kibana.userQueryLanguage') || uiSettings.get('search:queryLanguage'),
+ };
+
+ const visStateToEditorState = () => {
+ const savedVisState = visualizations.convertFromSerializedVis(vis.serialize());
+ return {
+ uiState: vis.uiState.toJSON(),
+ query: vis.data.searchSource.getOwnField('query') || defaultQuery,
+ filters: vis.data.searchSource.getOwnField('filter') || [],
+ vis: { ...savedVisState.visState, title: vis.title },
+ linked: !!savedVis.savedSearchId,
+ };
+ };
+
+ const stateDefaults = visStateToEditorState();
+
+ const { stateContainer, stopStateSync } = useVisualizeAppState({
+ stateDefaults,
+ kbnUrlStateStorage,
+ });
+
+ $scope.eventEmitter.on('dirtyStateChange', ({ isDirty }) => {
+ if (!isDirty) {
+ stateContainer.transitions.updateVisState(visStateToEditorState().vis);
+ }
+ $timeout(() => {
+ $scope.dirty = isDirty;
+ });
+ });
+
+ $scope.eventEmitter.on('updateVis', () => {
+ embeddableHandler.reload();
});
+ $scope.embeddableHandler = embeddableHandler;
+
$scope.topNavMenu = [
...(visualizeCapabilities.save
? [
@@ -135,10 +173,10 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
),
testId: 'visualizeSaveButton',
disableButton() {
- return Boolean(vis.dirty);
+ return Boolean($scope.dirty);
},
tooltip() {
- if (vis.dirty) {
+ if ($scope.dirty) {
return i18n.translate(
'kbn.visualize.topNavMenu.saveVisualizationDisabledButtonTooltip',
{
@@ -207,7 +245,7 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
}),
testId: 'shareTopNavButton',
run: anchorElement => {
- const hasUnappliedChanges = vis.dirty;
+ const hasUnappliedChanges = $scope.dirty;
const hasUnsavedChanges = $appStatus.dirty;
share.toggleShareContextMenu({
anchorElement,
@@ -233,17 +271,17 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
}),
testId: 'openInspectorButton',
disableButton() {
- return !vis.hasInspector || !vis.hasInspector();
+ return !embeddableHandler.hasInspector || !embeddableHandler.hasInspector();
},
run() {
- const inspectorSession = vis.openInspector();
+ const inspectorSession = embeddableHandler.openInspector();
// Close the inspector if this scope is destroyed (e.g. because the user navigates away).
const removeWatch = $scope.$on('$destroy', () => inspectorSession.close());
// Remove that watch in case the user closes the inspector session herself.
inspectorSession.onClose.finally(removeWatch);
},
tooltip() {
- if (!vis.hasInspector || !vis.hasInspector()) {
+ if (!embeddableHandler.hasInspector || !embeddableHandler.hasInspector()) {
return i18n.translate('kbn.visualize.topNavMenu.openInspectorDisabledButtonTooltip', {
defaultMessage: `This visualization doesn't support any inspectors.`,
});
@@ -257,7 +295,7 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
defaultMessage: 'Refresh',
}),
run: function() {
- vis.forceReload();
+ embeddableHandler.reload();
},
testId: 'visualizeRefreshButton',
},
@@ -267,28 +305,6 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
chrome.docTitle.change(savedVis.title);
}
- const defaultQuery = {
- query: '',
- language:
- localStorage.get('kibana.userQueryLanguage') || uiSettings.get('search:queryLanguage'),
- };
-
- // Extract visualization state with filtered aggs. You can see these filtered aggs in the URL.
- // Consists of things like aggs, params, listeners, title, type, etc.
- const savedVisState = vis.getState();
- const stateDefaults = {
- uiState: savedVis.uiStateJSON ? JSON.parse(savedVis.uiStateJSON) : {},
- query: searchSource.getOwnField('query') || defaultQuery,
- filters: searchSource.getOwnField('filter') || [],
- vis: savedVisState,
- linked: !!savedVis.savedSearchId,
- };
-
- const { stateContainer, stopStateSync } = useVisualizeAppState({
- stateDefaults,
- kbnUrlStateStorage,
- });
-
// sync initial app filters from state to filterManager
filterManager.setAppFilters(_.cloneDeep(stateContainer.getState().filters));
// setup syncing of app filters between appState and filterManager
@@ -315,7 +331,8 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
// appState then they won't be equal.
if (!_.isEqual(stateContainer.getState().vis, stateDefaults.vis)) {
try {
- vis.setState(stateContainer.getState().vis);
+ const { aggs, ...visState } = stateContainer.getState().vis;
+ vis.setState({ ...visState, data: { aggs } });
} catch (error) {
// stop syncing url updtes with the state to prevent extra syncing
stopAllSyncing();
@@ -369,8 +386,8 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
};
function init() {
- if (vis.indexPattern) {
- $scope.indexPattern = vis.indexPattern;
+ if (vis.data.indexPattern) {
+ $scope.indexPattern = vis.data.indexPattern;
} else {
indexPatterns.getDefault().then(defaultIndexPattern => {
$scope.indexPattern = defaultIndexPattern;
@@ -379,22 +396,14 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
const initialState = stateContainer.getState();
- $scope.appState = {
- // mock implementation of the legacy appState.save()
- // this could be even replaced by passing only "updateAppState" callback
- save() {
- stateContainer.transitions.updateVisState(vis.getState());
- },
- };
-
const handleLinkedSearch = linked => {
if (linked && !savedVis.savedSearchId && savedSearch) {
savedVis.savedSearchId = savedSearch.id;
- vis.savedSearchId = savedSearch.id;
+ vis.data.savedSearchId = savedSearch.id;
searchSource.setParent(savedSearch.searchSource);
} else if (!linked && savedVis.savedSearchId) {
delete savedVis.savedSearchId;
- delete vis.savedSearchId;
+ delete vis.data.savedSearchId;
}
};
@@ -403,6 +412,7 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
'uiState',
stateContainer
);
+ vis.uiState = persistedState;
$scope.uiState = persistedState;
$scope.savedVis = savedVis;
$scope.query = initialState.query;
@@ -427,7 +437,7 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
$scope.showQueryBarTimePicker = () => {
// tsvb loads without an indexPattern initially (TODO investigate).
// hide timefilter only if timeFieldName is explicitly undefined.
- const hasTimeField = vis.indexPattern ? !!vis.indexPattern.timeFieldName : true;
+ const hasTimeField = vis.data.indexPattern ? !!vis.data.indexPattern.timeFieldName : true;
return vis.type.options.showTimePicker && hasTimeField;
};
@@ -442,10 +452,24 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
updateSavedQueryFromUrl(state.savedQuery);
// if the browser history was changed manually we need to reflect changes in the editor
- if (!_.isEqual(vis.getState(), state.vis)) {
- vis.setState(state.vis);
- vis.forceReload();
- vis.emit('updateEditor');
+ if (
+ !_.isEqual(
+ {
+ ...visualizations.convertFromSerializedVis(vis.serialize()).visState,
+ title: vis.title,
+ },
+ state.vis
+ )
+ ) {
+ const { aggs, ...visState } = state.vis;
+ vis.setState({
+ ...visState,
+ data: {
+ aggs,
+ },
+ });
+ embeddableHandler.reload();
+ $scope.eventEmitter.emit('updateEditor');
}
$appStatus.dirty = true;
@@ -498,8 +522,8 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
const { query, linked, filters } = stateContainer.getState();
$scope.query = query;
handleLinkedSearch(linked);
- savedVis.searchSource.setField('query', query);
- savedVis.searchSource.setField('filter', filters);
+ vis.data.searchSource.setField('query', query);
+ vis.data.searchSource.setField('filter', filters);
$scope.$broadcast('render');
};
@@ -533,7 +557,7 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
}
savedVis.destroy();
subscriptions.unsubscribe();
- $scope.vis.off('apply', _applyVis);
+ $scope.eventEmitter.off('apply', _applyVis);
unsubscribePersisted();
unsubscribeStateUpdates();
@@ -556,7 +580,7 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
// If nothing has changed, trigger the fetch manually, otherwise it will happen as a result of the changes
if (!isUpdate) {
- $scope.vis.forceReload();
+ embeddableHandler.reload();
}
};
@@ -605,8 +629,10 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
title: savedVis.title,
type: savedVis.type || stateContainer.getState().vis.type,
});
+ savedVis.searchSource.setField('query', stateContainer.getState().query);
+ savedVis.searchSource.setField('filter', stateContainer.getState().filters);
savedVis.visState = stateContainer.getState().vis;
- savedVis.uiStateJSON = angular.toJson($scope.uiState.getChanges());
+ savedVis.uiStateJSON = angular.toJson($scope.uiState.toJSON());
$appStatus.dirty = false;
return savedVis.save(saveOptions).then(
@@ -720,7 +746,7 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
);
};
- vis.on('unlinkFromSavedSearch', unlinkFromSavedSearch);
+ $scope.eventEmitter.on('unlinkFromSavedSearch', unlinkFromSavedSearch);
addHelpMenuToAppChrome(chrome, docLinks);
diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/visualization.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/visualization.js
index c8acea168444f..fbabd6fc87c98 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/visualization.js
+++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/visualization.js
@@ -17,12 +17,12 @@
* under the License.
*/
-export function initVisualizationDirective(app, deps) {
+export function initVisualizationDirective(app) {
app.directive('visualizationEmbedded', function($timeout) {
return {
restrict: 'E',
scope: {
- savedObj: '=',
+ embeddableHandler: '=',
uiState: '=?',
timeRange: '=',
filters: '=',
@@ -31,24 +31,16 @@ export function initVisualizationDirective(app, deps) {
},
link: function($scope, element) {
$scope.renderFunction = async () => {
- if (!$scope._handler) {
- $scope._handler = await deps.embeddable
- .getEmbeddableFactory('visualization')
- .createFromObject($scope.savedObj, {
- timeRange: $scope.timeRange,
- filters: $scope.filters || [],
- query: $scope.query,
- appState: $scope.appState,
- uiState: $scope.uiState,
- });
- $scope._handler.render(element[0]);
- } else {
- $scope._handler.updateInput({
- timeRange: $scope.timeRange,
- filters: $scope.filters || [],
- query: $scope.query,
- });
+ if (!$scope.rendered) {
+ $scope.embeddableHandler.render(element[0]);
+ $scope.rendered = true;
}
+
+ $scope.embeddableHandler.updateInput({
+ timeRange: $scope.timeRange,
+ filters: $scope.filters || [],
+ query: $scope.query,
+ });
};
$scope.$on('render', event => {
@@ -59,8 +51,8 @@ export function initVisualizationDirective(app, deps) {
});
$scope.$on('$destroy', () => {
- if ($scope._handler) {
- $scope._handler.destroy();
+ if ($scope.embeddableHandler) {
+ $scope.embeddableHandler.destroy();
}
});
},
diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/visualization_editor.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/visualization_editor.js
index f2d9cbe2ad84c..ef174dbaa5865 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/visualization_editor.js
+++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/visualization_editor.js
@@ -22,16 +22,23 @@ export function initVisEditorDirective(app, deps) {
return {
restrict: 'E',
scope: {
- savedObj: '=',
+ vis: '=',
uiState: '=?',
timeRange: '=',
filters: '=',
query: '=',
- appState: '=',
+ savedSearch: '=',
+ embeddableHandler: '=',
+ eventEmitter: '=',
},
link: function($scope, element) {
- const Editor = $scope.savedObj.vis.type.editor || deps.DefaultVisualizationEditor;
- const editor = new Editor(element[0], $scope.savedObj);
+ const Editor = $scope.vis.type.editor || deps.DefaultVisualizationEditor;
+ const editor = new Editor(
+ element[0],
+ $scope.vis,
+ $scope.eventEmitter,
+ $scope.embeddableHandler
+ );
$scope.renderFunction = () => {
editor.render({
@@ -42,8 +49,8 @@ export function initVisEditorDirective(app, deps) {
timeRange: $scope.timeRange,
filters: $scope.filters,
query: $scope.query,
- appState: $scope.appState,
- linked: !!$scope.savedObj.savedSearchId,
+ linked: !!$scope.vis.data.savedSearchId,
+ savedSearch: $scope.savedSearch,
});
};
diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js
index 0f1d50b149cd9..b0b1ae31a02a5 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js
+++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js
@@ -40,6 +40,50 @@ import {
getCreateBreadcrumbs,
getEditBreadcrumbs,
} from './breadcrumbs';
+import { createSavedSearchesLoader } from '../../../../../../plugins/discover/public';
+
+const getResolvedResults = deps => {
+ const { core, data, visualizations } = deps;
+
+ const results = {};
+
+ return savedVis => {
+ results.savedVis = savedVis;
+ return visualizations
+ .convertToSerializedVis(savedVis)
+ .then(serializedVis => visualizations.createVis(serializedVis.type, serializedVis))
+ .then(vis => {
+ if (vis.type.setup) {
+ return vis.type.setup(vis).catch(() => vis);
+ }
+ return vis;
+ })
+ .then(vis => {
+ results.vis = vis;
+ return deps.embeddable.getEmbeddableFactory('visualization').createFromObject(results.vis, {
+ timeRange: data.query.timefilter.timefilter.getTime(),
+ filters: data.query.filterManager.getFilters(),
+ });
+ })
+ .then(embeddableHandler => {
+ results.embeddableHandler = embeddableHandler;
+ if (results.vis.data.savedSearchId) {
+ return createSavedSearchesLoader({
+ savedObjectsClient: core.savedObjects.client,
+ indexPatterns: data.indexPatterns,
+ chrome: core.chrome,
+ overlays: core.overlays,
+ }).get(results.vis.data.savedSearchId);
+ }
+ })
+ .then(savedSearch => {
+ if (savedSearch) {
+ results.savedSearch = savedSearch;
+ }
+ return results;
+ });
+ };
+};
export function initVisualizeApp(app, deps) {
initVisualizeAppDirective(app, deps);
@@ -101,7 +145,7 @@ export function initVisualizeApp(app, deps) {
template: editorTemplate,
k7Breadcrumbs: getCreateBreadcrumbs,
resolve: {
- savedVis: function($route, history) {
+ resolved: function($route, history) {
const { core, data, savedVisualizations, visualizations, toastNotifications } = deps;
const visTypes = visualizations.all();
const visType = find(visTypes, { name: $route.current.params.type });
@@ -121,12 +165,7 @@ export function initVisualizeApp(app, deps) {
return ensureDefaultIndexPattern(core, data, history)
.then(() => savedVisualizations.get($route.current.params))
- .then(savedVis => {
- if (savedVis.vis.type.setup) {
- return savedVis.vis.type.setup(savedVis).catch(() => savedVis);
- }
- return savedVis;
- })
+ .then(getResolvedResults(deps))
.catch(
redirectWhenMissing({
history,
@@ -142,20 +181,16 @@ export function initVisualizeApp(app, deps) {
template: editorTemplate,
k7Breadcrumbs: getEditBreadcrumbs,
resolve: {
- savedVis: function($route, history) {
+ resolved: function($route, history) {
const { chrome, core, data, savedVisualizations, toastNotifications } = deps;
+
return ensureDefaultIndexPattern(core, data, history)
.then(() => savedVisualizations.get($route.current.params.id))
.then(savedVis => {
chrome.recentlyAccessed.add(savedVis.getFullPath(), savedVis.title, savedVis.id);
return savedVis;
})
- .then(savedVis => {
- if (savedVis.vis.type.setup) {
- return savedVis.vis.type.setup(savedVis).catch(() => savedVis);
- }
- return savedVis;
- })
+ .then(getResolvedResults(deps))
.catch(
redirectWhenMissing({
history,
diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts
index 01ce872aeb679..246a031f05769 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts
+++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts
@@ -29,8 +29,10 @@ import { PersistedState } from 'src/plugins/visualizations/public';
import { LegacyCoreStart } from 'kibana/public';
import { Vis } from 'src/legacy/core_plugins/visualizations/public';
import { VisSavedObject } from '../legacy_imports';
+import { SavedVisState } from '../../../../visualizations/public/np_ready/public/types';
+import { SavedSearch } from '../../../../../../plugins/discover/public';
-export type PureVisState = ReturnType;
+export type PureVisState = SavedVisState;
export interface VisualizeAppState {
filters: Filter[];
@@ -58,14 +60,13 @@ export interface VisualizeAppStateTransitions {
}
export interface EditorRenderProps {
- appState: { save(): void };
core: LegacyCoreStart;
data: DataPublicPluginStart;
- embeddable: EmbeddableStart;
filters: Filter[];
- uiState: PersistedState;
timeRange: TimeRange;
query?: Query;
+ savedSearch?: SavedSearch;
+ uiState: PersistedState;
/**
* Flag to determine if visualiztion is linked to the saved search
*/
diff --git a/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js b/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js
index 6bdb5d00e67d8..23ca99791e92e 100644
--- a/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js
+++ b/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js
@@ -21,7 +21,6 @@ import expect from '@kbn/expect';
import ngMock from 'ng_mock';
import _ from 'lodash';
import ChoroplethLayer from '../choropleth_layer';
-import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern';
import { ImageComparator } from 'test_utils/image_comparator';
import worldJson from './world.json';
import EMS_CATALOGUE from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_manifest.json';
@@ -38,13 +37,11 @@ import afterdatachangePng from './afterdatachange.png';
import afterdatachangeandresizePng from './afterdatachangeandresize.png';
import aftercolorchangePng from './aftercolorchange.png';
import changestartupPng from './changestartup.png';
-import {
- setup as visualizationsSetup,
- start as visualizationsStart,
-} from '../../../visualizations/public/np_ready/public/legacy';
+import { setup as visualizationsSetup } from '../../../visualizations/public/np_ready/public/legacy';
import { createRegionMapVisualization } from '../region_map_visualization';
import { createRegionMapTypeDefinition } from '../region_map_type';
+import { ExprVis } from '../../../visualizations/public/np_ready/public/expressions/vis';
const THRESHOLD = 0.45;
const PIXEL_DIFF = 96;
@@ -52,7 +49,6 @@ const PIXEL_DIFF = 96;
describe('RegionMapsVisualizationTests', function() {
let domNode;
let RegionMapsVisualization;
- let indexPattern;
let vis;
let dependencies;
@@ -115,7 +111,6 @@ describe('RegionMapsVisualizationTests', function() {
}
RegionMapsVisualization = createRegionMapVisualization(dependencies);
- indexPattern = Private(LogstashIndexPatternStubProvider);
ChoroplethLayer.prototype._makeJsonAjaxCall = async function() {
//simulate network call
@@ -158,7 +153,7 @@ describe('RegionMapsVisualizationTests', function() {
imageComparator = new ImageComparator();
- vis = visualizationsStart.createVis(indexPattern, {
+ vis = new ExprVis({
type: 'region_map',
});
diff --git a/src/legacy/core_plugins/region_map/public/region_map_type.js b/src/legacy/core_plugins/region_map/public/region_map_type.js
index 9a1a76362e094..4faa3f92abb5a 100644
--- a/src/legacy/core_plugins/region_map/public/region_map_type.js
+++ b/src/legacy/core_plugins/region_map/public/region_map_type.js
@@ -20,7 +20,6 @@ import React from 'react';
import { i18n } from '@kbn/i18n';
import { mapToLayerWithId } from './util';
import { createRegionMapVisualization } from './region_map_visualization';
-import { Status } from '../../visualizations/public';
import { RegionMapOptions } from './components/region_map_options';
import { truncatedColorSchemas } from '../../../../plugins/charts/public';
import { Schemas } from '../../vis_default_editor/public';
@@ -55,7 +54,6 @@ provided base maps, or add your own. Darker colors represent higher values.',
showAllShapes: true, //still under consideration
},
},
- requiresUpdateStatus: [Status.AGGS, Status.PARAMS, Status.RESIZE, Status.DATA, Status.UI_STATE],
visualization,
editorConfig: {
optionsTemplate: props => ,
@@ -100,9 +98,7 @@ provided base maps, or add your own. Darker colors represent higher values.',
},
]),
},
- setup: async savedVis => {
- const vis = savedVis.vis;
-
+ setup: async vis => {
const tmsLayers = await serviceSettings.getTMSServices();
vis.type.editorConfig.collections.tmsLayers = tmsLayers;
if (!vis.params.wms.selectedTmsLayer && tmsLayers.length) {
@@ -146,7 +142,7 @@ provided base maps, or add your own. Darker colors represent higher values.',
vis.params.selectedJoinField = selectedJoinField;
}
- return savedVis;
+ return vis;
},
};
}
diff --git a/src/legacy/core_plugins/region_map/public/region_map_visualization.js b/src/legacy/core_plugins/region_map/public/region_map_visualization.js
index 8b5812052a395..25641ea76809d 100644
--- a/src/legacy/core_plugins/region_map/public/region_map_visualization.js
+++ b/src/legacy/core_plugins/region_map/public/region_map_visualization.js
@@ -39,8 +39,8 @@ export function createRegionMapVisualization({ serviceSettings, $injector, uiSet
this._choroplethLayer = null;
}
- async render(esResponse, visParams, status) {
- await super.render(esResponse, visParams, status);
+ async render(esResponse, visParams) {
+ await super.render(esResponse, visParams);
if (this._choroplethLayer) {
await this._choroplethLayer.whenDataLoaded();
}
diff --git a/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js b/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js
index 6a08405b5b6a5..3b8a7dfbed313 100644
--- a/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js
+++ b/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js
@@ -19,7 +19,6 @@
import expect from '@kbn/expect';
import ngMock from 'ng_mock';
-import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern';
import { ImageComparator } from 'test_utils/image_comparator';
import dummyESResponse from './dummy_es_response.json';
import initial from './initial.png';
@@ -32,13 +31,11 @@ import EMS_TILES from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_
import EMS_STYLE_ROAD_MAP_BRIGHT from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_style_bright';
import EMS_STYLE_ROAD_MAP_DESATURATED from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_style_desaturated';
import EMS_STYLE_DARK_MAP from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_style_dark';
-import {
- setup as visualizationsSetup,
- start as visualizationsStart,
-} from '../../../visualizations/public/np_ready/public/legacy';
+import { setup as visualizationsSetup } from '../../../visualizations/public/np_ready/public/legacy';
import { createTileMapVisualization } from '../tile_map_visualization';
import { createTileMapTypeDefinition } from '../tile_map_type';
+import { ExprVis } from '../../../visualizations/public/np_ready/public/expressions/vis';
function mockRawData() {
const stack = [dummyESResponse];
@@ -67,7 +64,6 @@ let visRegComplete = false;
describe('CoordinateMapsVisualizationTest', function() {
let domNode;
let CoordinateMapsVisualization;
- let indexPattern;
let vis;
let dependencies;
@@ -92,7 +88,6 @@ describe('CoordinateMapsVisualizationTest', function() {
}
CoordinateMapsVisualization = createTileMapVisualization(dependencies);
- indexPattern = Private(LogstashIndexPatternStubProvider);
getManifestStub = serviceSettings.__debugStubManifestCalls(async url => {
//simulate network calls
@@ -124,7 +119,7 @@ describe('CoordinateMapsVisualizationTest', function() {
setupDOM('512px', '512px');
imageComparator = new ImageComparator();
- vis = visualizationsStart.createVis(indexPattern, {
+ vis = new ExprVis({
type: 'tile_map',
});
vis.params = {
diff --git a/src/legacy/core_plugins/tile_map/public/base_maps_visualization.js b/src/legacy/core_plugins/tile_map/public/base_maps_visualization.js
index ebb0c24243263..d38159c91ef9f 100644
--- a/src/legacy/core_plugins/tile_map/public/base_maps_visualization.js
+++ b/src/legacy/core_plugins/tile_map/public/base_maps_visualization.js
@@ -63,28 +63,21 @@ export function BaseMapsVisualizationProvider(serviceSettings) {
* @param status
* @return {Promise}
*/
- async render(esResponse, visParams, status) {
+ async render(esResponse, visParams) {
if (!this._kibanaMap) {
//the visualization has been destroyed;
return;
}
await this._mapIsLoaded;
-
- if (status.resize) {
- this._kibanaMap.resize();
- }
- if (status.params || status.aggs) {
- this._params = visParams;
- await this._updateParams();
- }
+ this._kibanaMap.resize();
+ this._params = visParams;
+ await this._updateParams();
if (this._hasESResponseChanged(esResponse)) {
await this._updateData(esResponse);
}
- if (status.uiState) {
- this._kibanaMap.useUiStateFromVisualization(this.vis);
- }
+ this._kibanaMap.useUiStateFromVisualization(this.vis);
await this._whenBaseLayerIsLoaded();
}
diff --git a/src/legacy/core_plugins/tile_map/public/tile_map_type.js b/src/legacy/core_plugins/tile_map/public/tile_map_type.js
index 0809bf6ecbab6..39d39a4c8f8fc 100644
--- a/src/legacy/core_plugins/tile_map/public/tile_map_type.js
+++ b/src/legacy/core_plugins/tile_map/public/tile_map_type.js
@@ -23,7 +23,6 @@ import { i18n } from '@kbn/i18n';
import { convertToGeoJson } from 'ui/vis/map/convert_to_geojson';
import { Schemas } from '../../vis_default_editor/public';
-import { Status } from '../../visualizations/public';
import { createTileMapVisualization } from './tile_map_visualization';
import { TileMapOptions } from './components/tile_map_options';
import { MapTypes } from './map_types';
@@ -57,7 +56,6 @@ export function createTileMapTypeDefinition(dependencies) {
wms: uiSettings.get('visualization:tileMap:WMSdefaults'),
},
},
- requiresUpdateStatus: [Status.AGGS, Status.PARAMS, Status.RESIZE, Status.UI_STATE],
requiresPartialRows: true,
visualization: CoordinateMapsVisualization,
responseHandler: convertToGeoJson,
@@ -143,21 +141,20 @@ export function createTileMapTypeDefinition(dependencies) {
},
]),
},
- setup: async savedVis => {
- const vis = savedVis.vis;
+ setup: async vis => {
let tmsLayers;
try {
tmsLayers = await serviceSettings.getTMSServices();
} catch (e) {
- return savedVis;
+ return vis;
}
vis.type.editorConfig.collections.tmsLayers = tmsLayers;
if (!vis.params.wms.selectedTmsLayer && tmsLayers.length) {
vis.params.wms.selectedTmsLayer = tmsLayers[0];
}
- return savedVis;
+ return vis;
},
};
}
diff --git a/src/legacy/core_plugins/timelion/index.ts b/src/legacy/core_plugins/timelion/index.ts
index 9e2bfd4023bd9..41a15dc4e0186 100644
--- a/src/legacy/core_plugins/timelion/index.ts
+++ b/src/legacy/core_plugins/timelion/index.ts
@@ -29,7 +29,7 @@ const experimentalLabel = i18n.translate('timelion.uiSettings.experimentalLabel'
const timelionPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) =>
new Plugin({
- require: ['kibana', 'elasticsearch', 'data'],
+ require: ['kibana', 'elasticsearch'],
config(Joi: any) {
return Joi.object({
enabled: Joi.boolean().default(true),
diff --git a/src/legacy/core_plugins/timelion/public/app.js b/src/legacy/core_plugins/timelion/public/app.js
index a9d678cfea79c..a50f8a2cd3e8d 100644
--- a/src/legacy/core_plugins/timelion/public/app.js
+++ b/src/legacy/core_plugins/timelion/public/app.js
@@ -24,10 +24,10 @@ import { i18n } from '@kbn/i18n';
import { capabilities } from 'ui/capabilities';
import { docTitle } from 'ui/doc_title';
import { fatalError, toastNotifications } from 'ui/notify';
-import { timezoneProvider } from 'ui/vis/lib/timezone';
import { timefilter } from 'ui/timefilter';
import { npStart } from 'ui/new_platform';
import { getSavedSheetBreadcrumbs, getCreateBreadcrumbs } from './breadcrumbs';
+import { getTimezone } from '../../vis_type_timelion/public';
import 'uiExports/savedObjectTypes';
@@ -38,7 +38,6 @@ import 'ui/directives/input_focus';
import './directives/saved_object_finder';
import 'ui/directives/listen';
import './directives/saved_object_save_as_checkbox';
-import '../../data/public/legacy';
import './services/saved_sheet_register';
import rootTemplate from 'plugins/timelion/index.html';
@@ -116,8 +115,7 @@ app.controller('timelion', function(
$timeout,
AppState,
config,
- kbnUrl,
- Private
+ kbnUrl
) {
// Keeping this at app scope allows us to keep the current page when the user
// switches to say, the timepicker.
@@ -128,7 +126,7 @@ app.controller('timelion', function(
timefilter.enableTimeRangeSelector();
const savedVisualizations = visualizations.savedVisualizationsLoader;
- const timezone = Private(timezoneProvider)();
+ const timezone = getTimezone(config);
const defaultExpression = '.es(*)';
const savedSheet = $route.current.locals.savedSheet;
diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg.test.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/agg.test.tsx
index 7e715be25bff3..feb5b3caa023b 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/components/agg.test.tsx
+++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg.test.tsx
@@ -22,12 +22,12 @@ import { mount, shallow } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { IndexPattern, IAggType, AggGroupNames } from 'src/plugins/data/public';
-import { VisState } from 'src/legacy/core_plugins/visualizations/public';
import { DefaultEditorAgg, DefaultEditorAggProps } from './agg';
import { DefaultEditorAggParams } from './agg_params';
import { AGGS_ACTION_KEYS } from './agg_group_state';
import { Schema } from '../schemas';
+import { EditorVisState } from './sidebar/state/reducers';
jest.mock('./agg_params', () => ({
DefaultEditorAggParams: () => null,
@@ -67,7 +67,7 @@ describe('DefaultEditorAgg component', () => {
isLastBucket: false,
isRemovable: false,
metricAggs: [],
- state: { params: {} } as VisState,
+ state: { params: {} } as EditorVisState,
setAggParamValue,
setStateParamValue,
onAggTypeChange: () => {},
diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_common_props.ts b/src/legacy/core_plugins/vis_default_editor/public/components/agg_common_props.ts
index ec92f511b6eee..3aae10879138a 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_common_props.ts
+++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg_common_props.ts
@@ -17,9 +17,10 @@
* under the License.
*/
-import { VisState, VisParams } from 'src/legacy/core_plugins/visualizations/public';
+import { VisParams } from 'src/legacy/core_plugins/visualizations/public';
import { IAggType, IAggConfig, IAggGroupNames } from 'src/plugins/data/public';
import { Schema } from '../schemas';
+import { EditorVisState } from './sidebar/state/reducers';
type AggId = IAggConfig['id'];
type AggParams = IAggConfig['params'];
@@ -31,7 +32,7 @@ export interface DefaultEditorCommonProps {
formIsTouched: boolean;
groupName: IAggGroupNames;
metricAggs: IAggConfig[];
- state: VisState;
+ state: EditorVisState;
setAggParamValue: (
aggId: AggId,
paramName: T,
diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.test.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.test.tsx
index 63f5e696c99f4..5d02f0a2c759e 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.test.tsx
+++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.test.tsx
@@ -20,12 +20,12 @@
import React from 'react';
import { mount, shallow } from 'enzyme';
import { act } from 'react-dom/test-utils';
-import { VisState } from 'src/legacy/core_plugins/visualizations/public';
import { IAggConfigs, IAggConfig } from 'src/plugins/data/public';
import { DefaultEditorAggGroup, DefaultEditorAggGroupProps } from './agg_group';
import { DefaultEditorAgg } from './agg';
import { DefaultEditorAggAdd } from './agg_add';
import { Schema } from '../schemas';
+import { EditorVisState } from './sidebar/state/reducers';
jest.mock('@elastic/eui', () => ({
EuiTitle: 'eui-title',
@@ -93,8 +93,8 @@ describe('DefaultEditorAgg component', () => {
metricAggs: [],
groupName: 'metrics',
state: {
- aggs,
- } as VisState,
+ data: { aggs },
+ } as EditorVisState,
schemas: [
{
name: 'metrics',
@@ -147,8 +147,8 @@ describe('DefaultEditorAgg component', () => {
});
expect(reorderAggs).toHaveBeenCalledWith(
- defaultProps.state.aggs.aggs[0],
- defaultProps.state.aggs.aggs[1]
+ defaultProps.state.data.aggs!.aggs[0],
+ defaultProps.state.data.aggs!.aggs[1]
);
});
diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.tsx
index 600612f2cf9d8..08b69ef37f528 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.tsx
+++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.tsx
@@ -73,9 +73,10 @@ function DefaultEditorAggGroup({
const schemaNames = getSchemasByGroup(schemas, groupName).map(s => s.name);
const group: IAggConfig[] = useMemo(
() =>
- state.aggs.aggs.filter((agg: IAggConfig) => agg.schema && schemaNames.includes(agg.schema)) ||
- [],
- [state.aggs.aggs, schemaNames]
+ state.data.aggs!.aggs.filter(
+ (agg: IAggConfig) => agg.schema && schemaNames.includes(agg.schema)
+ ) || [],
+ [state.data.aggs, schemaNames]
);
const stats = {
diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_param_props.ts b/src/legacy/core_plugins/vis_default_editor/public/components/agg_param_props.ts
index 7c2852798b403..aec332e8674d7 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_param_props.ts
+++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg_param_props.ts
@@ -18,10 +18,10 @@
*/
import { IAggConfig, AggParam, IndexPatternField } from 'src/plugins/data/public';
-import { VisState } from 'src/legacy/core_plugins/visualizations/public';
import { ComboBoxGroupedOptions } from '../utils';
import { EditorConfig } from './utils';
import { Schema } from '../schemas';
+import { EditorVisState } from './sidebar/state/reducers';
// NOTE: we cannot export the interface with export { InterfaceName }
// as there is currently a bug on babel typescript transform plugin for it
@@ -35,7 +35,7 @@ export interface AggParamCommonProps {
formIsTouched: boolean;
indexedFields?: ComboBoxGroupedOptions;
showValidation: boolean;
- state: VisState;
+ state: EditorVisState;
value?: T;
metricAggs: IAggConfig[];
schemas: Schema[];
diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_params.test.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/agg_params.test.tsx
index cd6486b6a1532..1c49ebf40640e 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_params.test.tsx
+++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg_params.test.tsx
@@ -20,7 +20,6 @@
import React from 'react';
import { mount } from 'enzyme';
-import { VisState } from 'src/legacy/core_plugins/visualizations/public';
import { IndexPattern, IAggConfig, AggGroupNames } from 'src/plugins/data/public';
import {
DefaultEditorAggParams as PureDefaultEditorAggParams,
@@ -28,6 +27,7 @@ import {
} from './agg_params';
import { KibanaContextProvider } from '../../../../../plugins/kibana_react/public';
import { dataPluginMock } from '../../../../../plugins/data/public/mocks';
+import { EditorVisState } from './sidebar/state/reducers';
const mockEditorConfig = {
useNormalizedEsInterval: { hidden: false, fixedValue: false },
@@ -108,7 +108,7 @@ describe('DefaultEditorAggParams component', () => {
formIsTouched: false,
indexPattern: {} as IndexPattern,
metricAggs: [],
- state: {} as VisState,
+ state: {} as EditorVisState,
setAggParamValue,
onAggTypeChange,
setTouched,
diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_params_helper.test.ts b/src/legacy/core_plugins/vis_default_editor/public/components/agg_params_helper.test.ts
index f2ebbdc87a60a..bed2561341737 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_params_helper.test.ts
+++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg_params_helper.test.ts
@@ -25,7 +25,6 @@ import {
IndexPattern,
IndexPatternField,
} from 'src/plugins/data/public';
-import { VisState } from 'src/legacy/core_plugins/visualizations/public';
import {
getAggParamsToRender,
getAggTypeOptions,
@@ -34,6 +33,7 @@ import {
import { FieldParamEditor, OrderByParamEditor } from './controls';
import { EditorConfig } from './utils';
import { Schema } from '../schemas';
+import { EditorVisState } from './sidebar/state/reducers';
jest.mock('../utils', () => ({
groupAndSortBy: jest.fn(() => ['indexedFields']),
@@ -58,7 +58,7 @@ describe('DefaultEditorAggParams helpers', () => {
hideCustomLabel: true,
} as Schema,
];
- const state = {} as VisState;
+ const state = {} as EditorVisState;
const metricAggs: IAggConfig[] = [];
const emptyParams = {
basic: [],
diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_params_helper.ts b/src/legacy/core_plugins/vis_default_editor/public/components/agg_params_helper.ts
index e07bf81697579..10590e1a59f4a 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_params_helper.ts
+++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg_params_helper.ts
@@ -28,7 +28,6 @@ import {
IndexPattern,
IndexPatternField,
} from 'src/plugins/data/public';
-import { VisState } from 'src/legacy/core_plugins/visualizations/public';
import { groupAndSortBy, ComboBoxGroupedOptions } from '../utils';
import { AggTypeState, AggParamsState } from './agg_params_state';
import { AggParamEditorProps } from './agg_param_props';
@@ -36,12 +35,13 @@ import { aggParamsMap } from './agg_params_map';
import { EditorConfig } from './utils';
import { Schema, getSchemaByName } from '../schemas';
import { search } from '../../../../../plugins/data/public';
+import { EditorVisState } from './sidebar/state/reducers';
interface ParamInstanceBase {
agg: IAggConfig;
editorConfig: EditorConfig;
metricAggs: IAggConfig[];
- state: VisState;
+ state: EditorVisState;
schemas: Schema[];
hideCustomLabel?: boolean;
}
diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/field.test.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/controls/field.test.tsx
index 1043431475494..b33149dc51a19 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/field.test.tsx
+++ b/src/legacy/core_plugins/vis_default_editor/public/components/controls/field.test.tsx
@@ -23,9 +23,9 @@ import { mount, shallow, ReactWrapper } from 'enzyme';
import { EuiComboBoxProps, EuiComboBox } from '@elastic/eui';
import { IAggConfig, IndexPatternField } from 'src/plugins/data/public';
-import { VisState } from 'src/legacy/core_plugins/visualizations/public';
import { ComboBoxGroupedOptions } from '../../utils';
import { FieldParamEditor, FieldParamEditorProps } from './field';
+import { EditorVisState } from '../sidebar/state/reducers';
function callComboBoxOnChange(comp: ReactWrapper, value: any = []) {
const comboBoxProps = comp.find(EuiComboBox).props() as EuiComboBoxProps;
@@ -78,7 +78,7 @@ describe('FieldParamEditor component', () => {
setValue,
setValidity,
setTouched,
- state: {} as VisState,
+ state: {} as EditorVisState,
metricAggs: [] as IAggConfig[],
schemas: [],
};
diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/percentiles.test.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/controls/percentiles.test.tsx
index 76eb12af8c4e2..82166440cf8e8 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/percentiles.test.tsx
+++ b/src/legacy/core_plugins/vis_default_editor/public/components/controls/percentiles.test.tsx
@@ -20,9 +20,9 @@
import React from 'react';
import { AggParamEditorProps } from '../agg_param_props';
import { IAggConfig } from 'src/plugins/data/public';
-import { VisState } from 'src/legacy/core_plugins/visualizations/public';
import { mount } from 'enzyme';
import { PercentilesEditor } from './percentiles';
+import { EditorVisState } from '../sidebar/state/reducers';
describe('PercentilesEditor component', () => {
let setValue: jest.Mock;
@@ -45,7 +45,7 @@ describe('PercentilesEditor component', () => {
setValue,
setValidity,
setTouched,
- state: {} as VisState,
+ state: {} as EditorVisState,
metricAggs: [] as IAggConfig[],
schemas: [],
};
diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/test_utils.ts b/src/legacy/core_plugins/vis_default_editor/public/components/controls/test_utils.ts
index b816e61cce355..7c7431015d175 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/test_utils.ts
+++ b/src/legacy/core_plugins/vis_default_editor/public/components/controls/test_utils.ts
@@ -17,9 +17,9 @@
* under the License.
*/
-import { VisState } from 'src/legacy/core_plugins/visualizations/public';
import { IAggConfig, AggParam } from 'src/plugins/data/public';
import { EditorConfig } from '../utils';
+import { EditorVisState } from '../sidebar/state/reducers';
export const aggParamCommonPropsMock = {
agg: {} as IAggConfig,
@@ -27,7 +27,7 @@ export const aggParamCommonPropsMock = {
editorConfig: {} as EditorConfig,
formIsTouched: false,
metricAggs: [] as IAggConfig[],
- state: {} as VisState,
+ state: {} as EditorVisState,
showValidation: false,
schemas: [],
};
diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/data_tab.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/data_tab.tsx
index 6f92c27e90ec1..a6a1980210be4 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/data_tab.tsx
+++ b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/data_tab.tsx
@@ -21,7 +21,6 @@ import React, { useMemo, useCallback } from 'react';
import { findLast } from 'lodash';
import { EuiSpacer } from '@elastic/eui';
-import { VisState } from 'src/legacy/core_plugins/visualizations/public';
import {
AggGroupNames,
IAggConfig,
@@ -40,6 +39,7 @@ import {
} from './state';
import { AddSchema, ReorderAggs, DefaultEditorAggCommonProps } from '../agg_common_props';
import { ISchemas } from '../../schemas';
+import { EditorVisState } from './state/reducers';
export interface DefaultEditorDataTabProps {
dispatch: React.Dispatch;
@@ -47,7 +47,7 @@ export interface DefaultEditorDataTabProps {
isTabSelected: boolean;
metricAggs: IAggConfig[];
schemas: ISchemas;
- state: VisState;
+ state: EditorVisState;
setTouched(isTouched: boolean): void;
setValidity(modelName: string, value: boolean): void;
setStateValue: DefaultEditorAggCommonProps['setStateParamValue'];
diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar.tsx
index 2508ef3a55537..04c931f593e5a 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar.tsx
+++ b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar.tsx
@@ -21,6 +21,7 @@ import React, { useMemo, useState, useCallback, KeyboardEventHandler, useEffect
import { get, isEqual } from 'lodash';
import { i18n } from '@kbn/i18n';
import { keyCodes, EuiButtonIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import { EventEmitter } from 'events';
import { Vis } from 'src/legacy/core_plugins/visualizations/public';
import { DefaultEditorNavBar, OptionTab } from './navbar';
@@ -40,6 +41,7 @@ interface DefaultEditorSideBarProps {
uiState: PersistedState;
vis: Vis;
isLinkedSearch: boolean;
+ eventEmitter: EventEmitter;
savedSearch?: SavedSearch;
}
@@ -50,14 +52,17 @@ function DefaultEditorSideBar({
uiState,
vis,
isLinkedSearch,
+ eventEmitter,
savedSearch,
}: DefaultEditorSideBarProps) {
const [selectedTab, setSelectedTab] = useState(optionTabs[0].name);
const [isDirty, setDirty] = useState(false);
- const [state, dispatch] = useEditorReducer(vis);
+ const [state, dispatch] = useEditorReducer(vis, eventEmitter);
const { formState, setTouched, setValidity, resetValidity } = useEditorFormState();
- const responseAggs = useMemo(() => state.aggs.getResponseAggs(), [state.aggs]);
+ const responseAggs = useMemo(() => (state.data.aggs ? state.data.aggs.getResponseAggs() : []), [
+ state.data.aggs,
+ ]);
const metricSchemas = getSchemasByGroup(vis.type.schemas.all || [], AggGroupNames.Metrics).map(
s => s.name
);
@@ -90,17 +95,20 @@ function DefaultEditorSideBar({
const applyChanges = useCallback(() => {
if (formState.invalid || !isDirty) {
setTouched(true);
-
return;
}
- vis.setCurrentState(state);
- vis.updateState();
- vis.emit('dirtyStateChange', {
+ vis.setState({
+ ...vis.serialize(),
+ params: state.params,
+ data: { aggs: state.data.aggs ? (state.data.aggs.aggs.map(agg => agg.toJSON()) as any) : [] },
+ });
+ eventEmitter.emit('updateVis');
+ eventEmitter.emit('dirtyStateChange', {
isDirty: false,
});
setTouched(false);
- }, [vis, state, formState.invalid, setTouched, isDirty]);
+ }, [vis, state, formState.invalid, setTouched, isDirty, eventEmitter]);
const onSubmit: KeyboardEventHandler = useCallback(
event => {
@@ -122,18 +130,22 @@ function DefaultEditorSideBar({
resetValidity();
}
};
- vis.on('dirtyStateChange', changeHandler);
+ eventEmitter.on('dirtyStateChange', changeHandler);
- return () => vis.off('dirtyStateChange', changeHandler);
- }, [resetValidity, vis]);
+ return () => {
+ eventEmitter.off('dirtyStateChange', changeHandler);
+ };
+ }, [resetValidity, eventEmitter]);
// subscribe on external vis changes using browser history, for example press back button
useEffect(() => {
const resetHandler = () => dispatch(discardChanges(vis));
- vis.on('updateEditor', resetHandler);
+ eventEmitter.on('updateEditor', resetHandler);
- return () => vis.off('updateEditor', resetHandler);
- }, [dispatch, vis]);
+ return () => {
+ eventEmitter.off('updateEditor', resetHandler);
+ };
+ }, [dispatch, vis, eventEmitter]);
const dataTabProps = {
dispatch,
@@ -147,7 +159,7 @@ function DefaultEditorSideBar({
};
const optionTabProps = {
- aggs: state.aggs,
+ aggs: state.data.aggs!,
hasHistogramAgg,
stateParams: state.params,
vis,
@@ -173,7 +185,12 @@ function DefaultEditorSideBar({
onKeyDownCapture={onSubmit}
>
{vis.type.requiresSearch && (
-
+
)}
{optionTabs.length > 1 && (
diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar_title.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar_title.tsx
index 876404851aed4..575ad5ae2a95c 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar_title.tsx
+++ b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar_title.tsx
@@ -18,6 +18,7 @@
*/
import React, { useCallback, useState } from 'react';
+import { EventEmitter } from 'events';
import {
EuiButton,
EuiButtonEmpty,
@@ -39,23 +40,24 @@ import { SavedSearch } from '../../../../../../plugins/discover/public';
interface LinkedSearchProps {
savedSearch: SavedSearch;
- vis: Vis;
+ eventEmitter: EventEmitter;
}
interface SidebarTitleProps {
isLinkedSearch: boolean;
savedSearch?: SavedSearch;
vis: Vis;
+ eventEmitter: EventEmitter;
}
-export function LinkedSearch({ savedSearch, vis }: LinkedSearchProps) {
+export function LinkedSearch({ savedSearch, eventEmitter }: LinkedSearchProps) {
const [showPopover, setShowPopover] = useState(false);
const closePopover = useCallback(() => setShowPopover(false), []);
const onClickButtonLink = useCallback(() => setShowPopover(v => !v), []);
const onClickUnlikFromSavedSearch = useCallback(() => {
setShowPopover(false);
- vis.emit('unlinkFromSavedSearch');
- }, [vis]);
+ eventEmitter.emit('unlinkFromSavedSearch');
+ }, [eventEmitter]);
const linkButtonAriaLabel = i18n.translate(
'visDefaultEditor.sidebar.savedSearch.linkButtonAriaLabel',
@@ -151,20 +153,20 @@ export function LinkedSearch({ savedSearch, vis }: LinkedSearchProps) {
);
}
-function SidebarTitle({ savedSearch, vis, isLinkedSearch }: SidebarTitleProps) {
+function SidebarTitle({ savedSearch, vis, isLinkedSearch, eventEmitter }: SidebarTitleProps) {
return isLinkedSearch && savedSearch ? (
-
+
) : vis.type.options.showIndexSelection ? (
- {vis.indexPattern.title}
+ {vis.data.indexPattern!.title}
) : (
diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/index.ts b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/index.ts
index 6383ac866dcfc..11cbc3f93e9d3 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/index.ts
+++ b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/index.ts
@@ -17,20 +17,23 @@
* under the License.
*/
-import { useEffect, useReducer, useCallback } from 'react';
-import { isEqual } from 'lodash';
+import { useReducer, useCallback } from 'react';
+import { EventEmitter } from 'events';
-import { Vis, VisState, VisParams } from 'src/legacy/core_plugins/visualizations/public';
-import { createEditorStateReducer, initEditorState } from './reducers';
+import { Vis } from 'src/legacy/core_plugins/visualizations/public';
+import { createEditorStateReducer, initEditorState, EditorVisState } from './reducers';
import { EditorStateActionTypes } from './constants';
-import { EditorAction, updateStateParams } from './actions';
+import { EditorAction } from './actions';
import { useKibana } from '../../../../../../../plugins/kibana_react/public';
import { VisDefaultEditorKibanaServices } from '../../../types';
export * from './editor_form_state';
export * from './actions';
-export function useEditorReducer(vis: Vis): [VisState, React.Dispatch] {
+export function useEditorReducer(
+ vis: Vis,
+ eventEmitter: EventEmitter
+): [EditorVisState, React.Dispatch] {
const { services } = useKibana();
const [state, dispatch] = useReducer(
createEditorStateReducer(services.data.search),
@@ -38,28 +41,15 @@ export function useEditorReducer(vis: Vis): [VisState, React.Dispatch {
- const handleVisUpdate = (params: VisParams) => {
- if (!isEqual(params, state.params)) {
- dispatch(updateStateParams(params));
- }
- };
-
- // fires when visualization state changes, and we need to copy changes to editorState
- vis.on('updateEditorStateParams', handleVisUpdate);
-
- return () => vis.off('updateEditorStateParams', handleVisUpdate);
- }, [vis, state.params]);
-
const wrappedDispatch = useCallback(
(action: EditorAction) => {
dispatch(action);
- vis.emit('dirtyStateChange', {
+ eventEmitter.emit('dirtyStateChange', {
isDirty: action.type !== EditorStateActionTypes.DISCARD_CHANGES,
});
},
- [vis]
+ [eventEmitter]
);
return [state, wrappedDispatch];
diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts
index 67220fd9fd91b..6e5bec7c69c90 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts
+++ b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts
@@ -19,35 +19,45 @@
import { cloneDeep } from 'lodash';
-import { Vis, VisState } from 'src/legacy/core_plugins/visualizations/public';
+import { Vis } from 'src/legacy/core_plugins/visualizations/public';
import { AggGroupNames, DataPublicPluginStart } from '../../../../../../../plugins/data/public';
import { EditorStateActionTypes } from './constants';
import { getEnabledMetricAggsCount } from '../../agg_group_helper';
import { EditorAction } from './actions';
function initEditorState(vis: Vis) {
- return vis.copyCurrentState(true);
+ return {
+ ...vis.clone(),
+ };
}
+export type EditorVisState = Pick;
+
const createEditorStateReducer = ({
aggs: { createAggConfigs },
-}: DataPublicPluginStart['search']) => (state: VisState, action: EditorAction): VisState => {
+}: DataPublicPluginStart['search']) => (
+ state: EditorVisState,
+ action: EditorAction
+): EditorVisState => {
switch (action.type) {
case EditorStateActionTypes.ADD_NEW_AGG: {
const { schema } = action.payload;
const defaultConfig =
- !state.aggs.aggs.find(agg => agg.schema === schema.name) && schema.defaults
+ !state.data.aggs!.aggs.find(agg => agg.schema === schema.name) && schema.defaults
? (schema as any).defaults.slice(0, schema.max)
: { schema: schema.name };
- const aggConfig = state.aggs.createAggConfig(defaultConfig, {
+ const aggConfig = state.data.aggs!.createAggConfig(defaultConfig, {
addToAggConfigs: false,
});
aggConfig.brandNew = true;
- const newAggs = [...state.aggs.aggs, aggConfig];
+ const newAggs = [...state.data.aggs!.aggs, aggConfig];
return {
...state,
- aggs: createAggConfigs(state.aggs.indexPattern, newAggs),
+ data: {
+ ...state.data,
+ aggs: createAggConfigs(state.data.indexPattern!, newAggs),
+ },
};
}
@@ -58,7 +68,7 @@ const createEditorStateReducer = ({
case EditorStateActionTypes.CHANGE_AGG_TYPE: {
const { aggId, value } = action.payload;
- const newAggs = state.aggs.aggs.map(agg => {
+ const newAggs = state.data.aggs!.aggs.map(agg => {
if (agg.id === aggId) {
agg.type = value;
@@ -70,14 +80,17 @@ const createEditorStateReducer = ({
return {
...state,
- aggs: createAggConfigs(state.aggs.indexPattern, newAggs),
+ data: {
+ ...state.data,
+ aggs: createAggConfigs(state.data.indexPattern!, newAggs),
+ },
};
}
case EditorStateActionTypes.SET_AGG_PARAM_VALUE: {
const { aggId, paramName, value } = action.payload;
- const newAggs = state.aggs.aggs.map(agg => {
+ const newAggs = state.data.aggs!.aggs.map(agg => {
if (agg.id === aggId) {
const parsedAgg = agg.toJSON();
@@ -95,7 +108,10 @@ const createEditorStateReducer = ({
return {
...state,
- aggs: createAggConfigs(state.aggs.indexPattern, newAggs),
+ data: {
+ ...state.data,
+ aggs: createAggConfigs(state.data.indexPattern!, newAggs),
+ },
};
}
@@ -113,7 +129,7 @@ const createEditorStateReducer = ({
case EditorStateActionTypes.REMOVE_AGG: {
let isMetric = false;
- const newAggs = state.aggs.aggs.filter(({ id, schema }) => {
+ const newAggs = state.data.aggs!.aggs.filter(({ id, schema }) => {
if (id === action.payload.aggId) {
const schemaDef = action.payload.schemas.find(s => s.name === schema);
if (schemaDef && schemaDef.group === AggGroupNames.Metrics) {
@@ -136,26 +152,36 @@ const createEditorStateReducer = ({
return {
...state,
- aggs: createAggConfigs(state.aggs.indexPattern, newAggs),
+ data: {
+ ...state.data,
+ aggs: createAggConfigs(state.data.indexPattern!, newAggs),
+ },
};
}
case EditorStateActionTypes.REORDER_AGGS: {
const { sourceAgg, destinationAgg } = action.payload;
- const destinationIndex = state.aggs.aggs.indexOf(destinationAgg);
- const newAggs = [...state.aggs.aggs];
- newAggs.splice(destinationIndex, 0, newAggs.splice(state.aggs.aggs.indexOf(sourceAgg), 1)[0]);
+ const destinationIndex = state.data.aggs!.aggs.indexOf(destinationAgg);
+ const newAggs = [...state.data.aggs!.aggs];
+ newAggs.splice(
+ destinationIndex,
+ 0,
+ newAggs.splice(state.data.aggs!.aggs.indexOf(sourceAgg), 1)[0]
+ );
return {
...state,
- aggs: createAggConfigs(state.aggs.indexPattern, newAggs),
+ data: {
+ ...state.data,
+ aggs: createAggConfigs(state.data.indexPattern!, newAggs),
+ },
};
}
case EditorStateActionTypes.TOGGLE_ENABLED_AGG: {
const { aggId, enabled } = action.payload;
- const newAggs = state.aggs.aggs.map(agg => {
+ const newAggs = state.data.aggs!.aggs.map(agg => {
if (agg.id === aggId) {
const parsedAgg = agg.toJSON();
@@ -170,7 +196,10 @@ const createEditorStateReducer = ({
return {
...state,
- aggs: createAggConfigs(state.aggs.indexPattern, newAggs),
+ data: {
+ ...state.data,
+ aggs: createAggConfigs(state.data.indexPattern!, newAggs),
+ },
};
}
diff --git a/src/legacy/core_plugins/vis_default_editor/public/default_editor.tsx b/src/legacy/core_plugins/vis_default_editor/public/default_editor.tsx
index fa3213d244e7e..b504dfd6a55e9 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/default_editor.tsx
+++ b/src/legacy/core_plugins/vis_default_editor/public/default_editor.tsx
@@ -20,10 +20,6 @@
import React, { useEffect, useRef, useState, useCallback } from 'react';
import { EditorRenderProps } from '../../kibana/public/visualize/np_ready/types';
-import {
- VisualizeEmbeddableContract as VisualizeEmbeddable,
- VisualizeEmbeddableFactoryContract as VisualizeEmbeddableFactory,
-} from '../../visualizations/public/';
import { PanelsContainer, Panel } from '../../../../plugins/kibana_react/public';
import './vis_type_agg_filter';
@@ -32,68 +28,44 @@ import { DefaultEditorControllerState } from './default_editor_controller';
import { getInitialWidth } from './editor_size';
function DefaultEditor({
- embeddable,
- savedObj,
+ vis,
uiState,
timeRange,
filters,
- appState,
optionTabs,
query,
+ embeddableHandler,
+ eventEmitter,
linked,
+ savedSearch,
}: DefaultEditorControllerState & Omit) {
const visRef = useRef(null);
- const visHandler = useRef(null);
const [isCollapsed, setIsCollapsed] = useState(false);
- const [factory, setFactory] = useState(null);
- const { vis, savedSearch } = savedObj;
const onClickCollapse = useCallback(() => {
setIsCollapsed(value => !value);
}, []);
useEffect(() => {
- async function visualize() {
- if (!visRef.current || (!visHandler.current && factory)) {
- return;
- }
-
- if (!visHandler.current) {
- const embeddableFactory = embeddable.getEmbeddableFactory(
- 'visualization'
- ) as VisualizeEmbeddableFactory;
- setFactory(embeddableFactory);
-
- visHandler.current = (await embeddableFactory.createFromObject(savedObj, {
- // should be look through createFromObject interface again because of "id" param
- id: '',
- uiState,
- appState,
- timeRange,
- filters,
- query,
- })) as VisualizeEmbeddable;
-
- visHandler.current.render(visRef.current);
- } else {
- visHandler.current.updateInput({
- timeRange,
- filters,
- query,
- });
- }
+ if (!visRef.current) {
+ return;
}
- visualize();
- }, [uiState, savedObj, timeRange, filters, appState, query, factory, embeddable]);
+ embeddableHandler.render(visRef.current);
+ setTimeout(() => {
+ eventEmitter.emit('apply');
+ });
+
+ return () => embeddableHandler.destroy();
+ }, [embeddableHandler, eventEmitter]);
useEffect(() => {
- return () => {
- if (visHandler.current) {
- visHandler.current.destroy();
- }
- };
- }, []);
+ embeddableHandler.updateInput({
+ timeRange,
+ filters,
+ query,
+ });
+ }, [embeddableHandler, timeRange, filters, query]);
const editorInitialWidth = getInitialWidth(vis.type.editorConfig.defaultSize);
@@ -120,6 +92,7 @@ function DefaultEditor({
uiState={uiState}
isLinkedSearch={linked}
savedSearch={savedSearch}
+ eventEmitter={eventEmitter}
/>
diff --git a/src/legacy/core_plugins/vis_default_editor/public/default_editor_controller.tsx b/src/legacy/core_plugins/vis_default_editor/public/default_editor_controller.tsx
index db910604eddd1..13fcabd799959 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/default_editor_controller.tsx
+++ b/src/legacy/core_plugins/vis_default_editor/public/default_editor_controller.tsx
@@ -21,18 +21,22 @@ import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { i18n } from '@kbn/i18n';
import { I18nProvider } from '@kbn/i18n/react';
+import { EventEmitter } from 'events';
import { EditorRenderProps } from 'src/legacy/core_plugins/kibana/public/visualize/np_ready/types';
-import { VisSavedObject } from 'src/legacy/core_plugins/visualizations/public/';
+import { Vis } from 'src/legacy/core_plugins/visualizations/public/';
import { Storage } from '../../../../plugins/kibana_utils/public';
import { KibanaContextProvider } from '../../../../plugins/kibana_react/public';
import { DefaultEditor } from './default_editor';
import { DefaultEditorDataTab, OptionTab } from './components/sidebar';
+import { VisualizeEmbeddable } from '../../visualizations/public/np_ready/public/embeddable';
const localStorage = new Storage(window.localStorage);
export interface DefaultEditorControllerState {
- savedObj: VisSavedObject;
+ vis: Vis;
+ eventEmitter: EventEmitter;
+ embeddableHandler: VisualizeEmbeddable;
optionTabs: OptionTab[];
}
@@ -40,9 +44,9 @@ class DefaultEditorController {
private el: HTMLElement;
private state: DefaultEditorControllerState;
- constructor(el: HTMLElement, savedObj: VisSavedObject) {
+ constructor(el: HTMLElement, vis: Vis, eventEmitter: EventEmitter, embeddableHandler: any) {
this.el = el;
- const { type: visType } = savedObj.vis;
+ const { type: visType } = vis;
const optionTabs = [
...(visType.schemas.buckets || visType.schemas.metrics
@@ -71,8 +75,10 @@ class DefaultEditorController {
];
this.state = {
- savedObj,
+ vis,
optionTabs,
+ eventEmitter,
+ embeddableHandler,
};
}
diff --git a/src/legacy/core_plugins/vis_default_editor/public/vis_options_props.tsx b/src/legacy/core_plugins/vis_default_editor/public/vis_options_props.tsx
index 2e8f20946c73a..3239e871a2465 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/vis_options_props.tsx
+++ b/src/legacy/core_plugins/vis_default_editor/public/vis_options_props.tsx
@@ -17,8 +17,8 @@
* under the License.
*/
+import { PersistedState } from 'src/plugins/visualizations/public';
import { IAggConfigs } from 'src/plugins/data/public';
-import { PersistedState } from '../../../../plugins/visualizations/public';
import { Vis } from '../../visualizations/public';
export interface VisOptionsProps {
diff --git a/src/legacy/core_plugins/vis_type_markdown/public/markdown_vis_controller.test.tsx b/src/legacy/core_plugins/vis_type_markdown/public/markdown_vis_controller.test.tsx
index 5bcb2961c42de..103879cb6e6df 100644
--- a/src/legacy/core_plugins/vis_type_markdown/public/markdown_vis_controller.test.tsx
+++ b/src/legacy/core_plugins/vis_type_markdown/public/markdown_vis_controller.test.tsx
@@ -21,11 +21,6 @@ import React from 'react';
import { render, mount } from 'enzyme';
import { MarkdownVisWrapper } from './markdown_vis_controller';
-// We need Markdown to do these tests, so mock data plugin
-jest.mock('../../data/public/legacy', () => {
- return {};
-});
-
describe('markdown vis controller', () => {
it('should set html from markdown params', () => {
const vis = {
diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.test.tsx b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.test.tsx
index 6a466c9cd0211..7ba4fe017522d 100644
--- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.test.tsx
+++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.test.tsx
@@ -20,12 +20,12 @@
import React from 'react';
import { shallow } from 'enzyme';
-import { Vis } from 'src/legacy/core_plugins/visualizations/public';
import { MetricVisComponent, MetricVisComponentProps } from './metric_vis_component';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { npStart } from 'ui/new_platform';
import { fieldFormats } from '../../../../../plugins/data/public';
import { identity } from 'lodash';
+import { ExprVis } from '../../../visualizations/public/np_ready/public/expressions/vis';
jest.mock('ui/new_platform');
@@ -37,7 +37,7 @@ const baseVisData = {
} as any;
describe('MetricVisComponent', function() {
- const vis: Vis = {
+ const vis: ExprVis = {
params: {
metric: {
colorSchema: 'Green to Red',
@@ -57,7 +57,7 @@ describe('MetricVisComponent', function() {
const getComponent = (propOverrides: Partial = {} as Partial) => {
const props: Props = {
vis,
- visParams: vis.params,
+ visParams: vis.params as any,
visData: baseVisData,
renderComplete: jest.fn(),
...propOverrides,
diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx
index a93bb618da31f..175458497a05e 100644
--- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx
+++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx
@@ -27,12 +27,13 @@ import { FieldFormatsContentType, IFieldFormat } from '../../../../../plugins/da
import { KibanaDatatable } from '../../../../../plugins/expressions/public';
import { getHeatmapColors } from '../../../../../plugins/charts/public';
import { VisParams, MetricVisMetric } from '../types';
-import { SchemaConfig, Vis } from '../../../visualizations/public';
+import { SchemaConfig } from '../../../visualizations/public';
+import { ExprVis } from '../../../visualizations/public/np_ready/public/expressions/vis';
export interface MetricVisComponentProps {
visParams: VisParams;
visData: Input;
- vis: Vis;
+ vis: ExprVis;
renderComplete: () => void;
}
diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts
index cce5864aa50a1..c0bfa47bff502 100644
--- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts
+++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts
@@ -61,11 +61,22 @@ describe('metric_vis - createMetricVisTypeDefinition', () => {
labelTemplate: 'ip[{{value}}]',
});
+ const searchSource = {
+ getField: (name: string) => {
+ if (name === 'index') {
+ return stubIndexPattern;
+ }
+ },
+ };
+
// TODO: remove when Vis is converted to typescript. Only importing Vis as type
// @ts-ignore
- vis = visualizationsStart.createVis(stubIndexPattern, {
+ vis = visualizationsStart.createVis('metric', {
type: 'metric',
- aggs: [{ id: '1', type: 'top_hits', schema: 'metric', params: { field: 'ip' } }],
+ data: {
+ searchSource,
+ aggs: [{ id: '1', type: 'top_hits', schema: 'metric', params: { field: 'ip' } }],
+ },
});
vis.params.dimensions = {
diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js
index 8edef2ea16353..211b79e915038 100644
--- a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js
+++ b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js
@@ -50,57 +50,73 @@ describe('Table Vis - AggTable Directive', function() {
const tabifiedData = {};
const init = () => {
- const vis1 = visualizationsStart.createVis(indexPattern, 'table');
- tabifiedData.metricOnly = tabifyAggResponse(vis1.aggs, metricOnly);
+ const searchSource = {
+ getField: name => {
+ if (name === 'index') {
+ return indexPattern;
+ }
+ },
+ };
+ const vis1 = visualizationsStart.createVis('table', {
+ type: 'table',
+ data: { searchSource, aggs: [] },
+ });
+ tabifiedData.metricOnly = tabifyAggResponse(vis1.data.aggs, metricOnly);
- const vis2 = visualizationsStart.createVis(indexPattern, {
+ const vis2 = visualizationsStart.createVis('table', {
type: 'table',
params: {
showMetricsAtAllLevels: true,
},
- aggs: [
- { type: 'avg', schema: 'metric', params: { field: 'bytes' } },
- { type: 'terms', schema: 'bucket', params: { field: 'extension' } },
- { type: 'terms', schema: 'bucket', params: { field: 'geo.src' } },
- { type: 'terms', schema: 'bucket', params: { field: 'machine.os' } },
- ],
+ data: {
+ aggs: [
+ { type: 'avg', schema: 'metric', params: { field: 'bytes' } },
+ { type: 'terms', schema: 'bucket', params: { field: 'extension' } },
+ { type: 'terms', schema: 'bucket', params: { field: 'geo.src' } },
+ { type: 'terms', schema: 'bucket', params: { field: 'machine.os' } },
+ ],
+ searchSource,
+ },
});
- vis2.aggs.aggs.forEach(function(agg, i) {
+ vis2.data.aggs.aggs.forEach(function(agg, i) {
agg.id = 'agg_' + (i + 1);
});
- tabifiedData.threeTermBuckets = tabifyAggResponse(vis2.aggs, threeTermBuckets, {
+ tabifiedData.threeTermBuckets = tabifyAggResponse(vis2.data.aggs, threeTermBuckets, {
metricsAtAllLevels: true,
});
- const vis3 = visualizationsStart.createVis(indexPattern, {
+ const vis3 = visualizationsStart.createVis('table', {
type: 'table',
- aggs: [
- { type: 'avg', schema: 'metric', params: { field: 'bytes' } },
- { type: 'min', schema: 'metric', params: { field: '@timestamp' } },
- { type: 'terms', schema: 'bucket', params: { field: 'extension' } },
- {
- type: 'date_histogram',
- schema: 'bucket',
- params: { field: '@timestamp', interval: 'd' },
- },
- {
- type: 'derivative',
- schema: 'metric',
- params: { metricAgg: 'custom', customMetric: { id: '5-orderAgg', type: 'count' } },
- },
- {
- type: 'top_hits',
- schema: 'metric',
- params: { field: 'bytes', aggregate: { val: 'min' }, size: 1 },
- },
- ],
+ data: {
+ aggs: [
+ { type: 'avg', schema: 'metric', params: { field: 'bytes' } },
+ { type: 'min', schema: 'metric', params: { field: '@timestamp' } },
+ { type: 'terms', schema: 'bucket', params: { field: 'extension' } },
+ {
+ type: 'date_histogram',
+ schema: 'bucket',
+ params: { field: '@timestamp', interval: 'd' },
+ },
+ {
+ type: 'derivative',
+ schema: 'metric',
+ params: { metricAgg: 'custom', customMetric: { id: '5-orderAgg', type: 'count' } },
+ },
+ {
+ type: 'top_hits',
+ schema: 'metric',
+ params: { field: 'bytes', aggregate: { val: 'min' }, size: 1 },
+ },
+ ],
+ searchSource,
+ },
});
- vis3.aggs.aggs.forEach(function(agg, i) {
+ vis3.data.aggs.aggs.forEach(function(agg, i) {
agg.id = 'agg_' + (i + 1);
});
tabifiedData.oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative = tabifyAggResponse(
- vis3.aggs,
+ vis3.data.aggs,
oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative
);
};
diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js
index 89900d2144030..77f817e44ba79 100644
--- a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js
+++ b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js
@@ -38,22 +38,35 @@ describe('Table Vis - AggTableGroup Directive', function() {
const tabifiedData = {};
const init = () => {
- const vis1 = visualizationsStart.createVis(indexPattern, 'table');
- tabifiedData.metricOnly = tabifyAggResponse(vis1.aggs, metricOnly);
+ const searchSource = {
+ getField: name => {
+ if (name === 'index') {
+ return indexPattern;
+ }
+ },
+ };
+ const vis1 = visualizationsStart.createVis('table', {
+ type: 'table',
+ data: { searchSource, aggs: [] },
+ });
+ tabifiedData.metricOnly = tabifyAggResponse(vis1.data.aggs, metricOnly);
- const vis2 = visualizationsStart.createVis(indexPattern, {
+ const vis2 = visualizationsStart.createVis('pie', {
type: 'pie',
- aggs: [
- { type: 'avg', schema: 'metric', params: { field: 'bytes' } },
- { type: 'terms', schema: 'split', params: { field: 'extension' } },
- { type: 'terms', schema: 'segment', params: { field: 'geo.src' } },
- { type: 'terms', schema: 'segment', params: { field: 'machine.os' } },
- ],
+ data: {
+ aggs: [
+ { type: 'avg', schema: 'metric', params: { field: 'bytes' } },
+ { type: 'terms', schema: 'split', params: { field: 'extension' } },
+ { type: 'terms', schema: 'segment', params: { field: 'geo.src' } },
+ { type: 'terms', schema: 'segment', params: { field: 'machine.os' } },
+ ],
+ searchSource,
+ },
});
- vis2.aggs.aggs.forEach(function(agg, i) {
+ vis2.data.aggs.aggs.forEach(function(agg, i) {
agg.id = 'agg_' + (i + 1);
});
- tabifiedData.threeTermBuckets = tabifyAggResponse(vis2.aggs, threeTermBuckets);
+ tabifiedData.threeTermBuckets = tabifyAggResponse(vis2.data.aggs, threeTermBuckets);
};
const initLocalAngular = () => {
diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts
index 327a47093f535..ad56607e9296c 100644
--- a/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts
+++ b/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts
@@ -118,20 +118,22 @@ describe('Table Vis - Controller', () => {
return ({
type: tableVisTypeDefinition,
params: Object.assign({}, tableVisTypeDefinition.visConfig.defaults, params),
- aggs: createAggConfigs(stubIndexPattern, [
- { type: 'count', schema: 'metric' },
- {
- type: 'range',
- schema: 'bucket',
- params: {
- field: 'bytes',
- ranges: [
- { from: 0, to: 1000 },
- { from: 1000, to: 2000 },
- ],
+ data: {
+ aggs: createAggConfigs(stubIndexPattern, [
+ { type: 'count', schema: 'metric' },
+ {
+ type: 'range',
+ schema: 'bucket',
+ params: {
+ field: 'bytes',
+ ranges: [
+ { from: 0, to: 1000 },
+ { from: 1000, to: 2000 },
+ ],
+ },
},
- },
- ]),
+ ]),
+ },
} as unknown) as Vis;
}
@@ -151,11 +153,11 @@ describe('Table Vis - Controller', () => {
// basically a parameterized beforeEach
function initController(vis: Vis) {
- vis.aggs.aggs.forEach((agg: IAggConfig, i: number) => {
+ vis.data.aggs!.aggs.forEach((agg: IAggConfig, i: number) => {
agg.id = 'agg_' + (i + 1);
});
- tabifiedResponse = tabifyAggResponse(vis.aggs, oneRangeBucket);
+ tabifiedResponse = tabifyAggResponse(vis.data.aggs!, oneRangeBucket);
$rootScope.vis = vis;
$rootScope.visParams = vis.params;
$rootScope.uiState = {
diff --git a/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts b/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts
index 2d27a99bdd8af..2feaad9f4e6b6 100644
--- a/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts
+++ b/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts
@@ -19,12 +19,12 @@
import angular, { IModule, auto, IRootScopeService, IScope, ICompileService } from 'angular';
import $ from 'jquery';
-import { isEqual } from 'lodash';
-import { Vis, VisParams } from '../../visualizations/public';
+import { VisParams } from '../../visualizations/public';
import { npStart } from './legacy_imports';
import { getAngularModule } from './get_inner_angular';
import { initTableVisLegacyModule } from './table_vis_legacy_module';
+import { ExprVis } from '../../visualizations/public/np_ready/public/expressions/vis';
const innerAngularName = 'kibana/table_vis';
@@ -32,12 +32,12 @@ export class TableVisualizationController {
private tableVisModule: IModule | undefined;
private injector: auto.IInjectorService | undefined;
el: JQuery;
- vis: Vis;
+ vis: ExprVis;
$rootScope: IRootScopeService | null = null;
$scope: (IScope & { [key: string]: any }) | undefined;
$compile: ICompileService | undefined;
- constructor(domeElement: Element, vis: Vis) {
+ constructor(domeElement: Element, vis: ExprVis) {
this.el = $(domeElement);
this.vis = vis;
}
@@ -60,7 +60,7 @@ export class TableVisualizationController {
}
}
- async render(esResponse: object, visParams: VisParams, status: { [key: string]: boolean }) {
+ async render(esResponse: object, visParams: VisParams) {
this.initLocalAngular();
return new Promise(async (resolve, reject) => {
@@ -77,15 +77,10 @@ export class TableVisualizationController {
this.$scope.visState = { params: visParams };
this.$scope.esResponse = esResponse;
- if (!isEqual(this.$scope.visParams, visParams)) {
- this.vis.emit('updateEditorStateParams', visParams);
- }
-
this.$scope.visParams = visParams;
this.$scope.renderComplete = resolve;
this.$scope.renderFailed = reject;
this.$scope.resize = Date.now();
- this.$scope.updateStatus = status;
this.$scope.$apply();
};
@@ -93,7 +88,7 @@ export class TableVisualizationController {
this.$scope = this.$rootScope.$new();
this.$scope.uiState = this.vis.getUiState();
updateScope();
- this.el.find('div').append(this.$compile(this.vis.type.visConfig.template)(this.$scope));
+ this.el.find('div').append(this.$compile(this.vis.type!.visConfig.template)(this.$scope));
this.$scope.$apply();
} else {
updateScope();
diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/components/__tests__/tag_cloud_visualization.js b/src/legacy/core_plugins/vis_type_tagcloud/public/components/__tests__/tag_cloud_visualization.js
index 3091b3340cd6d..6f54744a2f508 100644
--- a/src/legacy/core_plugins/vis_type_tagcloud/public/components/__tests__/tag_cloud_visualization.js
+++ b/src/legacy/core_plugins/vis_type_tagcloud/public/components/__tests__/tag_cloud_visualization.js
@@ -19,7 +19,6 @@
import expect from '@kbn/expect';
import ngMock from 'ng_mock';
-import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern';
import { start as visualizationsStart } from '../../../../../core_plugins/visualizations/public/np_ready/public/legacy';
import { ImageComparator } from 'test_utils/image_comparator';
import { createTagCloudVisualization } from '../tag_cloud_visualization';
@@ -36,7 +35,6 @@ const PIXEL_DIFF = 64;
describe('TagCloudVisualizationTest', function() {
let domNode;
- let indexPattern;
let vis;
let imageComparator;
@@ -66,22 +64,18 @@ describe('TagCloudVisualizationTest', function() {
});
beforeEach(ngMock.module('kibana'));
- beforeEach(
- ngMock.inject(Private => {
- indexPattern = Private(LogstashIndexPatternStubProvider);
- })
- );
describe('TagCloudVisualization - basics', function() {
beforeEach(async function() {
setupDOM('512px', '512px');
imageComparator = new ImageComparator();
- vis = visualizationsStart.createVis(indexPattern, {
+ vis = visualizationsStart.createVis('tagcloud', {
type: 'tagcloud',
params: {
bucket: { accessor: 0, format: {} },
metric: { accessor: 0, format: {} },
},
+ data: {},
});
});
diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_visualization.js b/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_visualization.js
index 114643c9a74e0..04f447bf78d50 100644
--- a/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_visualization.js
+++ b/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_visualization.js
@@ -79,17 +79,10 @@ export function createTagCloudVisualization({ colors }) {
render(, this._labelNode);
}
- async render(data, visParams, status) {
- if (!(status.resize || status.data || status.params)) return;
-
- if (status.params || status.data) {
- this._updateParams(visParams);
- this._updateData(data);
- }
-
- if (status.resize) {
- this._resize();
- }
+ async render(data, visParams) {
+ this._updateParams(visParams);
+ this._updateData(data);
+ this._resize();
await this._renderComplete$.pipe(take(1)).toPromise();
diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_type.ts b/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_type.ts
index 9a522fe6e648e..5a8cc3004a315 100644
--- a/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_type.ts
+++ b/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_type.ts
@@ -20,7 +20,6 @@
import { i18n } from '@kbn/i18n';
import { Schemas } from '../../vis_default_editor/public';
-import { Status } from '../../visualizations/public';
import { TagCloudOptions } from './components/tag_cloud_options';
@@ -44,7 +43,6 @@ export const createTagCloudVisTypeDefinition = (deps: TagCloudVisDependencies) =
showLabel: true,
},
},
- requiresUpdateStatus: [Status.PARAMS, Status.RESIZE, Status.DATA],
visualization: createTagCloudVisualization(deps),
editorConfig: {
collections: {
diff --git a/src/legacy/core_plugins/vis_type_timelion/index.ts b/src/legacy/core_plugins/vis_type_timelion/index.ts
index 4664bebb4f38a..6c1e3f452959e 100644
--- a/src/legacy/core_plugins/vis_type_timelion/index.ts
+++ b/src/legacy/core_plugins/vis_type_timelion/index.ts
@@ -25,7 +25,7 @@ import { LegacyPluginApi, LegacyPluginInitializer } from '../../../../src/legacy
const timelionVisPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) =>
new Plugin({
id: 'timelion_vis',
- require: ['kibana', 'elasticsearch', 'visualizations', 'data'],
+ require: ['kibana', 'elasticsearch', 'visualizations'],
publicDir: resolve(__dirname, 'public'),
uiExports: {
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx
index 9e11fd5d3f45c..f55d1602ea342 100644
--- a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx
+++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx
@@ -20,16 +20,16 @@
import React from 'react';
import { IUiSettingsClient } from 'kibana/public';
-import { Vis } from 'src/legacy/core_plugins/visualizations/public';
import { ChartComponent } from './chart';
import { VisParams } from '../timelion_vis_fn';
import { TimelionSuccessResponse } from '../helpers/timelion_request_handler';
+import { ExprVis } from '../../../visualizations/public/np_ready/public/expressions/vis';
export interface TimelionVisComponentProp {
config: IUiSettingsClient;
renderComplete(): void;
updateStatus: object;
- vis: Vis;
+ vis: ExprVis;
visData: TimelionSuccessResponse;
visParams: VisParams;
}
diff --git a/src/legacy/ui/public/vis/lib/timezone.js b/src/legacy/core_plugins/vis_type_timelion/public/helpers/get_timezone.ts
similarity index 70%
rename from src/legacy/ui/public/vis/lib/timezone.js
rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/get_timezone.ts
index a526ca55f9c86..f1e8fc56901e1 100644
--- a/src/legacy/ui/public/vis/lib/timezone.js
+++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/get_timezone.ts
@@ -18,15 +18,14 @@
*/
import moment from 'moment-timezone';
+import { IUiSettingsClient } from 'kibana/public';
-export function timezoneProvider(config) {
- return function() {
- if (config.isDefault('dateFormat:tz')) {
- const detectedTimezone = moment.tz.guess();
- if (detectedTimezone) return detectedTimezone;
- else return moment().format('Z');
- } else {
- return config.get('dateFormat:tz', 'Browser');
- }
- };
+export function getTimezone(config: IUiSettingsClient) {
+ if (config.isDefault('dateFormat:tz')) {
+ const detectedTimezone = moment.tz.guess();
+ if (detectedTimezone) return detectedTimezone;
+ else return moment().format('Z');
+ } else {
+ return config.get('dateFormat:tz', 'Browser');
+ }
}
diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts
index 603c911438f2a..47bfed6340e93 100644
--- a/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts
+++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts
@@ -21,8 +21,8 @@ import { i18n } from '@kbn/i18n';
import { KIBANA_CONTEXT_NAME } from 'src/plugins/expressions/public';
import { VisParams } from 'src/legacy/core_plugins/visualizations/public';
import { TimeRange, Filter, esQuery, Query } from '../../../../../plugins/data/public';
-import { timezoneProvider } from '../legacy_imports';
import { TimelionVisDependencies } from '../plugin';
+import { getTimezone } from './get_timezone';
interface Stats {
cacheCount: number;
@@ -66,7 +66,7 @@ export function getTimelionRequestHandler({
http,
timefilter,
}: TimelionVisDependencies) {
- const timezone = timezoneProvider(uiSettings)();
+ const timezone = getTimezone(uiSettings);
return async function({
timeRange,
diff --git a/src/legacy/core_plugins/vis_type_timelion/public/index.ts b/src/legacy/core_plugins/vis_type_timelion/public/index.ts
index 98cc35877094e..6292e2ad3eb08 100644
--- a/src/legacy/core_plugins/vis_type_timelion/public/index.ts
+++ b/src/legacy/core_plugins/vis_type_timelion/public/index.ts
@@ -23,3 +23,5 @@ import { TimelionVisPlugin as Plugin } from './plugin';
export function plugin(initializerContext: PluginInitializerContext) {
return new Plugin(initializerContext);
}
+
+export { getTimezone } from './helpers/get_timezone';
diff --git a/src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.ts b/src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.ts
index a00240ee06828..e7612b288fb24 100644
--- a/src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.ts
+++ b/src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.ts
@@ -19,6 +19,3 @@
export { npSetup, npStart } from 'ui/new_platform';
export { PluginsStart } from 'ui/new_platform/new_platform';
-
-// @ts-ignore
-export { timezoneProvider } from 'ui/vis/lib/timezone';
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_ui.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_ui.js
index b931c8084a61e..f94c2f609da8f 100644
--- a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_ui.js
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_ui.js
@@ -135,6 +135,8 @@ class PercentilesUi extends Component {
{
- return {
- QueryStringInput: () => ,
- };
-});
-
jest.mock('../lib/get_default_query_language', () => ({
getDefaultQueryLanguage: () => 'kuery',
}));
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js
index ff2546f75c51a..b4845696fc8c0 100644
--- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js
@@ -65,7 +65,7 @@ export class VisEditor extends Component {
}
get uiState() {
- return this.props.vis.getUiState();
+ return this.props.vis.uiState;
}
getConfig = (...args) => {
@@ -73,17 +73,15 @@ export class VisEditor extends Component {
};
handleUiState = (field, value) => {
- this.props.vis.uiStateVal(field, value);
+ this.props.vis.uiState.set(field, value);
};
updateVisState = debounce(() => {
this.props.vis.params = this.state.model;
- this.props.vis.updateState();
- // This check should be redundant, since this method should only be called when we're in editor
- // mode where there's also an appState passed into us.
- if (this.props.appState) {
- this.props.appState.save();
- }
+ this.props.eventEmitter.emit('updateVis');
+ this.props.eventEmitter.emit('dirtyStateChange', {
+ isDirty: false,
+ });
}, VIS_STATE_DEBOUNCE_DELAY);
isValidKueryQuery = filterQuery => {
@@ -184,7 +182,8 @@ export class VisEditor extends Component {
dirty={this.state.dirty}
autoApply={this.state.autoApply}
model={model}
- savedObj={this.props.savedObj}
+ embeddableHandler={this.props.embeddableHandler}
+ vis={this.props.vis}
timeRange={this.props.timeRange}
uiState={this.uiState}
onCommit={this.handleCommit}
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor_visualization.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor_visualization.js
index c45a4d68e8aad..fbd17d99be9bf 100644
--- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor_visualization.js
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor_visualization.js
@@ -29,7 +29,6 @@ import {
AUTO_INTERVAL,
} from './lib/get_interval';
import { PANEL_TYPES } from '../../../../../plugins/vis_type_timeseries/common/panel_types';
-import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy';
const MIN_CHART_HEIGHT = 300;
@@ -70,15 +69,9 @@ class VisEditorVisualizationUI extends Component {
return;
}
- const { timeRange, savedObj, onDataChange } = this.props;
+ const { onDataChange, embeddableHandler } = this.props;
- this._handler = await embeddables
- .getEmbeddableFactory('visualization')
- .createFromObject(savedObj, {
- vis: {},
- timeRange: timeRange,
- filters: [],
- });
+ this._handler = embeddableHandler;
await this._handler.render(this._visEl.current);
this._subscription = this._handler.handler.data$.subscribe(data => {
@@ -285,7 +278,7 @@ VisEditorVisualizationUI.propTypes = {
onCommit: PropTypes.func,
uiState: PropTypes.object,
onToggleAutoApply: PropTypes.func,
- savedObj: PropTypes.object,
+ embeddableHandler: PropTypes.object,
timeRange: PropTypes.object,
dirty: PropTypes.bool,
autoApply: PropTypes.bool,
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/gauge/series.test.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/gauge/series.test.js
index 4efd5bb65451c..65bf7561e3866 100644
--- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/gauge/series.test.js
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/gauge/series.test.js
@@ -20,12 +20,6 @@ import React from 'react';
import { GaugeSeries } from './series';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
-jest.mock('plugins/data', () => {
- return {
- QueryStringInput: () => ,
- };
-});
-
const defaultProps = {
disableAdd: true,
disableDelete: true,
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/metric/series.test.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/metric/series.test.js
index 299e7c12f931a..94a12266df3b3 100644
--- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/metric/series.test.js
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/metric/series.test.js
@@ -21,12 +21,6 @@ import React from 'react';
import { MetricSeries } from './series';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
-jest.mock('plugins/data', () => {
- return {
- QueryStringInput: () => ,
- };
-});
-
const defaultProps = {
disableAdd: false,
disableDelete: true,
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/editor_controller.js b/src/legacy/core_plugins/vis_type_timeseries/public/editor_controller.js
index 4d029553145da..16a6348712065 100644
--- a/src/legacy/core_plugins/vis_type_timeseries/public/editor_controller.js
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/editor_controller.js
@@ -23,12 +23,15 @@ import { fetchIndexPatternFields } from './lib/fetch_fields';
import { getSavedObjectsClient, getUISettings, getI18n } from './services';
export class EditorController {
- constructor(el, savedObj) {
+ constructor(el, vis, eventEmitter, embeddableHandler) {
this.el = el;
+ this.embeddableHandler = embeddableHandler;
+ this.eventEmitter = eventEmitter;
+
this.state = {
- savedObj: savedObj,
- vis: savedObj.vis,
+ fields: [],
+ vis: vis,
isLoaded: false,
};
}
@@ -47,7 +50,7 @@ export class EditorController {
this.state.vis.params.default_index_pattern = title;
this.state.vis.params.default_timefield = timeFieldName;
- this.state.vis.fields = await fetchIndexPatternFields(this.state.vis);
+ this.state.fields = await fetchIndexPatternFields(this.state.vis);
this.state.isLoaded = true;
};
@@ -67,13 +70,14 @@ export class EditorController {
{}}
isEditorMode={true}
appState={params.appState}
+ embeddableHandler={this.embeddableHandler}
+ eventEmitter={this.eventEmitter}
/>
,
this.el
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/legacy_imports.ts b/src/legacy/core_plugins/vis_type_timeseries/public/legacy_imports.ts
deleted file mode 100644
index 7cf0a12e8567c..0000000000000
--- a/src/legacy/core_plugins/vis_type_timeseries/public/legacy_imports.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-// @ts-ignore
-export { timezoneProvider } from 'ui/vis/lib/timezone';
diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/vis_type_timeseries/public/lib/get_timezone.ts
similarity index 67%
rename from src/legacy/core_plugins/data/public/index.ts
rename to src/legacy/core_plugins/vis_type_timeseries/public/lib/get_timezone.ts
index 27a3dd825485d..f1e8fc56901e1 100644
--- a/src/legacy/core_plugins/data/public/index.ts
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/lib/get_timezone.ts
@@ -17,10 +17,15 @@
* under the License.
*/
-import { DataPlugin as Plugin } from './plugin';
+import moment from 'moment-timezone';
+import { IUiSettingsClient } from 'kibana/public';
-export function plugin() {
- return new Plugin();
+export function getTimezone(config: IUiSettingsClient) {
+ if (config.isDefault('dateFormat:tz')) {
+ const detectedTimezone = moment.tz.guess();
+ if (detectedTimezone) return detectedTimezone;
+ else return moment().format('Z');
+ } else {
+ return config.get('dateFormat:tz', 'Browser');
+ }
}
-
-export { DataSetup, DataStart } from './plugin';
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/request_handler.js b/src/legacy/core_plugins/vis_type_timeseries/public/request_handler.js
index 032ef335314d9..2cac1567a6eb7 100644
--- a/src/legacy/core_plugins/vis_type_timeseries/public/request_handler.js
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/request_handler.js
@@ -18,7 +18,7 @@
*/
import { validateInterval } from './lib/validate_interval';
-import { timezoneProvider } from './legacy_imports';
+import { getTimezone } from './lib/get_timezone';
import { getUISettings, getDataStart, getCoreStart } from './services';
export const metricsRequestHandler = async ({
@@ -30,7 +30,7 @@ export const metricsRequestHandler = async ({
savedObjectId,
}) => {
const config = getUISettings();
- const timezone = timezoneProvider(config)();
+ const timezone = getTimezone(config);
const uiStateObj = uiState.get(visParams.type, {});
const parsedTimeRange = getDataStart().query.timefilter.timefilter.calculateBounds(timeRange);
const scaledDataFormat = config.get('dateFormat:scaled');
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/constants/chart.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/constants/chart.js
index 2e7eae438de12..d5ecbaa2ade06 100644
--- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/constants/chart.js
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/constants/chart.js
@@ -32,6 +32,7 @@ export const GRID_LINE_CONFIG = {
export const X_ACCESSOR_INDEX = 0;
export const STACK_ACCESSORS = [0];
export const Y_ACCESSOR_INDEXES = [1];
+export const Y0_ACCESSOR_INDEXES = [2];
export const STACKED_OPTIONS = {
NONE: 'none',
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/area_decorator.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/area_decorator.js
index 923024ff690a4..0afe773266a61 100644
--- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/area_decorator.js
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/area_decorator.js
@@ -21,7 +21,7 @@ import React from 'react';
import { ScaleType, AreaSeries } from '@elastic/charts';
import { getAreaStyles } from '../utils/series_styles';
import { ChartsEntities } from '../model/charts';
-import { X_ACCESSOR_INDEX, Y_ACCESSOR_INDEXES } from '../../../constants';
+import { X_ACCESSOR_INDEX, Y_ACCESSOR_INDEXES, Y0_ACCESSOR_INDEXES } from '../../../constants';
export function AreaSeriesDecorator({
seriesId,
@@ -40,6 +40,8 @@ export function AreaSeriesDecorator({
enableHistogramMode,
useDefaultGroupDomain,
sortIndex,
+ y1AccessorFormat,
+ y0AccessorFormat,
}) {
const id = seriesId;
const groupId = seriesGroupId;
@@ -54,6 +56,9 @@ export function AreaSeriesDecorator({
hideInLegend,
xAccessor: X_ACCESSOR_INDEX,
yAccessors: Y_ACCESSOR_INDEXES,
+ y0Accessors: lines.mode === 'band' ? Y0_ACCESSOR_INDEXES : undefined,
+ y1AccessorFormat,
+ y0AccessorFormat,
stackAccessors,
stackAsPercentage,
xScaleType,
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/bar_decorator.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/bar_decorator.js
index 6d2cd7b8dd935..c979920caac6d 100644
--- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/bar_decorator.js
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/bar_decorator.js
@@ -21,7 +21,7 @@ import React from 'react';
import { ScaleType, BarSeries } from '@elastic/charts';
import { getBarStyles } from '../utils/series_styles';
import { ChartsEntities } from '../model/charts';
-import { X_ACCESSOR_INDEX, Y_ACCESSOR_INDEXES } from '../../../constants';
+import { X_ACCESSOR_INDEX, Y_ACCESSOR_INDEXES, Y0_ACCESSOR_INDEXES } from '../../../constants';
export function BarSeriesDecorator({
seriesId,
@@ -39,6 +39,8 @@ export function BarSeriesDecorator({
enableHistogramMode,
useDefaultGroupDomain,
sortIndex,
+ y1AccessorFormat,
+ y0AccessorFormat,
}) {
const id = seriesId;
const groupId = seriesGroupId;
@@ -53,6 +55,9 @@ export function BarSeriesDecorator({
hideInLegend,
xAccessor: X_ACCESSOR_INDEX,
yAccessors: Y_ACCESSOR_INDEXES,
+ y0Accessors: bars.mode === 'band' ? Y0_ACCESSOR_INDEXES : undefined,
+ y1AccessorFormat,
+ y0AccessorFormat,
stackAccessors,
stackAsPercentage,
xScaleType,
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js
index 5673f560214c7..3ce3aae2649e1 100644
--- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js
@@ -31,8 +31,7 @@ import {
TooltipType,
} from '@elastic/charts';
import { EuiIcon } from '@elastic/eui';
-
-import { timezoneProvider } from '../../../legacy_imports';
+import { getTimezone } from '../../../lib/get_timezone';
import { eventBus, ACTIVE_CURSOR } from '../../lib/active_cursor';
import { getUISettings } from '../../../services';
import { GRID_LINE_CONFIG, ICON_TYPES_MAP, STACKED_OPTIONS } from '../../constants';
@@ -87,7 +86,7 @@ export const TimeSeries = ({
const tooltipFormatter = decorateFormatter(xAxisFormatter);
const uiSettings = getUISettings();
- const timeZone = timezoneProvider(uiSettings)();
+ const timeZone = getTimezone(uiSettings);
const hasBarChart = series.some(({ bars }) => bars.show);
// compute the theme based on the bg color
@@ -156,6 +155,8 @@ export const TimeSeries = ({
stack,
points,
useDefaultGroupDomain,
+ y1AccessorFormat,
+ y0AccessorFormat,
},
sortIndex
) => {
@@ -182,6 +183,8 @@ export const TimeSeries = ({
enableHistogramMode={enableHistogramMode}
useDefaultGroupDomain={useDefaultGroupDomain}
sortIndex={sortIndex}
+ y1AccessorFormat={y1AccessorFormat}
+ y0AccessorFormat={y0AccessorFormat}
/>
);
}
@@ -206,6 +209,8 @@ export const TimeSeries = ({
enableHistogramMode={enableHistogramMode}
useDefaultGroupDomain={useDefaultGroupDomain}
sortIndex={sortIndex}
+ y1AccessorFormat={y1AccessorFormat}
+ y0AccessorFormat={y0AccessorFormat}
/>
);
}
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/theme.ts b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/theme.ts
index a25d5e1ce1d35..2694732aa381d 100644
--- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/theme.ts
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/theme.ts
@@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
+// @ts-ignore
import colorJS from 'color';
import { Theme, LIGHT_THEME, DARK_THEME } from '@elastic/charts';
diff --git a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js
index 5befc09b24544..0db3e6cefa724 100644
--- a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js
+++ b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js
@@ -22,7 +22,6 @@ import expect from '@kbn/expect';
import ngMock from 'ng_mock';
import $ from 'jquery';
import { createVegaVisualization } from '../vega_visualization';
-import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern';
import { ImageComparator } from 'test_utils/image_comparator';
import vegaliteGraph from '!!raw-loader!./vegalite_graph.hjson';
@@ -57,7 +56,6 @@ const PIXEL_DIFF = 30;
describe('VegaVisualizations', () => {
let domNode;
let VegaVisualization;
- let indexPattern;
let vis;
let imageComparator;
let vegaVisualizationDependencies;
@@ -71,7 +69,7 @@ describe('VegaVisualizations', () => {
beforeEach(ngMock.module('kibana'));
beforeEach(
- ngMock.inject((Private, $injector) => {
+ ngMock.inject($injector => {
vegaVisualizationDependencies = {
serviceSettings: $injector.get('serviceSettings'),
core: {
@@ -99,7 +97,6 @@ describe('VegaVisualizations', () => {
}
VegaVisualization = createVegaVisualization(vegaVisualizationDependencies);
- indexPattern = Private(LogstashIndexPatternStubProvider);
})
);
@@ -108,7 +105,7 @@ describe('VegaVisualizations', () => {
setupDOM('512px', '512px');
imageComparator = new ImageComparator();
- vis = visualizationsStart.createVis(indexPattern, { type: 'vega' });
+ vis = visualizationsStart.createVis('vega', { type: 'vega' });
});
afterEach(function() {
diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts b/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts
index 78f9c170ab62d..b0ec90d2c378f 100644
--- a/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts
+++ b/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts
@@ -19,7 +19,6 @@
import { i18n } from '@kbn/i18n';
// @ts-ignore
-import { Status } from '../../visualizations/public';
import { DefaultEditorSize } from '../../vis_default_editor/public';
import { VegaVisualizationDependencies } from './plugin';
import { VegaVisEditor } from './components';
@@ -51,7 +50,6 @@ export const createVegaTypeDefinition = (dependencies: VegaVisualizationDependen
},
visualization,
requestHandler,
- requiresUpdateStatus: [Status.DATA, Status.RESIZE],
responseHandler: 'none',
options: {
showIndexSelection: false,
diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js b/src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js
index 3d48eeaaf3f94..96835ef3b10bc 100644
--- a/src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js
+++ b/src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js
@@ -69,7 +69,7 @@ export const createVegaVisualization = ({ serviceSettings }) =>
* @param {*} status
* @returns {Promise}
*/
- async render(visData, visParams, status) {
+ async render(visData) {
const { toasts } = getNotifications();
if (!visData && !this._vegaView) {
@@ -82,7 +82,7 @@ export const createVegaVisualization = ({ serviceSettings }) =>
}
try {
- await this._render(visData, status);
+ await this._render(visData);
} catch (error) {
if (this._vegaView) {
this._vegaView.onError(error);
@@ -96,8 +96,8 @@ export const createVegaVisualization = ({ serviceSettings }) =>
}
}
- async _render(vegaParser, status) {
- if (vegaParser && (status.data || !this._vegaView)) {
+ async _render(vegaParser) {
+ if (vegaParser) {
// New data received, rebuild the graph
if (this._vegaView) {
await this._vegaView.destroy();
@@ -121,9 +121,6 @@ export const createVegaVisualization = ({ serviceSettings }) =>
this._vegaView = new VegaView(vegaViewParams);
}
await this._vegaView.init();
- } else if (status.resize) {
- // the graph has been resized
- await this._vegaView.resize();
}
}
diff --git a/src/legacy/core_plugins/vis_type_vislib/index.ts b/src/legacy/core_plugins/vis_type_vislib/index.ts
index 74c8f3f96e669..1f75aea31ba0b 100644
--- a/src/legacy/core_plugins/vis_type_vislib/index.ts
+++ b/src/legacy/core_plugins/vis_type_vislib/index.ts
@@ -25,7 +25,7 @@ import { LegacyPluginApi, LegacyPluginInitializer } from '../../types';
const visTypeVislibPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) =>
new Plugin({
id: 'vis_type_vislib',
- require: ['kibana', 'elasticsearch', 'visualizations', 'interpreter', 'data'],
+ require: ['kibana', 'elasticsearch', 'visualizations', 'interpreter'],
publicDir: resolve(__dirname, 'public'),
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
uiExports: {
diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap
index 80ed3b6a7ebff..442bc826d51fc 100644
--- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap
+++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap
@@ -54,7 +54,7 @@ exports[`MetricsAxisOptions component should init with the default set of props
}
vis={
Object {
- "setVisType": [MockFunction],
+ "setState": [MockFunction],
"type": Object {
"schemas": Object {
"metrics": Array [
@@ -126,7 +126,7 @@ exports[`MetricsAxisOptions component should init with the default set of props
}
vis={
Object {
- "setVisType": [MockFunction],
+ "setState": [MockFunction],
"type": Object {
"schemas": Object {
"metrics": Array [
@@ -169,7 +169,7 @@ exports[`MetricsAxisOptions component should init with the default set of props
setCategoryAxis={[Function]}
vis={
Object {
- "setVisType": [MockFunction],
+ "setState": [MockFunction],
"type": Object {
"schemas": Object {
"metrics": Array [
diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.test.tsx
index 032dd10cf11d2..a3f150e718817 100644
--- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.test.tsx
+++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.test.tsx
@@ -94,7 +94,7 @@ describe('MetricsAxisOptions component', () => {
type: ChartTypes.AREA,
schemas: { metrics: [{ name: 'metric' }] },
},
- setVisType: jest.fn(),
+ setState: jest.fn(),
},
stateParams: {
valueAxes: [axis],
@@ -145,7 +145,7 @@ describe('MetricsAxisOptions component', () => {
},
});
- expect(defaultProps.vis.setVisType).toHaveBeenLastCalledWith(ChartTypes.LINE);
+ expect(defaultProps.vis.setState).toHaveBeenLastCalledWith({ type: ChartTypes.LINE });
});
it('should set histogram visType when multiple seriesParam', () => {
@@ -159,7 +159,7 @@ describe('MetricsAxisOptions component', () => {
},
});
- expect(defaultProps.vis.setVisType).toHaveBeenLastCalledWith(ChartTypes.HISTOGRAM);
+ expect(defaultProps.vis.setState).toHaveBeenLastCalledWith({ type: ChartTypes.HISTOGRAM });
});
});
diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.tsx
index a6f4a967d9c76..c7b4562b1087e 100644
--- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.tsx
+++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.tsx
@@ -299,7 +299,7 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps)
}, [stateParams.seriesParams]);
useEffect(() => {
- vis.setVisType(visType);
+ vis.setState({ type: visType } as any);
}, [vis, visType]);
return isTabSelected ? (
diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/point_series.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/point_series.tsx
index 229c4922145e8..b9872ab94bd0b 100644
--- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/point_series.tsx
+++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/point_series.tsx
@@ -44,7 +44,9 @@ function PointSeriesOptions(props: ValidationVisOptionsProps)
- {vis.hasSchemaAgg('segment', 'date_histogram') ? (
+ {vis.data.aggs!.aggs.some(
+ agg => agg.schema === 'segment' && agg.type.name === 'date_histogram'
+ ) ? (
{
legendEl: HTMLDivElement;
vislibVis: any;
- constructor(public el: Element, public vis: Vis) {
+ constructor(public el: Element, public vis: ExprVis) {
this.el = el;
this.vis = vis;
this.unmount = null;
diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js
index 43e3b987f1962..21f4e60e4bc6e 100644
--- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js
+++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js
@@ -133,21 +133,30 @@ describe('No global chart settings', function() {
responseHandler = vislibSlicesResponseHandler;
let id1 = 1;
- stubVis1 = visualizationsStart.createVis(indexPattern, {
+ stubVis1 = visualizationsStart.createVis('pie', {
type: 'pie',
- aggs: rowAgg,
+ data: {
+ aggs: rowAgg,
+ searchSource: {
+ getField: name => {
+ if (name === 'index') {
+ return indexPattern;
+ }
+ },
+ },
+ },
});
stubVis1.isHierarchical = () => true;
// We need to set the aggs to a known value.
- _.each(stubVis1.aggs.aggs, function(agg) {
+ _.each(stubVis1.data.aggs.aggs, function(agg) {
agg.id = 'agg_' + id1++;
});
});
beforeEach(async () => {
- const table1 = tabifyAggResponse(stubVis1.aggs, threeTermBuckets, {
+ const table1 = tabifyAggResponse(stubVis1.data.aggs, threeTermBuckets, {
metricsAtAllLevels: true,
});
data1 = await responseHandler(table1, rowAggDimensions);
@@ -222,19 +231,28 @@ describe('Vislib PieChart Class Test Suite', function() {
responseHandler = vislibSlicesResponseHandler;
let id = 1;
- stubVis = visualizationsStart.createVis(indexPattern, {
+ stubVis = visualizationsStart.createVis('pie', {
type: 'pie',
- aggs: dataAgg,
+ data: {
+ aggs: dataAgg,
+ searchSource: {
+ getField: name => {
+ if (name === 'index') {
+ return indexPattern;
+ }
+ },
+ },
+ },
});
// We need to set the aggs to a known value.
- _.each(stubVis.aggs.aggs, function(agg) {
+ _.each(stubVis.data.aggs.aggs, function(agg) {
agg.id = 'agg_' + id++;
});
});
beforeEach(async () => {
- const table = tabifyAggResponse(stubVis.aggs, threeTermBuckets, {
+ const table = tabifyAggResponse(stubVis.data.aggs, threeTermBuckets, {
metricsAtAllLevels: true,
});
data = await responseHandler(table, dataDimensions);
diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx
index d82941b7b8cee..afd974d6d9b40 100644
--- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx
+++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx
@@ -23,22 +23,11 @@ import { compact, uniq, map, every, isUndefined } from 'lodash';
import { i18n } from '@kbn/i18n';
import { EuiPopoverProps, EuiIcon, keyCodes, htmlIdGenerator } from '@elastic/eui';
-import { IAggConfig } from '../../../../../../../plugins/data/public';
+import { createFiltersFromEvent } from '../../../legacy_imports';
import { CUSTOM_LEGEND_VIS_TYPES, LegendItem } from './models';
import { VisLegendItem } from './legend_item';
import { getPieNames } from './pie_utils';
-import { Vis } from '../../../../../visualizations/public';
-import { createFiltersFromEvent, tabifyGetColumns } from '../../../legacy_imports';
-
-const getTableAggs = (vis: Vis): IAggConfig[] => {
- if (!vis.aggs || !vis.aggs.getResponseAggs) {
- return [];
- }
- const columns = tabifyGetColumns(vis.aggs.getResponseAggs(), !vis.isHierarchical());
- return columns.map(c => c.aggConfig);
-};
-
export interface VisLegendProps {
vis: any;
vislibVis: any;
@@ -50,7 +39,6 @@ export interface VisLegendProps {
export interface VisLegendState {
open: boolean;
labels: any[];
- tableAggs: any[];
filterableLabels: Set;
selectedLabel: string | null;
}
@@ -66,7 +54,6 @@ export class VisLegend extends PureComponent {
this.state = {
open,
labels: [],
- tableAggs: [],
filterableLabels: new Set(),
selectedLabel: null,
};
@@ -200,7 +187,6 @@ export class VisLegend extends PureComponent {
this.getColor = this.props.vislibVis.visConfig.data.getColorFunc();
}
- this.setState({ tableAggs: getTableAggs(this.props.vis) });
this.setLabels(this.props.visData, vislibVis.visConfigArgs.type);
};
diff --git a/src/legacy/core_plugins/vis_type_xy/index.ts b/src/legacy/core_plugins/vis_type_xy/index.ts
index 975399f891503..58d2e425eef40 100644
--- a/src/legacy/core_plugins/vis_type_xy/index.ts
+++ b/src/legacy/core_plugins/vis_type_xy/index.ts
@@ -31,7 +31,7 @@ export interface ConfigSchema {
const visTypeXyPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) =>
new Plugin({
id: 'visTypeXy',
- require: ['kibana', 'elasticsearch', 'visualizations', 'interpreter', 'data'],
+ require: ['kibana', 'elasticsearch', 'visualizations', 'interpreter'],
publicDir: resolve(__dirname, 'public'),
uiExports: {
hacks: [resolve(__dirname, 'public/legacy')],
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization.test.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization.test.js
index 4773fa482e62d..f2844e3aab113 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization.test.js
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization.test.js
@@ -55,7 +55,7 @@ describe('', () => {
beforeEach(() => {
vis = {
- _setUiState: function(uiState) {
+ setUiState: function(uiState) {
this.uiState = uiState;
},
getUiState: function() {
@@ -79,15 +79,6 @@ describe('', () => {
expect(wrapper.text()).toBe('No results found');
});
- it('should display error message when there is a request error that should be shown and no data', () => {
- const errorVis = { ...vis, requestError: { message: 'Request error' }, showRequestError: true };
- const data = null;
- const wrapper = render(
-
- );
- expect(wrapper.text()).toBe('Request error');
- });
-
it('should render chart when data is present', () => {
const wrapper = render(
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization.tsx b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization.tsx
index 33830c45848e4..5296de365daec 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization.tsx
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization.tsx
@@ -23,10 +23,9 @@ import { PersistedState } from '../../../../../../../plugins/visualizations/publ
import { memoizeLast } from '../legacy/memoize';
import { VisualizationChart } from './visualization_chart';
import { VisualizationNoResults } from './visualization_noresults';
-import { VisualizationRequestError } from './visualization_requesterror';
-import { Vis } from '..';
+import { ExprVis } from '../expressions/vis';
-function shouldShowNoResultsMessage(vis: Vis, visData: any): boolean {
+function shouldShowNoResultsMessage(vis: ExprVis, visData: any): boolean {
const requiresSearch = get(vis, 'type.requiresSearch');
const rows: object[] | undefined = get(visData, 'rows');
const isZeroHits = get(visData, 'hits') === 0 || (rows && !rows.length);
@@ -35,17 +34,11 @@ function shouldShowNoResultsMessage(vis: Vis, visData: any): boolean {
return Boolean(requiresSearch && isZeroHits && shouldShowMessage);
}
-function shouldShowRequestErrorMessage(vis: Vis, visData: any): boolean {
- const requestError = get(vis, 'requestError');
- const showRequestError = get(vis, 'showRequestError');
- return Boolean(!visData && requestError && showRequestError);
-}
-
interface VisualizationProps {
listenOnChange: boolean;
onInit?: () => void;
uiState: PersistedState;
- vis: Vis;
+ vis: ExprVis;
visData: any;
visParams: any;
}
@@ -56,20 +49,17 @@ export class Visualization extends React.Component {
constructor(props: VisualizationProps) {
super(props);
- props.vis._setUiState(props.uiState);
+ props.vis.setUiState(props.uiState);
}
public render() {
const { vis, visData, visParams, onInit, uiState, listenOnChange } = this.props;
const noResults = this.showNoResultsMessage(vis, visData);
- const requestError = shouldShowRequestErrorMessage(vis, visData);
return (
- {requestError ? (
-
- ) : noResults ? (
+ {noResults ? (
) : (
void;
uiState: PersistedState;
- vis: Vis;
+ vis: ExprVis;
visData: any;
visParams: any;
listenOnChange: boolean;
@@ -40,10 +40,9 @@ class VisualizationChart extends React.Component {
private chartDiv = React.createRef();
private containerDiv = React.createRef();
private renderSubject: Rx.Subject<{
- vis: Vis;
+ vis: ExprVis;
visParams: any;
visData: any;
- container: HTMLElement;
}>;
private renderSubscription: Rx.Subscription;
@@ -54,11 +53,9 @@ class VisualizationChart extends React.Component {
const render$ = this.renderSubject.asObservable().pipe(share());
const success$ = render$.pipe(
- filter(
- ({ vis, visData, container }) => vis && container && (!vis.type.requiresSearch || visData)
- ),
+ filter(({ vis, visData }) => vis && (!vis.type.requiresSearch || visData)),
debounceTime(100),
- switchMap(async ({ vis, visData, visParams, container }) => {
+ switchMap(async ({ vis, visData, visParams }) => {
if (!this.visualization) {
// This should never happen, since we only should trigger another rendering
// after this component has mounted and thus the visualization implementation
@@ -66,15 +63,11 @@ class VisualizationChart extends React.Component {
throw new Error('Visualization implementation was not initialized on first render.');
}
- vis.size = [container.clientWidth, container.clientHeight];
- const status = getUpdateStatus(vis.type.requiresUpdateStatus, this, this.props);
- return this.visualization.render(visData, visParams, status);
+ return this.visualization.render(visData, visParams);
})
);
- const requestError$ = render$.pipe(filter(({ vis }) => vis.requestError));
-
- this.renderSubscription = Rx.merge(success$, requestError$).subscribe(() => {
+ this.renderSubscription = success$.subscribe(() => {
if (this.props.onInit) {
this.props.onInit();
}
@@ -145,7 +138,6 @@ class VisualizationChart extends React.Component {
vis: this.props.vis,
visData: this.props.visData,
visParams: this.props.visParams,
- container: this.containerDiv.current,
});
}
}
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/get_index_pattern.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/get_index_pattern.ts
index 51d839275fd27..05ce68221eaf0 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/get_index_pattern.ts
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/get_index_pattern.ts
@@ -28,18 +28,18 @@ import { getUISettings, getSavedObjects } from '../services';
export async function getIndexPattern(
savedVis: VisSavedObject
): Promise {
- if (savedVis.vis.type.name !== 'metrics') {
- return savedVis.vis.indexPattern;
+ if (savedVis.visState.type !== 'metrics') {
+ return savedVis.searchSource!.getField('index');
}
const savedObjectsClient = getSavedObjects().client;
const defaultIndex = getUISettings().get('defaultIndex');
- if (savedVis.vis.params.index_pattern) {
+ if (savedVis.visState.params.index_pattern) {
const indexPatternObjects = await savedObjectsClient.find({
type: 'index-pattern',
fields: ['title', 'fields'],
- search: `"${savedVis.vis.params.index_pattern}"`,
+ search: `"${savedVis.visState.params.index_pattern}"`,
searchFields: ['title'],
});
const [indexPattern] = indexPatternObjects.savedObjects.map(indexPatterns.getFromSavedObject);
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts
index c45e6832dc836..342824bade3dd 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts
@@ -45,13 +45,12 @@ import { PersistedState } from '../../../../../../../plugins/visualizations/publ
import { buildPipeline } from '../legacy/build_pipeline';
import { Vis } from '../vis';
import { getExpressions, getUiActions } from '../services';
-import { VisSavedObject } from '../types';
import { VIS_EVENT_TO_TRIGGER } from './events';
const getKeys = (o: T): Array => Object.keys(o) as Array;
export interface VisualizeEmbeddableConfiguration {
- savedVisualization: VisSavedObject;
+ vis: Vis;
indexPatterns?: IIndexPattern[];
editUrl: string;
editable: boolean;
@@ -73,7 +72,6 @@ export interface VisualizeInput extends EmbeddableInput {
export interface VisualizeOutput extends EmbeddableOutput {
editUrl: string;
indexPatterns?: IIndexPattern[];
- savedObjectId: string;
visTypeName: string;
}
@@ -81,9 +79,6 @@ type ExpressionLoader = InstanceType;
export class VisualizeEmbeddable extends Embeddable {
private handler?: ExpressionLoader;
- private savedVisualization: VisSavedObject;
- private appState: { save(): void } | undefined;
- private uiState: PersistedState;
private timefilter: TimefilterContract;
private timeRange?: TimeRange;
private query?: Query;
@@ -99,49 +94,24 @@ export class VisualizeEmbeddable extends Embeddable {
@@ -184,16 +154,16 @@ export class VisualizeEmbeddable extends Embeddable {
- this.uiState.set(key, visCustomizations[key]);
+ this.vis.uiState.set(key, visCustomizations[key]);
});
- this.uiState.on('change', this.uiStateChangeHandler);
+ this.vis.uiState.on('change', this.uiStateChangeHandler);
}
- } else if (!this.appState) {
- this.uiState.clearAllKeys();
+ } else if (this.parent) {
+ this.vis.uiState.clearAllKeys();
}
}
@@ -227,8 +197,8 @@ export class VisualizeEmbeddable extends Embeddable {
+ const visTypesWithoutInspector = [
+ 'markdown',
+ 'input_control_vis',
+ 'metrics',
+ 'vega',
+ 'timelion',
+ ];
+ if (visTypesWithoutInspector.includes(this.vis.type.name)) {
+ return false;
+ }
+ return this.getInspectorAdapters();
+ };
+
/**
*
* @param {Element} domNode
@@ -245,26 +231,6 @@ export class VisualizeEmbeddable extends Embeddable {
- const visTypesWithoutInspector = [
- 'markdown',
- 'input_control_vis',
- 'metrics',
- 'vega',
- 'timelion',
- ];
- if (visTypesWithoutInspector.includes(this.vis.type.name)) {
- return false;
- }
- return this.getInspectorAdapters();
- };
-
- this.vis.openInspector = this.openInspector;
-
const div = document.createElement('div');
div.className = `visualize panel-content panel-content--fullWidth`;
domNode.appendChild(div);
@@ -277,12 +243,12 @@ export class VisualizeEmbeddable extends Embeddable {
// maps hack, remove once esaggs function is cleaned up and ready to accept variables
if (event.name === 'bounds') {
- const agg = this.vis.getAggConfig().aggs.find((a: any) => {
+ const agg = this.vis.data.aggs!.aggs.find((a: any) => {
return get(a, 'type.dslName') === 'geohash_grid';
});
if (
- agg.params.precision !== event.data.precision ||
- !_.isEqual(agg.params.boundingBox, event.data.boundingBox)
+ (agg && agg.params.precision !== event.data.precision) ||
+ (agg && !_.isEqual(agg.params.boundingBox, event.data.boundingBox))
) {
agg.params.boundingBox = event.data.boundingBox;
agg.params.precision = event.data.precision;
@@ -296,7 +262,7 @@ export class VisualizeEmbeddable extends Embeddable s.unsubscribe());
- this.uiState.off('change', this.uiStateChangeHandler);
- this.savedVisualization.vis.removeListener('reload', this.reload);
- this.savedVisualization.vis.removeListener('update', this.handleVisUpdate);
- this.savedVisualization.destroy();
+ this.vis.uiState.off('change', this.uiStateChangeHandler);
+
if (this.handler) {
this.handler.destroy();
this.handler.getElement().remove();
@@ -361,35 +325,25 @@ export class VisualizeEmbeddable extends Embeddable {
- if (this.appState) {
- this.appState.save();
- }
-
this.updateHandler();
};
private uiStateChangeHandler = () => {
this.updateInput({
- ...this.uiState.toJSON(),
+ ...this.vis.uiState.toJSON(),
});
};
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable_factory.tsx
index 1cd97115ee10e..911f5530e97e3 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable_factory.tsx
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable_factory.tsx
@@ -26,9 +26,8 @@ import {
ErrorEmbeddable,
} from '../../../../../../../plugins/embeddable/public';
import { DisabledLabEmbeddable } from './disabled_lab_embeddable';
-import { getIndexPattern } from './get_index_pattern';
import { VisualizeEmbeddable, VisualizeInput, VisualizeOutput } from './visualize_embeddable';
-import { VisSavedObject } from '../types';
+import { Vis } from '../types';
import { VISUALIZE_EMBEDDABLE_TYPE } from './constants';
import {
getCapabilities,
@@ -39,6 +38,7 @@ import {
getTimeFilter,
} from '../services';
import { showNewVisModal } from '../wizard';
+import { convertToSerializedVis } from '../saved_visualizations/_saved_vis';
interface VisualizationAttributes extends SavedObjectAttributes {
visState: string;
@@ -94,31 +94,31 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory<
}
public async createFromObject(
- savedObject: VisSavedObject,
+ vis: Vis,
input: Partial & { id: string },
parent?: Container
): Promise {
const savedVisualizations = getSavedVisualizationsLoader();
try {
- const visId = savedObject.id as string;
+ const visId = vis.id as string;
const editUrl = visId
? getHttp().basePath.prepend(`/app/kibana${savedVisualizations.urlFor(visId)}`)
: '';
const isLabsEnabled = getUISettings().get('visualize:enableLabs');
- if (!isLabsEnabled && savedObject.vis.type.stage === 'experimental') {
- return new DisabledLabEmbeddable(savedObject.title, input);
+ if (!isLabsEnabled && vis.type.stage === 'experimental') {
+ return new DisabledLabEmbeddable(vis.title, input);
}
- const indexPattern = await getIndexPattern(savedObject);
+ const indexPattern = vis.data.indexPattern;
const indexPatterns = indexPattern ? [indexPattern] : [];
const editable = await this.isEditable();
return new VisualizeEmbeddable(
getTimeFilter(),
{
- savedVisualization: savedObject,
+ vis,
indexPatterns,
editUrl,
editable,
@@ -143,7 +143,8 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory<
try {
const savedObject = await savedVisualizations.get(savedObjectId);
- return this.createFromObject(savedObject, input, parent);
+ const vis = new Vis(savedObject.visState.type, await convertToSerializedVis(savedObject));
+ return this.createFromObject(vis, input, parent);
} catch (e) {
console.error(e); // eslint-disable-line no-console
return new ErrorEmbeddable(e, input, parent);
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/vis.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/vis.ts
similarity index 67%
rename from src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/vis.js
rename to src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/vis.ts
index a891140677d60..3b0458a6c8dcc 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/vis.js
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/vis.ts
@@ -32,25 +32,47 @@ import _ from 'lodash';
import { PersistedState } from '../../../../../../../plugins/visualizations/public';
import { getTypes } from '../services';
+import { VisType } from '../vis_types';
+import { VisParams } from '../types';
-export class Vis extends EventEmitter {
- constructor(visState = { type: 'histogram' }) {
+export interface ExprVisState {
+ title?: string;
+ type: VisType | string;
+ params?: VisParams;
+}
+
+export interface ExprVisAPIEvents {
+ filter: (data: any) => void;
+ brush: (data: any) => void;
+}
+
+export interface ExprVisAPI {
+ events: ExprVisAPIEvents;
+}
+
+export class ExprVis extends EventEmitter {
+ public title: string = '';
+ public type: VisType;
+ public params: VisParams = {};
+ public sessionState: Record = {};
+ public API: ExprVisAPI;
+ public eventsSubject: any;
+ private uiState: PersistedState;
+
+ constructor(visState: ExprVisState = { type: 'histogram' }) {
super();
- this._setUiState(new PersistedState());
+ this.type = this.getType(visState.type);
+ this.uiState = new PersistedState();
this.setState(visState);
- // Session state is for storing information that is transitory, and will not be saved with the visualization.
- // For instance, map bounds, which depends on the view port, browser window size, etc.
- this.sessionState = {};
-
this.API = {
events: {
- filter: data => {
+ filter: (data: any) => {
if (!this.eventsSubject) return;
this.eventsSubject.next({ name: 'filterBucket', data });
},
- brush: data => {
+ brush: (data: any) => {
if (!this.eventsSubject) return;
this.eventsSubject.next({ name: 'brush', data });
},
@@ -58,18 +80,22 @@ export class Vis extends EventEmitter {
};
}
- setState(state) {
- this.title = state.title || '';
- const type = state.type || this.type;
+ private getType(type: string | VisType) {
if (_.isString(type)) {
- this.type = getTypes().get(type);
+ return getTypes().get(type);
if (!this.type) {
throw new Error(`Invalid type "${type}"`);
}
} else {
- this.type = type;
+ return type;
}
+ }
+ setState(state: ExprVisState) {
+ this.title = state.title || '';
+ if (state.type) {
+ this.type = this.getType(state.type);
+ }
this.params = _.defaultsDeep(
{},
_.cloneDeep(state.params || {}),
@@ -77,10 +103,6 @@ export class Vis extends EventEmitter {
);
}
- setCurrentState(state) {
- this.setState(state);
- }
-
getState() {
return {
title: this.title,
@@ -106,34 +128,27 @@ export class Vis extends EventEmitter {
}
hasUiState() {
- return !!this.__uiState;
+ return !!this.uiState;
}
- /***
- * this should not be used outside of visualize
- * @param uiState
- * @private
- */
- _setUiState(uiState) {
- if (uiState instanceof PersistedState) {
- this.__uiState = uiState;
- }
+ getUiState() {
+ return this.uiState;
}
- getUiState() {
- return this.__uiState;
+ setUiState(state: PersistedState) {
+ this.uiState = state;
}
/**
* Currently this is only used to extract map-specific information
* (e.g. mapZoom, mapCenter).
*/
- uiStateVal(key, val) {
+ uiStateVal(key: string, val: any) {
if (this.hasUiState()) {
if (_.isUndefined(val)) {
- return this.__uiState.get(key);
+ return this.uiState.get(key);
}
- return this.__uiState.set(key, val);
+ return this.uiState.set(key, val);
}
return val;
}
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_renderer.tsx b/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_renderer.tsx
index 02a31447d23c1..0fd81c753da24 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_renderer.tsx
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_renderer.tsx
@@ -20,8 +20,9 @@
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
// @ts-ignore
-import { Vis } from './vis';
+import { ExprVis } from './vis';
import { Visualization } from '../components';
+import { VisParams } from '../types';
export const visualization = () => ({
name: 'visualization',
@@ -31,9 +32,9 @@ export const visualization = () => ({
const { visData, visConfig, params } = config;
const visType = config.visType || visConfig.type;
- const vis = new Vis({
- type: visType,
- params: visConfig,
+ const vis = new ExprVis({
+ type: visType as string,
+ params: visConfig as VisParams,
});
vis.eventsSubject = { next: handlers.event };
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts
index b59eb2277411c..078cc4a3f4035 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts
@@ -39,12 +39,11 @@ export { VisualizationsSetup, VisualizationsStart };
/** @public types */
export { VisTypeAlias, VisType } from './vis_types';
export { VisSavedObject } from './types';
-export { Vis, VisParams, VisState } from './vis';
+export { Vis, VisParams, SerializedVis, SerializedVisData, VisData } from './vis';
import { VisualizeEmbeddableFactory, VisualizeEmbeddable } from './embeddable';
export type VisualizeEmbeddableFactoryContract = PublicContract;
export type VisualizeEmbeddableContract = PublicContract;
export { TypesService } from './vis_types/types_service';
-export { Status } from './legacy/update_status'; // should remove
export { VISUALIZE_EMBEDDABLE_TYPE, VisualizeInput } from './embeddable';
export { SchemaConfig } from './legacy/build_pipeline';
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.test.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.test.ts
index 9446069182e19..d5c532b53a53e 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.test.ts
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.test.ts
@@ -27,7 +27,7 @@ import {
Schemas,
} from './build_pipeline';
import { Vis } from '..';
-import { searchSourceMock, dataPluginMock } from '../../../../../../../plugins/data/public/mocks';
+import { dataPluginMock } from '../../../../../../../plugins/data/public/mocks';
import { IAggConfig } from '../../../../../../../plugins/data/public';
jest.mock('ui/new_platform');
@@ -78,19 +78,11 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
});
describe('buildPipelineVisFunction', () => {
- let visStateDef: ReturnType;
let schemaConfig: SchemaConfig;
let schemasDef: Schemas;
let uiState: any;
beforeEach(() => {
- visStateDef = {
- title: 'title',
- // @ts-ignore
- type: 'type',
- params: {},
- } as ReturnType;
-
schemaConfig = {
accessor: 0,
label: '',
@@ -105,66 +97,53 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
it('handles vega function', () => {
const vis = {
- ...visStateDef,
params: { spec: 'this is a test' },
};
- const actual = buildPipelineVisFunction.vega(vis, schemasDef, uiState);
+ const actual = buildPipelineVisFunction.vega(vis.params, schemasDef, uiState);
expect(actual).toMatchSnapshot();
});
it('handles input_control_vis function', () => {
- const visState = {
- ...visStateDef,
- params: {
- some: 'nested',
- data: { here: true },
- },
+ const params = {
+ some: 'nested',
+ data: { here: true },
};
- const actual = buildPipelineVisFunction.input_control_vis(visState, schemasDef, uiState);
+ const actual = buildPipelineVisFunction.input_control_vis(params, schemasDef, uiState);
expect(actual).toMatchSnapshot();
});
it('handles metrics/tsvb function', () => {
- const visState = { ...visStateDef, params: { foo: 'bar' } };
- const actual = buildPipelineVisFunction.metrics(visState, schemasDef, uiState);
+ const params = { foo: 'bar' };
+ const actual = buildPipelineVisFunction.metrics(params, schemasDef, uiState);
expect(actual).toMatchSnapshot();
});
it('handles timelion function', () => {
- const visState = {
- ...visStateDef,
- params: { expression: 'foo', interval: 'bar' },
- };
- const actual = buildPipelineVisFunction.timelion(visState, schemasDef, uiState);
+ const params = { expression: 'foo', interval: 'bar' };
+ const actual = buildPipelineVisFunction.timelion(params, schemasDef, uiState);
expect(actual).toMatchSnapshot();
});
it('handles markdown function', () => {
- const visState = {
- ...visStateDef,
- params: {
- markdown: '## hello _markdown_',
- fontSize: 12,
- openLinksInNewTab: true,
- foo: 'bar',
- },
+ const params = {
+ markdown: '## hello _markdown_',
+ fontSize: 12,
+ openLinksInNewTab: true,
+ foo: 'bar',
};
- const actual = buildPipelineVisFunction.markdown(visState, schemasDef, uiState);
+ const actual = buildPipelineVisFunction.markdown(params, schemasDef, uiState);
expect(actual).toMatchSnapshot();
});
it('handles undefined markdown function', () => {
- const visState = {
- ...visStateDef,
- params: { fontSize: 12, openLinksInNewTab: true, foo: 'bar' },
- };
- const actual = buildPipelineVisFunction.markdown(visState, schemasDef, uiState);
+ const params = { fontSize: 12, openLinksInNewTab: true, foo: 'bar' };
+ const actual = buildPipelineVisFunction.markdown(params, schemasDef, uiState);
expect(actual).toMatchSnapshot();
});
describe('handles table function', () => {
it('without splits or buckets', () => {
- const visState = { ...visStateDef, params: { foo: 'bar' } };
+ const params = { foo: 'bar' };
const schemas = {
...schemasDef,
metric: [
@@ -172,22 +151,22 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
{ ...schemaConfig, accessor: 1 },
],
};
- const actual = buildPipelineVisFunction.table(visState, schemas, uiState);
+ const actual = buildPipelineVisFunction.table(params, schemas, uiState);
expect(actual).toMatchSnapshot();
});
it('with splits', () => {
- const visState = { ...visStateDef, params: { foo: 'bar' } };
+ const params = { foo: 'bar' };
const schemas = {
...schemasDef,
split_row: [1, 2],
};
- const actual = buildPipelineVisFunction.table(visState, schemas, uiState);
+ const actual = buildPipelineVisFunction.table(params, schemas, uiState);
expect(actual).toMatchSnapshot();
});
it('with splits and buckets', () => {
- const visState = { ...visStateDef, params: { foo: 'bar' } };
+ const params = { foo: 'bar' };
const schemas = {
...schemasDef,
metric: [
@@ -197,17 +176,14 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
split_row: [2, 4],
bucket: [3],
};
- const actual = buildPipelineVisFunction.table(visState, schemas, uiState);
+ const actual = buildPipelineVisFunction.table(params, schemas, uiState);
expect(actual).toMatchSnapshot();
});
it('with showPartialRows=true and showMetricsAtAllLevels=true', () => {
- const visState = {
- ...visStateDef,
- params: {
- showMetricsAtAllLevels: true,
- showPartialRows: true,
- },
+ const params = {
+ showMetricsAtAllLevels: true,
+ showPartialRows: true,
};
const schemas = {
...schemasDef,
@@ -219,17 +195,14 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
],
bucket: [0, 3],
};
- const actual = buildPipelineVisFunction.table(visState, schemas, uiState);
+ const actual = buildPipelineVisFunction.table(params, schemas, uiState);
expect(actual).toMatchSnapshot();
});
it('with showPartialRows=true and showMetricsAtAllLevels=false', () => {
- const visState = {
- ...visStateDef,
- params: {
- showMetricsAtAllLevels: false,
- showPartialRows: true,
- },
+ const params = {
+ showMetricsAtAllLevels: false,
+ showPartialRows: true,
};
const schemas = {
...schemasDef,
@@ -241,14 +214,14 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
],
bucket: [0, 3],
};
- const actual = buildPipelineVisFunction.table(visState, schemas, uiState);
+ const actual = buildPipelineVisFunction.table(params, schemas, uiState);
expect(actual).toMatchSnapshot();
});
});
describe('handles metric function', () => {
it('without buckets', () => {
- const visState = { ...visStateDef, params: { metric: {} } };
+ const params = { metric: {} };
const schemas = {
...schemasDef,
metric: [
@@ -256,12 +229,12 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
{ ...schemaConfig, accessor: 1 },
],
};
- const actual = buildPipelineVisFunction.metric(visState, schemas, uiState);
+ const actual = buildPipelineVisFunction.metric(params, schemas, uiState);
expect(actual).toMatchSnapshot();
});
it('with buckets', () => {
- const visState = { ...visStateDef, params: { metric: {} } };
+ const params = { metric: {} };
const schemas = {
...schemasDef,
metric: [
@@ -270,21 +243,21 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
],
group: [{ accessor: 2 }],
};
- const actual = buildPipelineVisFunction.metric(visState, schemas, uiState);
+ const actual = buildPipelineVisFunction.metric(params, schemas, uiState);
expect(actual).toMatchSnapshot();
});
it('with percentage mode should have percentage format', () => {
- const visState = { ...visStateDef, params: { metric: { percentageMode: true } } };
+ const params = { metric: { percentageMode: true } };
const schemas = { ...schemasDef };
- const actual = buildPipelineVisFunction.metric(visState, schemas, uiState);
+ const actual = buildPipelineVisFunction.metric(params, schemas, uiState);
expect(actual).toMatchSnapshot();
});
});
describe('handles tagcloud function', () => {
it('without buckets', () => {
- const actual = buildPipelineVisFunction.tagcloud(visStateDef, schemasDef, uiState);
+ const actual = buildPipelineVisFunction.tagcloud({}, schemasDef, uiState);
expect(actual).toMatchSnapshot();
});
@@ -293,21 +266,21 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
...schemasDef,
segment: [{ accessor: 1 }],
};
- const actual = buildPipelineVisFunction.tagcloud(visStateDef, schemas, uiState);
+ const actual = buildPipelineVisFunction.tagcloud({}, schemas, uiState);
expect(actual).toMatchSnapshot();
});
it('with boolean param showLabel', () => {
- const visState = { ...visStateDef, params: { showLabel: false } };
- const actual = buildPipelineVisFunction.tagcloud(visState, schemasDef, uiState);
+ const params = { showLabel: false };
+ const actual = buildPipelineVisFunction.tagcloud(params, schemasDef, uiState);
expect(actual).toMatchSnapshot();
});
});
describe('handles region_map function', () => {
it('without buckets', () => {
- const visState = { ...visStateDef, params: { metric: {} } };
- const actual = buildPipelineVisFunction.region_map(visState, schemasDef, uiState);
+ const params = { metric: {} };
+ const actual = buildPipelineVisFunction.region_map(params, schemasDef, uiState);
expect(actual).toMatchSnapshot();
});
@@ -316,19 +289,19 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
...schemasDef,
segment: [1, 2],
};
- const actual = buildPipelineVisFunction.region_map(visStateDef, schemas, uiState);
+ const actual = buildPipelineVisFunction.region_map({}, schemas, uiState);
expect(actual).toMatchSnapshot();
});
});
it('handles tile_map function', () => {
- const visState = { ...visStateDef, params: { metric: {} } };
+ const params = { metric: {} };
const schemas = {
...schemasDef,
segment: [1, 2],
geo_centroid: [3, 4],
};
- const actual = buildPipelineVisFunction.tile_map(visState, schemas, uiState);
+ const actual = buildPipelineVisFunction.tile_map(params, schemas, uiState);
expect(actual).toMatchSnapshot();
});
@@ -337,7 +310,7 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
...schemasDef,
segment: [1, 2],
};
- const actual = buildPipelineVisFunction.pie(visStateDef, schemas, uiState);
+ const actual = buildPipelineVisFunction.pie({}, schemas, uiState);
expect(actual).toMatchSnapshot();
});
});
@@ -347,11 +320,16 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
it('calls toExpression on vis_type if it exists', async () => {
const vis = ({
- getCurrentState: () => {},
- getUiState: () => null,
+ getState: () => {},
isHierarchical: () => false,
- aggs: {
- getResponseAggs: () => [],
+ data: {
+ aggs: {
+ getResponseAggs: () => [],
+ },
+ searchSource: {
+ getField: jest.fn(),
+ getParent: jest.fn(),
+ },
},
// @ts-ignore
type: {
@@ -359,7 +337,6 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
},
} as unknown) as Vis;
const expression = await buildPipeline(vis, {
- searchSource: searchSourceMock,
timefilter: dataStart.query.timefilter.timefilter,
});
expect(expression).toMatchSnapshot();
@@ -370,7 +347,6 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
const dataStart = dataPluginMock.createStartContract();
let aggs: IAggConfig[];
- let visState: any;
let vis: Vis;
let params: any;
@@ -397,7 +373,11 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
describe('test y dimension format for histogram chart', () => {
beforeEach(() => {
- visState = {
+ vis = {
+ // @ts-ignore
+ type: {
+ name: 'histogram',
+ },
params: {
seriesParams: [
{
@@ -414,24 +394,16 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
},
],
},
- };
-
- vis = {
- // @ts-ignore
- type: {
- name: 'histogram',
- },
- aggs: {
- getResponseAggs: () => {
- return aggs;
- },
+ data: {
+ aggs: {
+ getResponseAggs: () => {
+ return aggs;
+ },
+ } as any,
},
isHierarchical: () => {
return false;
},
- getCurrentState: () => {
- return visState;
- },
};
});
@@ -443,7 +415,7 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
});
it('with one numeric metric in percentage mode', async () => {
- visState.params.valueAxes[0].scale.mode = 'percentage';
+ vis.params.valueAxes[0].scale.mode = 'percentage';
const dimensions = await buildVislibDimensions(vis, params);
const expected = { id: 'percent' };
const actual = dimensions.y[0].format;
@@ -454,33 +426,31 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
const aggConfig = aggs[0];
aggs = [{ ...aggConfig } as IAggConfig, { ...aggConfig, id: '5' } as IAggConfig];
- visState = {
- params: {
- seriesParams: [
- {
- data: { id: '0' },
- valueAxis: 'axis-y-1',
- },
- {
- data: { id: '5' },
- valueAxis: 'axis-y-2',
- },
- ],
- valueAxes: [
- {
- id: 'axis-y-1',
- scale: {
- mode: 'normal',
- },
+ vis.params = {
+ seriesParams: [
+ {
+ data: { id: '0' },
+ valueAxis: 'axis-y-1',
+ },
+ {
+ data: { id: '5' },
+ valueAxis: 'axis-y-2',
+ },
+ ],
+ valueAxes: [
+ {
+ id: 'axis-y-1',
+ scale: {
+ mode: 'normal',
},
- {
- id: 'axis-y-2',
- scale: {
- mode: 'percentage',
- },
+ },
+ {
+ id: 'axis-y-2',
+ scale: {
+ mode: 'percentage',
},
- ],
- },
+ },
+ ],
};
const dimensions = await buildVislibDimensions(vis, params);
@@ -493,29 +463,27 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
describe('test y dimension format for gauge chart', () => {
beforeEach(() => {
- visState = { params: { gauge: {} } };
-
vis = {
// @ts-ignore
type: {
name: 'gauge',
},
- aggs: {
- getResponseAggs: () => {
- return aggs;
- },
+ params: { gauge: {} },
+ data: {
+ aggs: {
+ getResponseAggs: () => {
+ return aggs;
+ },
+ } as any,
},
isHierarchical: () => {
return false;
},
- getCurrentState: () => {
- return visState;
- },
};
});
it('with percentageMode = false', async () => {
- visState.params.gauge.percentageMode = false;
+ vis.params.gauge.percentageMode = false;
const dimensions = await buildVislibDimensions(vis, params);
const expected = { id: 'number' };
const actual = dimensions.y[0].format;
@@ -523,7 +491,7 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
});
it('with percentageMode = true', async () => {
- visState.params.gauge.percentageMode = true;
+ vis.params.gauge.percentageMode = true;
const dimensions = await buildVislibDimensions(vis, params);
const expected = { id: 'percent' };
const actual = dimensions.y[0].format;
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts
index de974e6e969ef..ea15cd9201fd7 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts
@@ -21,14 +21,13 @@ import { get } from 'lodash';
import moment from 'moment';
import { SerializedFieldFormat } from '../../../../../../../plugins/expressions/public';
import {
- fieldFormats,
IAggConfig,
- ISearchSource,
+ fieldFormats,
search,
TimefilterContract,
} from '../../../../../../../plugins/data/public';
-const { isDateHistogramBucketAggConfig } = search.aggs;
import { Vis, VisParams } from '../types';
+const { isDateHistogramBucketAggConfig } = search.aggs;
interface SchemaConfigParams {
precision?: number;
@@ -59,7 +58,7 @@ export interface Schemas {
}
type buildVisFunction = (
- visState: ReturnType,
+ params: VisParams,
schemas: Schemas,
uiState: any,
meta?: { savedObjectId?: string }
@@ -139,7 +138,12 @@ const getSchemas = (
const schemas: Schemas = {
metric: [],
};
- const responseAggs = vis.aggs.getResponseAggs().filter((agg: IAggConfig) => agg.enabled);
+
+ if (!vis.data.aggs) {
+ return schemas;
+ }
+
+ const responseAggs = vis.data.aggs.getResponseAggs().filter((agg: IAggConfig) => agg.enabled);
const isHierarchical = vis.isHierarchical();
const metrics = responseAggs.filter((agg: IAggConfig) => agg.type.type === 'metrics');
responseAggs.forEach((agg: IAggConfig) => {
@@ -228,9 +232,8 @@ export const prepareDimension = (variable: string, data: any) => {
};
const adjustVislibDimensionFormmaters = (vis: Vis, dimensions: { y: any[] }): void => {
- const visState = vis.getCurrentState();
- const visConfig = visState.params;
- const responseAggs = vis.aggs.getResponseAggs().filter((agg: IAggConfig) => agg.enabled);
+ const visConfig = vis.params;
+ const responseAggs = vis.data.aggs!.getResponseAggs().filter((agg: IAggConfig) => agg.enabled);
(dimensions.y || []).forEach(yDimension => {
const yAgg = responseAggs[yDimension.accessor];
@@ -252,27 +255,26 @@ const adjustVislibDimensionFormmaters = (vis: Vis, dimensions: { y: any[] }): vo
};
export const buildPipelineVisFunction: BuildPipelineVisFunction = {
- vega: visState => {
- return `vega ${prepareString('spec', visState.params.spec)}`;
+ vega: params => {
+ return `vega ${prepareString('spec', params.spec)}`;
},
- input_control_vis: visState => {
- return `input_control_vis ${prepareJson('visConfig', visState.params)}`;
+ input_control_vis: params => {
+ return `input_control_vis ${prepareJson('visConfig', params)}`;
},
- metrics: (visState, schemas, uiState = {}, meta) => {
- const paramsJson = prepareJson('params', visState.params);
+ metrics: (params, schemas, uiState = {}) => {
+ const paramsJson = prepareJson('params', params);
const uiStateJson = prepareJson('uiState', uiState);
- const savedObjectIdParam = prepareString('savedObjectId', meta?.savedObjectId);
- const params = [paramsJson, uiStateJson, savedObjectIdParam].filter(param => Boolean(param));
- return `tsvb ${params.join(' ')}`;
+ const paramsArray = [paramsJson, uiStateJson].filter(param => Boolean(param));
+ return `tsvb ${paramsArray.join(' ')}`;
},
- timelion: visState => {
- const expression = prepareString('expression', visState.params.expression);
- const interval = prepareString('interval', visState.params.interval);
+ timelion: params => {
+ const expression = prepareString('expression', params.expression);
+ const interval = prepareString('interval', params.interval);
return `timelion_vis ${expression}${interval}`;
},
- markdown: visState => {
- const { markdown, fontSize, openLinksInNewTab } = visState.params;
+ markdown: params => {
+ const { markdown, fontSize, openLinksInNewTab } = params;
let escapedMarkdown = '';
if (typeof markdown === 'string' || markdown instanceof String) {
escapedMarkdown = escapeString(markdown.toString());
@@ -282,14 +284,14 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = {
expr += prepareValue('openLinksInNewTab', openLinksInNewTab);
return expr;
},
- table: (visState, schemas) => {
+ table: (params, schemas) => {
const visConfig = {
- ...visState.params,
- ...buildVisConfig.table(schemas, visState.params),
+ ...params,
+ ...buildVisConfig.table(schemas, params),
};
return `kibana_table ${prepareJson('visConfig', visConfig)}`;
},
- metric: (visState, schemas) => {
+ metric: (params, schemas) => {
const {
percentageMode,
useRanges,
@@ -299,11 +301,11 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = {
labels,
invertColors,
style,
- } = visState.params.metric;
+ } = params.metric;
const { metrics, bucket } = buildVisConfig.metric(schemas).dimensions;
// fix formatter for percentage mode
- if (get(visState.params, 'metric.percentageMode') === true) {
+ if (get(params, 'metric.percentageMode') === true) {
metrics.forEach((metric: SchemaConfig) => {
metric.format = { id: 'percent' };
});
@@ -335,8 +337,8 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = {
return expr;
},
- tagcloud: (visState, schemas) => {
- const { scale, orientation, minFontSize, maxFontSize, showLabel } = visState.params;
+ tagcloud: (params, schemas) => {
+ const { scale, orientation, minFontSize, maxFontSize, showLabel } = params;
const { metric, bucket } = buildVisConfig.tagcloud(schemas);
let expr = `tagcloud metric={visdimension ${metric.accessor}} `;
expr += prepareValue('scale', scale);
@@ -348,23 +350,23 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = {
return expr;
},
- region_map: (visState, schemas) => {
+ region_map: (params, schemas) => {
const visConfig = {
- ...visState.params,
+ ...params,
...buildVisConfig.region_map(schemas),
};
return `regionmap ${prepareJson('visConfig', visConfig)}`;
},
- tile_map: (visState, schemas) => {
+ tile_map: (params, schemas) => {
const visConfig = {
- ...visState.params,
+ ...params,
...buildVisConfig.tile_map(schemas),
};
return `tilemap ${prepareJson('visConfig', visConfig)}`;
},
- pie: (visState, schemas) => {
+ pie: (params, schemas) => {
const visConfig = {
- ...visState.params,
+ ...params,
...buildVisConfig.pie(schemas),
};
return `kibana_pie ${prepareJson('visConfig', visConfig)}`;
@@ -440,7 +442,6 @@ const buildVisConfig: BuildVisConfigFunction = {
export const buildVislibDimensions = async (
vis: any,
params: {
- searchSource: any;
timefilter: TimefilterContract;
timeRange?: any;
abortSignal?: AbortSignal;
@@ -460,7 +461,7 @@ export const buildVislibDimensions = async (
splitColumn: schemas.split_column,
};
if (schemas.segment) {
- const xAgg = vis.aggs.getResponseAggs()[dimensions.x.accessor];
+ const xAgg = vis.data.aggs.getResponseAggs()[dimensions.x.accessor];
if (xAgg.type.name === 'date_histogram') {
dimensions.x.params.date = true;
const { esUnit, esValue } = xAgg.buckets.getInterval();
@@ -472,7 +473,7 @@ export const buildVislibDimensions = async (
} else if (xAgg.type.name === 'histogram') {
const intervalParam = xAgg.type.paramByName('interval');
const output = { params: {} as any };
- await intervalParam.modifyAggConfigOnSearchRequestStart(xAgg, params.searchSource, {
+ await intervalParam.modifyAggConfigOnSearchRequestStart(xAgg, vis.data.searchSource, {
abortSignal: params.abortSignal,
});
intervalParam.write(xAgg, output);
@@ -487,18 +488,14 @@ export const buildVislibDimensions = async (
export const buildPipeline = async (
vis: Vis,
params: {
- searchSource: ISearchSource;
timefilter: TimefilterContract;
timeRange?: any;
- savedObjectId?: string;
}
) => {
- const { searchSource } = params;
- const { indexPattern } = vis;
- const query = searchSource.getField('query');
- const filters = searchSource.getField('filter');
- const visState = vis.getCurrentState();
- const uiState = vis.getUiState();
+ const { indexPattern, searchSource } = vis.data;
+ const query = searchSource!.getField('query');
+ const filters = searchSource!.getField('filter');
+ const { uiState } = vis;
// context
let pipeline = `kibana | kibana_context `;
@@ -508,18 +505,18 @@ export const buildPipeline = async (
if (filters) {
pipeline += prepareJson('filters', filters);
}
- if (vis.savedSearchId) {
- pipeline += prepareString('savedSearchId', vis.savedSearchId);
+ if (vis.data.savedSearchId) {
+ pipeline += prepareString('savedSearchId', vis.data.savedSearchId);
}
pipeline += '| ';
// request handler
if (vis.type.requestHandler === 'courier') {
pipeline += `esaggs
- ${prepareString('index', indexPattern.id)}
+ ${prepareString('index', indexPattern!.id)}
metricsAtAllLevels=${vis.isHierarchical()}
partialRows=${vis.type.requiresPartialRows || vis.params.showPartialRows || false}
- ${prepareJson('aggConfigs', visState.aggs)} | `;
+ ${prepareJson('aggConfigs', vis.data.aggs!.aggs)} | `;
}
const schemas = getSchemas(vis, {
@@ -527,18 +524,16 @@ export const buildPipeline = async (
timefilter: params.timefilter,
});
if (buildPipelineVisFunction[vis.type.name]) {
- pipeline += buildPipelineVisFunction[vis.type.name](visState, schemas, uiState, {
- savedObjectId: params.savedObjectId,
- });
+ pipeline += buildPipelineVisFunction[vis.type.name](vis.params, schemas, uiState);
} else if (vislibCharts.includes(vis.type.name)) {
- const visConfig = visState.params;
+ const visConfig = { ...vis.params };
visConfig.dimensions = await buildVislibDimensions(vis, params);
- pipeline += `vislib type='${vis.type.name}' ${prepareJson('visConfig', visState.params)}`;
+ pipeline += `vislib type='${vis.type.name}' ${prepareJson('visConfig', visConfig)}`;
} else if (vis.type.toExpression) {
pipeline += await vis.type.toExpression(vis, params);
} else {
- const visConfig = visState.params;
+ const visConfig = { ...vis.params };
visConfig.dimensions = schemas;
pipeline += `visualization type='${vis.type.name}'
${prepareJson('visConfig', visConfig)}
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/update_status.test.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/update_status.test.js
deleted file mode 100644
index c63a8cd48e625..0000000000000
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/update_status.test.js
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { getUpdateStatus, Status } from './update_status';
-
-// Parts of the tests in this file are generated more dynamically, based on the
-// values inside the Status object.Make sure this object has one function per entry
-// in Status, that actually change on the passed $scope, what needs to be changed
-// so that we expect the getUpdateStatus function to actually detect a change.
-const changeFunctions = {
- [Status.AGGS]: $scope => ($scope.vis.aggs = { foo: 'new' }),
- [Status.DATA]: $scope => ($scope.visData = { foo: 'new' }),
- [Status.PARAMS]: $scope => ($scope.vis.params = { foo: 'new' }),
- [Status.RESIZE]: $scope => ($scope.vis.size = [50, 50]),
- [Status.TIME]: $scope => ($scope.vis.filters.timeRange = { from: 'now-7d', to: 'now' }),
- [Status.UI_STATE]: $scope => ($scope.uiState = { foo: 'new' }),
-};
-
-describe('getUpdateStatus', () => {
- function getScope() {
- return {
- vis: {
- aggs: {},
- size: [100, 100],
- params: {},
- filters: {},
- },
- uiState: {},
- visData: {},
- };
- }
-
- function initStatusCheckerAndChangeProperty(type, requiresUpdateStatus) {
- const $scope = getScope();
- // Call the getUpdateStatus function initially, so it can store it's current state
- getUpdateStatus(requiresUpdateStatus, $scope, $scope);
-
- // Get the change function for that specific change type
- const changeFn = changeFunctions[type];
- if (!changeFn) {
- throw new Error(`Please implement the test change function for ${type}.`);
- }
-
- // Call that change function to manipulate the scope so it changed.
- changeFn($scope);
-
- return getUpdateStatus(requiresUpdateStatus, $scope, $scope);
- }
-
- it('should be a function', () => {
- expect(typeof getUpdateStatus).toBe('function');
- });
-
- Object.entries(Status).forEach(([typeKey, typeValue]) => {
- // This block automatically creates very simple tests for each of the Status
- // keys, so we have simple tests per changed property.
- // If it makes sense to test more specific behavior of a specific change detection
- // please add additional tests for that.
-
- it(`should detect changes for Status.${typeKey}`, () => {
- // Check whether the required change type is not correctly determined
- const status = initStatusCheckerAndChangeProperty(typeValue, [typeValue]);
- expect(status[typeValue]).toBe(true);
- });
-
- it(`should not detect changes in other properties when changing Status.${typeKey}`, () => {
- // Only change typeKey, but track changes for all status changes
- const status = initStatusCheckerAndChangeProperty(typeValue, Object.values(Status));
- Object.values(Status)
- // Filter out the actual changed property so we only test for all other properties
- .filter(stat => stat !== typeValue)
- .forEach(otherProp => {
- expect(status[otherProp]).toBeFalsy();
- });
- });
-
- it(`should not detect changes if not requested for Status.${typeKey}`, () => {
- const allOtherStatusProperties = Object.values(Status).filter(stat => stat !== typeValue);
- // Change only the typeKey property, but do not listen for changes on it
- // listen on all other status changes instead.
- const status = initStatusCheckerAndChangeProperty(typeValue, allOtherStatusProperties);
- // The typeValue check should be falsy, since we did not request tracking it.
- expect(status[typeValue]).toBeFalsy();
- });
- });
-});
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/update_status.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/update_status.ts
deleted file mode 100644
index 92a9ce8366f4f..0000000000000
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/update_status.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { PersistedState } from '../../../../../../../plugins/visualizations/public';
-import { calculateObjectHash } from '../../../../../../../plugins/kibana_utils/common';
-import { Vis } from '../vis';
-
-enum Status {
- AGGS = 'aggs',
- DATA = 'data',
- PARAMS = 'params',
- RESIZE = 'resize',
- TIME = 'time',
- UI_STATE = 'uiState',
-}
-
-/**
- * Checks whether the hash of a specific key in the given oldStatus has changed
- * compared to the new valueHash passed.
- */
-function hasHashChanged(
- valueHash: string,
- oldStatus: { [key in T]?: string },
- name: T
-): boolean {
- const oldHash = oldStatus[name];
- return oldHash !== valueHash;
-}
-
-interface Size {
- width: number;
- height: number;
-}
-
-function hasSizeChanged(size: Size, oldSize?: Size): boolean {
- if (!oldSize) {
- return true;
- }
- return oldSize.width !== size.width || oldSize.height !== size.height;
-}
-
-function getUpdateStatus(
- requiresUpdateStatus: T[] = [],
- obj: any,
- param: { vis: Vis; visData: any; uiState: PersistedState }
-): { [reqStats in T]: boolean } {
- const status = {} as { [reqStats in Status]: boolean };
-
- // If the vis type doesn't need update status, skip all calculations
- if (requiresUpdateStatus.length === 0) {
- return status;
- }
-
- if (!obj._oldStatus) {
- obj._oldStatus = {};
- }
-
- for (const requiredStatus of requiresUpdateStatus) {
- let hash;
- // Calculate all required status updates for this visualization
- switch (requiredStatus) {
- case Status.AGGS:
- hash = calculateObjectHash(param.vis.aggs);
- status.aggs = hasHashChanged(hash, obj._oldStatus, 'aggs');
- obj._oldStatus.aggs = hash;
- break;
- case Status.DATA:
- hash = calculateObjectHash(param.visData);
- status.data = hasHashChanged(hash, obj._oldStatus, 'data');
- obj._oldStatus.data = hash;
- break;
- case Status.PARAMS:
- hash = calculateObjectHash(param.vis.params);
- status.params = hasHashChanged(hash, obj._oldStatus, 'param');
- obj._oldStatus.param = hash;
- break;
- case Status.RESIZE:
- const width: number = param.vis.size ? param.vis.size[0] : 0;
- const height: number = param.vis.size ? param.vis.size[1] : 0;
- const size = { width, height };
- status.resize = hasSizeChanged(size, obj._oldStatus.resize);
- obj._oldStatus.resize = size;
- break;
- case Status.TIME:
- const timeRange = param.vis.filters && param.vis.filters.timeRange;
- hash = calculateObjectHash(timeRange);
- status.time = hasHashChanged(hash, obj._oldStatus, 'time');
- obj._oldStatus.time = hash;
- break;
- case Status.UI_STATE:
- hash = calculateObjectHash(param.uiState);
- status.uiState = hasHashChanged(hash, obj._oldStatus, 'uiState');
- obj._oldStatus.uiState = hash;
- break;
- }
- }
-
- return status;
-}
-
-export { getUpdateStatus, Status };
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts
index 4ee727e46f4d6..17f777e4e80e1 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts
@@ -41,6 +41,8 @@ const createStartContract = (): VisualizationsStart => ({
savedVisualizationsLoader: {} as any,
showNewVisModal: jest.fn(),
createVis: jest.fn(),
+ convertFromSerializedVis: jest.fn(),
+ convertToSerializedVis: jest.fn(),
});
const createInstance = async () => {
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts
index 953caecefb974..3ade6cee0d4d2 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts
@@ -39,6 +39,8 @@ import {
setSavedVisualizationsLoader,
setTimeFilter,
setAggs,
+ setChrome,
+ setOverlays,
} from './services';
import { VISUALIZE_EMBEDDABLE_TYPE, VisualizeEmbeddableFactory } from './embeddable';
import { ExpressionsSetup, ExpressionsStart } from '../../../../../../plugins/expressions/public';
@@ -48,14 +50,16 @@ import { visualization as visualizationRenderer } from './expressions/visualizat
import {
DataPublicPluginSetup,
DataPublicPluginStart,
- IIndexPattern,
} from '../../../../../../plugins/data/public';
import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/public';
import { createSavedVisLoader, SavedVisualizationsLoader } from './saved_visualizations';
-import { VisImpl } from './vis_impl';
+import { SerializedVis, Vis } from './vis';
import { showNewVisModal } from './wizard';
import { UiActionsStart } from '../../../../../../plugins/ui_actions/public';
-import { VisState } from './types';
+import {
+ convertFromSerializedVis,
+ convertToSerializedVis,
+} from './saved_visualizations/_saved_vis';
/**
* Interface for this plugin's returned setup/start contracts.
@@ -67,7 +71,9 @@ export type VisualizationsSetup = TypesSetup;
export interface VisualizationsStart extends TypesStart {
savedVisualizationsLoader: SavedVisualizationsLoader;
- createVis: (indexPattern: IIndexPattern, visState?: VisState) => VisImpl;
+ createVis: (visType: string, visState?: SerializedVis) => Vis;
+ convertToSerializedVis: typeof convertToSerializedVis;
+ convertFromSerializedVis: typeof convertFromSerializedVis;
showNewVisModal: typeof showNewVisModal;
}
@@ -138,6 +144,8 @@ export class VisualizationsPlugin
setUiActions(uiActions);
setTimeFilter(data.query.timefilter.timefilter);
setAggs(data.search.aggs);
+ setOverlays(core.overlays);
+ setChrome(core.chrome);
const savedVisualizationsLoader = createSavedVisLoader({
savedObjectsClient: core.savedObjects.client,
indexPatterns: data.indexPatterns,
@@ -155,8 +163,9 @@ export class VisualizationsPlugin
* @param {IIndexPattern} indexPattern - index pattern to use
* @param {VisState} visState - visualization configuration
*/
- createVis: (indexPattern: IIndexPattern, visState?: VisState) =>
- new VisImpl(indexPattern, visState),
+ createVis: (visType: string, visState?: SerializedVis) => new Vis(visType, visState),
+ convertToSerializedVis,
+ convertFromSerializedVis,
savedVisualizationsLoader,
};
}
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/_saved_vis.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/_saved_vis.ts
index e381a01edef8b..c9906428ccb31 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/_saved_vis.ts
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/_saved_vis.ts
@@ -32,65 +32,74 @@ import {
// @ts-ignore
import { updateOldState } from '../legacy/vis_update_state';
import { extractReferences, injectReferences } from './saved_visualization_references';
-import { IIndexPattern } from '../../../../../../../plugins/data/public';
-import { VisSavedObject } from '../types';
-import { VisImpl } from '../vis_impl';
+import {
+ IIndexPattern,
+ ISearchSource,
+ SearchSource,
+} from '../../../../../../../plugins/data/public';
+import { ISavedVis, SerializedVis } from '../types';
import { createSavedSearchesLoader } from '../../../../../../../plugins/discover/public';
-
-async function _afterEsResp(savedVis: VisSavedObject, services: any) {
- await _getLinkedSavedSearch(savedVis, services);
- savedVis.searchSource!.setField('size', 0);
- savedVis.vis = savedVis.vis ? _updateVis(savedVis) : await _createVis(savedVis);
- return savedVis;
-}
-
-async function _getLinkedSavedSearch(savedVis: VisSavedObject, services: any) {
- const linkedSearch = !!savedVis.savedSearchId;
- const current = savedVis.savedSearch;
-
- if (linkedSearch && current && current.id === savedVis.savedSearchId) {
- return;
- }
-
- if (savedVis.savedSearch) {
- savedVis.searchSource!.setParent(savedVis.savedSearch.searchSource.getParent());
- savedVis.savedSearch.destroy();
- delete savedVis.savedSearch;
- }
- const savedSearches = createSavedSearchesLoader(services);
-
- if (linkedSearch) {
- savedVis.savedSearch = await savedSearches.get(savedVis.savedSearchId!);
- savedVis.searchSource!.setParent(savedVis.savedSearch!.searchSource);
- }
-}
-
-async function _createVis(savedVis: VisSavedObject) {
- savedVis.visState = updateOldState(savedVis.visState);
-
- // visState doesn't yet exist when importing a visualization, so we can't
- // assume that exists at this point. If it does exist, then we're not
- // importing a visualization, so we want to sync the title.
- if (savedVis.visState) {
- savedVis.visState.title = savedVis.title;
- }
-
- savedVis.vis = new VisImpl(savedVis.searchSource!.getField('index')!, savedVis.visState);
-
- savedVis.vis!.savedSearchId = savedVis.savedSearchId;
-
- return savedVis.vis;
-}
-
-function _updateVis(savedVis: VisSavedObject) {
- if (savedVis.vis && savedVis.searchSource) {
- savedVis.vis.indexPattern = savedVis.searchSource.getField('index');
- savedVis.visState.title = savedVis.title;
- savedVis.vis.setState(savedVis.visState);
- savedVis.vis.savedSearchId = savedVis.savedSearchId;
+import { getChrome, getOverlays, getIndexPatterns, getSavedObjects } from '../services';
+
+export const convertToSerializedVis = async (savedVis: ISavedVis): Promise => {
+ const { visState } = savedVis;
+ const searchSource =
+ savedVis.searchSource && (await getSearchSource(savedVis.searchSource, savedVis.savedSearchId));
+
+ const indexPattern =
+ searchSource && searchSource.getField('index') ? searchSource.getField('index')!.id : undefined;
+
+ const aggs = indexPattern ? visState.aggs || [] : visState.aggs;
+
+ return {
+ id: savedVis.id,
+ title: savedVis.title,
+ type: visState.type,
+ description: savedVis.description,
+ params: visState.params,
+ uiState: JSON.parse(savedVis.uiStateJSON || '{}'),
+ data: {
+ indexPattern,
+ aggs,
+ searchSource,
+ savedSearchId: savedVis.savedSearchId,
+ },
+ };
+};
+
+export const convertFromSerializedVis = (vis: SerializedVis): ISavedVis => {
+ return {
+ id: vis.id,
+ title: vis.title,
+ description: vis.description,
+ visState: {
+ type: vis.type,
+ aggs: vis.data.aggs,
+ params: vis.params,
+ },
+ uiStateJSON: JSON.stringify(vis.uiState),
+ searchSource: vis.data.searchSource!,
+ savedSearchId: vis.data.savedSearchId,
+ };
+};
+
+const getSearchSource = async (inputSearchSource: ISearchSource, savedSearchId?: string) => {
+ const searchSource = inputSearchSource.createCopy
+ ? inputSearchSource.createCopy()
+ : new SearchSource({ ...(inputSearchSource as any).fields });
+ if (savedSearchId) {
+ const savedSearch = await createSavedSearchesLoader({
+ savedObjectsClient: getSavedObjects().client,
+ indexPatterns: getIndexPatterns(),
+ chrome: getChrome(),
+ overlays: getOverlays(),
+ }).get(savedSearchId);
+
+ searchSource.setParent(savedSearch.searchSource);
}
- return savedVis.vis;
-}
+ searchSource!.setField('size', 0);
+ return searchSource;
+};
export function createSavedVisClass(services: SavedObjectKibanaServices) {
const SavedObjectClass = createSavedObjectClass(services);
@@ -131,8 +140,16 @@ export function createSavedVisClass(services: SavedObjectKibanaServices) {
savedSearchId: opts.savedSearchId,
version: 1,
},
- afterESResp: (savedObject: SavedObject) => {
- return _afterEsResp(savedObject as VisSavedObject, services) as Promise;
+ afterESResp: async (savedObject: SavedObject) => {
+ const savedVis = (savedObject as any) as ISavedVis;
+ savedVis.visState = await updateOldState(savedVis.visState);
+ if (savedVis.savedSearchId && savedVis.searchSource) {
+ savedObject.searchSource = await getSearchSource(
+ savedVis.searchSource,
+ savedVis.savedSearchId
+ );
+ }
+ return (savedVis as any) as SavedObject;
},
});
this.showInRecentlyAccessed = true;
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/saved_visualization_references.test.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/saved_visualization_references.test.ts
index 98af6d99025c2..2e3a4f0f58b27 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/saved_visualization_references.test.ts
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/saved_visualization_references.test.ts
@@ -18,7 +18,7 @@
*/
import { extractReferences, injectReferences } from './saved_visualization_references';
-import { VisSavedObject, VisState } from '../types';
+import { VisSavedObject, SavedVisState } from '../types';
describe('extractReferences', () => {
test('extracts nothing if savedSearchId is empty', () => {
@@ -140,7 +140,7 @@ Object {
},
],
},
- } as unknown) as VisState,
+ } as unknown) as SavedVisState,
} as VisSavedObject;
const references = [
{
@@ -201,7 +201,7 @@ Object {
},
],
},
- } as unknown) as VisState,
+ } as unknown) as SavedVisState,
} as VisSavedObject;
expect(() => injectReferences(context, [])).toThrowErrorMatchingInlineSnapshot(
`"Could not find index pattern reference \\"control_0_index_pattern\\""`
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts
index b2eebe8b5b57d..23cdeae7d15ff 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts
@@ -18,10 +18,13 @@
*/
import {
+ ApplicationStart,
Capabilities,
+ ChromeStart,
HttpStart,
I18nStart,
IUiSettingsClient,
+ OverlayStart,
SavedObjectsStart,
} from '../../../../../../core/public';
import { TypesStart } from './vis_types';
@@ -76,3 +79,9 @@ export const [getSavedVisualizationsLoader, setSavedVisualizationsLoader] = crea
export const [getAggs, setAggs] = createGetterSetter(
'AggConfigs'
);
+
+export const [getOverlays, setOverlays] = createGetterSetter('Overlays');
+
+export const [getChrome, setChrome] = createGetterSetter('Chrome');
+
+export const [getApplication, setApplication] = createGetterSetter('Application');
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/types.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/types.ts
index d8e3ccdeb065e..8f93a179af3bc 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/types.ts
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/types.ts
@@ -18,21 +18,33 @@
*/
import { SavedObject } from '../../../../../../plugins/saved_objects/public';
-import { Vis, VisState, VisParams, VisualizationController } from './vis';
-import { ISearchSource } from '../../../../../../plugins/data/public/';
-import { SavedSearch } from '../../../../../../plugins/discover/public';
+import { ISearchSource, AggConfigOptions } from '../../../../../../plugins/data/public';
+import { SerializedVis, Vis, VisParams } from './vis';
-export { Vis, VisState, VisParams, VisualizationController };
+export { Vis, SerializedVis, VisParams };
-export interface VisSavedObject extends SavedObject {
- vis: Vis;
- description?: string;
- searchSource: ISearchSource;
+export interface VisualizationController {
+ render(visData: any, visParams: any): Promise;
+ destroy(): void;
+ isLoaded?(): Promise | void;
+}
+
+export interface SavedVisState {
+ type: string;
+ params: VisParams;
+ aggs: AggConfigOptions[];
+}
+
+export interface ISavedVis {
+ id: string;
title: string;
+ description?: string;
+ visState: SavedVisState;
+ searchSource?: ISearchSource;
uiStateJSON?: string;
- destroy: () => void;
savedSearchRefName?: string;
savedSearchId?: string;
- savedSearch?: SavedSearch;
- visState: VisState;
}
+
+// @ts-ignore-next-line
+export interface VisSavedObject extends SavedObject, ISavedVis {}
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.ts
index eb262966a4a22..0ba936c9f6567 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.ts
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.ts
@@ -17,47 +17,182 @@
* under the License.
*/
+/**
+ * @name Vis
+ *
+ * @description This class consists of aggs, params, listeners, title, and type.
+ * - Aggs: Instances of IAggConfig.
+ * - Params: The settings in the Options tab.
+ *
+ * Not to be confused with vislib/vis.js.
+ */
+
+import { isFunction, defaults, cloneDeep } from 'lodash';
+import { PersistedState } from '../../../../../../../src/plugins/visualizations/public';
+// @ts-ignore
+import { updateVisualizationConfig } from './legacy/vis_update';
+import { getTypes, getAggs } from './services';
import { VisType } from './vis_types';
-import { Status } from './legacy/update_status';
-import { IAggConfigs } from '../../../../../../plugins/data/public';
-
-export interface Vis {
- type: VisType;
- getCurrentState: (
- includeDisabled?: boolean
- ) => {
- title: string;
- type: string;
- params: VisParams;
- aggs: Array<{ [key: string]: any }>;
- };
-
- /**
- * If a visualization based on the saved search,
- * the id is necessary for building an expression function in src/plugins/expressions/common/expression_functions/specs/kibana_context.ts
- */
+import {
+ IAggConfigs,
+ IndexPattern,
+ ISearchSource,
+ AggConfigOptions,
+} from '../../../../../../plugins/data/public';
+
+export interface SerializedVisData {
+ expression?: string;
+ aggs: AggConfigOptions[];
+ indexPattern?: string;
+ searchSource?: ISearchSource;
savedSearchId?: string;
+}
- // Since we haven't typed everything here yet, we basically "any" the rest
- // of that interface. This should be removed as soon as this type definition
- // has been completed. But that way we at least have typing for a couple of
- // properties on that type.
- [key: string]: any;
+export interface SerializedVis {
+ id: string;
+ title: string;
+ description?: string;
+ type: string;
+ params: VisParams;
+ uiState?: any;
+ data: SerializedVisData;
+}
+
+export interface VisData {
+ ast?: string;
+ aggs?: IAggConfigs;
+ indexPattern?: IndexPattern;
+ searchSource?: ISearchSource;
+ savedSearchId?: string;
}
export interface VisParams {
[key: string]: any;
}
-export interface VisState {
- title: string;
- type: VisType;
- params: VisParams;
- aggs: IAggConfigs;
-}
+export class Vis {
+ public readonly type: VisType;
+ public readonly id: string;
+ public title: string = '';
+ public description: string = '';
+ public params: VisParams = {};
+ // Session state is for storing information that is transitory, and will not be saved with the visualization.
+ // For instance, map bounds, which depends on the view port, browser window size, etc.
+ public sessionState: Record = {};
+ public data: VisData = {};
+
+ public readonly uiState: PersistedState;
+
+ constructor(visType: string, visState: SerializedVis = {} as any) {
+ this.type = this.getType(visType);
+ this.params = this.getParams(visState.params);
+ this.uiState = new PersistedState(visState.uiState);
+ this.id = visState.id;
+
+ this.setState(visState || {});
+ }
+
+ private getType(visType: string) {
+ const type = getTypes().get(visType);
+ if (!type) {
+ throw new Error(`Invalid type "${visType}"`);
+ }
+ return type;
+ }
+
+ private getParams(params: VisParams) {
+ return defaults({}, cloneDeep(params || {}), cloneDeep(this.type.visConfig.defaults || {}));
+ }
+
+ setState(state: SerializedVis) {
+ let typeChanged = false;
+ if (state.type && this.type.name !== state.type) {
+ // @ts-ignore
+ this.type = this.getType(state.type);
+ typeChanged = true;
+ }
+ if (state.title !== undefined) {
+ this.title = state.title;
+ }
+ if (state.description !== undefined) {
+ this.description = state.description;
+ }
+ if (state.params || typeChanged) {
+ this.params = this.getParams(state.params);
+ }
+
+ // move to migration script
+ updateVisualizationConfig(state.params, this.params);
+
+ if (state.data && state.data.searchSource) {
+ this.data.searchSource = state.data.searchSource!;
+ this.data.indexPattern = this.data.searchSource.getField('index');
+ }
+ if (state.data && state.data.savedSearchId) {
+ this.data.savedSearchId = state.data.savedSearchId;
+ }
+ if (state.data && state.data.aggs) {
+ let configStates = state.data.aggs;
+ configStates = this.initializeDefaultsFromSchemas(configStates, this.type.schemas.all || []);
+ if (!this.data.indexPattern) {
+ if (state.data.aggs.length) {
+ throw new Error('trying to initialize aggs without index pattern');
+ }
+ return;
+ }
+ this.data.aggs = getAggs().createAggConfigs(this.data.indexPattern, configStates);
+ }
+ }
+
+ clone() {
+ return new Vis(this.type.name, this.serialize());
+ }
+
+ serialize(): SerializedVis {
+ const aggs = this.data.aggs ? this.data.aggs.aggs.map(agg => agg.toJSON()) : [];
+ const indexPattern = this.data.searchSource && this.data.searchSource.getField('index');
+ return {
+ id: this.id,
+ title: this.title,
+ type: this.type.name,
+ params: cloneDeep(this.params) as any,
+ uiState: this.uiState.toJSON(),
+ data: {
+ aggs: aggs as any,
+ indexPattern: indexPattern ? indexPattern.id : undefined,
+ searchSource: this.data.searchSource!.createCopy(),
+ savedSearchId: this.data.savedSearchId,
+ },
+ };
+ }
+
+ toAST() {
+ return this.type.toAST(this.params);
+ }
+
+ // deprecated
+ isHierarchical() {
+ if (isFunction(this.type.hierarchicalData)) {
+ return !!this.type.hierarchicalData(this);
+ } else {
+ return !!this.type.hierarchicalData;
+ }
+ }
-export interface VisualizationController {
- render(visData: any, visParams: any, update: { [key in Status]: boolean }): Promise;
- destroy(): void;
- isLoaded?(): Promise | void;
+ private initializeDefaultsFromSchemas(configStates: AggConfigOptions[], schemas: any) {
+ // Set the defaults for any schema which has them. If the defaults
+ // for some reason has more then the max only set the max number
+ // of defaults (not sure why a someone define more...
+ // but whatever). Also if a schema.name is already set then don't
+ // set anything.
+ const newConfigs = [...configStates];
+ schemas
+ .filter((schema: any) => Array.isArray(schema.defaults) && schema.defaults.length > 0)
+ .filter((schema: any) => !configStates.find(agg => agg.schema && agg.schema === schema.name))
+ .forEach((schema: any) => {
+ const defaultSchemaConfig = schema.defaults.slice(0, schema.max);
+ defaultSchemaConfig.forEach((d: any) => newConfigs.push(d));
+ });
+ return newConfigs;
+ }
}
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.d.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.d.ts
deleted file mode 100644
index 0e759c3d9872c..0000000000000
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.d.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { Vis, VisState, VisParams } from './vis';
-import { VisType } from './vis_types';
-import { IAggConfig, IIndexPattern } from '../../../../../../plugins/data/public';
-import { Schema } from '../../../../vis_default_editor/public';
-
-type InitVisStateType =
- | Partial
- | Partial & { type: string }>
- | string;
-
-export type VisImplConstructor = new (
- indexPattern: IIndexPattern,
- visState?: InitVisStateType
-) => VisImpl;
-
-export declare class VisImpl implements Vis {
- constructor(indexPattern: IIndexPattern, visState?: InitVisStateType);
-
- type: VisType;
- getCurrentState: (
- includeDisabled?: boolean
- ) => {
- title: string;
- type: string;
- params: VisParams;
- aggs: Array<{ [key: string]: any }>;
- };
-
- private initializeDefaultsFromSchemas(configStates: IAggConfig[], schemas: Schema[]);
-
- // Since we haven't typed everything here yet, we basically "any" the rest
- // of that interface. This should be removed as soon as this type definition
- // has been completed. But that way we at least have typing for a couple of
- // properties on that type.
- [key: string]: any;
-}
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.js
deleted file mode 100644
index abd8f351ae94d..0000000000000
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.js
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * @name Vis
- *
- * @description This class consists of aggs, params, listeners, title, and type.
- * - Aggs: Instances of IAggConfig.
- * - Params: The settings in the Options tab.
- *
- * Not to be confused with vislib/vis.js.
- */
-
-import { EventEmitter } from 'events';
-import _ from 'lodash';
-import { PersistedState } from '../../../../../../../src/plugins/visualizations/public';
-import { updateVisualizationConfig } from './legacy/vis_update';
-import { getTypes, getAggs } from './services';
-
-class VisImpl extends EventEmitter {
- constructor(indexPattern, visState) {
- super();
- visState = visState || {};
-
- if (_.isString(visState)) {
- visState = {
- type: visState,
- };
- }
-
- this.indexPattern = indexPattern;
- this._setUiState(new PersistedState());
- this.setCurrentState(visState);
- this.setState(this.getCurrentState(), false);
-
- // Session state is for storing information that is transitory, and will not be saved with the visualization.
- // For instance, map bounds, which depends on the view port, browser window size, etc.
- this.sessionState = {};
-
- this.API = {
- events: {
- filter: data => this.eventsSubject.next({ name: 'filterBucket', data }),
- brush: data => this.eventsSubject.next({ name: 'brush', data }),
- },
- };
- }
-
- initializeDefaultsFromSchemas(configStates, schemas) {
- // Set the defaults for any schema which has them. If the defaults
- // for some reason has more then the max only set the max number
- // of defaults (not sure why a someone define more...
- // but whatever). Also if a schema.name is already set then don't
- // set anything.
- const newConfigs = [...configStates];
- schemas
- .filter(schema => Array.isArray(schema.defaults) && schema.defaults.length > 0)
- .filter(schema => !configStates.find(agg => agg.schema && agg.schema === schema.name))
- .forEach(schema => {
- const defaults = schema.defaults.slice(0, schema.max);
- defaults.forEach(d => newConfigs.push(d));
- });
- return newConfigs;
- }
-
- setCurrentState(state) {
- this.title = state.title || '';
- const type = state.type || this.type;
- if (_.isString(type)) {
- this.type = getTypes().get(type);
- if (!this.type) {
- throw new Error(`Invalid type "${type}"`);
- }
- } else {
- this.type = type;
- }
-
- this.params = _.defaults(
- {},
- _.cloneDeep(state.params || {}),
- _.cloneDeep(this.type.visConfig.defaults || {})
- );
-
- updateVisualizationConfig(state.params, this.params);
-
- if (state.aggs || !this.aggs) {
- let configStates = state.aggs ? state.aggs.aggs || state.aggs : [];
- configStates = this.initializeDefaultsFromSchemas(configStates, this.type.schemas.all || []);
- this.aggs = getAggs().createAggConfigs(this.indexPattern, configStates);
- }
- }
-
- setState(state, updateCurrentState = true) {
- this._state = _.cloneDeep(state);
- if (updateCurrentState) {
- this.setCurrentState(this._state);
- }
- }
-
- setVisType(type) {
- this.type.type = type;
- }
-
- updateState() {
- this.setState(this.getCurrentState(true));
- this.emit('update');
- }
-
- forceReload() {
- this.emit('reload');
- }
-
- getCurrentState(includeDisabled) {
- return {
- title: this.title,
- type: this.type.name,
- params: _.cloneDeep(this.params),
- aggs: this.aggs.aggs
- .map(agg => agg.toJSON())
- .filter(agg => includeDisabled || agg.enabled)
- .filter(Boolean),
- };
- }
-
- copyCurrentState(includeDisabled = false) {
- const state = this.getCurrentState(includeDisabled);
- state.aggs = getAggs().createAggConfigs(
- this.indexPattern,
- state.aggs.aggs || state.aggs,
- this.type.schemas.all
- );
- return state;
- }
-
- getStateInternal(includeDisabled) {
- return {
- title: this._state.title,
- type: this._state.type,
- params: this._state.params,
- aggs: this._state.aggs.filter(agg => includeDisabled || agg.enabled),
- };
- }
-
- getEnabledState() {
- return this.getStateInternal(false);
- }
-
- getAggConfig() {
- return this.aggs.clone({ enabledOnly: true });
- }
-
- getState() {
- return this.getStateInternal(true);
- }
-
- isHierarchical() {
- if (_.isFunction(this.type.hierarchicalData)) {
- return !!this.type.hierarchicalData(this);
- } else {
- return !!this.type.hierarchicalData;
- }
- }
-
- hasSchemaAgg(schemaName, aggTypeName) {
- const aggs = this.aggs.bySchemaName(schemaName) || [];
- return aggs.some(function(agg) {
- if (!agg.type || !agg.type.name) return false;
- return agg.type.name === aggTypeName;
- });
- }
-
- hasUiState() {
- return !!this.__uiState;
- }
-
- /***
- * this should not be used outside of visualize
- * @param uiState
- * @private
- */
- _setUiState(uiState) {
- if (uiState instanceof PersistedState) {
- this.__uiState = uiState;
- }
- }
-
- getUiState() {
- return this.__uiState;
- }
-
- /**
- * Currently this is only used to extract map-specific information
- * (e.g. mapZoom, mapCenter).
- */
- uiStateVal(key, val) {
- if (this.hasUiState()) {
- if (_.isUndefined(val)) {
- return this.__uiState.get(key);
- }
- return this.__uiState.set(key, val);
- }
- return val;
- }
-}
-
-VisImpl.prototype.type = 'histogram';
-
-export { VisImpl };
diff --git a/src/legacy/ui/public/field_editor/__snapshots__/field_editor.test.js.snap b/src/legacy/ui/public/field_editor/__snapshots__/field_editor.test.js.snap
index 6c454370f59f5..19d12f4bbbd4c 100644
--- a/src/legacy/ui/public/field_editor/__snapshots__/field_editor.test.js.snap
+++ b/src/legacy/ui/public/field_editor/__snapshots__/field_editor.test.js.snap
@@ -945,6 +945,10 @@ exports[`FieldEditor should show deprecated lang warning 1`] = `
"text": "_source",
"value": "_source",
},
+ Object {
+ "text": "histogram",
+ "value": "histogram",
+ },
Object {
"text": "conflict",
"value": "conflict",
diff --git a/src/plugins/data/common/kbn_field_types/kbn_field_types.test.ts b/src/plugins/data/common/kbn_field_types/kbn_field_types.test.ts
index 09fc4555992a8..a3fe19fa9b2fc 100644
--- a/src/plugins/data/common/kbn_field_types/kbn_field_types.test.ts
+++ b/src/plugins/data/common/kbn_field_types/kbn_field_types.test.ts
@@ -87,6 +87,7 @@ describe('utils/kbn_field_types', () => {
KBN_FIELD_TYPES.DATE,
KBN_FIELD_TYPES.GEO_POINT,
KBN_FIELD_TYPES.GEO_SHAPE,
+ KBN_FIELD_TYPES.HISTOGRAM,
KBN_FIELD_TYPES.IP,
KBN_FIELD_TYPES.MURMUR3,
KBN_FIELD_TYPES.NESTED,
diff --git a/src/plugins/data/common/kbn_field_types/kbn_field_types_factory.ts b/src/plugins/data/common/kbn_field_types/kbn_field_types_factory.ts
index 192e8bc4f3727..cb9357eb9865e 100644
--- a/src/plugins/data/common/kbn_field_types/kbn_field_types_factory.ts
+++ b/src/plugins/data/common/kbn_field_types/kbn_field_types_factory.ts
@@ -95,6 +95,11 @@ export const createKbnFieldTypes = (): KbnFieldType[] => [
name: KBN_FIELD_TYPES._SOURCE,
esTypes: [ES_FIELD_TYPES._SOURCE],
}),
+ new KbnFieldType({
+ name: KBN_FIELD_TYPES.HISTOGRAM,
+ filterable: true,
+ esTypes: [ES_FIELD_TYPES.HISTOGRAM],
+ }),
new KbnFieldType({
name: KBN_FIELD_TYPES.CONFLICT,
}),
diff --git a/src/plugins/data/common/kbn_field_types/types.ts b/src/plugins/data/common/kbn_field_types/types.ts
index 11c62e8f86dce..acd7a36b01fb3 100644
--- a/src/plugins/data/common/kbn_field_types/types.ts
+++ b/src/plugins/data/common/kbn_field_types/types.ts
@@ -59,6 +59,8 @@ export enum ES_FIELD_TYPES {
ATTACHMENT = 'attachment',
TOKEN_COUNT = 'token_count',
MURMUR3 = 'murmur3',
+
+ HISTOGRAM = 'histogram',
}
/** @public **/
@@ -77,4 +79,5 @@ export enum KBN_FIELD_TYPES {
CONFLICT = 'conflict',
OBJECT = 'object',
NESTED = 'nested',
+ HISTOGRAM = 'histogram',
}
diff --git a/src/plugins/data/public/field_formats/utils/deserialize.ts b/src/plugins/data/public/field_formats/utils/deserialize.ts
index c735ad196fbee..840e023a11589 100644
--- a/src/plugins/data/public/field_formats/utils/deserialize.ts
+++ b/src/plugins/data/public/field_formats/utils/deserialize.ts
@@ -70,7 +70,8 @@ export const deserializeFieldFormat: FormatFactory = function(
const { id } = mapping;
if (id === 'range') {
const RangeFormat = FieldFormat.from((range: any) => {
- const format = getFieldFormat(this, id, mapping.params);
+ const nestedFormatter = mapping.params as SerializedFieldFormat;
+ const format = getFieldFormat(this, nestedFormatter.id, nestedFormatter.params);
const gte = '\u2265';
const lt = '\u003c';
return i18n.translate('data.aggTypes.buckets.ranges.rangesFormatMessage', {
diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts
index 339a5fea91c5f..977b9568ceaa6 100644
--- a/src/plugins/data/public/index.ts
+++ b/src/plugins/data/public/index.ts
@@ -328,6 +328,7 @@ export {
AggParamType,
AggTypeFieldFilters, // TODO convert to interface
AggTypeFilters, // TODO convert to interface
+ AggConfigOptions,
BUCKET_TYPES,
DateRangeKey, // only used in field formatter deserialization, which will live in data
IAggConfig,
diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md
index dad3a8e639bc5..f0807187fc254 100644
--- a/src/plugins/data/public/public.api.md
+++ b/src/plugins/data/public/public.api.md
@@ -54,6 +54,22 @@ import { Unit } from '@elastic/datemath';
import { UnregisterCallback } from 'history';
import { UserProvidedValues } from 'src/core/server/types';
+// Warning: (ae-missing-release-tag) "AggConfigOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export interface AggConfigOptions {
+ // (undocumented)
+ enabled?: boolean;
+ // (undocumented)
+ id?: string;
+ // (undocumented)
+ params?: Record;
+ // (undocumented)
+ schema?: string;
+ // (undocumented)
+ type: IAggType;
+}
+
// Warning: (ae-missing-release-tag) "AggGroupNames" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -284,6 +300,8 @@ export enum ES_FIELD_TYPES {
// (undocumented)
HALF_FLOAT = "half_float",
// (undocumented)
+ HISTOGRAM = "histogram",
+ // (undocumented)
_ID = "_id",
// (undocumented)
_INDEX = "_index",
@@ -1126,6 +1144,8 @@ export enum KBN_FIELD_TYPES {
// (undocumented)
GEO_SHAPE = "geo_shape",
// (undocumented)
+ HISTOGRAM = "histogram",
+ // (undocumented)
IP = "ip",
// (undocumented)
MURMUR3 = "murmur3",
@@ -1828,21 +1848,21 @@ export type TSearchStrategyProvider = (context: ISearc
// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "getRoutes" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:379:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:379:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:379:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:379:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:384:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:385:1 - (ae-forgotten-export) The symbol "convertDateRangeToString" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:387:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:396:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:397:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:398:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:405:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:406:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:409:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:380:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:380:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:380:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:380:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:385:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:386:1 - (ae-forgotten-export) The symbol "convertDateRangeToString" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:388:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:397:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:398:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:403:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:406:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:407:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:410: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 "createFiltersFromEvent" needs to be exported by the entry point index.d.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/cardinality.ts b/src/plugins/data/public/search/aggs/metrics/cardinality.ts
index aa41307b2a052..88cdf3175665e 100644
--- a/src/plugins/data/public/search/aggs/metrics/cardinality.ts
+++ b/src/plugins/data/public/search/aggs/metrics/cardinality.ts
@@ -45,6 +45,9 @@ export const cardinalityMetricAgg = new MetricAggType({
{
name: 'field',
type: 'field',
+ filterFieldTypes: Object.values(KBN_FIELD_TYPES).filter(
+ type => type !== KBN_FIELD_TYPES.HISTOGRAM
+ ),
},
],
});
diff --git a/src/plugins/data/public/search/aggs/metrics/median.ts b/src/plugins/data/public/search/aggs/metrics/median.ts
index f2636d52e3484..faa0694cd5312 100644
--- a/src/plugins/data/public/search/aggs/metrics/median.ts
+++ b/src/plugins/data/public/search/aggs/metrics/median.ts
@@ -40,7 +40,7 @@ export const medianMetricAgg = new MetricAggType({
{
name: 'field',
type: 'field',
- filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE],
+ filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE, KBN_FIELD_TYPES.HISTOGRAM],
write(agg, output) {
output.params.field = agg.getParam('field').name;
output.params.percents = [50];
diff --git a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts
index 71b1c1415d98e..7dc0f70ea7b80 100644
--- a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts
+++ b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts
@@ -59,7 +59,7 @@ export const percentileRanksMetricAgg = new MetricAggType({
{
name: 'field',
type: 'field',
- filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE],
+ filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE, KBN_FIELD_TYPES.HISTOGRAM],
},
{
name: 'percents',
diff --git a/src/plugins/data/public/search/aggs/metrics/top_hit.ts b/src/plugins/data/public/search/aggs/metrics/top_hit.ts
index 738de6b62bccb..d0c668c577e62 100644
--- a/src/plugins/data/public/search/aggs/metrics/top_hit.ts
+++ b/src/plugins/data/public/search/aggs/metrics/top_hit.ts
@@ -60,7 +60,9 @@ export const topHitMetricAgg = new MetricAggType({
name: 'field',
type: 'field',
onlyAggregatable: false,
- filterFieldTypes: '*',
+ filterFieldTypes: Object.values(KBN_FIELD_TYPES).filter(
+ type => type !== KBN_FIELD_TYPES.HISTOGRAM
+ ),
write(agg, output) {
const field = agg.getParam('field');
output.params = {};
diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md
index 178b2949a9456..5c231cdc05e61 100644
--- a/src/plugins/data/server/server.api.md
+++ b/src/plugins/data/server/server.api.md
@@ -176,6 +176,8 @@ export enum ES_FIELD_TYPES {
// (undocumented)
HALF_FLOAT = "half_float",
// (undocumented)
+ HISTOGRAM = "histogram",
+ // (undocumented)
_ID = "_id",
// (undocumented)
_INDEX = "_index",
@@ -547,6 +549,8 @@ export enum KBN_FIELD_TYPES {
// (undocumented)
GEO_SHAPE = "geo_shape",
// (undocumented)
+ HISTOGRAM = "histogram",
+ // (undocumented)
IP = "ip",
// (undocumented)
MURMUR3 = "murmur3",
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/percentile.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/percentile.js
index 669a96a43ff8d..00fb48c88ec3f 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/percentile.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/percentile.js
@@ -17,7 +17,6 @@
* under the License.
*/
-import _ from 'lodash';
import { getAggValue } from '../../helpers/get_agg_value';
import { getDefaultDecoration } from '../../helpers/get_default_decoration';
import { getSplits } from '../../helpers/get_splits';
@@ -35,41 +34,45 @@ export function percentile(resp, panel, series, meta) {
getSplits(resp, panel, series, meta).forEach(split => {
metric.percentiles.forEach(percentile => {
const percentileValue = percentile.value ? percentile.value : 0;
- const label = `${split.label} (${percentileValue})`;
+ const id = `${split.id}:${percentile.id}`;
const data = split.timeseries.buckets.map(bucket => {
- const m = _.assign({}, metric, { percent: percentileValue });
- return [bucket.key, getAggValue(bucket, m)];
+ const higherMetric = { ...metric, percent: percentileValue };
+ const serieData = [bucket.key, getAggValue(bucket, higherMetric)];
+
+ if (percentile.mode === 'band') {
+ const lowerMetric = { ...metric, percent: percentile.percentile };
+ serieData.push(getAggValue(bucket, lowerMetric));
+ }
+
+ return serieData;
});
if (percentile.mode === 'band') {
- const fillData = split.timeseries.buckets.map(bucket => {
- const m = _.assign({}, metric, { percent: percentile.percentile });
- return [bucket.key, getAggValue(bucket, m)];
- });
results.push({
- id: `${split.id}:${percentile.id}`,
+ id,
color: split.color,
- label,
+ label: split.label,
data,
- lines: { show: true, fill: percentile.shade, lineWidth: 0 },
- points: { show: false },
- legend: false,
- fillBetween: `${split.id}:${percentile.id}:${percentile.percentile}`,
- });
- results.push({
- id: `${split.id}:${percentile.id}:${percentile.percentile}`,
- color: split.color,
- label,
- data: fillData,
- lines: { show: true, fill: false, lineWidth: 0 },
- legend: false,
+ lines: {
+ show: series.chart_type === 'line',
+ fill: Number(percentile.shade),
+ lineWidth: 0,
+ mode: 'band',
+ },
+ bars: {
+ show: series.chart_type === 'bar',
+ fill: Number(percentile.shade),
+ mode: 'band',
+ },
points: { show: false },
+ y1AccessorFormat: ` (${percentileValue})`,
+ y0AccessorFormat: ` (${percentile.percentile})`,
});
} else {
const decoration = getDefaultDecoration(series);
results.push({
- id: `${split.id}:${percentile.id}`,
+ id,
color: split.color,
- label,
+ label: `${split.label} (${percentileValue})`,
data,
...decoration,
});
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/percentile.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/percentile.test.js
index 9cb08de8dad23..aec1c45cf97e1 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/percentile.test.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/percentile.test.js
@@ -89,63 +89,45 @@ describe('percentile(resp, panel, series)', () => {
test('creates a series', () => {
const next = results => results;
const results = percentile(resp, panel, series)(next)([]);
- expect(results).toHaveLength(3);
+ expect(results).toHaveLength(2);
expect(results[0]).toHaveProperty('id', 'test:10-90');
expect(results[0]).toHaveProperty('color', 'rgb(255, 0, 0)');
- expect(results[0]).toHaveProperty('fillBetween', 'test:10-90:90');
- expect(results[0]).toHaveProperty('label', 'Percentile of cpu (10)');
- expect(results[0]).toHaveProperty('legend', false);
+ expect(results[0]).toHaveProperty('label', 'Percentile of cpu');
expect(results[0]).toHaveProperty('lines');
expect(results[0].lines).toEqual({
fill: 0.2,
lineWidth: 0,
show: true,
+ mode: 'band',
});
expect(results[0]).toHaveProperty('points');
expect(results[0].points).toEqual({ show: false });
expect(results[0].data).toEqual([
- [1, 1],
- [2, 1.2],
+ [1, 1, 5],
+ [2, 1.2, 5.3],
]);
- expect(results[1]).toHaveProperty('id', 'test:10-90:90');
+ expect(results[1]).toHaveProperty('id', 'test:50');
expect(results[1]).toHaveProperty('color', 'rgb(255, 0, 0)');
- expect(results[1]).toHaveProperty('label', 'Percentile of cpu (10)');
- expect(results[1]).toHaveProperty('legend', false);
+ expect(results[1]).toHaveProperty('label', 'Percentile of cpu (50)');
+ expect(results[1]).toHaveProperty('stack', false);
expect(results[1]).toHaveProperty('lines');
expect(results[1].lines).toEqual({
- fill: false,
- lineWidth: 0,
- show: true,
- });
- expect(results[1]).toHaveProperty('points');
- expect(results[1].points).toEqual({ show: false });
- expect(results[1].data).toEqual([
- [1, 5],
- [2, 5.3],
- ]);
-
- expect(results[2]).toHaveProperty('id', 'test:50');
- expect(results[2]).toHaveProperty('color', 'rgb(255, 0, 0)');
- expect(results[2]).toHaveProperty('label', 'Percentile of cpu (50)');
- expect(results[2]).toHaveProperty('stack', false);
- expect(results[2]).toHaveProperty('lines');
- expect(results[2].lines).toEqual({
fill: 0,
lineWidth: 1,
show: true,
steps: false,
});
- expect(results[2]).toHaveProperty('bars');
- expect(results[2].bars).toEqual({
+ expect(results[1]).toHaveProperty('bars');
+ expect(results[1].bars).toEqual({
fill: 0,
lineWidth: 1,
show: false,
});
- expect(results[2]).toHaveProperty('points');
- expect(results[2].points).toEqual({ show: true, lineWidth: 1, radius: 1 });
- expect(results[2].data).toEqual([
+ expect(results[1]).toHaveProperty('points');
+ expect(results[1].points).toEqual({ show: true, lineWidth: 1, radius: 1 });
+ expect(results[1].data).toEqual([
[1, 2.5],
[2, 2.7],
]);
diff --git a/src/plugins/visualizations/public/persisted_state/persisted_state.ts b/src/plugins/visualizations/public/persisted_state/persisted_state.ts
index b81b651c73509..3e675574fd678 100644
--- a/src/plugins/visualizations/public/persisted_state/persisted_state.ts
+++ b/src/plugins/visualizations/public/persisted_state/persisted_state.ts
@@ -85,7 +85,7 @@ export class PersistedState extends EventEmitter {
setSilent(key: PersistedStateKey | any, value?: any) {
const params = prepSetParams(key, value, this._path);
- if (params.key) {
+ if (params.key || params.value) {
return this.setValue(params.key, params.value, true);
}
}
diff --git a/test/functional/apps/visualize/_data_table.js b/test/functional/apps/visualize/_data_table.js
index 0a9ff1e77a2ef..a6305e158007d 100644
--- a/test/functional/apps/visualize/_data_table.js
+++ b/test/functional/apps/visualize/_data_table.js
@@ -99,9 +99,9 @@ export default function({ getService, getPageObjects }) {
async function expectValidTableData() {
const data = await PageObjects.visChart.getTableVisData();
expect(data.trim().split('\n')).to.be.eql([
- '≥ 0 and < 1000',
+ '≥ 0B and < 1,000B',
'1,351 64.7%',
- '≥ 1000 and < 2000',
+ '≥ 1,000B and < 1.953KB',
'737 35.3%',
]);
}
@@ -144,9 +144,9 @@ export default function({ getService, getPageObjects }) {
const data = await PageObjects.visChart.getTableVisData();
expect(data.trim().split('\n')).to.be.eql([
- '≥ 0 and < 1000',
+ '≥ 0B and < 1,000B',
'344.094B',
- '≥ 1000 and < 2000',
+ '≥ 1,000B and < 1.953KB',
'1.697KB',
]);
});
@@ -248,9 +248,9 @@ export default function({ getService, getPageObjects }) {
await PageObjects.visEditor.clickGo();
const data = await PageObjects.visChart.getTableVisData();
expect(data.trim().split('\n')).to.be.eql([
- '≥ 0 and < 1000',
+ '≥ 0B and < 1,000B',
'1,351',
- '≥ 1000 and < 2000',
+ '≥ 1,000B and < 1.953KB',
'737',
]);
});
diff --git a/vars/githubPr.groovy b/vars/githubPr.groovy
index 0176424452d07..965fb1d4e108e 100644
--- a/vars/githubPr.groovy
+++ b/vars/githubPr.groovy
@@ -169,7 +169,20 @@ def getNextCommentMessage(previousCommentInfo = [:]) {
## :broken_heart: Build Failed
* [continuous-integration/kibana-ci/pull-request](${env.BUILD_URL})
* Commit: ${getCommitHash()}
+ * [Pipeline Steps](${env.BUILD_URL}flowGraphTable) (look for red circles / failed steps)
+ * [Interpreting CI Failures](https://www.elastic.co/guide/en/kibana/current/interpreting-ci-failures.html)
"""
+
+ try {
+ def steps = getFailedSteps()
+ if (steps?.size() > 0) {
+ def list = steps.collect { "* [${it.displayName}](${it.logs})" }.join("\n")
+ messages << "### Failed CI Steps\n${list}"
+ }
+ } catch (ex) {
+ buildUtils.printStacktrace(ex)
+ print "Error retrieving failed pipeline steps for PR comment, will skip this section"
+ }
}
messages << getTestFailuresMessage()
@@ -220,3 +233,9 @@ def deleteComment(commentId) {
def getCommitHash() {
return env.ghprbActualCommit
}
+
+def getFailedSteps() {
+ return jenkinsApi.getFailedSteps()?.findAll { step ->
+ step.displayName != 'Check out from version control'
+ }
+}
diff --git a/vars/jenkinsApi.groovy b/vars/jenkinsApi.groovy
new file mode 100644
index 0000000000000..1ea4c3dd76b8d
--- /dev/null
+++ b/vars/jenkinsApi.groovy
@@ -0,0 +1,21 @@
+def getSteps() {
+ def url = "${env.BUILD_URL}api/json?tree=actions[nodes[iconColor,running,displayName,id,parents]]"
+ def responseRaw = httpRequest([ method: "GET", url: url ])
+ def response = toJSON(responseRaw)
+
+ def graphAction = response?.actions?.find { it._class == "org.jenkinsci.plugins.workflow.job.views.FlowGraphAction" }
+
+ return graphAction?.nodes
+}
+
+def getFailedSteps() {
+ def steps = getSteps()
+ def failedSteps = steps?.findAll { it.iconColor == "red" && it._class == "org.jenkinsci.plugins.workflow.cps.nodes.StepAtomNode" }
+ failedSteps.each { step ->
+ step.logs = "${env.BUILD_URL}execution/node/${step.id}/log".toString()
+ }
+
+ return failedSteps
+}
+
+return this
diff --git a/vars/prChanges.groovy b/vars/prChanges.groovy
index a9eb9027a0597..d7f46ee7be23e 100644
--- a/vars/prChanges.groovy
+++ b/vars/prChanges.groovy
@@ -1,3 +1,6 @@
+import groovy.transform.Field
+
+public static @Field PR_CHANGES_CACHE = null
def getSkippablePaths() {
return [
@@ -36,9 +39,13 @@ def areChangesSkippable() {
}
def getChanges() {
- withGithubCredentials {
- return githubPrs.getChanges(env.ghprbPullId)
+ if (!PR_CHANGES_CACHE && env.ghprbPullId) {
+ withGithubCredentials {
+ PR_CHANGES_CACHE = githubPrs.getChanges(env.ghprbPullId)
+ }
}
+
+ return PR_CHANGES_CACHE
}
def getChangedFiles() {
diff --git a/vars/whenChanged.groovy b/vars/whenChanged.groovy
new file mode 100644
index 0000000000000..c58ec83f2b051
--- /dev/null
+++ b/vars/whenChanged.groovy
@@ -0,0 +1,57 @@
+/*
+ whenChanged('some/path') { yourCode() } can be used to execute pipeline code in PRs only when changes are detected on paths that you specify.
+ The specified code blocks will also always be executed during the non-PR jobs for tracked branches.
+
+ You have the option of passing in path prefixes, or regexes. Single or multiple.
+ Path specifications are NOT globby, they are only prefixes.
+ Specifying multiple will treat them as ORs.
+
+ Example Usages:
+ whenChanged('a/path/prefix/') { someCode() }
+ whenChanged(startsWith: 'a/path/prefix/') { someCode() } // Same as above
+ whenChanged(['prefix1/', 'prefix2/']) { someCode() }
+ whenChanged(regex: /\.test\.js$/) { someCode() }
+ whenChanged(regex: [/abc/, /xyz/]) { someCode() }
+*/
+
+def call(String startsWithString, Closure closure) {
+ return whenChanged([ startsWith: startsWithString ], closure)
+}
+
+def call(List startsWithStrings, Closure closure) {
+ return whenChanged([ startsWith: startsWithStrings ], closure)
+}
+
+def call(Map params, Closure closure) {
+ if (!githubPr.isPr()) {
+ return closure()
+ }
+
+ def files = prChanges.getChangedFiles()
+ def hasMatch = false
+
+ if (params.regex) {
+ params.regex = [] + params.regex
+ print "Checking PR for changes that match: ${params.regex.join(', ')}"
+ hasMatch = !!files.find { file ->
+ params.regex.find { regex -> file =~ regex }
+ }
+ }
+
+ if (!hasMatch && params.startsWith) {
+ params.startsWith = [] + params.startsWith
+ print "Checking PR for changes that start with: ${params.startsWith.join(', ')}"
+ hasMatch = !!files.find { file ->
+ params.startsWith.find { str -> file.startsWith(str) }
+ }
+ }
+
+ if (hasMatch) {
+ print "Changes found, executing pipeline."
+ closure()
+ } else {
+ print "No changes found, skipping."
+ }
+}
+
+return this
diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/PlatinumLicensePrompt.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/PlatinumLicensePrompt.tsx
deleted file mode 100644
index 77f0b64ba0fb1..0000000000000
--- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/PlatinumLicensePrompt.tsx
+++ /dev/null
@@ -1,74 +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 {
- EuiButton,
- EuiPanel,
- EuiFlexGroup,
- EuiFlexItem,
- EuiTitle,
- EuiText,
- EuiSpacer
-} from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import React from 'react';
-import { invalidLicenseMessage } from '../../../../../../../plugins/apm/common/service_map';
-import { useKibanaUrl } from '../../../hooks/useKibanaUrl';
-
-export function PlatinumLicensePrompt() {
- // Set the height to give it some top margin
- const flexGroupStyle = { height: '60vh' };
- const flexItemStyle = { width: 600, textAlign: 'center' as const };
-
- const licensePageUrl = useKibanaUrl(
- '/app/kibana',
- '/management/elasticsearch/license_management/home'
- );
-
- return (
-
-
-
-
-
-
-
- {i18n.translate('xpack.apm.serviceMap.licensePromptTitle', {
- defaultMessage: 'Service maps is available in Platinum.'
- })}
-
-
-
-
- {invalidLicenseMessage}
-
-
-
- {i18n.translate('xpack.apm.serviceMap.licensePromptButtonText', {
- defaultMessage: 'Start 30-day Platinum trial'
- })}
-
-
-
-
-
- );
-}
diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx
index 5770771e01905..4974553f6ca93 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx
@@ -4,21 +4,25 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import theme from '@elastic/eui/dist/eui_theme_light.json';
import React from 'react';
-import { isValidPlatinumLicense } from '../../../../../../../plugins/apm/common/service_map';
+import {
+ invalidLicenseMessage,
+ isValidPlatinumLicense
+} from '../../../../../../../plugins/apm/common/service_map';
import { useFetcher } from '../../../hooks/useFetcher';
import { useLicense } from '../../../hooks/useLicense';
import { useUrlParams } from '../../../hooks/useUrlParams';
import { callApmApi } from '../../../services/rest/createCallApmApi';
-import { BetaBadge } from './BetaBadge';
+import { LicensePrompt } from '../../shared/LicensePrompt';
import { Controls } from './Controls';
import { Cytoscape } from './Cytoscape';
import { cytoscapeDivStyle } from './cytoscapeOptions';
import { EmptyBanner } from './EmptyBanner';
-import { PlatinumLicensePrompt } from './PlatinumLicensePrompt';
import { Popover } from './Popover';
import { useRefDimensions } from './useRefDimensions';
+import { BetaBadge } from './BetaBadge';
interface ServiceMapProps {
serviceName?: string;
@@ -74,6 +78,18 @@ export function ServiceMap({ serviceName }: ServiceMapProps) {
) : (
-
+
+
+
+
+
);
}
diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/Documentation.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/Documentation.tsx
new file mode 100644
index 0000000000000..48a0288f11ae5
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/Documentation.tsx
@@ -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 React from 'react';
+import { ElasticDocsLink } from '../../../../../shared/Links/ElasticDocsLink';
+
+interface Props {
+ label: string;
+}
+export const Documentation = ({ label }: Props) => (
+
+ {label}
+
+);
diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/FiltersSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/FiltersSection.tsx
index 69fecf25f5143..1c253b2fa8bff 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/FiltersSection.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/FiltersSection.tsx
@@ -16,12 +16,11 @@ import {
import { i18n } from '@kbn/i18n';
import { isEmpty } from 'lodash';
import React from 'react';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { FilterOptions } from '../../../../../../../../../../plugins/apm/server/routes/settings/custom_link';
+import { FilterOptions } from '../../../../../../../../../../plugins/apm/common/custom_link_filter_options';
import {
DEFAULT_OPTION,
- Filters,
- filterSelectOptions,
+ FilterKeyValue,
+ FILTER_SELECT_OPTIONS,
getSelectOptions
} from './helper';
@@ -29,10 +28,10 @@ export const FiltersSection = ({
filters,
onChangeFilters
}: {
- filters: Filters;
- onChangeFilters: (filters: Filters) => void;
+ filters: FilterKeyValue[];
+ onChangeFilters: (filters: FilterKeyValue[]) => void;
}) => {
- const onChangeFilter = (filter: Filters[0], idx: number) => {
+ const onChangeFilter = (filter: FilterKeyValue, idx: number) => {
const newFilters = [...filters];
newFilters[idx] = filter;
onChangeFilters(newFilters);
@@ -40,7 +39,8 @@ export const FiltersSection = ({
const onRemoveFilter = (idx: number) => {
// remove without mutating original array
- const newFilters = [...filters].splice(idx, 1);
+ const newFilters = [...filters];
+ newFilters.splice(idx, 1);
// if there is only one item left it should not be removed
// but reset to empty
@@ -68,12 +68,12 @@ export const FiltersSection = ({
-
+
{i18n.translate(
'xpack.apm.settings.customizeUI.customLink.flyout.filters.subtitle',
{
defaultMessage:
- 'Add additional values within the same field by comma separating values.'
+ 'Use the filter options to scope them to only appear for specific services.'
}
)}
@@ -83,12 +83,12 @@ export const FiltersSection = ({
{filters.map((filter, idx) => {
const [key, value] = filter;
const filterId = `filter-${idx}`;
- const selectOptions = getSelectOptions(filters, idx);
+ const selectOptions = getSelectOptions(filters, key);
return (
onRemoveFilter(idx)}
- disabled={!key && filters.length === 1}
+ disabled={!value && !key && filters.length === 1}
/>
@@ -139,7 +140,7 @@ export const FiltersSection = ({
>
);
diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkPreview.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkPreview.test.tsx
new file mode 100644
index 0000000000000..9b487cf916089
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkPreview.test.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 { LinkPreview } from '../CustomLinkFlyout/LinkPreview';
+import { render, getNodeText, getByTestId } from '@testing-library/react';
+
+describe('LinkPreview', () => {
+ const getElementValue = (container: HTMLElement, id: string) =>
+ getNodeText(
+ ((getByTestId(container, id) as HTMLDivElement)
+ .children as HTMLCollection)[0] as HTMLDivElement
+ );
+
+ it('shows label and url default values', () => {
+ const { container } = render(
+
+ );
+ expect(getElementValue(container, 'preview-label')).toEqual('Elastic.co');
+ expect(getElementValue(container, 'preview-url')).toEqual(
+ 'https://www.elastic.co'
+ );
+ });
+
+ it('shows label and url values', () => {
+ const { container } = render(
+
+ );
+ expect(getElementValue(container, 'preview-label')).toEqual('foo');
+ expect(
+ (getByTestId(container, 'preview-link') as HTMLAnchorElement).text
+ ).toEqual('https://baz.co');
+ });
+
+ it('shows warning when couldnt replace context variables', () => {
+ const { container } = render(
+
+ );
+ expect(getElementValue(container, 'preview-label')).toEqual('foo');
+ expect(
+ (getByTestId(container, 'preview-link') as HTMLAnchorElement).text
+ ).toEqual('https://baz.co?service.name={{invalid}');
+ expect(getByTestId(container, 'preview-warning')).toBeInTheDocument();
+ });
+});
diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkPreview.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkPreview.tsx
new file mode 100644
index 0000000000000..0ad3455ab271f
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkPreview.tsx
@@ -0,0 +1,124 @@
+/*
+ * 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, { useEffect, useState } from 'react';
+import {
+ EuiPanel,
+ EuiText,
+ EuiSpacer,
+ EuiLink,
+ EuiToolTip,
+ EuiIcon,
+ EuiFlexGroup,
+ EuiFlexItem
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { debounce } from 'lodash';
+import { Transaction } from '../../../../../../../../../../plugins/apm/typings/es_schemas/ui/transaction';
+import { callApmApi } from '../../../../../../services/rest/createCallApmApi';
+import {
+ FilterKeyValue,
+ convertFiltersToObject,
+ replaceTemplateVariables
+} from './helper';
+
+interface Props {
+ label: string;
+ url: string;
+ filters: FilterKeyValue[];
+}
+
+const fetchTransaction = debounce(
+ async (
+ filters: FilterKeyValue[],
+ callback: (transaction: Transaction) => void
+ ) => {
+ const transaction = await callApmApi({
+ pathname: '/api/apm/settings/custom_links/transaction',
+ params: { query: convertFiltersToObject(filters) }
+ });
+ callback(transaction);
+ },
+ 1000
+);
+
+const getTextColor = (value?: string) => (value ? 'default' : 'subdued');
+
+export const LinkPreview = ({ label, url, filters }: Props) => {
+ const [transaction, setTransaction] = useState();
+
+ useEffect(() => {
+ fetchTransaction(filters, setTransaction);
+ }, [filters]);
+
+ const { formattedUrl, error } = replaceTemplateVariables(url, transaction);
+
+ return (
+
+
+ {label
+ ? label
+ : i18n.translate(
+ 'xpack.apm.settings.customizeUI.customLink.default.label',
+ { defaultMessage: 'Elastic.co' }
+ )}
+
+
+
+ {url ? (
+
+ {formattedUrl}
+
+ ) : (
+ i18n.translate(
+ 'xpack.apm.settings.customizeUI.customLink.default.url',
+ { defaultMessage: 'https://www.elastic.co' }
+ )
+ )}
+
+
+
+
+
+ {i18n.translate(
+ 'xpack.apm.settings.customizeUI.customLink.linkPreview.descrition',
+ {
+ defaultMessage:
+ 'Test your link with values from an example transaction document based on the filters above.'
+ }
+ )}
+
+
+
+
+ {error && (
+
+
+
+ )}
+
+
+
+ );
+};
diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkSection.tsx
index 89f55a6c682ca..8bcebc2aea09e 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkSection.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkSection.tsx
@@ -13,11 +13,12 @@ import {
import { i18n } from '@kbn/i18n';
import React from 'react';
import { CustomLink } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types';
+import { Documentation } from './Documentation';
interface InputField {
name: keyof CustomLink;
label: string;
- helpText: string;
+ helpText: string | React.ReactNode;
placeholder: string;
onChange: (value: string) => void;
value?: string;
@@ -69,13 +70,25 @@ export const LinkSection = ({
defaultMessage: 'URL'
}
),
- helpText: i18n.translate(
- 'xpack.apm.settings.customizeUI.customLink.flyout.link.url.helpText',
- {
- defaultMessage:
- 'Add fieldname variables to your URL to apply values e.g. {sample}. TODO: Learn more in the docs.',
- values: { sample: '{{trace.id}}' }
- }
+ helpText: (
+ <>
+ {i18n.translate(
+ 'xpack.apm.settings.customizeUI.customLink.flyout.link.url.helpText',
+ {
+ defaultMessage:
+ 'Add field name variables to your URL to apply values e.g. {sample}.',
+ values: { sample: '{{trace.id}}' }
+ }
+ )}{' '}
+
+ >
),
placeholder: i18n.translate(
'xpack.apm.settings.customizeUI.customLink.flyout.link.url.placeholder',
@@ -125,7 +138,7 @@ export const LinkSection = ({
fullWidth
value={field.value}
onChange={e => field.onChange(e.target.value)}
- aria-label={field.name}
+ data-test-subj={field.name}
/>
);
diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/helper.test.ts b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/helper.test.ts
new file mode 100644
index 0000000000000..ac01ee48f2fe5
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/helper.test.ts
@@ -0,0 +1,205 @@
+/*
+ * 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 {
+ convertFiltersToArray,
+ convertFiltersToObject,
+ getSelectOptions,
+ replaceTemplateVariables
+} from '../CustomLinkFlyout/helper';
+import { CustomLink } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types';
+import { Transaction } from '../../../../../../../../../../plugins/apm/typings/es_schemas/ui/transaction';
+
+describe('Custom link helper', () => {
+ describe('convertFiltersToArray', () => {
+ it('returns array of tuple when custom link not defined', () => {
+ expect(convertFiltersToArray()).toEqual([['', '']]);
+ });
+ it('returns filters as array', () => {
+ expect(
+ convertFiltersToArray({
+ 'service.name': 'foo',
+ 'transaction.type': 'bar'
+ } as CustomLink)
+ ).toEqual([
+ ['service.name', 'foo'],
+ ['transaction.type', 'bar']
+ ]);
+ });
+ it('returns empty when no filter is added', () => {
+ expect(
+ convertFiltersToArray({
+ label: 'foo',
+ url: 'bar'
+ } as CustomLink)
+ ).toEqual([['', '']]);
+ });
+ });
+
+ describe('convertFiltersToObject', () => {
+ it('returns undefined when any filter is added', () => {
+ expect(convertFiltersToObject([['', '']])).toBeUndefined();
+ });
+ it('removes uncompleted filters', () => {
+ expect(
+ convertFiltersToObject([
+ ['service.name', ''],
+ ['', 'foo'],
+ ['transaction.type', 'bar']
+ ])
+ ).toEqual({ 'transaction.type': ['bar'] });
+ });
+ it('splits the value by comma', () => {
+ expect(
+ convertFiltersToObject([
+ ['service.name', 'foo'],
+ ['service.environment', 'foo, bar'],
+ ['transaction.type', 'foo, '],
+ ['transaction.name', 'foo,']
+ ])
+ ).toEqual({
+ 'service.name': ['foo'],
+ 'service.environment': ['foo', 'bar'],
+ 'transaction.type': ['foo'],
+ 'transaction.name': ['foo']
+ });
+ });
+ });
+
+ describe('getSelectOptions', () => {
+ it('returns all available options when no filters were selected', () => {
+ expect(
+ getSelectOptions(
+ [
+ ['', ''],
+ ['', ''],
+ ['', ''],
+ ['', '']
+ ],
+ ''
+ )
+ ).toEqual([
+ { value: 'DEFAULT', text: 'Select field...' },
+ { value: 'service.name', text: 'service.name' },
+ { value: 'service.environment', text: 'service.environment' },
+ { value: 'transaction.type', text: 'transaction.type' },
+ { value: 'transaction.name', text: 'transaction.name' }
+ ]);
+ });
+ it('removes item added in another filter', () => {
+ expect(
+ getSelectOptions(
+ [
+ ['service.name', 'foo'],
+ ['', ''],
+ ['', ''],
+ ['', '']
+ ],
+ ''
+ )
+ ).toEqual([
+ { value: 'DEFAULT', text: 'Select field...' },
+ { value: 'service.environment', text: 'service.environment' },
+ { value: 'transaction.type', text: 'transaction.type' },
+ { value: 'transaction.name', text: 'transaction.name' }
+ ]);
+ });
+ it('removes item added in another filter but keep the current selected', () => {
+ expect(
+ getSelectOptions(
+ [
+ ['service.name', 'foo'],
+ ['transaction.name', 'bar'],
+ ['', ''],
+ ['', '']
+ ],
+ 'transaction.name'
+ )
+ ).toEqual([
+ { value: 'DEFAULT', text: 'Select field...' },
+ { value: 'service.environment', text: 'service.environment' },
+ { value: 'transaction.type', text: 'transaction.type' },
+ { value: 'transaction.name', text: 'transaction.name' }
+ ]);
+ });
+ it('returns empty when all option were selected', () => {
+ expect(
+ getSelectOptions(
+ [
+ ['service.name', 'foo'],
+ ['transaction.name', 'bar'],
+ ['service.environment', 'baz'],
+ ['transaction.type', 'qux']
+ ],
+ ''
+ )
+ ).toEqual([{ value: 'DEFAULT', text: 'Select field...' }]);
+ });
+ });
+
+ describe('replaceTemplateVariables', () => {
+ const transaction = ({
+ service: { name: 'foo' },
+ trace: { id: '123' }
+ } as unknown) as Transaction;
+
+ it('replaces template variables', () => {
+ expect(
+ replaceTemplateVariables(
+ 'https://elastic.co?service.name={{service.name}}&trace.id={{trace.id}}',
+ transaction
+ )
+ ).toEqual({
+ error: undefined,
+ formattedUrl: 'https://elastic.co?service.name=foo&trace.id=123'
+ });
+ });
+
+ it('returns error when transaction is not defined', () => {
+ const expectedResult = {
+ error:
+ "We couldn't find a matching transaction document based on the defined filters.",
+ formattedUrl: 'https://elastic.co?service.name=&trace.id='
+ };
+ expect(
+ replaceTemplateVariables(
+ 'https://elastic.co?service.name={{service.name}}&trace.id={{trace.id}}'
+ )
+ ).toEqual(expectedResult);
+ expect(
+ replaceTemplateVariables(
+ 'https://elastic.co?service.name={{service.name}}&trace.id={{trace.id}}',
+ ({} as unknown) as Transaction
+ )
+ ).toEqual(expectedResult);
+ });
+
+ it('returns error when could not replace variables', () => {
+ expect(
+ replaceTemplateVariables(
+ 'https://elastic.co?service.name={{service.nam}}&trace.id={{trace.i}}',
+ transaction
+ )
+ ).toEqual({
+ error:
+ "We couldn't find a value match for {{service.nam}}, {{trace.i}} in the example transaction document.",
+ formattedUrl: 'https://elastic.co?service.name=&trace.id='
+ });
+ });
+
+ it('returns error when variable is invalid', () => {
+ expect(
+ replaceTemplateVariables(
+ 'https://elastic.co?service.name={{service.name}',
+ transaction
+ )
+ ).toEqual({
+ error:
+ "We couldn't find an example transaction document due to invalid variable(s) defined.",
+ formattedUrl: 'https://elastic.co?service.name={{service.name}'
+ });
+ });
+ });
+});
diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/helper.ts b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/helper.ts
index bb86a251594ab..df99c82c71b70 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/helper.ts
+++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/helper.ts
@@ -4,15 +4,18 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
-import { isEmpty, pick } from 'lodash';
+import Mustache from 'mustache';
+import { isEmpty, pick, get } from 'lodash';
+import { Transaction } from '../../../../../../../../../../plugins/apm/typings/es_schemas/ui/transaction';
import {
FilterOptions,
- filterOptions
- // eslint-disable-next-line @kbn/eslint/no-restricted-paths
-} from '../../../../../../../../../../plugins/apm/server/routes/settings/custom_link';
+ FILTER_OPTIONS
+} from '../../../../../../../../../../plugins/apm/common/custom_link_filter_options';
import { CustomLink } from '../../../../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types';
-export type Filters = Array<[keyof FilterOptions | '', string]>;
+type FilterKey = keyof FilterOptions | '';
+type FilterValue = string;
+export type FilterKeyValue = [FilterKey, FilterValue];
interface FilterSelectOption {
value: 'DEFAULT' | keyof FilterOptions;
@@ -33,9 +36,13 @@ interface FilterSelectOption {
* results: [['service.name', 'opbeans-java'],['transaction.type', 'request']]
* @param customLink
*/
-export const convertFiltersToArray = (customLink?: CustomLink): Filters => {
+export const convertFiltersToArray = (
+ customLink?: CustomLink
+): FilterKeyValue[] => {
if (customLink) {
- const filters = Object.entries(pick(customLink, filterOptions)) as Filters;
+ const filters = Object.entries(
+ pick(customLink, FILTER_OPTIONS)
+ ) as FilterKeyValue[];
if (!isEmpty(filters)) {
return filters;
}
@@ -54,9 +61,18 @@ export const convertFiltersToArray = (customLink?: CustomLink): Filters => {
* }
* @param filters
*/
-export const convertFiltersToObject = (filters: Filters) => {
+export const convertFiltersToObject = (filters: FilterKeyValue[]) => {
const convertedFilters = Object.fromEntries(
- filters.filter(([key, value]) => !isEmpty(key) && !isEmpty(value))
+ filters
+ .filter(([key, value]) => !isEmpty(key) && !isEmpty(value))
+ .map(([key, value]) => [
+ key,
+ // Splits the value by comma, removes whitespace from both ends and filters out empty values
+ value
+ .split(',')
+ .map(v => v.trim())
+ .filter(v => v)
+ ])
);
if (!isEmpty(convertedFilters)) {
return convertedFilters;
@@ -71,9 +87,9 @@ export const DEFAULT_OPTION: FilterSelectOption = {
)
};
-export const filterSelectOptions: FilterSelectOption[] = [
+export const FILTER_SELECT_OPTIONS: FilterSelectOption[] = [
DEFAULT_OPTION,
- ...filterOptions.map(filter => ({
+ ...FILTER_OPTIONS.map(filter => ({
value: filter as keyof FilterOptions,
text: filter
}))
@@ -83,14 +99,76 @@ export const filterSelectOptions: FilterSelectOption[] = [
* Returns the options available, removing filters already added, but keeping the selected filter.
*
* @param filters
- * @param idx
+ * @param selectedKey
*/
-export const getSelectOptions = (filters: Filters, idx: number) => {
- return filterSelectOptions.filter(option => {
- const indexUsedFilter = filters.findIndex(
- filter => filter[0] === option.value
+export const getSelectOptions = (
+ filters: FilterKeyValue[],
+ selectedKey: FilterKey
+) => {
+ return FILTER_SELECT_OPTIONS.filter(
+ ({ value }) =>
+ !filters.some(
+ ([filterKey]) => filterKey === value && filterKey !== selectedKey
+ )
+ );
+};
+
+const getInvalidTemplateVariables = (
+ template: string,
+ transaction: Transaction
+) => {
+ return (Mustache.parse(template) as Array<[string, string]>)
+ .filter(([type]) => type === 'name')
+ .map(([, value]) => value)
+ .filter(templateVar => get(transaction, templateVar) == null);
+};
+
+const validateUrl = (url: string, transaction?: Transaction) => {
+ if (!transaction || isEmpty(transaction)) {
+ return i18n.translate(
+ 'xpack.apm.settings.customizeUI.customLink.preview.transaction.notFound',
+ {
+ defaultMessage:
+ "We couldn't find a matching transaction document based on the defined filters."
+ }
+ );
+ }
+ try {
+ const invalidVariables = getInvalidTemplateVariables(url, transaction);
+ if (!isEmpty(invalidVariables)) {
+ return i18n.translate(
+ 'xpack.apm.settings.customizeUI.customLink.preview.contextVariable.noMatch',
+ {
+ defaultMessage:
+ "We couldn't find a value match for {variables} in the example transaction document.",
+ values: {
+ variables: invalidVariables
+ .map(variable => `{{${variable}}}`)
+ .join(', ')
+ }
+ }
+ );
+ }
+ } catch (e) {
+ return i18n.translate(
+ 'xpack.apm.settings.customizeUI.customLink.preview.contextVariable.invalid',
+ {
+ defaultMessage:
+ "We couldn't find an example transaction document due to invalid variable(s) defined."
+ }
);
- // Filter out all items already added, besides the one selected in the current filter.
- return indexUsedFilter === -1 || idx === indexUsedFilter;
- });
+ }
+};
+
+export const replaceTemplateVariables = (
+ url: string,
+ transaction?: Transaction
+) => {
+ const error = validateUrl(url, transaction);
+ try {
+ return { formattedUrl: Mustache.render(url, transaction), error };
+ } catch (e) {
+ // errors will be caught on validateUrl function
+ return { formattedUrl: url, error };
+ }
};
diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/index.tsx
index 88358c888160b..68755bad5f652 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/index.tsx
@@ -21,6 +21,8 @@ import { FlyoutFooter } from './FlyoutFooter';
import { LinkSection } from './LinkSection';
import { saveCustomLink } from './saveCustomLink';
import { convertFiltersToArray, convertFiltersToObject } from './helper';
+import { LinkPreview } from './LinkPreview';
+import { Documentation } from './Documentation';
interface Props {
onClose: () => void;
@@ -87,9 +89,17 @@ export const CustomLinkFlyout = ({
'xpack.apm.settings.customizeUI.customLink.flyout.label',
{
defaultMessage:
- 'Links will be available in the context of transaction details throughout the APM app. You can create an unlimited number of links and use the filter options to scope them to only appear for specific services. You can refer to dynamic variables by using any of the transaction metadata to fill in your URLs. TODO: Learn more about it in the docs.'
+ 'Links will be available in the context of transaction details throughout the APM app. You can create an unlimited number of links. You can refer to dynamic variables by using any of the transaction metadata to fill in your URLs. More information, including examples, are available in the'
}
- )}
+ )}{' '}
+
@@ -105,6 +115,10 @@ export const CustomLinkFlyout = ({
+
+
+
+
{
+ let callApmApiSpy: Function;
+ beforeAll(() => {
+ callApmApiSpy = spyOn(apmApi, 'callApmApi').and.returnValue({});
+ });
+ afterAll(() => {
+ jest.resetAllMocks();
+ });
+ const goldLicense = new License({
+ signature: 'test signature',
+ license: {
+ expiryDateInMillis: 0,
+ mode: 'gold',
+ status: 'active',
+ type: 'gold',
+ uid: '1'
+ }
+ });
describe('empty prompt', () => {
beforeAll(() => {
spyOn(hooks, 'useFetcher').and.returnValue({
@@ -44,14 +64,20 @@ describe('CustomLink', () => {
jest.clearAllMocks();
});
it('shows when no link is available', () => {
- const component = render();
+ const component = render(
+
+
+
+ );
expectTextsInDocument(component, ['No links found.']);
});
it('opens flyout when click to create new link', () => {
const { queryByText, getByText } = render(
-
-
-
+
+
+
+
+
);
expect(queryByText('Create link')).not.toBeInTheDocument();
act(() => {
@@ -75,9 +101,11 @@ describe('CustomLink', () => {
it('shows a table with all custom link', () => {
const component = render(
-
-
-
+
+
+
+
+
);
expectTextsInDocument(component, [
'label 1',
@@ -89,9 +117,11 @@ describe('CustomLink', () => {
it('checks if create custom link button is available and working', () => {
const { queryByText, getByText } = render(
-
-
-
+
+
+
+
+
);
expect(queryByText('Create link')).not.toBeInTheDocument();
act(() => {
@@ -103,10 +133,8 @@ describe('CustomLink', () => {
describe('Flyout', () => {
const refetch = jest.fn();
- let callApmApiSpy: Function;
let saveCustomLinkSpy: Function;
beforeAll(() => {
- callApmApiSpy = spyOn(apmApi, 'callApmApi');
saveCustomLinkSpy = spyOn(saveCustomLink, 'saveCustomLink');
spyOn(hooks, 'useFetcher').and.returnValue({
data,
@@ -120,9 +148,11 @@ describe('CustomLink', () => {
const openFlyout = () => {
const component = render(
-
-
-
+
+
+
+
+
);
expect(component.queryByText('Create link')).not.toBeInTheDocument();
act(() => {
@@ -134,13 +164,13 @@ describe('CustomLink', () => {
it('creates a custom link', async () => {
const component = openFlyout();
- const labelInput = component.getByLabelText('label');
+ const labelInput = component.getByTestId('label');
act(() => {
fireEvent.change(labelInput, {
target: { value: 'foo' }
});
});
- const urlInput = component.getByLabelText('url');
+ const urlInput = component.getByTestId('url');
act(() => {
fireEvent.change(urlInput, {
target: { value: 'bar' }
@@ -154,9 +184,11 @@ describe('CustomLink', () => {
it('deletes a custom link', async () => {
const component = render(
-
-
-
+
+
+
+
+
);
expect(component.queryByText('Create link')).not.toBeInTheDocument();
const editButtons = component.getAllByLabelText('Edit');
@@ -204,9 +236,7 @@ describe('CustomLink', () => {
if (addNewFilter) {
addFilterField(component, 1);
}
- const field = component.getByLabelText(
- fieldName
- ) as HTMLSelectElement;
+ const field = component.getByTestId(fieldName) as HTMLSelectElement;
const optionsAvailable = Object.values(field)
.map(option => (option as HTMLOptionElement).text)
.filter(option => option);
@@ -248,4 +278,93 @@ describe('CustomLink', () => {
});
});
});
+
+ describe('invalid license', () => {
+ beforeAll(() => {
+ spyOn(hooks, 'useFetcher').and.returnValue({
+ data: [],
+ status: 'success'
+ });
+ });
+ it('shows license prompt when user has a basic license', () => {
+ const license = new License({
+ signature: 'test signature',
+ license: {
+ expiryDateInMillis: 0,
+ mode: 'basic',
+ status: 'active',
+ type: 'basic',
+ uid: '1'
+ }
+ });
+ const component = render(
+
+
+
+
+
+ );
+ expectTextsInDocument(component, ['Start free 30-day trial']);
+ });
+ it('shows license prompt when user has an invalid gold license', () => {
+ const license = new License({
+ signature: 'test signature',
+ license: {
+ expiryDateInMillis: 0,
+ mode: 'gold',
+ status: 'invalid',
+ type: 'gold',
+ uid: '1'
+ }
+ });
+ const component = render(
+
+
+
+
+
+ );
+ expectTextsInDocument(component, ['Start free 30-day trial']);
+ });
+ it('shows license prompt when user has an invalid trial license', () => {
+ const license = new License({
+ signature: 'test signature',
+ license: {
+ expiryDateInMillis: 0,
+ mode: 'trial',
+ status: 'invalid',
+ type: 'trial',
+ uid: '1'
+ }
+ });
+ const component = render(
+
+
+
+
+
+ );
+ expectTextsInDocument(component, ['Start free 30-day trial']);
+ });
+ it('doesnt show license prompt when user has a trial license', () => {
+ const license = new License({
+ signature: 'test signature',
+ license: {
+ expiryDateInMillis: 0,
+ mode: 'trial',
+ status: 'active',
+ type: 'trial',
+ uid: '1'
+ }
+ });
+ const component = render(
+
+
+
+
+
+ );
+ expectTextsNotInDocument(component, ['Start free 30-day trial']);
+ });
+ });
});
diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx
index bc1882c8c2785..a4985d4410699 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx
@@ -7,6 +7,8 @@
import { EuiPanel, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { isEmpty } from 'lodash';
import React, { useEffect, useState } from 'react';
+import { i18n } from '@kbn/i18n';
+import { useLicense } from '../../../../../hooks/useLicense';
import { CustomLink } from '../../../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types';
import { useFetcher, FETCH_STATUS } from '../../../../../hooks/useFetcher';
import { CustomLinkFlyout } from './CustomLinkFlyout';
@@ -14,8 +16,12 @@ import { CustomLinkTable } from './CustomLinkTable';
import { EmptyPrompt } from './EmptyPrompt';
import { Title } from './Title';
import { CreateCustomLinkButton } from './CreateCustomLinkButton';
+import { LicensePrompt } from '../../../../shared/LicensePrompt';
export const CustomLinkOverview = () => {
+ const license = useLicense();
+ const hasValidLicense = license?.isActive && license?.hasAtLeast('gold');
+
const [isFlyoutOpen, setIsFlyoutOpen] = useState(false);
const [customLinkSelected, setCustomLinkSelected] = useState<
CustomLink | undefined
@@ -65,7 +71,7 @@ export const CustomLinkOverview = () => {
- {!showEmptyPrompt && (
+ {hasValidLicense && !showEmptyPrompt && (
@@ -77,13 +83,24 @@ export const CustomLinkOverview = () => {
-
- {showEmptyPrompt ? (
-
+ {hasValidLicense ? (
+ showEmptyPrompt ? (
+
+ ) : (
+
+ )
) : (
-
)}
diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/PlatinumLicensePrompt.stories.tsx b/x-pack/legacy/plugins/apm/public/components/shared/LicensePrompt/LicensePrompt.stories.tsx
similarity index 79%
rename from x-pack/legacy/plugins/apm/public/components/app/ServiceMap/PlatinumLicensePrompt.stories.tsx
rename to x-pack/legacy/plugins/apm/public/components/shared/LicensePrompt/LicensePrompt.stories.tsx
index 80281c1a0a8fc..010bba7677f00 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/PlatinumLicensePrompt.stories.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/LicensePrompt/LicensePrompt.stories.tsx
@@ -6,13 +6,13 @@
import { storiesOf } from '@storybook/react';
import React from 'react';
-import { PlatinumLicensePrompt } from './PlatinumLicensePrompt';
import {
ApmPluginContext,
ApmPluginContextValue
} from '../../../context/ApmPluginContext';
+import { LicensePrompt } from '.';
-storiesOf('app/ServiceMap/PlatinumLicensePrompt', module).add(
+storiesOf('app/LicensePrompt', module).add(
'example',
() => {
const contextMock = ({
@@ -21,7 +21,7 @@ storiesOf('app/ServiceMap/PlatinumLicensePrompt', module).add(
return (
-
+
);
},
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/LicensePrompt/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/LicensePrompt/index.tsx
new file mode 100644
index 0000000000000..d2afefb83a568
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/shared/LicensePrompt/index.tsx
@@ -0,0 +1,63 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { EuiButton, EuiEmptyPrompt, EuiPanel } from '@elastic/eui';
+import React from 'react';
+import { i18n } from '@kbn/i18n';
+import { useKibanaUrl } from '../../../hooks/useKibanaUrl';
+
+interface Props {
+ text: string;
+ showBetaBadge?: boolean;
+}
+
+export const LicensePrompt = ({ text, showBetaBadge = false }: Props) => {
+ const licensePageUrl = useKibanaUrl(
+ '/app/kibana',
+ '/management/elasticsearch/license_management/home'
+ );
+
+ const renderLicenseBody = (
+
+ {i18n.translate('xpack.apm.license.title', {
+ defaultMessage: 'Start free 30-day trial'
+ })}
+
+ }
+ body={{text}
}
+ actions={
+
+ {i18n.translate('xpack.apm.license.button', {
+ defaultMessage: 'Start trial'
+ })}
+
+ }
+ />
+ );
+
+ const renderWithBetaBadge = (
+
+ {renderLicenseBody}
+
+ );
+
+ return <>{showBetaBadge ? renderWithBetaBadge : renderLicenseBody}>;
+};
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx
index 0e0c318ad3299..9fcab049e224f 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx
@@ -9,7 +9,7 @@ import { EuiLink, EuiLinkAnchorProps } from '@elastic/eui';
import { useApmPluginContext } from '../../../hooks/useApmPluginContext';
// union type constisting of valid guide sections that we link to
-type DocsSection = '/apm/get-started' | '/x-pack' | '/apm/server';
+type DocsSection = '/apm/get-started' | '/x-pack' | '/apm/server' | '/kibana';
interface Props extends EuiLinkAnchorProps {
section: DocsSection;
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/LoadingStatePrompt.tsx b/x-pack/legacy/plugins/apm/public/components/shared/LoadingStatePrompt.tsx
index e1cf07c03dee9..8a87de976f5ed 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/LoadingStatePrompt.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/LoadingStatePrompt.tsx
@@ -11,7 +11,7 @@ export function LoadingStatePrompt() {
return (
-
+
);
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/CustomLinkPopover.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/CustomLinkPopover.test.tsx
new file mode 100644
index 0000000000000..99789ca2ecdf5
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/CustomLinkPopover.test.tsx
@@ -0,0 +1,70 @@
+/*
+ * 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 { render, act, fireEvent } from '@testing-library/react';
+import { CustomLink } from '../../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types';
+import { Transaction } from '../../../../../../../../plugins/apm/typings/es_schemas/ui/transaction';
+import { CustomLinkPopover } from './CustomLinkPopover';
+import { expectTextsInDocument } from '../../../../utils/testHelpers';
+
+describe('CustomLinkPopover', () => {
+ const customLinks = [
+ { id: '1', label: 'foo', url: 'http://elastic.co' },
+ {
+ id: '2',
+ label: 'bar',
+ url: 'http://elastic.co?service.name={{service.name}}'
+ }
+ ] as CustomLink[];
+ const transaction = ({
+ service: { name: 'foo.bar' }
+ } as unknown) as Transaction;
+ it('renders popover', () => {
+ const component = render(
+
+ );
+ expectTextsInDocument(component, ['CUSTOM LINKS', 'Create', 'foo', 'bar']);
+ });
+
+ it('closes popover', () => {
+ const handleCloseMock = jest.fn();
+ const { getByText } = render(
+
+ );
+ expect(handleCloseMock).not.toHaveBeenCalled();
+ act(() => {
+ fireEvent.click(getByText('CUSTOM LINKS'));
+ });
+ expect(handleCloseMock).toHaveBeenCalled();
+ });
+
+ it('opens flyout to create new custom link', () => {
+ const handleCreateCustomLinkClickMock = jest.fn();
+ const { getByText } = render(
+
+ );
+ expect(handleCreateCustomLinkClickMock).not.toHaveBeenCalled();
+ act(() => {
+ fireEvent.click(getByText('Create'));
+ });
+ expect(handleCreateCustomLinkClickMock).toHaveBeenCalled();
+ });
+});
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/CustomLinkPopover.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/CustomLinkPopover.tsx
new file mode 100644
index 0000000000000..ee4aa25606a0c
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/CustomLinkPopover.tsx
@@ -0,0 +1,73 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React from 'react';
+import {
+ EuiPopoverTitle,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiButtonEmpty
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import styled from 'styled-components';
+import { Transaction } from '../../../../../../../../plugins/apm/typings/es_schemas/ui/transaction';
+import { CustomLink } from '../../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types';
+import { CustomLinkSection } from './CustomLinkSection';
+import { ManageCustomLink } from './ManageCustomLink';
+import { px } from '../../../../style/variables';
+
+const ScrollableContainer = styled.div`
+ max-height: ${px(535)};
+ overflow: scroll;
+`;
+
+export const CustomLinkPopover = ({
+ customLinks,
+ onCreateCustomLinkClick,
+ onClose,
+ transaction
+}: {
+ customLinks: CustomLink[];
+ onCreateCustomLinkClick: () => void;
+ onClose: () => void;
+ transaction: Transaction;
+}) => {
+ return (
+ <>
+
+
+
+
+ {i18n.translate(
+ 'xpack.apm.transactionActionMenu.customLink.popover.title',
+ {
+ defaultMessage: 'CUSTOM LINKS'
+ }
+ )}
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/CustomLinkSection.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/CustomLinkSection.test.tsx
new file mode 100644
index 0000000000000..4e52c302c6025
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/CustomLinkSection.test.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 from 'react';
+import { render } from '@testing-library/react';
+import { CustomLink } from '../../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types';
+import { CustomLinkSection } from './CustomLinkSection';
+import {
+ expectTextsInDocument,
+ expectTextsNotInDocument
+} from '../../../../utils/testHelpers';
+import { Transaction } from '../../../../../../../../plugins/apm/typings/es_schemas/ui/transaction';
+
+describe('CustomLinkSection', () => {
+ const customLinks = [
+ { id: '1', label: 'foo', url: 'http://elastic.co' },
+ {
+ id: '2',
+ label: 'bar',
+ url: 'http://elastic.co?service.name={{service.name}}'
+ }
+ ] as CustomLink[];
+ const transaction = ({
+ service: { name: 'foo.bar' }
+ } as unknown) as Transaction;
+ it('shows links', () => {
+ const component = render(
+
+ );
+ expectTextsInDocument(component, ['foo', 'bar']);
+ });
+
+ it('doesnt show any links', () => {
+ const component = render(
+
+ );
+ expectTextsNotInDocument(component, ['foo', 'bar']);
+ });
+});
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/CustomLinkSection.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/CustomLinkSection.tsx
new file mode 100644
index 0000000000000..601405dda6ece
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/CustomLinkSection.tsx
@@ -0,0 +1,40 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React from 'react';
+import Mustache from 'mustache';
+import { Transaction } from '../../../../../../../../plugins/apm/typings/es_schemas/ui/transaction';
+import { CustomLink } from '../../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types';
+import {
+ SectionLinks,
+ SectionLink
+} from '../../../../../../../../plugins/observability/public';
+
+export const CustomLinkSection = ({
+ customLinks,
+ transaction
+}: {
+ customLinks: CustomLink[];
+ transaction: Transaction;
+}) => (
+
+ {customLinks.map(link => {
+ let href = link.url;
+ try {
+ href = Mustache.render(link.url, transaction);
+ } catch (e) {
+ // ignores any error that happens
+ }
+ return (
+
+ );
+ })}
+
+);
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/ManageCustomLink.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/ManageCustomLink.test.tsx
new file mode 100644
index 0000000000000..9e7df53b0882f
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/ManageCustomLink.test.tsx
@@ -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 React from 'react';
+import { render, act, fireEvent } from '@testing-library/react';
+import { ManageCustomLink } from './ManageCustomLink';
+import {
+ expectTextsInDocument,
+ expectTextsNotInDocument
+} from '../../../../utils/testHelpers';
+
+describe('ManageCustomLink', () => {
+ it('renders with create button', () => {
+ const component = render(
+
+ );
+ expect(
+ component.getByLabelText('Custom links settings page')
+ ).toBeInTheDocument();
+ expectTextsInDocument(component, ['Create']);
+ });
+ it('renders without create button', () => {
+ const component = render(
+
+ );
+ expect(
+ component.getByLabelText('Custom links settings page')
+ ).toBeInTheDocument();
+ expectTextsNotInDocument(component, ['Create']);
+ });
+ it('opens flyout to create new custom link', () => {
+ const handleCreateCustomLinkClickMock = jest.fn();
+ const { getByText } = render(
+
+ );
+ expect(handleCreateCustomLinkClickMock).not.toHaveBeenCalled();
+ act(() => {
+ fireEvent.click(getByText('Create'));
+ });
+ expect(handleCreateCustomLinkClickMock).toHaveBeenCalled();
+ });
+});
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/ManageCustomLink.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/ManageCustomLink.tsx
new file mode 100644
index 0000000000000..fa9f8b2f07c53
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/ManageCustomLink.tsx
@@ -0,0 +1,59 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React from 'react';
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiToolTip,
+ EuiButtonEmpty,
+ EuiIcon
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { APMLink } from '../../Links/apm/APMLink';
+
+export const ManageCustomLink = ({
+ onCreateCustomLinkClick,
+ showCreateCustomLinkButton = true
+}: {
+ onCreateCustomLinkClick: () => void;
+ showCreateCustomLinkButton?: boolean;
+}) => (
+
+
+
+
+
+
+
+
+
+
+ {showCreateCustomLinkButton && (
+
+
+ {i18n.translate('xpack.apm.customLink.buttom.create.title', {
+ defaultMessage: 'Create'
+ })}
+
+
+ )}
+
+
+
+);
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/index.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/index.test.tsx
new file mode 100644
index 0000000000000..ba9c7eee8792b
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/index.test.tsx
@@ -0,0 +1,128 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { render, act, fireEvent } from '@testing-library/react';
+import { CustomLink } from '.';
+import { Transaction } from '../../../../../../../../plugins/apm/typings/es_schemas/ui/transaction';
+import { FETCH_STATUS } from '../../../../hooks/useFetcher';
+import {
+ expectTextsInDocument,
+ expectTextsNotInDocument
+} from '../../../../utils/testHelpers';
+import { CustomLink as CustomLinkType } from '../../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types';
+
+describe('Custom links', () => {
+ it('shows empty message when no custom link is available', () => {
+ const component = render(
+
+ );
+
+ expectTextsInDocument(component, [
+ 'No custom links found. Set up your own custom links i.e. a link to a specific Dashboard or external link.'
+ ]);
+ expectTextsNotInDocument(component, ['Create']);
+ });
+
+ it('shows loading while custom links are fetched', () => {
+ const { getByTestId } = render(
+
+ );
+ expect(getByTestId('loading-spinner')).toBeInTheDocument();
+ });
+
+ it('shows first 3 custom links available', () => {
+ const customLinks = [
+ { id: '1', label: 'foo', url: 'foo' },
+ { id: '2', label: 'bar', url: 'bar' },
+ { id: '3', label: 'baz', url: 'baz' },
+ { id: '4', label: 'qux', url: 'qux' }
+ ] as CustomLinkType[];
+ const component = render(
+
+ );
+ expectTextsInDocument(component, ['foo', 'bar', 'baz']);
+ expectTextsNotInDocument(component, ['qux']);
+ });
+
+ it('clicks on See more button', () => {
+ const customLinks = [
+ { id: '1', label: 'foo', url: 'foo' },
+ { id: '2', label: 'bar', url: 'bar' },
+ { id: '3', label: 'baz', url: 'baz' },
+ { id: '4', label: 'qux', url: 'qux' }
+ ] as CustomLinkType[];
+ const onSeeMoreClickMock = jest.fn();
+ const component = render(
+
+ );
+ expect(onSeeMoreClickMock).not.toHaveBeenCalled();
+ act(() => {
+ fireEvent.click(component.getByText('See more'));
+ });
+ expect(onSeeMoreClickMock).toHaveBeenCalled();
+ });
+
+ describe('create custom link buttons', () => {
+ it('shows create button below empty message', () => {
+ const component = render(
+
+ );
+
+ expectTextsInDocument(component, ['Create custom link']);
+ expectTextsNotInDocument(component, ['Create']);
+ });
+ it('shows create button besides the title', () => {
+ const customLinks = [
+ { id: '1', label: 'foo', url: 'foo' },
+ { id: '2', label: 'bar', url: 'bar' },
+ { id: '3', label: 'baz', url: 'baz' },
+ { id: '4', label: 'qux', url: 'qux' }
+ ] as CustomLinkType[];
+ const component = render(
+
+ );
+ expectTextsInDocument(component, ['Create']);
+ expectTextsNotInDocument(component, ['Create custom link']);
+ });
+ });
+});
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/index.tsx
new file mode 100644
index 0000000000000..9280f8e71bf9e
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/index.tsx
@@ -0,0 +1,128 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React from 'react';
+import {
+ EuiText,
+ EuiIcon,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiSpacer,
+ EuiButtonEmpty
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import styled from 'styled-components';
+import { isEmpty } from 'lodash';
+import { Transaction } from '../../../../../../../../plugins/apm/typings/es_schemas/ui/transaction';
+import { CustomLink as CustomLinkType } from '../../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types';
+import {
+ ActionMenuDivider,
+ SectionSubtitle
+} from '../../../../../../../../plugins/observability/public';
+import { CustomLinkSection } from './CustomLinkSection';
+import { ManageCustomLink } from './ManageCustomLink';
+import { FETCH_STATUS } from '../../../../hooks/useFetcher';
+import { LoadingStatePrompt } from '../../LoadingStatePrompt';
+import { px } from '../../../../style/variables';
+
+const SeeMoreButton = styled.button<{ show: boolean }>`
+ display: ${props => (props.show ? 'flex' : 'none')};
+ align-items: center;
+ width: 100%;
+ justify-content: space-between;
+ &:hover {
+ text-decoration: underline;
+ }
+`;
+
+export const CustomLink = ({
+ customLinks,
+ status,
+ onCreateCustomLinkClick,
+ onSeeMoreClick,
+ transaction
+}: {
+ customLinks: CustomLinkType[];
+ status: FETCH_STATUS;
+ onCreateCustomLinkClick: () => void;
+ onSeeMoreClick: () => void;
+ transaction: Transaction;
+}) => {
+ const renderEmptyPrompt = (
+ <>
+
+ {i18n.translate('xpack.apm.customLink.empty', {
+ defaultMessage:
+ 'No custom links found. Set up your own custom links i.e. a link to a specific Dashboard or external link.'
+ })}
+
+
+
+ {i18n.translate('xpack.apm.customLink.buttom.create', {
+ defaultMessage: 'Create custom link'
+ })}
+
+ >
+ );
+
+ const renderCustomLinkBottomSection = isEmpty(customLinks) ? (
+ renderEmptyPrompt
+ ) : (
+ 3}>
+
+ {i18n.translate('xpack.apm.transactionActionMenu.customLink.seeMore', {
+ defaultMessage: 'See more'
+ })}
+
+
+
+ );
+
+ return (
+ <>
+
+
+
+
+
+ {i18n.translate(
+ 'xpack.apm.transactionActionMenu.customLink.section',
+ {
+ defaultMessage: 'Custom Links'
+ }
+ )}
+
+
+
+
+
+
+
+
+
+ {i18n.translate('xpack.apm.transactionActionMenu.customLink.subtitle', {
+ defaultMessage: 'Links will open in a new window.'
+ })}
+
+
+
+ {status === FETCH_STATUS.LOADING ? (
+
+ ) : (
+ renderCustomLinkBottomSection
+ )}
+ >
+ );
+};
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx
index dd022626807d0..e3c412f40ba3a 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx
@@ -6,7 +6,10 @@
import { EuiButtonEmpty } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import React, { FunctionComponent, useState } from 'react';
+import React, { FunctionComponent, useMemo, useState } from 'react';
+import { FilterOptions } from '../../../../../../../plugins/apm/common/custom_link_filter_options';
+import { CustomLink as CustomLinkType } from '../../../../../../../plugins/apm/server/lib/settings/custom_link/custom_link_types';
+import { Transaction } from '../../../../../../../plugins/apm/typings/es_schemas/ui/transaction';
import {
ActionMenu,
ActionMenuDivider,
@@ -16,11 +19,16 @@ import {
SectionSubtitle,
SectionTitle
} from '../../../../../../../plugins/observability/public';
-import { Transaction } from '../../../../../../../plugins/apm/typings/es_schemas/ui/transaction';
import { useApmPluginContext } from '../../../hooks/useApmPluginContext';
+import { useFetcher } from '../../../hooks/useFetcher';
import { useLocation } from '../../../hooks/useLocation';
import { useUrlParams } from '../../../hooks/useUrlParams';
+import { CustomLinkFlyout } from '../../app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout';
+import { CustomLink } from './CustomLink';
+import { CustomLinkPopover } from './CustomLink/CustomLinkPopover';
import { getSections } from './sections';
+import { useLicense } from '../../../hooks/useLicense';
+import { px } from '../../../style/variables';
interface Props {
readonly transaction: Transaction;
@@ -37,11 +45,36 @@ const ActionMenuButton = ({ onClick }: { onClick: () => void }) => (
export const TransactionActionMenu: FunctionComponent = ({
transaction
}: Props) => {
+ const license = useLicense();
+ const hasValidLicense = license?.isActive && license?.hasAtLeast('gold');
+
const { core } = useApmPluginContext();
const location = useLocation();
const { urlParams } = useUrlParams();
- const [isOpen, setIsOpen] = useState(false);
+ const [isActionPopoverOpen, setIsActionPopoverOpen] = useState(false);
+ const [isCustomLinksPopoverOpen, setIsCustomLinksPopoverOpen] = useState(
+ false
+ );
+ const [isCustomLinkFlyoutOpen, setIsCustomLinkFlyoutOpen] = useState(false);
+
+ const filters: FilterOptions = useMemo(
+ () => ({
+ 'service.name': transaction?.service.name,
+ 'service.environment': transaction?.service.environment,
+ 'transaction.name': transaction?.transaction.name,
+ 'transaction.type': transaction?.transaction.type
+ }),
+ [transaction]
+ );
+ const { data: customLinks = [], status, refetch } = useFetcher(
+ callApmApi =>
+ callApmApi({
+ pathname: '/api/apm/settings/custom_links',
+ params: { query: filters }
+ }),
+ [filters]
+ );
const sections = getSections({
transaction,
@@ -50,39 +83,92 @@ export const TransactionActionMenu: FunctionComponent = ({
urlParams
});
+ const toggleCustomLinkFlyout = () => {
+ setIsCustomLinkFlyoutOpen(isOpen => !isOpen);
+ };
+
+ const toggleCustomLinkPopover = () => {
+ setIsCustomLinksPopoverOpen(isOpen => !isOpen);
+ };
+
return (
-
+ <>
+ {isCustomLinkFlyoutOpen && (
+ {
+ toggleCustomLinkFlyout();
+ refetch();
+ }}
+ onDelete={() => {
+ toggleCustomLinkFlyout();
+ refetch();
+ }}
+ />
+ )}
+
+ >
);
};
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx
index ac3616e8c134c..9094662e34914 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx
@@ -5,11 +5,18 @@
*/
import React from 'react';
-import { render, fireEvent } from '@testing-library/react';
+import { render, fireEvent, act } from '@testing-library/react';
import { TransactionActionMenu } from '../TransactionActionMenu';
import { Transaction } from '../../../../../../../../plugins/apm/typings/es_schemas/ui/transaction';
import * as Transactions from './mockData';
-import { MockApmPluginContextWrapper } from '../../../../utils/testHelpers';
+import {
+ MockApmPluginContextWrapper,
+ expectTextsNotInDocument,
+ expectTextsInDocument
+} from '../../../../utils/testHelpers';
+import * as hooks from '../../../../hooks/useFetcher';
+import { LicenseContext } from '../../../../context/LicenseContext';
+import { License } from '../../../../../../../../plugins/licensing/common/license';
const renderTransaction = async (transaction: Record) => {
const rendered = render(
@@ -23,6 +30,15 @@ const renderTransaction = async (transaction: Record) => {
};
describe('TransactionActionMenu component', () => {
+ beforeAll(() => {
+ spyOn(hooks, 'useFetcher').and.returnValue({
+ data: [],
+ status: 'success'
+ });
+ });
+ afterAll(() => {
+ jest.clearAllMocks();
+ });
it('should always render the discover link', async () => {
const { queryByText } = await renderTransaction(
Transactions.transactionWithMinimalData
@@ -124,4 +140,115 @@ describe('TransactionActionMenu component', () => {
expect(container).toMatchSnapshot();
});
+
+ describe('Custom links', () => {
+ it('doesnt show custom links when license is not valid', () => {
+ const license = new License({
+ signature: 'test signature',
+ license: {
+ expiryDateInMillis: 0,
+ mode: 'gold',
+ status: 'invalid',
+ type: 'gold',
+ uid: '1'
+ }
+ });
+ const component = render(
+
+
+
+
+
+ );
+ act(() => {
+ fireEvent.click(component.getByText('Actions'));
+ });
+ expectTextsNotInDocument(component, ['Custom Links']);
+ });
+ it('doesnt show custom links when basic license', () => {
+ const license = new License({
+ signature: 'test signature',
+ license: {
+ expiryDateInMillis: 0,
+ mode: 'basic',
+ status: 'active',
+ type: 'basic',
+ uid: '1'
+ }
+ });
+ const component = render(
+
+
+
+
+
+ );
+ act(() => {
+ fireEvent.click(component.getByText('Actions'));
+ });
+ expectTextsNotInDocument(component, ['Custom Links']);
+ });
+ it('shows custom links when trial license', () => {
+ const license = new License({
+ signature: 'test signature',
+ license: {
+ expiryDateInMillis: 0,
+ mode: 'trial',
+ status: 'active',
+ type: 'trial',
+ uid: '1'
+ }
+ });
+ const component = render(
+
+
+
+
+
+ );
+ act(() => {
+ fireEvent.click(component.getByText('Actions'));
+ });
+ expectTextsInDocument(component, ['Custom Links']);
+ });
+ it('shows custom links when gold license', () => {
+ const license = new License({
+ signature: 'test signature',
+ license: {
+ expiryDateInMillis: 0,
+ mode: 'gold',
+ status: 'active',
+ type: 'gold',
+ uid: '1'
+ }
+ });
+ const component = render(
+
+
+
+
+
+ );
+ act(() => {
+ fireEvent.click(component.getByText('Actions'));
+ });
+ expectTextsInDocument(component, ['Custom Links']);
+ });
+ });
});
diff --git a/x-pack/legacy/plugins/lens/index.ts b/x-pack/legacy/plugins/lens/index.ts
index 5eda6c4b4ff7a..b1c67fb81ba07 100644
--- a/x-pack/legacy/plugins/lens/index.ts
+++ b/x-pack/legacy/plugins/lens/index.ts
@@ -19,7 +19,7 @@ export const lens: LegacyPluginInitializer = kibana => {
id: PLUGIN_ID,
configPrefix: `xpack.${PLUGIN_ID}`,
// task_manager could be required, but is only used for telemetry
- require: ['kibana', 'elasticsearch', 'xpack_main', 'interpreter', 'data'],
+ require: ['kibana', 'elasticsearch', 'xpack_main', 'interpreter'],
publicDir: resolve(__dirname, 'public'),
uiExports: {
diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx
index d6312005a6c25..fbda18cc0e307 100644
--- a/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx
+++ b/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx
@@ -22,7 +22,6 @@ import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'
const dataStartMock = dataPluginMock.createStartContract();
import { TopNavMenuData } from '../../../../../../src/plugins/navigation/public';
-import { DataStart } from '../../../../../../src/legacy/core_plugins/data/public';
import { coreMock } from 'src/core/public/mocks';
jest.mock('ui/new_platform');
@@ -87,7 +86,6 @@ describe('Lens App', () => {
editorFrame: EditorFrameInstance;
data: typeof dataStartMock;
core: typeof core;
- dataShim: DataStart;
storage: Storage;
docId?: string;
docStorage: SavedObjectStore;
@@ -134,7 +132,6 @@ describe('Lens App', () => {
editorFrame: EditorFrameInstance;
data: typeof dataStartMock;
core: typeof core;
- dataShim: DataStart;
storage: Storage;
docId?: string;
docStorage: SavedObjectStore;
@@ -332,7 +329,6 @@ describe('Lens App', () => {
editorFrame: EditorFrameInstance;
data: typeof dataStartMock;
core: typeof core;
- dataShim: DataStart;
storage: Storage;
docId?: string;
docStorage: SavedObjectStore;
@@ -648,7 +644,6 @@ describe('Lens App', () => {
editorFrame: EditorFrameInstance;
data: typeof dataStartMock;
core: typeof core;
- dataShim: DataStart;
storage: Storage;
docId?: string;
docStorage: SavedObjectStore;
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx
index 41c317ccab290..f4485774bc942 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx
@@ -34,12 +34,6 @@ jest.mock('ui/new_platform');
jest.mock('../loader');
jest.mock('../state_helpers');
-// Used by indexpattern plugin, which is a dependency of a dependency
-jest.mock('ui/chrome');
-// Contains old and new platform data plugins, used for interpreter and filter ratio
-jest.mock('ui/new_platform');
-jest.mock('plugins/data/setup', () => ({ data: { query: { ui: {} } } }));
-
const expectedIndexPatterns = {
1: {
id: '1',
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx
index e86a16c1af9d6..4e48d0c0987b5 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx
@@ -12,13 +12,9 @@ import {
getDatasourceSuggestionsFromCurrentState,
} from './indexpattern_suggestions';
+jest.mock('ui/new_platform');
jest.mock('./loader');
jest.mock('../id_generator');
-// chrome, notify, storage are used by ./plugin
-jest.mock('ui/chrome');
-// Contains old and new platform data plugins, used for interpreter and filter ratio
-jest.mock('ui/new_platform');
-jest.mock('plugins/data/setup', () => ({ data: { query: { ui: {} } } }));
const expectedIndexPatterns = {
1: {
diff --git a/x-pack/legacy/plugins/siem/cypress/integration/detections.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/detections.spec.ts
index de17f40a3ac71..646132c3f88eb 100644
--- a/x-pack/legacy/plugins/siem/cypress/integration/detections.spec.ts
+++ b/x-pack/legacy/plugins/siem/cypress/integration/detections.spec.ts
@@ -16,6 +16,7 @@ import {
closeSignals,
goToClosedSignals,
goToOpenedSignals,
+ openFirstSignal,
openSignals,
selectNumberOfSignals,
waitForSignalsPanelToBeLoaded,
@@ -28,129 +29,181 @@ import { loginAndWaitForPage } from '../tasks/login';
import { DETECTIONS } from '../urls/navigation';
describe('Detections', () => {
- beforeEach(() => {
- esArchiverLoad('signals');
- loginAndWaitForPage(DETECTIONS);
+ context('Closing signals', () => {
+ beforeEach(() => {
+ esArchiverLoad('signals');
+ loginAndWaitForPage(DETECTIONS);
+ });
+
+ it('Closes and opens signals', () => {
+ waitForSignalsPanelToBeLoaded();
+ waitForSignalsToBeLoaded();
+
+ cy.get(NUMBER_OF_SIGNALS)
+ .invoke('text')
+ .then(numberOfSignals => {
+ cy.get(SHOWING_SIGNALS)
+ .invoke('text')
+ .should('eql', `Showing ${numberOfSignals} signals`);
+
+ const numberOfSignalsToBeClosed = 3;
+ selectNumberOfSignals(numberOfSignalsToBeClosed);
+
+ cy.get(SELECTED_SIGNALS)
+ .invoke('text')
+ .should('eql', `Selected ${numberOfSignalsToBeClosed} signals`);
+
+ closeSignals();
+ waitForSignals();
+ cy.reload();
+ waitForSignals();
+
+ const expectedNumberOfSignalsAfterClosing = +numberOfSignals - numberOfSignalsToBeClosed;
+ cy.get(NUMBER_OF_SIGNALS)
+ .invoke('text')
+ .should('eq', expectedNumberOfSignalsAfterClosing.toString());
+ cy.get(SHOWING_SIGNALS)
+ .invoke('text')
+ .should('eql', `Showing ${expectedNumberOfSignalsAfterClosing.toString()} signals`);
+
+ goToClosedSignals();
+ waitForSignals();
+
+ cy.get(NUMBER_OF_SIGNALS)
+ .invoke('text')
+ .should('eql', numberOfSignalsToBeClosed.toString());
+ cy.get(SHOWING_SIGNALS)
+ .invoke('text')
+ .should('eql', `Showing ${numberOfSignalsToBeClosed.toString()} signals`);
+ cy.get(SIGNALS).should('have.length', numberOfSignalsToBeClosed);
+
+ const numberOfSignalsToBeOpened = 1;
+ selectNumberOfSignals(numberOfSignalsToBeOpened);
+
+ cy.get(SELECTED_SIGNALS)
+ .invoke('text')
+ .should('eql', `Selected ${numberOfSignalsToBeOpened} signal`);
+
+ openSignals();
+ waitForSignals();
+ cy.reload();
+ waitForSignalsToBeLoaded();
+ waitForSignals();
+ goToClosedSignals();
+ waitForSignals();
+
+ const expectedNumberOfClosedSignalsAfterOpened = 2;
+ cy.get(NUMBER_OF_SIGNALS)
+ .invoke('text')
+ .should('eql', expectedNumberOfClosedSignalsAfterOpened.toString());
+ cy.get(SHOWING_SIGNALS)
+ .invoke('text')
+ .should(
+ 'eql',
+ `Showing ${expectedNumberOfClosedSignalsAfterOpened.toString()} signals`
+ );
+ cy.get(SIGNALS).should('have.length', expectedNumberOfClosedSignalsAfterOpened);
+
+ goToOpenedSignals();
+ waitForSignals();
+
+ const expectedNumberOfOpenedSignals =
+ +numberOfSignals - expectedNumberOfClosedSignalsAfterOpened;
+ cy.get(SHOWING_SIGNALS)
+ .invoke('text')
+ .should('eql', `Showing ${expectedNumberOfOpenedSignals.toString()} signals`);
+
+ cy.get('[data-test-subj="server-side-event-count"]')
+ .invoke('text')
+ .should('eql', expectedNumberOfOpenedSignals.toString());
+ });
+ });
+
+ it('Closes one signal when more than one opened signals are selected', () => {
+ waitForSignalsToBeLoaded();
+
+ cy.get(NUMBER_OF_SIGNALS)
+ .invoke('text')
+ .then(numberOfSignals => {
+ const numberOfSignalsToBeClosed = 1;
+ const numberOfSignalsToBeSelected = 3;
+
+ cy.get(OPEN_CLOSE_SIGNALS_BTN).should('have.attr', 'disabled');
+ selectNumberOfSignals(numberOfSignalsToBeSelected);
+ cy.get(OPEN_CLOSE_SIGNALS_BTN).should('not.have.attr', 'disabled');
+
+ closeFirstSignal();
+ cy.reload();
+ waitForSignalsToBeLoaded();
+ waitForSignals();
+
+ const expectedNumberOfSignals = +numberOfSignals - numberOfSignalsToBeClosed;
+ cy.get(NUMBER_OF_SIGNALS)
+ .invoke('text')
+ .should('eq', expectedNumberOfSignals.toString());
+ cy.get(SHOWING_SIGNALS)
+ .invoke('text')
+ .should('eql', `Showing ${expectedNumberOfSignals.toString()} signals`);
+
+ goToClosedSignals();
+ waitForSignals();
+
+ cy.get(NUMBER_OF_SIGNALS)
+ .invoke('text')
+ .should('eql', numberOfSignalsToBeClosed.toString());
+ cy.get(SHOWING_SIGNALS)
+ .invoke('text')
+ .should('eql', `Showing ${numberOfSignalsToBeClosed.toString()} signal`);
+ cy.get(SIGNALS).should('have.length', numberOfSignalsToBeClosed);
+ });
+ });
});
-
- it('Closes and opens signals', () => {
- waitForSignalsPanelToBeLoaded();
- waitForSignalsToBeLoaded();
-
- cy.get(NUMBER_OF_SIGNALS)
- .invoke('text')
- .then(numberOfSignals => {
- cy.get(SHOWING_SIGNALS)
- .invoke('text')
- .should('eql', `Showing ${numberOfSignals} signals`);
-
- const numberOfSignalsToBeClosed = 3;
- selectNumberOfSignals(numberOfSignalsToBeClosed);
-
- cy.get(SELECTED_SIGNALS)
- .invoke('text')
- .should('eql', `Selected ${numberOfSignalsToBeClosed} signals`);
-
- closeSignals();
- waitForSignals();
- cy.reload();
- waitForSignals();
- waitForSignalsToBeLoaded();
-
- const expectedNumberOfSignalsAfterClosing = +numberOfSignals - numberOfSignalsToBeClosed;
- cy.get(NUMBER_OF_SIGNALS)
- .invoke('text')
- .should('eq', expectedNumberOfSignalsAfterClosing.toString());
- cy.get(SHOWING_SIGNALS)
- .invoke('text')
- .should('eql', `Showing ${expectedNumberOfSignalsAfterClosing.toString()} signals`);
-
- goToClosedSignals();
- waitForSignals();
-
- cy.get(NUMBER_OF_SIGNALS)
- .invoke('text')
- .should('eql', numberOfSignalsToBeClosed.toString());
- cy.get(SHOWING_SIGNALS)
- .invoke('text')
- .should('eql', `Showing ${numberOfSignalsToBeClosed.toString()} signals`);
- cy.get(SIGNALS).should('have.length', numberOfSignalsToBeClosed);
-
- const numberOfSignalsToBeOpened = 1;
- selectNumberOfSignals(numberOfSignalsToBeOpened);
-
- cy.get(SELECTED_SIGNALS)
- .invoke('text')
- .should('eql', `Selected ${numberOfSignalsToBeOpened} signal`);
-
- openSignals();
- waitForSignals();
- cy.reload();
- waitForSignalsToBeLoaded();
- waitForSignals();
- goToClosedSignals();
- waitForSignals();
-
- const expectedNumberOfClosedSignalsAfterOpened = 2;
- cy.get(NUMBER_OF_SIGNALS)
- .invoke('text')
- .should('eql', expectedNumberOfClosedSignalsAfterOpened.toString());
- cy.get(SHOWING_SIGNALS)
- .invoke('text')
- .should('eql', `Showing ${expectedNumberOfClosedSignalsAfterOpened.toString()} signals`);
- cy.get(SIGNALS).should('have.length', expectedNumberOfClosedSignalsAfterOpened);
-
- goToOpenedSignals();
- waitForSignals();
-
- const expectedNumberOfOpenedSignals =
- +numberOfSignals - expectedNumberOfClosedSignalsAfterOpened;
- cy.get(SHOWING_SIGNALS)
- .invoke('text')
- .should('eql', `Showing ${expectedNumberOfOpenedSignals.toString()} signals`);
-
- cy.get('[data-test-subj="server-side-event-count"]')
- .invoke('text')
- .should('eql', expectedNumberOfOpenedSignals.toString());
- });
- });
-
- it('Closes one signal when more than one opened signals are selected', () => {
- waitForSignalsToBeLoaded();
-
- cy.get(NUMBER_OF_SIGNALS)
- .invoke('text')
- .then(numberOfSignals => {
- const numberOfSignalsToBeClosed = 1;
- const numberOfSignalsToBeSelected = 3;
-
- cy.get(OPEN_CLOSE_SIGNALS_BTN).should('have.attr', 'disabled');
- selectNumberOfSignals(numberOfSignalsToBeSelected);
- cy.get(OPEN_CLOSE_SIGNALS_BTN).should('not.have.attr', 'disabled');
-
- closeFirstSignal();
- cy.reload();
- waitForSignalsToBeLoaded();
- waitForSignals();
-
- const expectedNumberOfSignals = +numberOfSignals - numberOfSignalsToBeClosed;
- cy.get(NUMBER_OF_SIGNALS)
- .invoke('text')
- .should('eq', expectedNumberOfSignals.toString());
- cy.get(SHOWING_SIGNALS)
- .invoke('text')
- .should('eql', `Showing ${expectedNumberOfSignals.toString()} signals`);
-
- goToClosedSignals();
- waitForSignals();
-
- cy.get(NUMBER_OF_SIGNALS)
- .invoke('text')
- .should('eql', numberOfSignalsToBeClosed.toString());
- cy.get(SHOWING_SIGNALS)
- .invoke('text')
- .should('eql', `Showing ${numberOfSignalsToBeClosed.toString()} signal`);
- cy.get(SIGNALS).should('have.length', numberOfSignalsToBeClosed);
- });
+ context('Opening signals', () => {
+ beforeEach(() => {
+ esArchiverLoad('closed_signals');
+ loginAndWaitForPage(DETECTIONS);
+ });
+
+ it('Open one signal when more than one closed signals are selected', () => {
+ waitForSignals();
+ goToClosedSignals();
+ waitForSignalsToBeLoaded();
+
+ cy.get(NUMBER_OF_SIGNALS)
+ .invoke('text')
+ .then(numberOfSignals => {
+ const numberOfSignalsToBeOpened = 1;
+ const numberOfSignalsToBeSelected = 3;
+
+ cy.get(OPEN_CLOSE_SIGNALS_BTN).should('have.attr', 'disabled');
+ selectNumberOfSignals(numberOfSignalsToBeSelected);
+ cy.get(OPEN_CLOSE_SIGNALS_BTN).should('not.have.attr', 'disabled');
+
+ openFirstSignal();
+ cy.reload();
+ goToClosedSignals();
+ waitForSignalsToBeLoaded();
+ waitForSignals();
+
+ const expectedNumberOfSignals = +numberOfSignals - numberOfSignalsToBeOpened;
+ cy.get(NUMBER_OF_SIGNALS)
+ .invoke('text')
+ .should('eq', expectedNumberOfSignals.toString());
+ cy.get(SHOWING_SIGNALS)
+ .invoke('text')
+ .should('eql', `Showing ${expectedNumberOfSignals.toString()} signals`);
+
+ goToOpenedSignals();
+ waitForSignals();
+
+ cy.get(NUMBER_OF_SIGNALS)
+ .invoke('text')
+ .should('eql', numberOfSignalsToBeOpened.toString());
+ cy.get(SHOWING_SIGNALS)
+ .invoke('text')
+ .should('eql', `Showing ${numberOfSignalsToBeOpened.toString()} signal`);
+ cy.get(SIGNALS).should('have.length', numberOfSignalsToBeOpened);
+ });
+ });
});
});
diff --git a/x-pack/legacy/plugins/siem/cypress/tasks/detections.ts b/x-pack/legacy/plugins/siem/cypress/tasks/detections.ts
index 3416e3eb81de3..abea4a887b8ba 100644
--- a/x-pack/legacy/plugins/siem/cypress/tasks/detections.ts
+++ b/x-pack/legacy/plugins/siem/cypress/tasks/detections.ts
@@ -40,6 +40,12 @@ export const goToOpenedSignals = () => {
cy.get(OPENED_SIGNALS_BTN).click({ force: true });
};
+export const openFirstSignal = () => {
+ cy.get(OPEN_CLOSE_SIGNAL_BTN)
+ .first()
+ .click({ force: true });
+};
+
export const openSignals = () => {
cy.get(OPEN_CLOSE_SIGNALS_BTN).click({ force: true });
};
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/actions.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/actions.tsx
index 6253d431f8401..93536077f3a4c 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/actions.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/actions.tsx
@@ -32,7 +32,7 @@ export const getActions = ({
caseStatus === 'open'
? {
description: i18n.CLOSE_CASE,
- icon: 'magnet',
+ icon: 'folderCheck',
name: i18n.CLOSE_CASE,
onClick: (theCase: Case) =>
dispatchUpdate({
@@ -46,7 +46,7 @@ export const getActions = ({
}
: {
description: i18n.REOPEN_CASE,
- icon: 'magnet',
+ icon: 'folderExclamation',
name: i18n.REOPEN_CASE,
onClick: (theCase: Case) =>
dispatchUpdate({
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/bulk_actions/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/bulk_actions/index.tsx
index b9da834b929ea..74a255bf5ad49 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/bulk_actions/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/bulk_actions/index.tsx
@@ -27,8 +27,9 @@ export const getBulkItems = ({
caseStatus === 'open' ? (
{
closePopover();
updateCaseStatus('closed');
@@ -39,8 +40,9 @@ export const getBulkItems = ({
) : (
{
closePopover();
updateCaseStatus('open');
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx
index 08af603cb0dbf..0ac3adeb860ff 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx
@@ -105,7 +105,7 @@ export const CaseComponent = React.memo(({ caseId, initialData }) =>
title: i18n.CASE_OPENED,
buttonLabel: i18n.CLOSE_CASE,
status: caseData.status,
- icon: 'checkInCircleFilled',
+ icon: 'folderCheck',
badgeColor: 'secondary',
isSelected: false,
}
@@ -115,7 +115,7 @@ export const CaseComponent = React.memo(({ caseId, initialData }) =>
title: i18n.CASE_CLOSED,
buttonLabel: i18n.REOPEN_CASE,
status: caseData.status,
- icon: 'magnet',
+ icon: 'folderExclamation',
badgeColor: 'danger',
isSelected: true,
},
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx
index 04697e63b7451..6a3d319561353 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx
@@ -78,7 +78,7 @@ export const UserActionTree = React.memo(
id={DescriptionId}
isEditable={manageMarkdownEditIds.includes(DescriptionId)}
isLoading={isLoadingDescription}
- labelAction={i18n.EDIT_DESCRIPTION}
+ labelEditAction={i18n.EDIT_DESCRIPTION}
labelTitle={i18n.ADDED_DESCRIPTION}
fullName={caseData.createdBy.fullName ?? caseData.createdBy.username}
markdown={MarkdownDescription}
@@ -92,7 +92,7 @@ export const UserActionTree = React.memo(
id={comment.id}
isEditable={manageMarkdownEditIds.includes(comment.id)}
isLoading={isLoadingIds.includes(comment.id)}
- labelAction={i18n.EDIT_COMMENT}
+ labelEditAction={i18n.EDIT_COMMENT}
labelTitle={i18n.ADDED_COMMENT}
fullName={comment.createdBy.fullName ?? comment.createdBy.username}
markdown={
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_item.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_item.tsx
index 7b99f2ef76ab3..ca73f200f1793 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_item.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_item.tsx
@@ -16,7 +16,7 @@ interface UserActionItemProps {
id: string;
isEditable: boolean;
isLoading: boolean;
- labelAction?: string;
+ labelEditAction?: string;
labelTitle?: string;
fullName: string;
markdown: React.ReactNode;
@@ -71,7 +71,7 @@ export const UserActionItem = ({
id,
isEditable,
isLoading,
- labelAction,
+ labelEditAction,
labelTitle,
fullName,
markdown,
@@ -94,7 +94,7 @@ export const UserActionItem = ({
createdAt={createdAt}
id={id}
isLoading={isLoading}
- labelAction={labelAction ?? ''}
+ labelEditAction={labelEditAction ?? ''}
labelTitle={labelTitle ?? ''}
userName={userName}
onEdit={onEdit}
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.tsx
index 6ad60fb9f963e..0ed081e8852f0 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.tsx
@@ -25,7 +25,7 @@ interface UserActionTitleProps {
createdAt: string;
id: string;
isLoading: boolean;
- labelAction: string;
+ labelEditAction: string;
labelTitle: string;
userName: string;
onEdit: (id: string) => void;
@@ -35,7 +35,7 @@ export const UserActionTitle = ({
createdAt,
id,
isLoading,
- labelAction,
+ labelEditAction,
labelTitle,
userName,
onEdit,
@@ -43,8 +43,8 @@ export const UserActionTitle = ({
const propertyActions = useMemo(() => {
return [
{
- iconType: 'documentEdit',
- label: labelAction,
+ iconType: 'pencil',
+ label: labelEditAction,
onClick: () => onEdit(id),
},
];
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/anomaly_threshold_slider/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/anomaly_threshold_slider/index.tsx
index 18970ff935b8d..1e18023e0c326 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/anomaly_threshold_slider/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/anomaly_threshold_slider/index.tsx
@@ -10,12 +10,16 @@ import { EuiFlexGrid, EuiFlexItem, EuiRange, EuiFormRow } from '@elastic/eui';
import { FieldHook } from '../../../../../shared_imports';
interface AnomalyThresholdSliderProps {
+ describedByIds: string[];
field: FieldHook;
}
type Event = React.ChangeEvent;
type EventArg = Event | React.MouseEvent;
-export const AnomalyThresholdSlider: React.FC = ({ field }) => {
+export const AnomalyThresholdSlider: React.FC = ({
+ describedByIds = [],
+ field,
+}) => {
const threshold = field.value as number;
const onThresholdChange = useCallback(
(event: EventArg) => {
@@ -26,7 +30,12 @@ export const AnomalyThresholdSlider: React.FC = ({
);
return (
-
+
= ({ field }) => {
+export const MlJobSelect: React.FC = ({ describedByIds = [], field }) => {
const jobId = field.value as string;
const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field);
const [isLoading, siemJobs] = useSiemJobs(false);
@@ -41,7 +42,14 @@ export const MlJobSelect: React.FC = ({ field }) => {
}));
return (
-
+
(
+
+ {hasValidLicense ? (
+ i18n.ML_TYPE_DESCRIPTION
+ ) : (
+
+
+
+ ),
+ }}
+ />
+ )}
+
+);
+
interface SelectRuleTypeProps {
+ describedByIds?: string[];
field: FieldHook;
- isReadOnly: boolean;
+ hasValidLicense?: boolean;
+ isReadOnly?: boolean;
}
-export const SelectRuleType: React.FC = ({ field, isReadOnly = false }) => {
+export const SelectRuleType: React.FC = ({
+ describedByIds = [],
+ field,
+ hasValidLicense = false,
+ isReadOnly = false,
+}) => {
const ruleType = field.value as RuleType;
const setType = useCallback(
(type: RuleType) => {
@@ -27,10 +66,15 @@ export const SelectRuleType: React.FC = ({ field, isReadOnl
);
const setMl = useCallback(() => setType('machine_learning'), [setType]);
const setQuery = useCallback(() => setType('query'), [setType]);
- const license = true; // TODO
+ const mlCardDisabled = isReadOnly || !hasValidLicense;
return (
-
+
= ({ field, isReadOnl
}
icon={}
+ isDisabled={mlCardDisabled}
selectable={{
- isDisabled: isReadOnly,
+ isDisabled: mlCardDisabled,
onClick: setMl,
isSelected: isMlRule(ruleType),
}}
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/select_rule_type/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/select_rule_type/translations.ts
index 32b860e8f703e..4dc0a89af4a49 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/select_rule_type/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/select_rule_type/translations.ts
@@ -33,10 +33,3 @@ export const ML_TYPE_DESCRIPTION = i18n.translate(
defaultMessage: 'Select ML job to detect anomalous activity.',
}
);
-
-export const ML_TYPE_DISABLED_DESCRIPTION = i18n.translate(
- 'xpack.siem.detectionEngine.createRule.stepDefineRule.ruleTypeField.mlTypeDisabledDescription',
- {
- defaultMessage: 'Access to ML requires a Platinum subscription.',
- }
-);
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx
index d3ef185f3786b..cf8cc4b87b388 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx
@@ -13,13 +13,14 @@ import {
EuiButton,
} from '@elastic/eui';
import { isEmpty } from 'lodash/fp';
-import React, { FC, memo, useCallback, useState, useEffect } from 'react';
+import React, { FC, memo, useCallback, useState, useEffect, useContext } from 'react';
import styled from 'styled-components';
import deepEqual from 'fast-deep-equal';
import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/public';
import { useFetchIndexPatterns } from '../../../../../containers/detection_engine/rules';
import { DEFAULT_INDEX_KEY } from '../../../../../../common/constants';
+import { MlCapabilitiesContext } from '../../../../../components/ml/permissions/ml_capabilities_provider';
import { useUiSetting$ } from '../../../../../lib/kibana';
import { setFieldValue, isMlRule } from '../../helpers';
import * as RuleI18n from '../../translations';
@@ -103,6 +104,7 @@ const StepDefineRuleComponent: FC = ({
setForm,
setStepData,
}) => {
+ const mlCapabilities = useContext(MlCapabilitiesContext);
const [openTimelineSearch, setOpenTimelineSearch] = useState(false);
const [localUseIndicesConfig, setLocalUseIndicesConfig] = useState(false);
const [localIsMlRule, setIsMlRule] = useState(false);
@@ -182,6 +184,8 @@ const StepDefineRuleComponent: FC = ({
path="ruleType"
component={SelectRuleType}
componentProps={{
+ describedByIds: ['detectionEngineStepDefineRuleType'],
+ hasValidLicense: mlCapabilities.isPlatinumOrTrialLicense,
isReadOnly: isUpdateView,
}}
/>
@@ -220,7 +224,6 @@ const StepDefineRuleComponent: FC = ({
component={QueryBarDefineRule}
componentProps={{
browserFields,
- loading: indexPatternLoadingQueryBar,
idAria: 'detectionEngineStepDefineRuleQueryBar',
indexPattern: indexPatternQueryBar,
isDisabled: isLoading,
@@ -234,8 +237,20 @@ const StepDefineRuleComponent: FC = ({
<>
-
-
+
+
>
diff --git a/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.ts b/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.ts
index 7f1919fbea684..f30629789b4ed 100644
--- a/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.ts
+++ b/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.ts
@@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import Boom from 'boom';
import { i18n } from '@kbn/i18n';
import { AlertType } from '../../common';
import { AlertNavigationHandler } from './types';
@@ -36,7 +35,7 @@ export class AlertNavigationRegistry {
public registerDefault(consumer: string, handler: AlertNavigationHandler) {
if (this.hasDefaultHandler(consumer)) {
- throw Boom.badRequest(
+ throw new Error(
i18n.translate('xpack.alerting.alertNavigationRegistry.register.duplicateDefaultError', {
defaultMessage: 'Default Navigation within "{consumer}" is already registered.',
values: {
@@ -54,7 +53,7 @@ export class AlertNavigationRegistry {
public register(consumer: string, alertType: AlertType, handler: AlertNavigationHandler) {
if (this.hasTypedHandler(consumer, alertType)) {
- throw Boom.badRequest(
+ throw new Error(
i18n.translate('xpack.alerting.alertNavigationRegistry.register.duplicateNavigationError', {
defaultMessage:
'Navigation for Alert type "{alertType}" within "{consumer}" is already registered.',
@@ -78,7 +77,7 @@ export class AlertNavigationRegistry {
return (consumerHandlers.get(alertType.id) ?? consumerHandlers.get(DEFAULT_HANDLER))!;
}
- throw Boom.badRequest(
+ throw new Error(
i18n.translate('xpack.alerting.alertNavigationRegistry.get.missingNavigationError', {
defaultMessage:
'Navigation for Alert type "{alertType}" within "{consumer}" is not registered.',
diff --git a/x-pack/plugins/apm/common/custom_link_filter_options.ts b/x-pack/plugins/apm/common/custom_link_filter_options.ts
new file mode 100644
index 0000000000000..32b19ad60a646
--- /dev/null
+++ b/x-pack/plugins/apm/common/custom_link_filter_options.ts
@@ -0,0 +1,28 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import * as t from 'io-ts';
+import {
+ SERVICE_NAME,
+ SERVICE_ENVIRONMENT,
+ TRANSACTION_TYPE,
+ TRANSACTION_NAME
+} from './elasticsearch_fieldnames';
+
+export const FilterOptionsRt = t.partial({
+ [SERVICE_NAME]: t.union([t.string, t.array(t.string)]),
+ [SERVICE_ENVIRONMENT]: t.union([t.string, t.array(t.string)]),
+ [TRANSACTION_NAME]: t.union([t.string, t.array(t.string)]),
+ [TRANSACTION_TYPE]: t.union([t.string, t.array(t.string)])
+});
+
+export type FilterOptions = t.TypeOf;
+
+export const FILTER_OPTIONS: ReadonlyArray = [
+ SERVICE_NAME,
+ SERVICE_ENVIRONMENT,
+ TRANSACTION_TYPE,
+ TRANSACTION_NAME
+] as const;
diff --git a/x-pack/plugins/apm/server/lib/helpers/create_or_update_index.ts b/x-pack/plugins/apm/server/lib/helpers/create_or_update_index.ts
index 0a0da332e73ae..cc01c990bf985 100644
--- a/x-pack/plugins/apm/server/lib/helpers/create_or_update_index.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/create_or_update_index.ts
@@ -18,6 +18,7 @@ export type Mappings =
scaling_factor?: number;
ignore_malformed?: boolean;
coerce?: boolean;
+ fields?: Record;
};
export async function createOrUpdateIndex({
diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/__snapshots__/get_transaction.test.ts.snap b/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/__snapshots__/get_transaction.test.ts.snap
new file mode 100644
index 0000000000000..16a270fd6d25b
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/__snapshots__/get_transaction.test.ts.snap
@@ -0,0 +1,73 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`custom link get transaction fetches with all filter 1`] = `
+Object {
+ "body": Object {
+ "query": Object {
+ "bool": Object {
+ "filter": Array [
+ Object {
+ "term": Object {
+ "service.name": "foo",
+ },
+ },
+ Object {
+ "term": Object {
+ "service.environment": "bar",
+ },
+ },
+ Object {
+ "term": Object {
+ "transaction.type": "qux",
+ },
+ },
+ Object {
+ "term": Object {
+ "transaction.name": "baz",
+ },
+ },
+ ],
+ },
+ },
+ },
+ "index": "myIndex",
+ "size": 1,
+ "terminateAfter": 1,
+}
+`;
+
+exports[`custom link get transaction fetches without filter 1`] = `
+Object {
+ "body": Object {
+ "query": Object {
+ "bool": Object {
+ "filter": Array [],
+ },
+ },
+ },
+ "index": "myIndex",
+ "size": 1,
+ "terminateAfter": 1,
+}
+`;
+
+exports[`custom link get transaction removes not listed filters from query 1`] = `
+Object {
+ "body": Object {
+ "query": Object {
+ "bool": Object {
+ "filter": Array [
+ Object {
+ "term": Object {
+ "service.name": "foo",
+ },
+ },
+ ],
+ },
+ },
+ },
+ "index": "myIndex",
+ "size": 1,
+ "terminateAfter": 1,
+}
+`;
diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/__snapshots__/list_custom_links.test.ts.snap b/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/__snapshots__/list_custom_links.test.ts.snap
index b3819ace40d6c..bb8f6dcb22902 100644
--- a/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/__snapshots__/list_custom_links.test.ts.snap
+++ b/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/__snapshots__/list_custom_links.test.ts.snap
@@ -8,6 +8,13 @@ Object {
"filter": Array [],
},
},
+ "sort": Array [
+ Object {
+ "label.keyword": Object {
+ "order": "asc",
+ },
+ },
+ ],
},
"index": "myIndex",
"size": 500,
@@ -69,6 +76,13 @@ Object {
],
},
},
+ "sort": Array [
+ Object {
+ "label.keyword": Object {
+ "order": "asc",
+ },
+ },
+ ],
},
"index": "myIndex",
"size": 500,
diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/get_transaction.test.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/get_transaction.test.ts
new file mode 100644
index 0000000000000..4fc22298a476c
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/settings/custom_link/__test__/get_transaction.test.ts
@@ -0,0 +1,56 @@
+/*
+ * 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 {
+ inspectSearchParams,
+ SearchParamsMock
+} from '../../../../../../../legacy/plugins/apm/public/utils/testHelpers';
+import { getTransaction } from '../get_transaction';
+import { Setup } from '../../../helpers/setup_request';
+import {
+ SERVICE_NAME,
+ TRANSACTION_TYPE,
+ SERVICE_ENVIRONMENT,
+ TRANSACTION_NAME
+} from '../../../../../common/elasticsearch_fieldnames';
+
+describe('custom link get transaction', () => {
+ let mock: SearchParamsMock;
+ it('removes not listed filters from query', async () => {
+ mock = await inspectSearchParams(setup =>
+ getTransaction({
+ setup: (setup as unknown) as Setup,
+ // @ts-ignore ignoring the _debug is not part of filter options
+ filters: { _debug: true, [SERVICE_NAME]: 'foo' }
+ })
+ );
+
+ expect(mock.params).toMatchSnapshot();
+ });
+ it('fetches without filter', async () => {
+ mock = await inspectSearchParams(setup =>
+ getTransaction({
+ setup: (setup as unknown) as Setup
+ })
+ );
+
+ expect(mock.params).toMatchSnapshot();
+ });
+ it('fetches with all filter', async () => {
+ mock = await inspectSearchParams(setup =>
+ getTransaction({
+ setup: (setup as unknown) as Setup,
+ filters: {
+ [SERVICE_NAME]: 'foo',
+ [SERVICE_ENVIRONMENT]: 'bar',
+ [TRANSACTION_NAME]: 'baz',
+ [TRANSACTION_TYPE]: 'qux'
+ }
+ })
+ );
+
+ expect(mock.params).toMatchSnapshot();
+ });
+});
diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/create_custom_link_index.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/create_custom_link_index.ts
index cdb3cff616030..1583e15bdecd5 100644
--- a/x-pack/plugins/apm/server/lib/settings/custom_link/create_custom_link_index.ts
+++ b/x-pack/plugins/apm/server/lib/settings/custom_link/create_custom_link_index.ts
@@ -31,7 +31,13 @@ const mappings: Mappings = {
type: 'date'
},
label: {
- type: 'text'
+ type: 'text',
+ fields: {
+ // Adding keyword type to be able to sort by label alphabetically
+ keyword: {
+ type: 'keyword'
+ }
+ }
},
url: {
type: 'keyword'
diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.ts
index 809fe2050a072..5dce371e4f307 100644
--- a/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.ts
+++ b/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.ts
@@ -5,7 +5,7 @@
*/
import { pick } from 'lodash';
-import { filterOptions } from '../../../routes/settings/custom_link';
+import { FILTER_OPTIONS } from '../../../../common/custom_link_filter_options';
import { APMIndexDocumentParams } from '../../helpers/es_client';
import { Setup } from '../../helpers/setup_request';
import { CustomLink } from './custom_link_types';
@@ -28,7 +28,7 @@ export async function createOrUpdateCustomLink({
'@timestamp': Date.now(),
label: customLink.label,
url: customLink.url,
- ...pick(customLink, filterOptions)
+ ...pick(customLink, FILTER_OPTIONS)
}
};
diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/custom_link_types.d.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/custom_link_types.d.ts
index 60b97712713a9..edb9eb35b9029 100644
--- a/x-pack/plugins/apm/server/lib/settings/custom_link/custom_link_types.d.ts
+++ b/x-pack/plugins/apm/server/lib/settings/custom_link/custom_link_types.d.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import * as t from 'io-ts';
-import { FilterOptions } from '../../../routes/settings/custom_link';
+import { FilterOptions } from '../../../../common/custom_link_filter_options';
export type CustomLink = {
id?: string;
diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/get_transaction.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/get_transaction.ts
new file mode 100644
index 0000000000000..396a7cb29f014
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/settings/custom_link/get_transaction.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 { pick } from 'lodash';
+import {
+ FilterOptions,
+ FILTER_OPTIONS
+} from '../../../../common/custom_link_filter_options';
+import { Transaction } from '../../../../typings/es_schemas/ui/transaction';
+import { Setup } from '../../helpers/setup_request';
+
+export async function getTransaction({
+ setup,
+ filters = {}
+}: {
+ setup: Setup;
+ filters?: FilterOptions;
+}) {
+ const { client, indices } = setup;
+
+ const esFilters = Object.entries(pick(filters, FILTER_OPTIONS)).map(
+ ([key, value]) => {
+ return { term: { [key]: value } };
+ }
+ );
+
+ const params = {
+ terminateAfter: 1,
+ index: indices['apm_oss.transactionIndices'],
+ size: 1,
+ body: { query: { bool: { filter: esFilters } } }
+ };
+ const resp = await client.search(params);
+ return resp.hits.hits[0]?._source;
+}
diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts
index e6052da73b0db..67956ef3a60ce 100644
--- a/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts
+++ b/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts
@@ -4,9 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { FilterOptions } from '../../../../common/custom_link_filter_options';
import { Setup } from '../../helpers/setup_request';
import { CustomLink } from './custom_link_types';
-import { FilterOptions } from '../../../routes/settings/custom_link';
export async function listCustomLinks({
setup,
@@ -37,7 +37,14 @@ export async function listCustomLinks({
bool: {
filter: esFilters
}
- }
+ },
+ sort: [
+ {
+ 'label.keyword': {
+ order: 'asc'
+ }
+ }
+ ]
}
};
const resp = await internalClient.search(params);
diff --git a/x-pack/plugins/apm/server/routes/create_apm_api.ts b/x-pack/plugins/apm/server/routes/create_apm_api.ts
index 34f0536a90b4d..50a794067bfad 100644
--- a/x-pack/plugins/apm/server/routes/create_apm_api.ts
+++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts
@@ -63,7 +63,8 @@ import {
createCustomLinkRoute,
updateCustomLinkRoute,
deleteCustomLinkRoute,
- listCustomLinksRoute
+ listCustomLinksRoute,
+ customLinkTransactionRoute
} from './settings/custom_link';
const createApmApi = () => {
@@ -138,7 +139,8 @@ const createApmApi = () => {
.add(createCustomLinkRoute)
.add(updateCustomLinkRoute)
.add(deleteCustomLinkRoute)
- .add(listCustomLinksRoute);
+ .add(listCustomLinksRoute)
+ .add(customLinkTransactionRoute);
return api;
};
diff --git a/x-pack/plugins/apm/server/routes/settings/custom_link.ts b/x-pack/plugins/apm/server/routes/settings/custom_link.ts
index 5988d7f85b186..e11c1df9d4b16 100644
--- a/x-pack/plugins/apm/server/routes/settings/custom_link.ts
+++ b/x-pack/plugins/apm/server/routes/settings/custom_link.ts
@@ -4,33 +4,25 @@
* you may not use this file except in compliance with the Elastic License.
*/
import * as t from 'io-ts';
-import {
- SERVICE_NAME,
- SERVICE_ENVIRONMENT,
- TRANSACTION_NAME,
- TRANSACTION_TYPE
-} from '../../../common/elasticsearch_fieldnames';
+import { FilterOptionsRt } from '../../../common/custom_link_filter_options';
import { createRoute } from '../create_route';
import { setupRequest } from '../../lib/helpers/setup_request';
import { createOrUpdateCustomLink } from '../../lib/settings/custom_link/create_or_update_custom_link';
import { deleteCustomLink } from '../../lib/settings/custom_link/delete_custom_link';
import { listCustomLinks } from '../../lib/settings/custom_link/list_custom_links';
+import { getTransaction } from '../../lib/settings/custom_link/get_transaction';
-const FilterOptionsRt = t.partial({
- [SERVICE_NAME]: t.string,
- [SERVICE_ENVIRONMENT]: t.string,
- [TRANSACTION_NAME]: t.string,
- [TRANSACTION_TYPE]: t.string
-});
-
-export type FilterOptions = t.TypeOf;
-
-export const filterOptions: Array = [
- SERVICE_NAME,
- SERVICE_ENVIRONMENT,
- TRANSACTION_TYPE,
- TRANSACTION_NAME
-];
+export const customLinkTransactionRoute = createRoute(core => ({
+ path: '/api/apm/settings/custom_links/transaction',
+ params: {
+ query: FilterOptionsRt
+ },
+ handler: async ({ context, request }) => {
+ const setup = await setupRequest(context, request);
+ const { params } = context;
+ return await getTransaction({ setup, filters: params.query });
+ }
+}));
export const listCustomLinksRoute = createRoute(core => ({
path: '/api/apm/settings/custom_links',
diff --git a/x-pack/plugins/apm/typings/es_schemas/raw/fields/service.ts b/x-pack/plugins/apm/typings/es_schemas/raw/fields/service.ts
index 09020ce61c6e4..3ef852ebf6dd6 100644
--- a/x-pack/plugins/apm/typings/es_schemas/raw/fields/service.ts
+++ b/x-pack/plugins/apm/typings/es_schemas/raw/fields/service.ts
@@ -6,6 +6,7 @@
export interface Service {
name: string;
+ environment?: string;
framework?: {
name: string;
version: string;
diff --git a/x-pack/plugins/infra/kibana.json b/x-pack/plugins/infra/kibana.json
index bb40d65d311e8..b8796ad7a358e 100644
--- a/x-pack/plugins/infra/kibana.json
+++ b/x-pack/plugins/infra/kibana.json
@@ -11,7 +11,8 @@
"data",
"dataEnhanced",
"metrics",
- "alerting"
+ "alerting",
+ "triggers_actions_ui"
],
"server": true,
"ui": true,
diff --git a/x-pack/plugins/infra/public/apps/start_app.tsx b/x-pack/plugins/infra/public/apps/start_app.tsx
index a797e4c9d4ba7..a986ee6ece352 100644
--- a/x-pack/plugins/infra/public/apps/start_app.tsx
+++ b/x-pack/plugins/infra/public/apps/start_app.tsx
@@ -15,7 +15,8 @@ import { CoreStart, AppMountParameters } from 'kibana/public';
// TODO use theme provided from parentApp when kibana supports it
import { EuiErrorBoundary } from '@elastic/eui';
-import { EuiThemeProvider } from '../../../observability/public';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { EuiThemeProvider } from '../../../observability/public/typings/eui_styled_components';
import { InfraFrontendLibs } from '../lib/lib';
import { createStore } from '../store';
import { ApolloClientContext } from '../utils/apollo_context';
@@ -26,6 +27,8 @@ import {
KibanaContextProvider,
} from '../../../../../src/plugins/kibana_react/public';
import { AppRouter } from '../routers';
+import { TriggersAndActionsUIPublicPluginSetup } from '../../../triggers_actions_ui/public';
+import { TriggersActionsProvider } from '../utils/triggers_actions_context';
import '../index.scss';
export const CONTAINER_CLASSNAME = 'infra-container-element';
@@ -35,7 +38,8 @@ export async function startApp(
core: CoreStart,
plugins: object,
params: AppMountParameters,
- Router: AppRouter
+ Router: AppRouter,
+ triggersActionsUI: TriggersAndActionsUIPublicPluginSetup
) {
const { element, appBasePath } = params;
const history = createBrowserHistory({ basename: appBasePath });
@@ -51,19 +55,21 @@ export async function startApp(
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/alert_dropdown.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/alert_dropdown.tsx
new file mode 100644
index 0000000000000..0a464d91fbe06
--- /dev/null
+++ b/x-pack/plugins/infra/public/components/alerting/metrics/alert_dropdown.tsx
@@ -0,0 +1,62 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useState, useCallback, useMemo } from 'react';
+import { EuiPopover, EuiButtonEmpty, EuiContextMenuItem, EuiContextMenuPanel } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { AlertFlyout } from './alert_flyout';
+import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
+
+export const AlertDropdown = () => {
+ const [popoverOpen, setPopoverOpen] = useState(false);
+ const [flyoutVisible, setFlyoutVisible] = useState(false);
+ const kibana = useKibana();
+
+ const closePopover = useCallback(() => {
+ setPopoverOpen(false);
+ }, [setPopoverOpen]);
+
+ const openPopover = useCallback(() => {
+ setPopoverOpen(true);
+ }, [setPopoverOpen]);
+
+ const menuItems = useMemo(() => {
+ return [
+ setFlyoutVisible(true)}>
+
+ ,
+
+
+ ,
+ ];
+ }, [kibana.services]);
+
+ return (
+ <>
+
+
+
+ }
+ isOpen={popoverOpen}
+ closePopover={closePopover}
+ >
+
+
+
+ >
+ );
+};
diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx
new file mode 100644
index 0000000000000..a00d63af8aac2
--- /dev/null
+++ b/x-pack/plugins/infra/public/components/alerting/metrics/alert_flyout.tsx
@@ -0,0 +1,53 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useContext } from 'react';
+import { AlertsContextProvider, AlertAdd } from '../../../../../triggers_actions_ui/public';
+import { TriggerActionsContext } from '../../../utils/triggers_actions_context';
+import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { METRIC_THRESHOLD_ALERT_TYPE_ID } from '../../../../server/lib/alerting/metric_threshold/types';
+import { MetricsExplorerOptions } from '../../../containers/metrics_explorer/use_metrics_explorer_options';
+import { MetricsExplorerSeries } from '../../../../common/http_api/metrics_explorer';
+
+interface Props {
+ visible?: boolean;
+ options?: Partial;
+ series?: MetricsExplorerSeries;
+ setVisible: React.Dispatch>;
+}
+
+export const AlertFlyout = (props: Props) => {
+ const { triggersActionsUI } = useContext(TriggerActionsContext);
+ const { services } = useKibana();
+
+ return (
+ <>
+ {triggersActionsUI && (
+
+
+
+ )}
+ >
+ );
+};
diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx
new file mode 100644
index 0000000000000..ea8dd1484a670
--- /dev/null
+++ b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx
@@ -0,0 +1,473 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useCallback, useMemo, useEffect, useState } from 'react';
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiButtonIcon,
+ EuiSpacer,
+ EuiText,
+ EuiFormRow,
+ EuiButtonEmpty,
+} from '@elastic/eui';
+import { IFieldType } from 'src/plugins/data/public';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { i18n } from '@kbn/i18n';
+import { euiStyled } from '../../../../../observability/public';
+import {
+ WhenExpression,
+ OfExpression,
+ ThresholdExpression,
+ ForLastExpression,
+ // eslint-disable-next-line @kbn/eslint/no-restricted-paths
+} from '../../../../../triggers_actions_ui/public/common';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { IErrorObject } from '../../../../../triggers_actions_ui/public/types';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { AlertsContextValue } from '../../../../../triggers_actions_ui/public/application/context/alerts_context';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { MetricsExplorerOptions } from '../../../containers/metrics_explorer/use_metrics_explorer_options';
+import { MetricsExplorerKueryBar } from '../../metrics_explorer/kuery_bar';
+import { MetricsExplorerSeries } from '../../../../common/http_api/metrics_explorer';
+import { useSource } from '../../../containers/source';
+import { MetricsExplorerGroupBy } from '../../metrics_explorer/group_by';
+
+export interface MetricExpression {
+ aggType?: string;
+ metric?: string;
+ comparator?: Comparator;
+ threshold?: number[];
+ timeSize?: number;
+ timeUnit?: TimeUnit;
+ indexPattern?: string;
+}
+
+interface AlertContextMeta {
+ currentOptions?: Partial;
+ series?: MetricsExplorerSeries;
+}
+
+interface Props {
+ errors: IErrorObject[];
+ alertParams: {
+ criteria: MetricExpression[];
+ groupBy?: string;
+ filterQuery?: string;
+ };
+ alertsContext: AlertsContextValue;
+ setAlertParams(key: string, value: any): void;
+ setAlertProperty(key: string, value: any): void;
+}
+
+type Comparator = '>' | '>=' | 'between' | '<' | '<=';
+type TimeUnit = 's' | 'm' | 'h' | 'd';
+
+export const Expressions: React.FC = props => {
+ const { setAlertParams, alertParams, errors, alertsContext } = props;
+ const { source, createDerivedIndexPattern } = useSource({ sourceId: 'default' });
+ const [timeSize, setTimeSize] = useState(1);
+ const [timeUnit, setTimeUnit] = useState('s');
+
+ const derivedIndexPattern = useMemo(() => createDerivedIndexPattern('metrics'), [
+ createDerivedIndexPattern,
+ ]);
+
+ const options = useMemo(() => {
+ if (alertsContext.metadata?.currentOptions?.metrics) {
+ return alertsContext.metadata.currentOptions as MetricsExplorerOptions;
+ } else {
+ return {
+ metrics: [],
+ aggregation: 'avg',
+ };
+ }
+ }, [alertsContext.metadata]);
+
+ const defaultExpression = useMemo(
+ () => ({
+ aggType: AGGREGATION_TYPES.MAX,
+ comparator: '>',
+ threshold: [],
+ timeSize: 1,
+ timeUnit: 's',
+ indexPattern: source?.configuration.metricAlias,
+ }),
+ [source]
+ );
+
+ const updateParams = useCallback(
+ (id, e: MetricExpression) => {
+ const exp = alertParams.criteria ? alertParams.criteria.slice() : [];
+ exp[id] = { ...exp[id], ...e };
+ setAlertParams('criteria', exp);
+ },
+ [setAlertParams, alertParams.criteria]
+ );
+
+ const addExpression = useCallback(() => {
+ const exp = alertParams.criteria.slice();
+ exp.push(defaultExpression);
+ setAlertParams('criteria', exp);
+ }, [setAlertParams, alertParams.criteria, defaultExpression]);
+
+ const removeExpression = useCallback(
+ (id: number) => {
+ const exp = alertParams.criteria.slice();
+ if (exp.length > 1) {
+ exp.splice(id, 1);
+ setAlertParams('criteria', exp);
+ }
+ },
+ [setAlertParams, alertParams.criteria]
+ );
+
+ const onFilterQuerySubmit = useCallback(
+ (filter: any) => {
+ setAlertParams('filterQuery', filter);
+ },
+ [setAlertParams]
+ );
+
+ const onGroupByChange = useCallback(
+ (group: string | null) => {
+ setAlertParams('groupBy', group || undefined);
+ },
+ [setAlertParams]
+ );
+
+ const emptyError = useMemo(() => {
+ return {
+ aggField: [],
+ timeSizeUnit: [],
+ timeWindowSize: [],
+ };
+ }, []);
+
+ const updateTimeSize = useCallback(
+ (ts: number | undefined) => {
+ const criteria = alertParams.criteria.map(c => ({
+ ...c,
+ timeSize: ts,
+ }));
+ setTimeSize(ts || undefined);
+ setAlertParams('criteria', criteria);
+ },
+ [alertParams.criteria, setAlertParams]
+ );
+
+ const updateTimeUnit = useCallback(
+ (tu: string) => {
+ const criteria = alertParams.criteria.map(c => ({
+ ...c,
+ timeUnit: tu,
+ }));
+ setTimeUnit(tu as TimeUnit);
+ setAlertParams('criteria', criteria);
+ },
+ [alertParams.criteria, setAlertParams]
+ );
+
+ useEffect(() => {
+ const md = alertsContext.metadata;
+ if (md) {
+ if (md.currentOptions?.metrics) {
+ setAlertParams(
+ 'criteria',
+ md.currentOptions.metrics.map(metric => ({
+ metric: metric.field,
+ comparator: '>',
+ threshold: [],
+ timeSize,
+ timeUnit,
+ indexPattern: source?.configuration.metricAlias,
+ aggType: metric.aggregation,
+ }))
+ );
+ } else {
+ setAlertParams('criteria', [defaultExpression]);
+ }
+
+ if (md.currentOptions) {
+ if (md.currentOptions.filterQuery) {
+ setAlertParams('filterQuery', md.currentOptions.filterQuery);
+ } else if (md.currentOptions.groupBy && md.series) {
+ const filter = `${md.currentOptions.groupBy}: "${md.series.id}"`;
+ setAlertParams('filterQuery', filter);
+ }
+
+ setAlertParams('groupBy', md.currentOptions.groupBy);
+ }
+ }
+ }, [alertsContext.metadata, defaultExpression, source]); // eslint-disable-line react-hooks/exhaustive-deps
+
+ return (
+ <>
+
+
+
+
+
+
+
+ {alertParams.criteria &&
+ alertParams.criteria.map((e, idx) => {
+ return (
+ 1}
+ fields={derivedIndexPattern.fields}
+ remove={removeExpression}
+ addExpression={addExpression}
+ key={idx} // idx's don't usually make good key's but here the index has semantic meaning
+ expressionId={idx}
+ setAlertParams={updateParams}
+ errors={errors[idx] || emptyError}
+ expression={e || {}}
+ />
+ );
+ })}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {alertsContext.metadata && (
+
+
+
+ )}
+ >
+ );
+};
+
+interface ExpressionRowProps {
+ fields: IFieldType[];
+ expressionId: number;
+ expression: MetricExpression;
+ errors: IErrorObject;
+ canDelete: boolean;
+ addExpression(): void;
+ remove(id: number): void;
+ setAlertParams(id: number, params: MetricExpression): void;
+}
+
+const StyledExpressionRow = euiStyled(EuiFlexGroup)`
+ display: flex;
+ flex-wrap: wrap;
+ margin: 0 -${props => props.theme.eui.euiSizeXS};
+`;
+
+const StyledExpression = euiStyled.div`
+ padding: 0 ${props => props.theme.eui.euiSizeXS};
+`;
+
+export const ExpressionRow: React.FC = props => {
+ const { setAlertParams, expression, errors, expressionId, remove, fields, canDelete } = props;
+ const { aggType = AGGREGATION_TYPES.MAX, metric, comparator = '>', threshold = [] } = expression;
+
+ const updateAggType = useCallback(
+ (at: string) => {
+ setAlertParams(expressionId, { ...expression, aggType: at });
+ },
+ [expressionId, expression, setAlertParams]
+ );
+
+ const updateMetric = useCallback(
+ (m?: string) => {
+ setAlertParams(expressionId, { ...expression, metric: m });
+ },
+ [expressionId, expression, setAlertParams]
+ );
+
+ const updateComparator = useCallback(
+ (c?: string) => {
+ setAlertParams(expressionId, { ...expression, comparator: c as Comparator });
+ },
+ [expressionId, expression, setAlertParams]
+ );
+
+ const updateThreshold = useCallback(
+ t => {
+ setAlertParams(expressionId, { ...expression, threshold: t });
+ },
+ [expressionId, expression, setAlertParams]
+ );
+
+ return (
+ <>
+
+
+
+
+
+
+ {aggType !== 'count' && (
+
+ ({
+ normalizedType: f.type,
+ name: f.name,
+ }))}
+ aggType={aggType}
+ errors={errors}
+ onChangeSelectedAggField={updateMetric}
+ />
+
+ )}
+
+ '}
+ threshold={threshold}
+ onChangeSelectedThresholdComparator={updateComparator}
+ onChangeSelectedThreshold={updateThreshold}
+ errors={errors}
+ />
+
+
+
+ {canDelete && (
+
+ remove(expressionId)}
+ />
+
+ )}
+
+
+ >
+ );
+};
+
+enum AGGREGATION_TYPES {
+ COUNT = 'count',
+ AVERAGE = 'avg',
+ SUM = 'sum',
+ MIN = 'min',
+ MAX = 'max',
+ RATE = 'rate',
+ CARDINALITY = 'cardinality',
+}
+
+export const aggregationType: { [key: string]: any } = {
+ avg: {
+ text: i18n.translate('xpack.infra.metrics.alertFlyout.aggregationText.avg', {
+ defaultMessage: 'Average',
+ }),
+ fieldRequired: true,
+ validNormalizedTypes: ['number'],
+ value: AGGREGATION_TYPES.AVERAGE,
+ },
+ max: {
+ text: i18n.translate('xpack.infra.metrics.alertFlyout.aggregationText.max', {
+ defaultMessage: 'Max',
+ }),
+ fieldRequired: true,
+ validNormalizedTypes: ['number', 'date'],
+ value: AGGREGATION_TYPES.MAX,
+ },
+ min: {
+ text: i18n.translate('xpack.infra.metrics.alertFlyout.aggregationText.min', {
+ defaultMessage: 'Min',
+ }),
+ fieldRequired: true,
+ validNormalizedTypes: ['number', 'date'],
+ value: AGGREGATION_TYPES.MIN,
+ },
+ cardinality: {
+ text: i18n.translate('xpack.infra.metrics.alertFlyout.aggregationText.cardinality', {
+ defaultMessage: 'Cardinality',
+ }),
+ fieldRequired: false,
+ value: AGGREGATION_TYPES.CARDINALITY,
+ validNormalizedTypes: ['number'],
+ },
+ rate: {
+ text: i18n.translate('xpack.infra.metrics.alertFlyout.aggregationText.rate', {
+ defaultMessage: 'Rate',
+ }),
+ fieldRequired: false,
+ value: AGGREGATION_TYPES.RATE,
+ validNormalizedTypes: ['number'],
+ },
+ count: {
+ text: i18n.translate('xpack.infra.metrics.alertFlyout.aggregationText.count', {
+ defaultMessage: 'Document count',
+ }),
+ fieldRequired: false,
+ value: AGGREGATION_TYPES.COUNT,
+ validNormalizedTypes: ['number'],
+ },
+};
diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/metric_threshold_alert_type.ts b/x-pack/plugins/infra/public/components/alerting/metrics/metric_threshold_alert_type.ts
new file mode 100644
index 0000000000000..d3b5aaa7c8796
--- /dev/null
+++ b/x-pack/plugins/infra/public/components/alerting/metrics/metric_threshold_alert_type.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { i18n } from '@kbn/i18n';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { AlertTypeModel } from '../../../../../triggers_actions_ui/public/types';
+import { Expressions } from './expression';
+import { validateMetricThreshold } from './validation';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { METRIC_THRESHOLD_ALERT_TYPE_ID } from '../../../../server/lib/alerting/metric_threshold/types';
+
+export function getAlertType(): AlertTypeModel {
+ return {
+ id: METRIC_THRESHOLD_ALERT_TYPE_ID,
+ name: i18n.translate('xpack.infra.metrics.alertFlyout.alertName', {
+ defaultMessage: 'Alert Trigger',
+ }),
+ iconClass: 'bell',
+ alertParamsExpression: Expressions,
+ validate: validateMetricThreshold,
+ };
+}
diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/validation.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/validation.tsx
new file mode 100644
index 0000000000000..0f5b07f8c0e13
--- /dev/null
+++ b/x-pack/plugins/infra/public/components/alerting/metrics/validation.tsx
@@ -0,0 +1,80 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+
+import { MetricExpression } from './expression';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { ValidationResult } from '../../../../../triggers_actions_ui/public/types';
+
+export function validateMetricThreshold({
+ criteria,
+}: {
+ criteria: MetricExpression[];
+}): ValidationResult {
+ const validationResult = { errors: {} };
+ const errors: {
+ [id: string]: {
+ aggField: string[];
+ timeSizeUnit: string[];
+ timeWindowSize: string[];
+ threshold0: string[];
+ threshold1: string[];
+ };
+ } = {};
+ validationResult.errors = errors;
+
+ if (!criteria || !criteria.length) {
+ return validationResult;
+ }
+
+ criteria.forEach((c, idx) => {
+ // Create an id for each criteria, so we can map errors to specific criteria.
+ const id = idx.toString();
+
+ errors[id] = errors[id] || {
+ aggField: [],
+ timeSizeUnit: [],
+ timeWindowSize: [],
+ threshold0: [],
+ threshold1: [],
+ };
+ if (!c.aggType) {
+ errors[id].aggField.push(
+ i18n.translate('xpack.infra.metrics.alertFlyout.error.aggregationRequired', {
+ defaultMessage: 'Aggreation is required.',
+ })
+ );
+ }
+
+ if (!c.threshold || !c.threshold.length) {
+ errors[id].threshold0.push(
+ i18n.translate('xpack.infra.metrics.alertFlyout.error.thresholdRequired', {
+ defaultMessage: 'Threshold is required.',
+ })
+ );
+ }
+
+ if (c.comparator === 'between' && (!c.threshold || c.threshold.length < 2)) {
+ errors[id].threshold1.push(
+ i18n.translate('xpack.infra.metrics.alertFlyout.error.thresholdRequired', {
+ defaultMessage: 'Threshold is required.',
+ })
+ );
+ }
+
+ if (!c.timeSize) {
+ errors[id].timeWindowSize.push(
+ i18n.translate('xpack.infra.metrics.alertFlyout.error.timeRequred', {
+ defaultMessage: 'Time size is Required.',
+ })
+ );
+ }
+ });
+
+ return validationResult;
+}
diff --git a/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.test.tsx b/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.test.tsx
index a23a2739a8e23..8ffef269a42ea 100644
--- a/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.test.tsx
+++ b/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.test.tsx
@@ -143,7 +143,7 @@ describe('MetricsExplorerChartContextMenu', () => {
uiCapabilities: customUICapabilities,
chartOptions,
});
- expect(component.find('button').length).toBe(0);
+ expect(component.find('button').length).toBe(1);
});
});
diff --git a/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx b/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx
index c50550f1de56f..75a04cbe9799e 100644
--- a/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx
+++ b/x-pack/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx
@@ -24,6 +24,7 @@ import { createTSVBLink } from './helpers/create_tsvb_link';
import { getNodeDetailUrl } from '../../pages/link_to/redirect_to_node_detail';
import { SourceConfiguration } from '../../utils/source_configuration';
import { InventoryItemType } from '../../../common/inventory_models/types';
+import { AlertFlyout } from '../alerting/metrics/alert_flyout';
import { useLinkProps } from '../../hooks/use_link_props';
export interface Props {
@@ -81,6 +82,7 @@ export const MetricsExplorerChartContextMenu: React.FC = ({
chartOptions,
}: Props) => {
const [isPopoverOpen, setPopoverState] = useState(false);
+ const [flyoutVisible, setFlyoutVisible] = useState(false);
const supportFiltering = options.groupBy != null && onFilter != null;
const handleFilter = useCallback(() => {
// onFilter needs check for Typescript even though it's
@@ -141,7 +143,20 @@ export const MetricsExplorerChartContextMenu: React.FC = ({
]
: [];
- const itemPanels = [...filterByItem, ...openInVisualize, ...viewNodeDetail];
+ const itemPanels = [
+ ...filterByItem,
+ ...openInVisualize,
+ ...viewNodeDetail,
+ {
+ name: i18n.translate('xpack.infra.metricsExplorer.alerts.createAlertButton', {
+ defaultMessage: 'Create alert',
+ }),
+ icon: 'bell',
+ onClick() {
+ setFlyoutVisible(true);
+ },
+ },
+ ];
// If there are no itemPanels then there is no reason to show the actions button.
if (itemPanels.length === 0) return null;
@@ -174,15 +189,24 @@ export const MetricsExplorerChartContextMenu: React.FC = ({
{actionLabel}
);
+
return (
-
-
-
+ <>
+
+
+
+
+ >
);
};
diff --git a/x-pack/plugins/infra/public/components/metrics_explorer/kuery_bar.tsx b/x-pack/plugins/infra/public/components/metrics_explorer/kuery_bar.tsx
index 0e18deedd404c..dcc160d05b6ad 100644
--- a/x-pack/plugins/infra/public/components/metrics_explorer/kuery_bar.tsx
+++ b/x-pack/plugins/infra/public/components/metrics_explorer/kuery_bar.tsx
@@ -16,6 +16,7 @@ interface Props {
derivedIndexPattern: IIndexPattern;
onSubmit: (query: string) => void;
value?: string | null;
+ placeholder?: string;
}
function validateQuery(query: string) {
@@ -27,7 +28,12 @@ function validateQuery(query: string) {
return true;
}
-export const MetricsExplorerKueryBar = ({ derivedIndexPattern, onSubmit, value }: Props) => {
+export const MetricsExplorerKueryBar = ({
+ derivedIndexPattern,
+ onSubmit,
+ value,
+ placeholder,
+}: Props) => {
const [draftQuery, setDraftQuery] = useState(value || '');
const [isValid, setValidation] = useState(true);
@@ -48,9 +54,12 @@ export const MetricsExplorerKueryBar = ({ derivedIndexPattern, onSubmit, value }
fields: derivedIndexPattern.fields.filter(field => isDisplayable(field)),
};
- const placeholder = i18n.translate('xpack.infra.homePage.toolbar.kqlSearchFieldPlaceholder', {
- defaultMessage: 'Search for infrastructure data… (e.g. host.name:host-1)',
- });
+ const defaultPlaceholder = i18n.translate(
+ 'xpack.infra.homePage.toolbar.kqlSearchFieldPlaceholder',
+ {
+ defaultMessage: 'Search for infrastructure data… (e.g. host.name:host-1)',
+ }
+ );
return (
@@ -62,7 +71,7 @@ export const MetricsExplorerKueryBar = ({ derivedIndexPattern, onSubmit, value }
loadSuggestions={loadSuggestions}
onChange={handleChange}
onSubmit={onSubmit}
- placeholder={placeholder}
+ placeholder={placeholder || defaultPlaceholder}
suggestions={suggestions}
value={draftQuery}
/>
diff --git a/x-pack/plugins/infra/public/components/metrics_explorer/toolbar.tsx b/x-pack/plugins/infra/public/components/metrics_explorer/toolbar.tsx
index 9e96819a36cac..0fbb0b6acad17 100644
--- a/x-pack/plugins/infra/public/components/metrics_explorer/toolbar.tsx
+++ b/x-pack/plugins/infra/public/components/metrics_explorer/toolbar.tsx
@@ -63,6 +63,7 @@ export const MetricsExplorerToolbar = ({
const isDefaultOptions = options.aggregation === 'avg' && options.metrics.length === 0;
const [timepickerQuickRanges] = useKibanaUiSetting('timepicker:quickRanges');
const commonlyUsedRanges = mapKibanaQuickRangesToDatePickerRanges(timepickerQuickRanges);
+
return (
diff --git a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx
index cc6a94c8a41a2..5f05cebd8f616 100644
--- a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx
+++ b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx
@@ -8,7 +8,7 @@ import { EuiPopoverProps, EuiCode } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-import React, { useMemo } from 'react';
+import React, { useMemo, useState } from 'react';
import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../lib/lib';
import { getNodeDetailUrl, getNodeLogsUrl } from '../../pages/link_to';
import { createUptimeLink } from './lib/create_uptime_link';
@@ -25,6 +25,7 @@ import {
SectionLink,
} from '../../../../observability/public';
import { useLinkProps } from '../../hooks/use_link_props';
+import { AlertFlyout } from '../alerting/metrics/alert_flyout';
interface Props {
options: InfraWaffleMapOptions;
@@ -46,6 +47,7 @@ export const NodeContextMenu: React.FC = ({
nodeType,
popoverPosition,
}) => {
+ const [flyoutVisible, setFlyoutVisible] = useState(false);
const inventoryModel = findInventoryModel(nodeType);
const nodeDetailFrom = currentTime - inventoryModel.metrics.defaultTimeRangeInSeconds * 1000;
const uiCapabilities = useKibana().services.application?.capabilities;
@@ -144,41 +146,48 @@ export const NodeContextMenu: React.FC = ({
};
return (
-
-
-
-
-
-
- {inventoryId.label && (
-
-
-
-
-
- )}
-
-
-
-
-
-
-
-
-
+ <>
+
+
+
+
+
+
+ {inventoryId.label && (
+
+
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ >
);
};
diff --git a/x-pack/plugins/infra/public/pages/infrastructure/index.tsx b/x-pack/plugins/infra/public/pages/infrastructure/index.tsx
index b4ff7aeff696c..730f67ab2bdca 100644
--- a/x-pack/plugins/infra/public/pages/infrastructure/index.tsx
+++ b/x-pack/plugins/infra/public/pages/infrastructure/index.tsx
@@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n';
import React from 'react';
import { Route, RouteComponentProps, Switch } from 'react-router-dom';
+import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
import { DocumentTitle } from '../../components/document_title';
import { HelpCenterContent } from '../../components/help_center_content';
import { RoutedTabs } from '../../components/navigation/routed_tabs';
@@ -24,9 +25,11 @@ import { MetricsSettingsPage } from './settings';
import { AppNavigation } from '../../components/navigation/app_navigation';
import { SourceLoadingPage } from '../../components/source_loading_page';
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
+import { AlertDropdown } from '../../components/alerting/metrics/alert_dropdown';
export const InfrastructurePage = ({ match }: RouteComponentProps) => {
const uiCapabilities = useKibana().services.application?.capabilities;
+
return (
@@ -59,31 +62,38 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => {
defaultMessage: 'Metrics',
})}
>
-
+
+
+
+
+
+
+
+
diff --git a/x-pack/plugins/infra/public/plugin.ts b/x-pack/plugins/infra/public/plugin.ts
index d576331662a08..15796f35856bd 100644
--- a/x-pack/plugins/infra/public/plugin.ts
+++ b/x-pack/plugins/infra/public/plugin.ts
@@ -29,6 +29,8 @@ import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/pl
import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public';
import { DataEnhancedSetup, DataEnhancedStart } from '../../data_enhanced/public';
import { LogsRouter, MetricsRouter } from './routers';
+import { TriggersAndActionsUIPublicPluginSetup } from '../../../plugins/triggers_actions_ui/public';
+import { getAlertType } from './components/alerting/metrics/metric_threshold_alert_type';
export type ClientSetup = void;
export type ClientStart = void;
@@ -38,6 +40,7 @@ export interface ClientPluginsSetup {
data: DataPublicPluginSetup;
usageCollection: UsageCollectionSetup;
dataEnhanced: DataEnhancedSetup;
+ triggers_actions_ui: TriggersAndActionsUIPublicPluginSetup;
}
export interface ClientPluginsStart {
@@ -58,6 +61,8 @@ export class Plugin
setup(core: CoreSetup, pluginsSetup: ClientPluginsSetup) {
registerFeatures(pluginsSetup.home);
+ pluginsSetup.triggers_actions_ui.alertTypeRegistry.register(getAlertType());
+
core.application.register({
id: 'logs',
title: i18n.translate('xpack.infra.logs.pluginTitle', {
@@ -76,7 +81,8 @@ export class Plugin
coreStart,
plugins,
params,
- LogsRouter
+ LogsRouter,
+ pluginsSetup.triggers_actions_ui
);
},
});
@@ -99,7 +105,8 @@ export class Plugin
coreStart,
plugins,
params,
- MetricsRouter
+ MetricsRouter,
+ pluginsSetup.triggers_actions_ui
);
},
});
diff --git a/x-pack/plugins/infra/public/utils/triggers_actions_context.tsx b/x-pack/plugins/infra/public/utils/triggers_actions_context.tsx
new file mode 100644
index 0000000000000..4ca4aedb4a08b
--- /dev/null
+++ b/x-pack/plugins/infra/public/utils/triggers_actions_context.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 * as React from 'react';
+import { TriggersAndActionsUIPublicPluginSetup } from '../../../triggers_actions_ui/public';
+
+interface ContextProps {
+ triggersActionsUI: TriggersAndActionsUIPublicPluginSetup | null;
+}
+
+export const TriggerActionsContext = React.createContext({
+ triggersActionsUI: null,
+});
+
+interface Props {
+ triggersActionsUI: TriggersAndActionsUIPublicPluginSetup;
+}
+
+export const TriggersActionsProvider: React.FC = props => {
+ return (
+
+ {props.children}
+
+ );
+};
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts
index a6b9b70feede2..feaa404ae960a 100644
--- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts
@@ -17,7 +17,7 @@ const alertInstances = new Map();
const services = {
callCluster(_: string, { body }: any) {
- const metric = body.query.bool.filter[1].exists.field;
+ const metric = body.query.bool.filter[1]?.exists.field;
if (body.aggs.groupings) {
if (body.aggs.groupings.composite.after) {
return mocks.compositeEndResponse;
@@ -228,6 +228,7 @@ describe('The metric threshold alert type', () => {
comparator,
threshold,
aggType: 'count',
+ metric: undefined,
},
],
},
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 8c509c017cf20..778889ba0c7a5 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
@@ -63,6 +63,12 @@ export const getElasticsearchMetricQuery = (
groupBy?: string,
filterQuery?: string
) => {
+ if (aggType === 'count' && metric) {
+ throw new Error('Cannot aggregate document count with a metric');
+ }
+ if (aggType !== 'count' && !metric) {
+ throw new Error('Can only aggregate without a metric if using the document count aggregator');
+ }
const interval = `${timeSize}${timeUnit}`;
const aggregations =
@@ -108,25 +114,32 @@ export const getElasticsearchMetricQuery = (
}
: baseAggs;
+ const rangeFilters = [
+ {
+ range: {
+ '@timestamp': {
+ gte: `now-${interval}`,
+ },
+ },
+ },
+ ];
+
+ const metricFieldFilters = metric
+ ? [
+ {
+ exists: {
+ field: metric,
+ },
+ },
+ ]
+ : [];
+
const parsedFilterQuery = getParsedFilterQuery(filterQuery);
return {
query: {
bool: {
- filter: [
- {
- range: {
- '@timestamp': {
- gte: `now-${interval}`,
- },
- },
- },
- {
- exists: {
- field: metric,
- },
- },
- ],
+ filter: [...rangeFilters, ...metricFieldFilters],
...parsedFilterQuery,
},
},
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 501d7549e1712..ed3a9b2f4fe36 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
@@ -17,22 +17,44 @@ export async function registerMetricThresholdAlertType(alertingPlugin: PluginSet
}
const alertUUID = uuid.v4();
+ const baseCriterion = {
+ threshold: schema.arrayOf(schema.number()),
+ comparator: schema.oneOf([
+ schema.literal('>'),
+ schema.literal('<'),
+ schema.literal('>='),
+ schema.literal('<='),
+ schema.literal('between'),
+ ]),
+ timeUnit: schema.string(),
+ timeSize: schema.number(),
+ indexPattern: schema.string(),
+ };
+
+ const nonCountCriterion = schema.object({
+ ...baseCriterion,
+ metric: schema.string(),
+ aggType: schema.oneOf([
+ schema.literal('avg'),
+ schema.literal('min'),
+ schema.literal('max'),
+ schema.literal('rate'),
+ schema.literal('cardinality'),
+ ]),
+ });
+
+ const countCriterion = schema.object({
+ ...baseCriterion,
+ aggType: schema.literal('count'),
+ metric: schema.never(),
+ });
+
alertingPlugin.registerType({
id: METRIC_THRESHOLD_ALERT_TYPE_ID,
name: 'Metric Alert - Threshold',
validate: {
params: schema.object({
- criteria: schema.arrayOf(
- schema.object({
- threshold: schema.arrayOf(schema.number()),
- comparator: schema.string(),
- aggType: schema.string(),
- metric: schema.string(),
- timeUnit: schema.string(),
- timeSize: schema.number(),
- indexPattern: schema.string(),
- })
- ),
+ criteria: schema.arrayOf(schema.oneOf([countCriterion, nonCountCriterion])),
groupBy: schema.maybe(schema.string()),
filterQuery: schema.maybe(schema.string()),
}),
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts
index 07739c9d81bc4..557a071ec9175 100644
--- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts
@@ -25,12 +25,22 @@ export enum AlertStates {
export type TimeUnit = 's' | 'm' | 'h' | 'd';
-export interface MetricExpressionParams {
- aggType: MetricsExplorerAggregation;
- metric: string;
+interface BaseMetricExpressionParams {
timeSize: number;
timeUnit: TimeUnit;
indexPattern: string;
threshold: number[];
comparator: Comparator;
}
+
+interface NonCountMetricExpressionParams extends BaseMetricExpressionParams {
+ aggType: Exclude;
+ metric: string;
+}
+
+interface CountMetricExpressionParams extends BaseMetricExpressionParams {
+ aggType: 'count';
+ metric: never;
+}
+
+export type MetricExpressionParams = NonCountMetricExpressionParams | CountMetricExpressionParams;
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js
index aec57e0d33cdd..29c79458fe431 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js
@@ -31,6 +31,7 @@ import { withKibana } from '../../../../../../../../../src/plugins/kibana_react/
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { collapseLiteralStrings } from '../../../../../../shared_imports';
+import { DATAFEED_STATE } from '../../../../../../common/constants/states';
export class EditJobFlyoutUI extends Component {
_initialJobFormState = null;
@@ -41,6 +42,7 @@ export class EditJobFlyoutUI extends Component {
this.state = {
job: {},
hasDatafeed: false,
+ datafeedRunning: false,
isFlyoutVisible: false,
isConfirmationModalVisible: false,
jobDescription: '',
@@ -157,10 +159,12 @@ export class EditJobFlyoutUI extends Component {
extractJob(job, hasDatafeed) {
this.extractInitialJobFormState(job, hasDatafeed);
+ const datafeedRunning = hasDatafeed && job.datafeed_config.state !== DATAFEED_STATE.STOPPED;
this.setState({
job,
hasDatafeed,
+ datafeedRunning,
jobModelMemoryLimitValidationError: '',
jobGroupsValidationError: '',
...cloneDeep(this._initialJobFormState),
@@ -283,6 +287,7 @@ export class EditJobFlyoutUI extends Component {
jobModelMemoryLimitValidationError,
isValidJobDetails,
isValidJobCustomUrls,
+ datafeedRunning,
} = this.state;
const tabs = [
@@ -293,6 +298,7 @@ export class EditJobFlyoutUI extends Component {
}),
content: (
),
},
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/datafeed.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/datafeed.js
index 096a03621d422..3d81b767021a0 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/datafeed.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/datafeed.js
@@ -7,7 +7,14 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
-import { EuiFieldText, EuiForm, EuiFormRow, EuiSpacer, EuiFieldNumber } from '@elastic/eui';
+import {
+ EuiFieldText,
+ EuiForm,
+ EuiFormRow,
+ EuiSpacer,
+ EuiFieldNumber,
+ EuiCallOut,
+} from '@elastic/eui';
import { calculateDatafeedFrequencyDefaultSeconds } from '../../../../../../../common/util/job_utils';
import { getNewJobDefaults } from '../../../../../services/ml_server_info';
@@ -72,9 +79,21 @@ export class Datafeed extends Component {
render() {
const { query, queryDelay, frequency, scrollSize, defaults } = this.state;
+ const { datafeedRunning } = this.props;
return (
+ {datafeedRunning && (
+ <>
+
+
+
+
+ >
+ )}
@@ -140,6 +163,7 @@ export class Datafeed extends Component {
}
}
Datafeed.propTypes = {
+ datafeedRunning: PropTypes.bool.isRequired,
datafeedQuery: PropTypes.string.isRequired,
datafeedQueryDelay: PropTypes.string.isRequired,
datafeedFrequency: PropTypes.string.isRequired,
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/job_details.js
index a609d6a7c3fba..672fd8cefaaba 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/job_details.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/job_details.js
@@ -105,6 +105,7 @@ export class JobDetails extends Component {
mmlValidationError,
groupsValidationError,
} = this.state;
+ const { datafeedRunning } = this.props;
return (
@@ -152,6 +153,14 @@ export class JobDetails extends Component {
defaultMessage="Model memory limit"
/>
}
+ helpText={
+ datafeedRunning ? (
+
+ ) : null
+ }
isInvalid={mmlValidationError !== ''}
error={mmlValidationError}
>
@@ -160,6 +169,7 @@ export class JobDetails extends Component {
onChange={this.onMmlChange}
isInvalid={mmlValidationError !== ''}
error={mmlValidationError}
+ disabled={datafeedRunning}
/>
@@ -168,6 +178,7 @@ export class JobDetails extends Component {
}
}
JobDetails.propTypes = {
+ datafeedRunning: PropTypes.bool.isRequired,
jobDescription: PropTypes.string.isRequired,
jobGroups: PropTypes.array.isRequired,
jobModelMemoryLimit: PropTypes.string.isRequired,
diff --git a/x-pack/plugins/ml/server/routes/schemas/datafeeds_schema.ts b/x-pack/plugins/ml/server/routes/schemas/datafeeds_schema.ts
index ee49da6538460..466e70197e3d1 100644
--- a/x-pack/plugins/ml/server/routes/schemas/datafeeds_schema.ts
+++ b/x-pack/plugins/ml/server/routes/schemas/datafeeds_schema.ts
@@ -24,7 +24,7 @@ export const datafeedConfigSchema = schema.object({
})
),
frequency: schema.maybe(schema.string()),
- indices: schema.arrayOf(schema.string()),
+ indices: schema.maybe(schema.arrayOf(schema.string())),
indexes: schema.maybe(schema.arrayOf(schema.string())),
job_id: schema.maybe(schema.string()),
query: schema.maybe(schema.any()),
diff --git a/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.test.ts b/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.test.ts
index 5be6ed8828e6f..10b3dbbd9b452 100644
--- a/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.test.ts
+++ b/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.test.ts
@@ -13,11 +13,12 @@ describe('cluster_serialization', () => {
expect(() => deserializeCluster('foo', 'bar')).toThrowError();
});
- it('should deserialize a complete cluster object', () => {
+ it('should deserialize a complete default cluster object', () => {
expect(
deserializeCluster('test_cluster', {
seeds: ['localhost:9300'],
connected: true,
+ mode: 'sniff',
num_nodes_connected: 1,
max_connections_per_cluster: 3,
initial_connect_timeout: '30s',
@@ -29,6 +30,7 @@ describe('cluster_serialization', () => {
})
).toEqual({
name: 'test_cluster',
+ mode: 'sniff',
seeds: ['localhost:9300'],
isConnected: true,
connectedNodesCount: 1,
@@ -40,6 +42,37 @@ describe('cluster_serialization', () => {
});
});
+ it('should deserialize a complete "proxy" mode cluster object', () => {
+ expect(
+ deserializeCluster('test_cluster', {
+ proxy_address: 'localhost:9300',
+ mode: 'proxy',
+ connected: true,
+ num_proxy_sockets_connected: 1,
+ max_proxy_socket_connections: 3,
+ initial_connect_timeout: '30s',
+ skip_unavailable: false,
+ server_name: 'my_server_name',
+ transport: {
+ ping_schedule: '-1',
+ compress: false,
+ },
+ })
+ ).toEqual({
+ name: 'test_cluster',
+ mode: 'proxy',
+ proxyAddress: 'localhost:9300',
+ isConnected: true,
+ connectedSocketsCount: 1,
+ proxySocketConnections: 3,
+ initialConnectTimeout: '30s',
+ skipUnavailable: false,
+ transportPingSchedule: '-1',
+ transportCompress: false,
+ serverName: 'my_server_name',
+ });
+ });
+
it('should deserialize a cluster object without transport information', () => {
expect(
deserializeCluster('test_cluster', {
diff --git a/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.ts b/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.ts
index 53dc72eb1695a..fbea311cdeefa 100644
--- a/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.ts
+++ b/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.ts
@@ -18,9 +18,10 @@ export interface ClusterEs {
ping_schedule?: string;
compress?: boolean;
};
- address?: string;
- max_socket_connections?: number;
- num_sockets_connected?: number;
+ proxy_address?: string;
+ max_proxy_socket_connections?: number;
+ num_proxy_sockets_connected?: number;
+ server_name?: string;
}
export interface Cluster {
@@ -77,9 +78,10 @@ export function deserializeCluster(
initial_connect_timeout: initialConnectTimeout,
skip_unavailable: skipUnavailable,
transport,
- address: proxyAddress,
- max_socket_connections: proxySocketConnections,
- num_sockets_connected: connectedSocketsCount,
+ proxy_address: proxyAddress,
+ max_proxy_socket_connections: proxySocketConnections,
+ num_proxy_sockets_connected: connectedSocketsCount,
+ server_name: serverName,
} = esClusterObject;
let deserializedClusterObject: Cluster = {
@@ -94,6 +96,7 @@ export function deserializeCluster(
proxyAddress,
proxySocketConnections,
connectedSocketsCount,
+ serverName,
};
if (transport) {
diff --git a/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts b/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts
index abd44977d8e46..8938f342674f0 100644
--- a/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts
+++ b/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts
@@ -45,16 +45,9 @@ export const register = (deps: RouteDependencies): void => {
? get(clusterSettings, `persistent.cluster.remote[${clusterName}].proxy`, undefined)
: undefined;
- // server_name is not available via the GET /_remote/info API, so we get it from the cluster settings
- // Per https://github.com/elastic/kibana/pull/26067#issuecomment-441848124, we only look at persistent settings
- const serverName = isPersistent
- ? get(clusterSettings, `persistent.cluster.remote[${clusterName}].server_name`, undefined)
- : undefined;
-
return {
...deserializeCluster(clusterName, cluster, deprecatedProxyAddress),
isConfiguredByNode,
- serverName,
};
});
diff --git a/x-pack/plugins/security/server/authentication/api_keys.test.ts b/x-pack/plugins/security/server/authentication/api_keys.test.ts
index bcb212e7bbf94..836740d0a547f 100644
--- a/x-pack/plugins/security/server/authentication/api_keys.test.ts
+++ b/x-pack/plugins/security/server/authentication/api_keys.test.ts
@@ -15,6 +15,8 @@ import {
} from '../../../../../src/core/server/mocks';
import { licenseMock } from '../../common/licensing/index.mock';
+const encodeToBase64 = (str: string) => Buffer.from(str).toString('base64');
+
describe('API Keys', () => {
let apiKeys: APIKeys;
let mockClusterClient: jest.Mocked;
@@ -81,6 +83,87 @@ describe('API Keys', () => {
});
});
+ describe('grantAsInternalUser()', () => {
+ it('returns null when security feature is disabled', async () => {
+ mockLicense.isEnabled.mockReturnValue(false);
+ const result = await apiKeys.grantAsInternalUser(httpServerMock.createKibanaRequest());
+ expect(result).toBeNull();
+
+ expect(mockClusterClient.callAsInternalUser).not.toHaveBeenCalled();
+ });
+
+ it('calls callAsInternalUser with proper parameters for the Basic scheme', async () => {
+ mockLicense.isEnabled.mockReturnValue(true);
+ mockClusterClient.callAsInternalUser.mockResolvedValueOnce({
+ id: '123',
+ name: 'key-name',
+ api_key: 'abc123',
+ });
+ const result = await apiKeys.grantAsInternalUser(
+ httpServerMock.createKibanaRequest({
+ headers: {
+ authorization: `Basic ${encodeToBase64('foo:bar')}`,
+ },
+ })
+ );
+ expect(result).toEqual({
+ api_key: 'abc123',
+ id: '123',
+ name: 'key-name',
+ });
+ expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('shield.grantAPIKey', {
+ body: {
+ grant_type: 'password',
+ username: 'foo',
+ password: 'bar',
+ },
+ });
+ });
+
+ it('calls callAsInternalUser with proper parameters for the Bearer scheme', async () => {
+ mockLicense.isEnabled.mockReturnValue(true);
+ mockClusterClient.callAsInternalUser.mockResolvedValueOnce({
+ id: '123',
+ name: 'key-name',
+ api_key: 'abc123',
+ });
+ const result = await apiKeys.grantAsInternalUser(
+ httpServerMock.createKibanaRequest({
+ headers: {
+ authorization: `Bearer foo-access-token`,
+ },
+ })
+ );
+ expect(result).toEqual({
+ api_key: 'abc123',
+ id: '123',
+ name: 'key-name',
+ });
+ expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('shield.grantAPIKey', {
+ body: {
+ grant_type: 'access_token',
+ access_token: 'foo-access-token',
+ },
+ });
+ });
+
+ it('throw error for other schemes', async () => {
+ mockLicense.isEnabled.mockReturnValue(true);
+ await expect(
+ apiKeys.grantAsInternalUser(
+ httpServerMock.createKibanaRequest({
+ headers: {
+ authorization: `Digest username="foo"`,
+ },
+ })
+ )
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
+ `"Unsupported scheme \\"Digest\\" for granting API Key"`
+ );
+ expect(mockClusterClient.callAsInternalUser).not.toHaveBeenCalled();
+ });
+ });
+
describe('invalidate()', () => {
it('returns null when security feature is disabled', async () => {
mockLicense.isEnabled.mockReturnValue(false);
@@ -142,4 +225,56 @@ describe('API Keys', () => {
);
});
});
+
+ describe('invalidateAsInternalUser()', () => {
+ it('returns null when security feature is disabled', async () => {
+ mockLicense.isEnabled.mockReturnValue(false);
+ const result = await apiKeys.invalidateAsInternalUser({ id: '123' });
+ expect(result).toBeNull();
+ expect(mockClusterClient.callAsInternalUser).not.toHaveBeenCalled();
+ });
+
+ it('calls callCluster with proper parameters', async () => {
+ mockLicense.isEnabled.mockReturnValue(true);
+ mockClusterClient.callAsInternalUser.mockResolvedValueOnce({
+ invalidated_api_keys: ['api-key-id-1'],
+ previously_invalidated_api_keys: [],
+ error_count: 0,
+ });
+ const result = await apiKeys.invalidateAsInternalUser({ id: '123' });
+ expect(result).toEqual({
+ invalidated_api_keys: ['api-key-id-1'],
+ previously_invalidated_api_keys: [],
+ error_count: 0,
+ });
+ expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('shield.invalidateAPIKey', {
+ body: {
+ id: '123',
+ },
+ });
+ });
+
+ it('Only passes id as a parameter', async () => {
+ mockLicense.isEnabled.mockReturnValue(true);
+ mockClusterClient.callAsInternalUser.mockResolvedValueOnce({
+ invalidated_api_keys: ['api-key-id-1'],
+ previously_invalidated_api_keys: [],
+ error_count: 0,
+ });
+ const result = await apiKeys.invalidateAsInternalUser({
+ id: '123',
+ name: 'abc',
+ } as any);
+ expect(result).toEqual({
+ invalidated_api_keys: ['api-key-id-1'],
+ previously_invalidated_api_keys: [],
+ error_count: 0,
+ });
+ expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('shield.invalidateAPIKey', {
+ body: {
+ id: '123',
+ },
+ });
+ });
+ });
});
diff --git a/x-pack/plugins/security/server/authentication/api_keys.ts b/x-pack/plugins/security/server/authentication/api_keys.ts
index 2b1a93d907471..9df7219cec334 100644
--- a/x-pack/plugins/security/server/authentication/api_keys.ts
+++ b/x-pack/plugins/security/server/authentication/api_keys.ts
@@ -6,6 +6,8 @@
import { IClusterClient, KibanaRequest, Logger } from '../../../../../src/core/server';
import { SecurityLicense } from '../../common/licensing';
+import { HTTPAuthorizationHeader } from './http_authentication';
+import { BasicHTTPAuthorizationHeaderCredentials } from './http_authentication';
/**
* Represents the options to create an APIKey class instance that will be
@@ -26,6 +28,13 @@ export interface CreateAPIKeyParams {
expiration?: string;
}
+interface GrantAPIKeyParams {
+ grant_type: 'password' | 'access_token';
+ username?: string;
+ password?: string;
+ access_token?: string;
+}
+
/**
* Represents the params for invalidating an API key
*/
@@ -58,6 +67,21 @@ export interface CreateAPIKeyResult {
api_key: string;
}
+export interface GrantAPIKeyResult {
+ /**
+ * Unique id for this API key
+ */
+ id: string;
+ /**
+ * Name for this API key
+ */
+ name: string;
+ /**
+ * Generated API key
+ */
+ api_key: string;
+}
+
/**
* The return value when invalidating an API key in Elasticsearch.
*/
@@ -131,31 +155,89 @@ export class APIKeys {
return result;
}
+ /**
+ * Tries to grant an API key for the current user.
+ * @param request Request instance.
+ */
+ async grantAsInternalUser(request: KibanaRequest) {
+ if (!this.license.isEnabled()) {
+ return null;
+ }
+
+ this.logger.debug('Trying to grant an API key');
+ const authorizationHeader = HTTPAuthorizationHeader.parseFromRequest(request);
+ if (authorizationHeader == null) {
+ throw new Error(
+ `Unable to grant an API Key, request does not contain an authorization header`
+ );
+ }
+ const params = this.getGrantParams(authorizationHeader);
+
+ // User needs `manage_api_key` or `grant_api_key` privilege to use this API
+ let result: GrantAPIKeyResult;
+ try {
+ result = (await this.clusterClient.callAsInternalUser('shield.grantAPIKey', {
+ body: params,
+ })) as GrantAPIKeyResult;
+ this.logger.debug('API key was granted successfully');
+ } catch (e) {
+ this.logger.error(`Failed to grant API key: ${e.message}`);
+ throw e;
+ }
+
+ return result;
+ }
+
/**
* Tries to invalidate an API key.
* @param request Request instance.
* @param params The params to invalidate an API key.
*/
- async invalidate(
- request: KibanaRequest,
- params: InvalidateAPIKeyParams
- ): Promise {
+ async invalidate(request: KibanaRequest, params: InvalidateAPIKeyParams) {
if (!this.license.isEnabled()) {
return null;
}
- this.logger.debug('Trying to invalidate an API key');
+ this.logger.debug('Trying to invalidate an API key as current user');
- // User needs `manage_api_key` privilege to use this API
let result: InvalidateAPIKeyResult;
try {
- result = (await this.clusterClient
+ // User needs `manage_api_key` privilege to use this API
+ result = await this.clusterClient
.asScoped(request)
.callAsCurrentUser('shield.invalidateAPIKey', {
body: {
id: params.id,
},
- })) as InvalidateAPIKeyResult;
+ });
+ this.logger.debug('API key was invalidated successfully as current user');
+ } catch (e) {
+ this.logger.error(`Failed to invalidate API key as current user: ${e.message}`);
+ throw e;
+ }
+
+ return result;
+ }
+
+ /**
+ * Tries to invalidate an API key by using the internal user.
+ * @param params The params to invalidate an API key.
+ */
+ async invalidateAsInternalUser(params: InvalidateAPIKeyParams) {
+ if (!this.license.isEnabled()) {
+ return null;
+ }
+
+ this.logger.debug('Trying to invalidate an API key');
+
+ let result: InvalidateAPIKeyResult;
+ try {
+ // Internal user needs `cluster:admin/xpack/security/api_key/invalidate` privilege to use this API
+ result = await this.clusterClient.callAsInternalUser('shield.invalidateAPIKey', {
+ body: {
+ id: params.id,
+ },
+ });
this.logger.debug('API key was invalidated successfully');
} catch (e) {
this.logger.error(`Failed to invalidate API key: ${e.message}`);
@@ -164,4 +246,26 @@ export class APIKeys {
return result;
}
+
+ private getGrantParams(authorizationHeader: HTTPAuthorizationHeader): GrantAPIKeyParams {
+ if (authorizationHeader.scheme.toLowerCase() === 'bearer') {
+ return {
+ grant_type: 'access_token',
+ access_token: authorizationHeader.credentials,
+ };
+ }
+
+ if (authorizationHeader.scheme.toLowerCase() === 'basic') {
+ const basicCredentials = BasicHTTPAuthorizationHeaderCredentials.parseFromCredentials(
+ authorizationHeader.credentials
+ );
+ return {
+ grant_type: 'password',
+ username: basicCredentials.username,
+ password: basicCredentials.password,
+ };
+ }
+
+ throw new Error(`Unsupported scheme "${authorizationHeader.scheme}" for granting API Key`);
+ }
}
diff --git a/x-pack/plugins/security/server/authentication/get_http_authentication_scheme.test.ts b/x-pack/plugins/security/server/authentication/get_http_authentication_scheme.test.ts
deleted file mode 100644
index 6a63634394ec0..0000000000000
--- a/x-pack/plugins/security/server/authentication/get_http_authentication_scheme.test.ts
+++ /dev/null
@@ -1,58 +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 { httpServerMock } from '../../../../../src/core/server/http/http_server.mocks';
-
-import { getHTTPAuthenticationScheme } from './get_http_authentication_scheme';
-
-describe('getHTTPAuthenticationScheme', () => {
- it('returns `null` if request does not have authorization header', () => {
- expect(getHTTPAuthenticationScheme(httpServerMock.createKibanaRequest())).toBeNull();
- });
-
- it('returns `null` if authorization header value isn not a string', () => {
- expect(
- getHTTPAuthenticationScheme(
- httpServerMock.createKibanaRequest({
- headers: { authorization: ['Basic xxx', 'Bearer xxx'] as any },
- })
- )
- ).toBeNull();
- });
-
- it('returns `null` if authorization header value is an empty string', () => {
- expect(
- getHTTPAuthenticationScheme(
- httpServerMock.createKibanaRequest({ headers: { authorization: '' } })
- )
- ).toBeNull();
- });
-
- it('returns only scheme portion of the authorization header value in lower case', () => {
- const headerValueAndSchemeMap = [
- ['Basic xxx', 'basic'],
- ['Basic xxx yyy', 'basic'],
- ['basic xxx', 'basic'],
- ['basic', 'basic'],
- // We don't trim leading whitespaces in scheme.
- [' Basic xxx', ''],
- ['Negotiate xxx', 'negotiate'],
- ['negotiate xxx', 'negotiate'],
- ['negotiate', 'negotiate'],
- ['ApiKey xxx', 'apikey'],
- ['apikey xxx', 'apikey'],
- ['Api Key xxx', 'api'],
- ];
-
- for (const [authorization, scheme] of headerValueAndSchemeMap) {
- expect(
- getHTTPAuthenticationScheme(
- httpServerMock.createKibanaRequest({ headers: { authorization } })
- )
- ).toBe(scheme);
- }
- });
-});
diff --git a/x-pack/plugins/security/server/authentication/get_http_authentication_scheme.ts b/x-pack/plugins/security/server/authentication/get_http_authentication_scheme.ts
deleted file mode 100644
index b9c53f34dbcab..0000000000000
--- a/x-pack/plugins/security/server/authentication/get_http_authentication_scheme.ts
+++ /dev/null
@@ -1,21 +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 { KibanaRequest } from '../../../../../src/core/server';
-
-/**
- * Parses request's `Authorization` HTTP header if present and extracts authentication scheme.
- * https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml#authschemes
- * @param request Request instance to extract authentication scheme for.
- */
-export function getHTTPAuthenticationScheme(request: KibanaRequest) {
- const authorizationHeaderValue = request.headers.authorization;
- if (!authorizationHeaderValue || typeof authorizationHeaderValue !== 'string') {
- return null;
- }
-
- return authorizationHeaderValue.split(/\s+/)[0].toLowerCase();
-}
diff --git a/x-pack/plugins/security/server/authentication/http_authentication/basic_http_authorization_header_credentials.test.ts b/x-pack/plugins/security/server/authentication/http_authentication/basic_http_authorization_header_credentials.test.ts
new file mode 100644
index 0000000000000..bd3c7047e77e7
--- /dev/null
+++ b/x-pack/plugins/security/server/authentication/http_authentication/basic_http_authorization_header_credentials.test.ts
@@ -0,0 +1,56 @@
+/*
+ * 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 { BasicHTTPAuthorizationHeaderCredentials } from './basic_http_authorization_header_credentials';
+
+const encodeToBase64 = (str: string) => Buffer.from(str).toString('base64');
+
+describe('BasicHTTPAuthorizationHeaderCredentials.parseFromRequest()', () => {
+ it('parses username from the left-side of the single colon', () => {
+ const basicCredentials = BasicHTTPAuthorizationHeaderCredentials.parseFromCredentials(
+ encodeToBase64('fOo:bAr')
+ );
+ expect(basicCredentials.username).toBe('fOo');
+ });
+
+ it('parses username from the left-side of the first colon', () => {
+ const basicCredentials = BasicHTTPAuthorizationHeaderCredentials.parseFromCredentials(
+ encodeToBase64('fOo:bAr:bAz')
+ );
+ expect(basicCredentials.username).toBe('fOo');
+ });
+
+ it('parses password from the right-side of the single colon', () => {
+ const basicCredentials = BasicHTTPAuthorizationHeaderCredentials.parseFromCredentials(
+ encodeToBase64('fOo:bAr')
+ );
+ expect(basicCredentials.password).toBe('bAr');
+ });
+
+ it('parses password from the right-side of the first colon', () => {
+ const basicCredentials = BasicHTTPAuthorizationHeaderCredentials.parseFromCredentials(
+ encodeToBase64('fOo:bAr:bAz')
+ );
+ expect(basicCredentials.password).toBe('bAr:bAz');
+ });
+
+ it('throws error if there is no colon', () => {
+ expect(() => {
+ BasicHTTPAuthorizationHeaderCredentials.parseFromCredentials(encodeToBase64('fOobArbAz'));
+ }).toThrowErrorMatchingInlineSnapshot(
+ `"Unable to parse basic authentication credentials without a colon"`
+ );
+ });
+});
+
+describe(`toString()`, () => {
+ it('concatenates username and password using a colon and then base64 encodes the string', () => {
+ const basicCredentials = new BasicHTTPAuthorizationHeaderCredentials('elastic', 'changeme');
+
+ expect(basicCredentials.toString()).toEqual(Buffer.from(`elastic:changeme`).toString('base64')); // I don't like that this so closely mirror the actual implementation
+ expect(basicCredentials.toString()).toEqual('ZWxhc3RpYzpjaGFuZ2VtZQ=='); // and I don't like that this is so opaque. Both together seem reasonable...
+ });
+});
diff --git a/x-pack/plugins/security/server/authentication/http_authentication/basic_http_authorization_header_credentials.ts b/x-pack/plugins/security/server/authentication/http_authentication/basic_http_authorization_header_credentials.ts
new file mode 100644
index 0000000000000..b8c3f1dadf1b2
--- /dev/null
+++ b/x-pack/plugins/security/server/authentication/http_authentication/basic_http_authorization_header_credentials.ts
@@ -0,0 +1,44 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export class BasicHTTPAuthorizationHeaderCredentials {
+ /**
+ * Username, referred to as the `user-id` in https://tools.ietf.org/html/rfc7617.
+ */
+ readonly username: string;
+
+ /**
+ * Password used to authenticate
+ */
+ readonly password: string;
+
+ constructor(username: string, password: string) {
+ this.username = username;
+ this.password = password;
+ }
+
+ /**
+ * Parses the username and password from the credentials included in a HTTP Authorization header
+ * for the Basic scheme https://tools.ietf.org/html/rfc7617
+ * @param credentials The credentials extracted from the HTTP Authorization header
+ */
+ static parseFromCredentials(credentials: string) {
+ const decoded = Buffer.from(credentials, 'base64').toString();
+ if (decoded.indexOf(':') === -1) {
+ throw new Error('Unable to parse basic authentication credentials without a colon');
+ }
+
+ const [username] = decoded.split(':');
+ // according to https://tools.ietf.org/html/rfc7617, everything
+ // after the first colon is considered to be part of the password
+ const password = decoded.substring(username.length + 1);
+ return new BasicHTTPAuthorizationHeaderCredentials(username, password);
+ }
+
+ toString() {
+ return Buffer.from(`${this.username}:${this.password}`).toString('base64');
+ }
+}
diff --git a/x-pack/plugins/security/server/authentication/http_authentication/http_authorization_header.test.ts b/x-pack/plugins/security/server/authentication/http_authentication/http_authorization_header.test.ts
new file mode 100644
index 0000000000000..d47a0c70f608a
--- /dev/null
+++ b/x-pack/plugins/security/server/authentication/http_authentication/http_authorization_header.test.ts
@@ -0,0 +1,85 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { httpServerMock } from '../../../../../../src/core/server/mocks';
+
+import { HTTPAuthorizationHeader } from './http_authorization_header';
+
+describe('HTTPAuthorizationHeader.parseFromRequest()', () => {
+ it('returns `null` if request does not have authorization header', () => {
+ expect(
+ HTTPAuthorizationHeader.parseFromRequest(httpServerMock.createKibanaRequest())
+ ).toBeNull();
+ });
+
+ it('returns `null` if authorization header value is not a string', () => {
+ expect(
+ HTTPAuthorizationHeader.parseFromRequest(
+ httpServerMock.createKibanaRequest({
+ headers: { authorization: ['Basic xxx', 'Bearer xxx'] as any },
+ })
+ )
+ ).toBeNull();
+ });
+
+ it('returns `null` if authorization header value is an empty string', () => {
+ expect(
+ HTTPAuthorizationHeader.parseFromRequest(
+ httpServerMock.createKibanaRequest({ headers: { authorization: '' } })
+ )
+ ).toBeNull();
+ });
+
+ it('parses scheme portion of the authorization header value', () => {
+ const headerValueAndSchemeMap = [
+ ['Basic xxx', 'Basic'],
+ ['Basic xxx yyy', 'Basic'],
+ ['basic xxx', 'basic'],
+ ['basic', 'basic'],
+ // We don't trim leading whitespaces in scheme.
+ [' Basic xxx', ''],
+ ['Negotiate xxx', 'Negotiate'],
+ ['negotiate xxx', 'negotiate'],
+ ['negotiate', 'negotiate'],
+ ['ApiKey xxx', 'ApiKey'],
+ ['apikey xxx', 'apikey'],
+ ['Api Key xxx', 'Api'],
+ ];
+
+ for (const [authorization, scheme] of headerValueAndSchemeMap) {
+ const header = HTTPAuthorizationHeader.parseFromRequest(
+ httpServerMock.createKibanaRequest({ headers: { authorization } })
+ );
+ expect(header).not.toBeNull();
+ expect(header!.scheme).toBe(scheme);
+ }
+ });
+
+ it('parses credentials portion of the authorization header value', () => {
+ const headerValueAndCredentialsMap = [
+ ['xxx fOo', 'fOo'],
+ ['xxx fOo bAr', 'fOo bAr'],
+ // We don't trim leading whitespaces in scheme.
+ [' xxx fOo', 'xxx fOo'],
+ ];
+
+ for (const [authorization, credentials] of headerValueAndCredentialsMap) {
+ const header = HTTPAuthorizationHeader.parseFromRequest(
+ httpServerMock.createKibanaRequest({ headers: { authorization } })
+ );
+ expect(header).not.toBeNull();
+ expect(header!.credentials).toBe(credentials);
+ }
+ });
+});
+
+describe('toString()', () => {
+ it('concatenates scheme and credentials using a space', () => {
+ const header = new HTTPAuthorizationHeader('Bearer', 'some-access-token');
+
+ expect(header.toString()).toEqual('Bearer some-access-token');
+ });
+});
diff --git a/x-pack/plugins/security/server/authentication/http_authentication/http_authorization_header.ts b/x-pack/plugins/security/server/authentication/http_authentication/http_authorization_header.ts
new file mode 100644
index 0000000000000..bfc757734ec72
--- /dev/null
+++ b/x-pack/plugins/security/server/authentication/http_authentication/http_authorization_header.ts
@@ -0,0 +1,45 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { KibanaRequest } from '../../../../../../src/core/server';
+
+export class HTTPAuthorizationHeader {
+ /**
+ * The authentication scheme. Should be consumed in a case-insensitive manner.
+ * https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml#authschemes
+ */
+ readonly scheme: string;
+
+ /**
+ * The authentication credentials for the scheme.
+ */
+ readonly credentials: string;
+
+ constructor(scheme: string, credentials: string) {
+ this.scheme = scheme;
+ this.credentials = credentials;
+ }
+
+ /**
+ * Parses request's `Authorization` HTTP header if present.
+ * @param request Request instance to extract the authorization header from.
+ */
+ static parseFromRequest(request: KibanaRequest) {
+ const authorizationHeaderValue = request.headers.authorization;
+ if (!authorizationHeaderValue || typeof authorizationHeaderValue !== 'string') {
+ return null;
+ }
+
+ const [scheme] = authorizationHeaderValue.split(/\s+/);
+ const credentials = authorizationHeaderValue.substring(scheme.length + 1);
+
+ return new HTTPAuthorizationHeader(scheme, credentials);
+ }
+
+ toString() {
+ return `${this.scheme} ${this.credentials}`;
+ }
+}
diff --git a/x-pack/plugins/security/server/authentication/http_authentication/index.ts b/x-pack/plugins/security/server/authentication/http_authentication/index.ts
new file mode 100644
index 0000000000000..94eb8762ecaf0
--- /dev/null
+++ b/x-pack/plugins/security/server/authentication/http_authentication/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 { BasicHTTPAuthorizationHeaderCredentials } from './basic_http_authorization_header_credentials';
+export { HTTPAuthorizationHeader } from './http_authorization_header';
diff --git a/x-pack/plugins/security/server/authentication/index.mock.ts b/x-pack/plugins/security/server/authentication/index.mock.ts
index c634e2c80c299..43892753f0d3f 100644
--- a/x-pack/plugins/security/server/authentication/index.mock.ts
+++ b/x-pack/plugins/security/server/authentication/index.mock.ts
@@ -13,7 +13,9 @@ export const authenticationMock = {
isProviderEnabled: jest.fn(),
createAPIKey: jest.fn(),
getCurrentUser: jest.fn(),
+ grantAPIKeyAsInternalUser: jest.fn(),
invalidateAPIKey: jest.fn(),
+ invalidateAPIKeyAsInternalUser: jest.fn(),
isAuthenticated: jest.fn(),
getSessionInfo: jest.fn(),
}),
diff --git a/x-pack/plugins/security/server/authentication/index.test.ts b/x-pack/plugins/security/server/authentication/index.test.ts
index 30929ba98d33b..21e5f18bc0282 100644
--- a/x-pack/plugins/security/server/authentication/index.test.ts
+++ b/x-pack/plugins/security/server/authentication/index.test.ts
@@ -33,7 +33,7 @@ import {
import { AuthenticatedUser } from '../../common/model';
import { ConfigType, createConfig$ } from '../config';
import { AuthenticationResult } from './authentication_result';
-import { setupAuthentication } from '.';
+import { Authentication, setupAuthentication } from '.';
import {
CreateAPIKeyResult,
CreateAPIKeyParams,
@@ -369,6 +369,24 @@ describe('setupAuthentication()', () => {
});
});
+ describe('grantAPIKeyAsInternalUser()', () => {
+ let grantAPIKeyAsInternalUser: (request: KibanaRequest) => Promise;
+ beforeEach(async () => {
+ grantAPIKeyAsInternalUser = (await setupAuthentication(mockSetupAuthenticationParams))
+ .grantAPIKeyAsInternalUser;
+ });
+
+ it('calls grantAsInternalUser', async () => {
+ const request = httpServerMock.createKibanaRequest();
+ const apiKeysInstance = jest.requireMock('./api_keys').APIKeys.mock.instances[0];
+ apiKeysInstance.grantAsInternalUser.mockResolvedValueOnce({ api_key: 'foo' });
+ await expect(grantAPIKeyAsInternalUser(request)).resolves.toEqual({
+ api_key: 'foo',
+ });
+ expect(apiKeysInstance.grantAsInternalUser).toHaveBeenCalledWith(request);
+ });
+ });
+
describe('invalidateAPIKey()', () => {
let invalidateAPIKey: (
request: KibanaRequest,
@@ -392,4 +410,25 @@ describe('setupAuthentication()', () => {
expect(apiKeysInstance.invalidate).toHaveBeenCalledWith(request, params);
});
});
+
+ describe('invalidateAPIKeyAsInternalUser()', () => {
+ let invalidateAPIKeyAsInternalUser: Authentication['invalidateAPIKeyAsInternalUser'];
+
+ beforeEach(async () => {
+ invalidateAPIKeyAsInternalUser = (await setupAuthentication(mockSetupAuthenticationParams))
+ .invalidateAPIKeyAsInternalUser;
+ });
+
+ it('calls invalidateAPIKeyAsInternalUser with given arguments', async () => {
+ const apiKeysInstance = jest.requireMock('./api_keys').APIKeys.mock.instances[0];
+ const params = {
+ id: '123',
+ };
+ apiKeysInstance.invalidateAsInternalUser.mockResolvedValueOnce({ success: true });
+ await expect(invalidateAPIKeyAsInternalUser(params)).resolves.toEqual({
+ success: true,
+ });
+ expect(apiKeysInstance.invalidateAsInternalUser).toHaveBeenCalledWith(params);
+ });
+ });
});
diff --git a/x-pack/plugins/security/server/authentication/index.ts b/x-pack/plugins/security/server/authentication/index.ts
index 1eed53efc6441..c5c72853e68e1 100644
--- a/x-pack/plugins/security/server/authentication/index.ts
+++ b/x-pack/plugins/security/server/authentication/index.ts
@@ -28,6 +28,10 @@ export {
CreateAPIKeyParams,
InvalidateAPIKeyParams,
} from './api_keys';
+export {
+ BasicHTTPAuthorizationHeaderCredentials,
+ HTTPAuthorizationHeader,
+} from './http_authentication';
interface SetupAuthenticationParams {
http: CoreSetup['http'];
@@ -169,8 +173,11 @@ export async function setupAuthentication({
getCurrentUser,
createAPIKey: (request: KibanaRequest, params: CreateAPIKeyParams) =>
apiKeys.create(request, params),
+ grantAPIKeyAsInternalUser: (request: KibanaRequest) => apiKeys.grantAsInternalUser(request),
invalidateAPIKey: (request: KibanaRequest, params: InvalidateAPIKeyParams) =>
apiKeys.invalidate(request, params),
+ invalidateAPIKeyAsInternalUser: (params: InvalidateAPIKeyParams) =>
+ apiKeys.invalidateAsInternalUser(params),
isAuthenticated: (request: KibanaRequest) => http.auth.isAuthenticated(request),
};
}
diff --git a/x-pack/plugins/security/server/authentication/providers/basic.ts b/x-pack/plugins/security/server/authentication/providers/basic.ts
index ad46aff8afa51..76a9f936eca48 100644
--- a/x-pack/plugins/security/server/authentication/providers/basic.ts
+++ b/x-pack/plugins/security/server/authentication/providers/basic.ts
@@ -8,7 +8,10 @@ import { KibanaRequest } from '../../../../../../src/core/server';
import { canRedirectRequest } from '../can_redirect_request';
import { AuthenticationResult } from '../authentication_result';
import { DeauthenticationResult } from '../deauthentication_result';
-import { getHTTPAuthenticationScheme } from '../get_http_authentication_scheme';
+import {
+ HTTPAuthorizationHeader,
+ BasicHTTPAuthorizationHeaderCredentials,
+} from '../http_authentication';
import { BaseAuthenticationProvider } from './base';
/**
@@ -54,7 +57,10 @@ export class BasicAuthenticationProvider extends BaseAuthenticationProvider {
this.logger.debug('Trying to perform a login.');
const authHeaders = {
- authorization: `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`,
+ authorization: new HTTPAuthorizationHeader(
+ 'Basic',
+ new BasicHTTPAuthorizationHeaderCredentials(username, password).toString()
+ ).toString(),
};
try {
@@ -76,7 +82,7 @@ export class BasicAuthenticationProvider extends BaseAuthenticationProvider {
public async authenticate(request: KibanaRequest, state?: ProviderState | null) {
this.logger.debug(`Trying to authenticate user request to ${request.url.path}.`);
- if (getHTTPAuthenticationScheme(request) != null) {
+ if (HTTPAuthorizationHeader.parseFromRequest(request) != null) {
this.logger.debug('Cannot authenticate requests with `Authorization` header.');
return AuthenticationResult.notHandled();
}
diff --git a/x-pack/plugins/security/server/authentication/providers/http.ts b/x-pack/plugins/security/server/authentication/providers/http.ts
index 57163bf8145b8..6b75ae2d48156 100644
--- a/x-pack/plugins/security/server/authentication/providers/http.ts
+++ b/x-pack/plugins/security/server/authentication/providers/http.ts
@@ -7,7 +7,7 @@
import { KibanaRequest } from '../../../../../../src/core/server';
import { AuthenticationResult } from '../authentication_result';
import { DeauthenticationResult } from '../deauthentication_result';
-import { getHTTPAuthenticationScheme } from '../get_http_authentication_scheme';
+import { HTTPAuthorizationHeader } from '../http_authentication';
import { AuthenticationProviderOptions, BaseAuthenticationProvider } from './base';
interface HTTPAuthenticationProviderOptions {
@@ -38,7 +38,9 @@ export class HTTPAuthenticationProvider extends BaseAuthenticationProvider {
if ((httpOptions?.supportedSchemes?.size ?? 0) === 0) {
throw new Error('Supported schemes should be specified');
}
- this.supportedSchemes = httpOptions.supportedSchemes;
+ this.supportedSchemes = new Set(
+ [...httpOptions.supportedSchemes].map(scheme => scheme.toLowerCase())
+ );
}
/**
@@ -56,26 +58,26 @@ export class HTTPAuthenticationProvider extends BaseAuthenticationProvider {
public async authenticate(request: KibanaRequest) {
this.logger.debug(`Trying to authenticate user request to ${request.url.path}.`);
- const authenticationScheme = getHTTPAuthenticationScheme(request);
- if (authenticationScheme == null) {
+ const authorizationHeader = HTTPAuthorizationHeader.parseFromRequest(request);
+ if (authorizationHeader == null) {
this.logger.debug('Authorization header is not presented.');
return AuthenticationResult.notHandled();
}
- if (!this.supportedSchemes.has(authenticationScheme)) {
- this.logger.debug(`Unsupported authentication scheme: ${authenticationScheme}`);
+ if (!this.supportedSchemes.has(authorizationHeader.scheme.toLowerCase())) {
+ this.logger.debug(`Unsupported authentication scheme: ${authorizationHeader.scheme}`);
return AuthenticationResult.notHandled();
}
try {
const user = await this.getUser(request);
this.logger.debug(
- `Request to ${request.url.path} has been authenticated via authorization header with "${authenticationScheme}" scheme.`
+ `Request to ${request.url.path} has been authenticated via authorization header with "${authorizationHeader.scheme}" scheme.`
);
return AuthenticationResult.succeeded(user);
} catch (err) {
this.logger.debug(
- `Failed to authenticate request to ${request.url.path} via authorization header with "${authenticationScheme}" scheme: ${err.message}`
+ `Failed to authenticate request to ${request.url.path} via authorization header with "${authorizationHeader.scheme}" scheme: ${err.message}`
);
return AuthenticationResult.failed(err);
}
diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.ts
index 632a07ca2b21a..dbd0a438d71c9 100644
--- a/x-pack/plugins/security/server/authentication/providers/kerberos.ts
+++ b/x-pack/plugins/security/server/authentication/providers/kerberos.ts
@@ -12,7 +12,7 @@ import {
} from '../../../../../../src/core/server';
import { AuthenticationResult } from '../authentication_result';
import { DeauthenticationResult } from '../deauthentication_result';
-import { getHTTPAuthenticationScheme } from '../get_http_authentication_scheme';
+import { HTTPAuthorizationHeader } from '../http_authentication';
import { Tokens, TokenPair } from '../tokens';
import { BaseAuthenticationProvider } from './base';
@@ -44,13 +44,13 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider {
public async authenticate(request: KibanaRequest, state?: ProviderState | null) {
this.logger.debug(`Trying to authenticate user request to ${request.url.path}.`);
- const authenticationScheme = getHTTPAuthenticationScheme(request);
- if (authenticationScheme && authenticationScheme !== 'negotiate') {
- this.logger.debug(`Unsupported authentication scheme: ${authenticationScheme}`);
+ const authorizationHeader = HTTPAuthorizationHeader.parseFromRequest(request);
+ if (authorizationHeader && authorizationHeader.scheme.toLowerCase() !== 'negotiate') {
+ this.logger.debug(`Unsupported authentication scheme: ${authorizationHeader.scheme}`);
return AuthenticationResult.notHandled();
}
- let authenticationResult = authenticationScheme
+ let authenticationResult = authorizationHeader
? await this.authenticateWithNegotiateScheme(request)
: AuthenticationResult.notHandled();
@@ -175,7 +175,9 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider {
try {
// Then attempt to query for the user details using the new token
- const authHeaders = { authorization: `Bearer ${tokens.access_token}` };
+ const authHeaders = {
+ authorization: new HTTPAuthorizationHeader('Bearer', tokens.access_token).toString(),
+ };
const user = await this.getUser(request, authHeaders);
this.logger.debug('User has been authenticated with new access token');
@@ -205,7 +207,9 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider {
}
try {
- const authHeaders = { authorization: `Bearer ${accessToken}` };
+ const authHeaders = {
+ authorization: new HTTPAuthorizationHeader('Bearer', accessToken).toString(),
+ };
const user = await this.getUser(request, authHeaders);
this.logger.debug('Request has been authenticated via state.');
@@ -242,7 +246,12 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider {
}
try {
- const authHeaders = { authorization: `Bearer ${refreshedTokenPair.accessToken}` };
+ const authHeaders = {
+ authorization: new HTTPAuthorizationHeader(
+ 'Bearer',
+ refreshedTokenPair.accessToken
+ ).toString(),
+ };
const user = await this.getUser(request, authHeaders);
this.logger.debug('Request has been authenticated via refreshed token.');
diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.ts b/x-pack/plugins/security/server/authentication/providers/oidc.ts
index d52466826c2be..21bce028b0d98 100644
--- a/x-pack/plugins/security/server/authentication/providers/oidc.ts
+++ b/x-pack/plugins/security/server/authentication/providers/oidc.ts
@@ -10,7 +10,7 @@ import { KibanaRequest } from '../../../../../../src/core/server';
import { AuthenticationResult } from '../authentication_result';
import { canRedirectRequest } from '../can_redirect_request';
import { DeauthenticationResult } from '../deauthentication_result';
-import { getHTTPAuthenticationScheme } from '../get_http_authentication_scheme';
+import { HTTPAuthorizationHeader } from '../http_authentication';
import { Tokens, TokenPair } from '../tokens';
import {
AuthenticationProviderOptions,
@@ -131,7 +131,7 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider {
public async authenticate(request: KibanaRequest, state?: ProviderState | null) {
this.logger.debug(`Trying to authenticate user request to ${request.url.path}.`);
- if (getHTTPAuthenticationScheme(request) != null) {
+ if (HTTPAuthorizationHeader.parseFromRequest(request) != null) {
this.logger.debug('Cannot authenticate requests with `Authorization` header.');
return AuthenticationResult.notHandled();
}
@@ -289,7 +289,9 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider {
}
try {
- const authHeaders = { authorization: `Bearer ${accessToken}` };
+ const authHeaders = {
+ authorization: new HTTPAuthorizationHeader('Bearer', accessToken).toString(),
+ };
const user = await this.getUser(request, authHeaders);
this.logger.debug('Request has been authenticated via state.');
@@ -345,7 +347,12 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider {
}
try {
- const authHeaders = { authorization: `Bearer ${refreshedTokenPair.accessToken}` };
+ const authHeaders = {
+ authorization: new HTTPAuthorizationHeader(
+ 'Bearer',
+ refreshedTokenPair.accessToken
+ ).toString(),
+ };
const user = await this.getUser(request, authHeaders);
this.logger.debug('Request has been authenticated via refreshed token.');
diff --git a/x-pack/plugins/security/server/authentication/providers/pki.ts b/x-pack/plugins/security/server/authentication/providers/pki.ts
index 252ab8cc67144..db022ff355702 100644
--- a/x-pack/plugins/security/server/authentication/providers/pki.ts
+++ b/x-pack/plugins/security/server/authentication/providers/pki.ts
@@ -9,7 +9,7 @@ import { DetailedPeerCertificate } from 'tls';
import { KibanaRequest } from '../../../../../../src/core/server';
import { AuthenticationResult } from '../authentication_result';
import { DeauthenticationResult } from '../deauthentication_result';
-import { getHTTPAuthenticationScheme } from '../get_http_authentication_scheme';
+import { HTTPAuthorizationHeader } from '../http_authentication';
import { Tokens } from '../tokens';
import { BaseAuthenticationProvider } from './base';
@@ -45,7 +45,7 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider {
public async authenticate(request: KibanaRequest, state?: ProviderState | null) {
this.logger.debug(`Trying to authenticate user request to ${request.url.path}.`);
- if (getHTTPAuthenticationScheme(request) != null) {
+ if (HTTPAuthorizationHeader.parseFromRequest(request) != null) {
this.logger.debug('Cannot authenticate requests with `Authorization` header.');
return AuthenticationResult.notHandled();
}
@@ -156,7 +156,9 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider {
}
try {
- const authHeaders = { authorization: `Bearer ${accessToken}` };
+ const authHeaders = {
+ authorization: new HTTPAuthorizationHeader('Bearer', accessToken).toString(),
+ };
const user = await this.getUser(request, authHeaders);
this.logger.debug('Request has been authenticated via state.');
@@ -207,7 +209,9 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider {
try {
// Then attempt to query for the user details using the new token
- const authHeaders = { authorization: `Bearer ${accessToken}` };
+ const authHeaders = {
+ authorization: new HTTPAuthorizationHeader('Bearer', accessToken).toString(),
+ };
const user = await this.getUser(request, authHeaders);
this.logger.debug('User has been authenticated with new access token');
diff --git a/x-pack/plugins/security/server/authentication/providers/saml.ts b/x-pack/plugins/security/server/authentication/providers/saml.ts
index 1152ee5048699..ddf6814989a49 100644
--- a/x-pack/plugins/security/server/authentication/providers/saml.ts
+++ b/x-pack/plugins/security/server/authentication/providers/saml.ts
@@ -10,7 +10,7 @@ import { KibanaRequest } from '../../../../../../src/core/server';
import { AuthenticationResult } from '../authentication_result';
import { DeauthenticationResult } from '../deauthentication_result';
import { canRedirectRequest } from '../can_redirect_request';
-import { getHTTPAuthenticationScheme } from '../get_http_authentication_scheme';
+import { HTTPAuthorizationHeader } from '../http_authentication';
import { Tokens, TokenPair } from '../tokens';
import { AuthenticationProviderOptions, BaseAuthenticationProvider } from './base';
@@ -181,7 +181,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider {
public async authenticate(request: KibanaRequest, state?: ProviderState | null) {
this.logger.debug(`Trying to authenticate user request to ${request.url.path}.`);
- if (getHTTPAuthenticationScheme(request) != null) {
+ if (HTTPAuthorizationHeader.parseFromRequest(request) != null) {
this.logger.debug('Cannot authenticate requests with `Authorization` header.');
return AuthenticationResult.notHandled();
}
@@ -390,7 +390,9 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider {
}
try {
- const authHeaders = { authorization: `Bearer ${accessToken}` };
+ const authHeaders = {
+ authorization: new HTTPAuthorizationHeader('Bearer', accessToken).toString(),
+ };
const user = await this.getUser(request, authHeaders);
this.logger.debug('Request has been authenticated via state.');
@@ -445,7 +447,12 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider {
}
try {
- const authHeaders = { authorization: `Bearer ${refreshedTokenPair.accessToken}` };
+ const authHeaders = {
+ authorization: new HTTPAuthorizationHeader(
+ 'Bearer',
+ refreshedTokenPair.accessToken
+ ).toString(),
+ };
const user = await this.getUser(request, authHeaders);
this.logger.debug('Request has been authenticated via refreshed token.');
diff --git a/x-pack/plugins/security/server/authentication/providers/token.ts b/x-pack/plugins/security/server/authentication/providers/token.ts
index fffac254ed30a..91808c22c4300 100644
--- a/x-pack/plugins/security/server/authentication/providers/token.ts
+++ b/x-pack/plugins/security/server/authentication/providers/token.ts
@@ -9,7 +9,7 @@ import { KibanaRequest } from '../../../../../../src/core/server';
import { AuthenticationResult } from '../authentication_result';
import { DeauthenticationResult } from '../deauthentication_result';
import { canRedirectRequest } from '../can_redirect_request';
-import { getHTTPAuthenticationScheme } from '../get_http_authentication_scheme';
+import { HTTPAuthorizationHeader } from '../http_authentication';
import { Tokens, TokenPair } from '../tokens';
import { BaseAuthenticationProvider } from './base';
@@ -60,7 +60,9 @@ export class TokenAuthenticationProvider extends BaseAuthenticationProvider {
this.logger.debug('Get token API request to Elasticsearch successful');
// Then attempt to query for the user details using the new token
- const authHeaders = { authorization: `Bearer ${accessToken}` };
+ const authHeaders = {
+ authorization: new HTTPAuthorizationHeader('Bearer', accessToken).toString(),
+ };
const user = await this.getUser(request, authHeaders);
this.logger.debug('Login has been successfully performed.');
@@ -82,7 +84,7 @@ export class TokenAuthenticationProvider extends BaseAuthenticationProvider {
public async authenticate(request: KibanaRequest, state?: ProviderState | null) {
this.logger.debug(`Trying to authenticate user request to ${request.url.path}.`);
- if (getHTTPAuthenticationScheme(request) != null) {
+ if (HTTPAuthorizationHeader.parseFromRequest(request) != null) {
this.logger.debug('Cannot authenticate requests with `Authorization` header.');
return AuthenticationResult.notHandled();
}
@@ -152,7 +154,9 @@ export class TokenAuthenticationProvider extends BaseAuthenticationProvider {
this.logger.debug('Trying to authenticate via state.');
try {
- const authHeaders = { authorization: `Bearer ${accessToken}` };
+ const authHeaders = {
+ authorization: new HTTPAuthorizationHeader('Bearer', accessToken).toString(),
+ };
const user = await this.getUser(request, authHeaders);
this.logger.debug('Request has been authenticated via state.');
@@ -199,7 +203,12 @@ export class TokenAuthenticationProvider extends BaseAuthenticationProvider {
}
try {
- const authHeaders = { authorization: `Bearer ${refreshedTokenPair.accessToken}` };
+ const authHeaders = {
+ authorization: new HTTPAuthorizationHeader(
+ 'Bearer',
+ refreshedTokenPair.accessToken
+ ).toString(),
+ };
const user = await this.getUser(request, authHeaders);
this.logger.debug('Request has been authenticated via refreshed token.');
diff --git a/x-pack/plugins/security/server/elasticsearch_client_plugin.ts b/x-pack/plugins/security/server/elasticsearch_client_plugin.ts
index 996dcb685f29b..529e8a8aa6e9c 100644
--- a/x-pack/plugins/security/server/elasticsearch_client_plugin.ts
+++ b/x-pack/plugins/security/server/elasticsearch_client_plugin.ts
@@ -538,6 +538,24 @@ export function elasticsearchClientPlugin(Client: any, config: unknown, componen
},
});
+ /**
+ * Grants an API key in Elasticsearch for the current user.
+ *
+ * @param {string} type The type of grant, either "password" or "access_token"
+ * @param {string} username Required when using the "password" type
+ * @param {string} password Required when using the "password" type
+ * @param {string} access_token Required when using the "access_token" type
+ *
+ * @returns {{api_key: string}}
+ */
+ shield.grantAPIKey = ca({
+ method: 'POST',
+ needBody: true,
+ url: {
+ fmt: '/_security/api_key/grant',
+ },
+ });
+
/**
* Invalidates an API key in Elasticsearch.
*
diff --git a/x-pack/plugins/security/server/plugin.test.ts b/x-pack/plugins/security/server/plugin.test.ts
index a1ef352056d6a..a011f7e7be11e 100644
--- a/x-pack/plugins/security/server/plugin.test.ts
+++ b/x-pack/plugins/security/server/plugin.test.ts
@@ -74,7 +74,9 @@ describe('Security Plugin', () => {
"createAPIKey": [Function],
"getCurrentUser": [Function],
"getSessionInfo": [Function],
+ "grantAPIKeyAsInternalUser": [Function],
"invalidateAPIKey": [Function],
+ "invalidateAPIKeyAsInternalUser": [Function],
"isAuthenticated": [Function],
"isProviderEnabled": [Function],
"login": [Function],
diff --git a/x-pack/plugins/security/server/routes/users/change_password.ts b/x-pack/plugins/security/server/routes/users/change_password.ts
index fc3ca4573d500..aa7e8bc26cc1f 100644
--- a/x-pack/plugins/security/server/routes/users/change_password.ts
+++ b/x-pack/plugins/security/server/routes/users/change_password.ts
@@ -8,6 +8,10 @@ import { schema } from '@kbn/config-schema';
import { canUserChangePassword } from '../../../common/model';
import { getErrorStatusCode, wrapIntoCustomErrorResponse } from '../../errors';
import { createLicensedRouteHandler } from '../licensed_route_handler';
+import {
+ HTTPAuthorizationHeader,
+ BasicHTTPAuthorizationHeaderCredentials,
+} from '../../authentication';
import { RouteDefinitionParams } from '..';
export function defineChangeUserPasswordRoutes({
@@ -43,9 +47,13 @@ export function defineChangeUserPasswordRoutes({
? {
headers: {
...request.headers,
- authorization: `Basic ${Buffer.from(`${username}:${currentPassword}`).toString(
- 'base64'
- )}`,
+ authorization: new HTTPAuthorizationHeader(
+ 'Basic',
+ new BasicHTTPAuthorizationHeaderCredentials(
+ username,
+ currentPassword || ''
+ ).toString()
+ ).toString(),
},
}
: request
diff --git a/x-pack/plugins/upgrade_assistant/common/types.ts b/x-pack/plugins/upgrade_assistant/common/types.ts
index 1114e889882c2..6c1b24b677754 100644
--- a/x-pack/plugins/upgrade_assistant/common/types.ts
+++ b/x-pack/plugins/upgrade_assistant/common/types.ts
@@ -30,7 +30,27 @@ export enum ReindexStatus {
export const REINDEX_OP_TYPE = 'upgrade-assistant-reindex-operation';
export interface QueueSettings extends SavedObjectAttributes {
+ /**
+ * A Unix timestamp of when the reindex operation was enqueued.
+ *
+ * @remark
+ * This is used by the reindexing scheduler to determine execution
+ * order.
+ */
queuedAt: number;
+
+ /**
+ * A Unix timestamp of when the reindex operation was started.
+ *
+ * @remark
+ * Updating this field is useful for _also_ updating the saved object "updated_at" field
+ * which is used to determine stale or abandoned reindex operations.
+ *
+ * For now this is used by the reindex worker scheduler to determine whether we have
+ * A queue item at the start of the queue.
+ *
+ */
+ startedAt?: number;
}
export interface ReindexOptions extends SavedObjectAttributes {
diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/error.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/error.ts
index 59922abd3e635..b1744c79bc26c 100644
--- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/error.ts
+++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/error.ts
@@ -13,6 +13,7 @@ import {
ReindexAlreadyInProgress,
MultipleReindexJobsFound,
ReindexCannotBeCancelled,
+ ReindexIsNotInQueue,
} from './error_symbols';
export class ReindexError extends Error {
@@ -32,6 +33,7 @@ export const error = {
reindexTaskFailed: createErrorFactory(ReindexTaskFailed),
reindexTaskCannotBeDeleted: createErrorFactory(ReindexTaskCannotBeDeleted),
reindexAlreadyInProgress: createErrorFactory(ReindexAlreadyInProgress),
+ reindexIsNotInQueue: createErrorFactory(ReindexIsNotInQueue),
multipleReindexJobsFound: createErrorFactory(MultipleReindexJobsFound),
reindexCannotBeCancelled: createErrorFactory(ReindexCannotBeCancelled),
};
diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/error_symbols.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/error_symbols.ts
index d5e8d643f4595..15d1b1bb9c6ae 100644
--- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/error_symbols.ts
+++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/error_symbols.ts
@@ -11,6 +11,7 @@ export const CannotCreateIndex = Symbol('CannotCreateIndex');
export const ReindexTaskFailed = Symbol('ReindexTaskFailed');
export const ReindexTaskCannotBeDeleted = Symbol('ReindexTaskCannotBeDeleted');
export const ReindexAlreadyInProgress = Symbol('ReindexAlreadyInProgress');
+export const ReindexIsNotInQueue = Symbol('ReindexIsNotInQueue');
export const ReindexCannotBeCancelled = Symbol('ReindexCannotBeCancelled');
export const MultipleReindexJobsFound = Symbol('MultipleReindexJobsFound');
diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/op_utils.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/op_utils.ts
index dbed7de13f010..ecba02e0d5466 100644
--- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/op_utils.ts
+++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/op_utils.ts
@@ -50,6 +50,9 @@ const orderQueuedReindexOperations = ({
),
});
+export const queuedOpHasStarted = (op: ReindexSavedObject) =>
+ Boolean(op.attributes.reindexOptions?.queueSettings?.startedAt);
+
export const sortAndOrderReindexOperations = flow(
sortReindexOperations,
orderQueuedReindexOperations
diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts
index b270998658db8..1fd022bce4dcf 100644
--- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts
+++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts
@@ -10,7 +10,6 @@ import { LicensingPluginSetup } from '../../../../licensing/server';
import {
IndexGroup,
- ReindexOptions,
ReindexSavedObject,
ReindexStatus,
ReindexStep,
@@ -59,7 +58,10 @@ export interface ReindexService {
* @param indexName
* @param opts Additional options when creating a new reindex operation
*/
- createReindexOperation(indexName: string, opts?: ReindexOptions): Promise;
+ createReindexOperation(
+ indexName: string,
+ opts?: { enqueue?: boolean }
+ ): Promise;
/**
* Retrieves all reindex operations that have the given status.
@@ -74,6 +76,12 @@ export interface ReindexService {
*/
findReindexOperation(indexName: string): Promise;
+ /**
+ * Delete reindex operations for completed indices with deprecations.
+ * @param indexNames
+ */
+ cleanupReindexOperations(indexNames: string[]): Promise | null;
+
/**
* Process the reindex operation through one step of the state machine and resolves
* to the updated reindex operation.
@@ -92,7 +100,21 @@ export interface ReindexService {
* @param indexName
* @param opts As with {@link createReindexOperation} we support this setting.
*/
- resumeReindexOperation(indexName: string, opts?: ReindexOptions): Promise;
+ resumeReindexOperation(
+ indexName: string,
+ opts?: { enqueue?: boolean }
+ ): Promise;
+
+ /**
+ * Update the update_at field on the reindex operation
+ *
+ * @remark
+ * Currently also sets a startedAt field on the SavedObject, not really used
+ * elsewhere, but is an indication that the object has started being processed.
+ *
+ * @param indexName
+ */
+ startQueuedReindexOperation(indexName: string): Promise;
/**
* Cancel an in-progress reindex operation for a given index. Only allowed when the
@@ -544,7 +566,7 @@ export const reindexServiceFactory = (
}
},
- async createReindexOperation(indexName: string, opts?: ReindexOptions) {
+ async createReindexOperation(indexName: string, opts?: { enqueue: boolean }) {
const indexExists = await callAsUser('indices.exists', { index: indexName });
if (!indexExists) {
throw error.indexNotFound(`Index ${indexName} does not exist in this cluster.`);
@@ -566,7 +588,10 @@ export const reindexServiceFactory = (
}
}
- return actions.createReindexOp(indexName, opts);
+ return actions.createReindexOp(
+ indexName,
+ opts?.enqueue ? { queueSettings: { queuedAt: Date.now() } } : undefined
+ );
},
async findReindexOperation(indexName: string) {
@@ -584,6 +609,23 @@ export const reindexServiceFactory = (
return findResponse.saved_objects[0];
},
+ async cleanupReindexOperations(indexNames: string[]) {
+ const performCleanup = async (indexName: string) => {
+ const existingReindexOps = await actions.findReindexOperations(indexName);
+
+ if (existingReindexOps && existingReindexOps.total !== 0) {
+ const existingOp = existingReindexOps.saved_objects[0];
+ if (existingOp.attributes.status === ReindexStatus.completed) {
+ // Delete the existing one if its status is completed, but still contains deprecation warnings
+ // example scenario: index was upgraded, but then deleted and restored with an old snapshot
+ await actions.deleteReindexOp(existingOp);
+ }
+ }
+ };
+
+ await Promise.all(indexNames.map(performCleanup));
+ },
+
findAllByStatus: actions.findAllByStatus,
async processNextStep(reindexOp: ReindexSavedObject) {
@@ -654,7 +696,7 @@ export const reindexServiceFactory = (
});
},
- async resumeReindexOperation(indexName: string, opts?: ReindexOptions) {
+ async resumeReindexOperation(indexName: string, opts?: { enqueue: boolean }) {
const reindexOp = await this.findReindexOperation(indexName);
if (!reindexOp) {
@@ -668,16 +710,30 @@ export const reindexServiceFactory = (
} else if (op.attributes.status !== ReindexStatus.paused) {
throw new Error(`Reindex operation must be paused in order to be resumed.`);
}
-
- const reindexOptions: ReindexOptions | undefined = opts
- ? {
- ...(op.attributes.reindexOptions ?? {}),
- ...opts,
- }
- : undefined;
+ const queueSettings = opts?.enqueue ? { queuedAt: Date.now() } : undefined;
return actions.updateReindexOp(op, {
status: ReindexStatus.inProgress,
+ reindexOptions: queueSettings ? { queueSettings } : undefined,
+ });
+ });
+ },
+
+ async startQueuedReindexOperation(indexName: string) {
+ const reindexOp = await this.findReindexOperation(indexName);
+
+ if (!reindexOp) {
+ throw error.indexNotFound(`No reindex operation found for index ${indexName}`);
+ }
+
+ if (!reindexOp.attributes.reindexOptions?.queueSettings) {
+ throw error.reindexIsNotInQueue(`Reindex operation ${indexName} is not in the queue.`);
+ }
+
+ return actions.runWhileLocked(reindexOp, async lockedReindexOp => {
+ const { reindexOptions } = lockedReindexOp.attributes;
+ reindexOptions!.queueSettings!.startedAt = Date.now();
+ return actions.updateReindexOp(lockedReindexOp, {
reindexOptions,
});
});
diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/worker.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/worker.ts
index 482b9f280ad7e..d6051ce46312f 100644
--- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/worker.ts
+++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/worker.ts
@@ -6,11 +6,11 @@
import { IClusterClient, Logger, SavedObjectsClientContract, FakeRequest } from 'src/core/server';
import moment from 'moment';
import { ReindexSavedObject, ReindexStatus } from '../../../common/types';
-import { CredentialStore } from './credential_store';
+import { Credential, CredentialStore } from './credential_store';
import { reindexActionsFactory } from './reindex_actions';
import { ReindexService, reindexServiceFactory } from './reindex_service';
import { LicensingPluginSetup } from '../../../../licensing/server';
-import { sortAndOrderReindexOperations } from './op_utils';
+import { sortAndOrderReindexOperations, queuedOpHasStarted } from './op_utils';
const POLL_INTERVAL = 30000;
// If no nodes have been able to update this index in 2 minutes (due to missing credentials), set to paused.
@@ -128,17 +128,34 @@ export class ReindexWorker {
}
};
+ private getCredentialScopedReindexService = (credential: Credential) => {
+ const fakeRequest: FakeRequest = { headers: credential };
+ const scopedClusterClient = this.clusterClient.asScoped(fakeRequest);
+ const callAsCurrentUser = scopedClusterClient.callAsCurrentUser.bind(scopedClusterClient);
+ const actions = reindexActionsFactory(this.client, callAsCurrentUser);
+ return reindexServiceFactory(callAsCurrentUser, actions, this.log, this.licensing);
+ };
+
private updateInProgressOps = async () => {
try {
const inProgressOps = await this.reindexService.findAllByStatus(ReindexStatus.inProgress);
const { parallel, queue } = sortAndOrderReindexOperations(inProgressOps);
- const [firstOpInQueue] = queue;
+ let [firstOpInQueue] = queue;
- if (firstOpInQueue) {
+ if (firstOpInQueue && !queuedOpHasStarted(firstOpInQueue)) {
this.log.debug(
`Queue detected; current length ${queue.length}, current item ReindexOperation(id: ${firstOpInQueue.id}, indexName: ${firstOpInQueue.attributes.indexName})`
);
+ const credential = this.credentialStore.get(firstOpInQueue);
+ if (credential) {
+ const service = this.getCredentialScopedReindexService(credential);
+ firstOpInQueue = await service.startQueuedReindexOperation(
+ firstOpInQueue.attributes.indexName
+ );
+ // Re-associate the credentials
+ this.credentialStore.set(firstOpInQueue, credential);
+ }
}
this.inProgressOps = parallel.concat(firstOpInQueue ? [firstOpInQueue] : []);
@@ -173,14 +190,7 @@ export class ReindexWorker {
}
}
- // Setup a ReindexService specific to these credentials.
- const fakeRequest: FakeRequest = { headers: credential };
-
- const scopedClusterClient = this.clusterClient.asScoped(fakeRequest);
- const callAsCurrentUser = scopedClusterClient.callAsCurrentUser.bind(scopedClusterClient);
- const actions = reindexActionsFactory(this.client, callAsCurrentUser);
-
- const service = reindexServiceFactory(callAsCurrentUser, actions, this.log, this.licensing);
+ const service = this.getCredentialScopedReindexService(credential);
reindexOp = await swallowExceptions(service.processNextStep, this.log)(reindexOp);
// Update credential store with most recent state.
diff --git a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts
index 22a121ab78683..fa4649f1c5dcd 100644
--- a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts
+++ b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts
@@ -7,8 +7,10 @@
import { getUpgradeAssistantStatus } from '../lib/es_migration_apis';
import { versionCheckHandlerWrapper } from '../lib/es_version_precheck';
import { RouteDependencies } from '../types';
+import { reindexActionsFactory } from '../lib/reindexing/reindex_actions';
+import { reindexServiceFactory } from '../lib/reindexing';
-export function registerClusterCheckupRoutes({ cloud, router }: RouteDependencies) {
+export function registerClusterCheckupRoutes({ cloud, router, licensing, log }: RouteDependencies) {
const isCloudEnabled = Boolean(cloud?.isCloudEnabled);
router.get(
@@ -20,6 +22,7 @@ export function registerClusterCheckupRoutes({ cloud, router }: RouteDependencie
async (
{
core: {
+ savedObjects: { client: savedObjectsClient },
elasticsearch: { dataClient },
},
},
@@ -27,8 +30,24 @@ export function registerClusterCheckupRoutes({ cloud, router }: RouteDependencie
response
) => {
try {
+ const status = await getUpgradeAssistantStatus(dataClient, isCloudEnabled);
+
+ const callAsCurrentUser = dataClient.callAsCurrentUser.bind(dataClient);
+ const reindexActions = reindexActionsFactory(savedObjectsClient, callAsCurrentUser);
+ const reindexService = reindexServiceFactory(
+ callAsCurrentUser,
+ reindexActions,
+ log,
+ licensing
+ );
+ const indexNames = status.indices
+ .filter(({ index }) => typeof index !== 'undefined')
+ .map(({ index }) => index as string);
+
+ await reindexService.cleanupReindexOperations(indexNames);
+
return response.ok({
- body: await getUpgradeAssistantStatus(dataClient, isCloudEnabled),
+ body: status,
});
} catch (e) {
if (e.status === 403) {
diff --git a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_handler.ts b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_handler.ts
index e640d03791cce..74c349d894839 100644
--- a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_handler.ts
+++ b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_handler.ts
@@ -8,7 +8,7 @@ import { IScopedClusterClient, Logger, SavedObjectsClientContract } from 'kibana
import { LicensingPluginSetup } from '../../../../licensing/server';
-import { ReindexOperation, ReindexOptions, ReindexStatus } from '../../../common/types';
+import { ReindexOperation, ReindexStatus } from '../../../common/types';
import { reindexActionsFactory } from '../../lib/reindexing/reindex_actions';
import { reindexServiceFactory } from '../../lib/reindexing';
@@ -53,17 +53,11 @@ export const reindexHandler = async ({
const existingOp = await reindexService.findReindexOperation(indexName);
- const opts: ReindexOptions | undefined = reindexOptions
- ? {
- queueSettings: reindexOptions.enqueue ? { queuedAt: Date.now() } : undefined,
- }
- : undefined;
-
// If the reindexOp already exists and it's paused, resume it. Otherwise create a new one.
const reindexOp =
existingOp && existingOp.attributes.status === ReindexStatus.paused
- ? await reindexService.resumeReindexOperation(indexName, opts)
- : await reindexService.createReindexOperation(indexName, opts);
+ ? await reindexService.resumeReindexOperation(indexName, reindexOptions)
+ : await reindexService.createReindexOperation(indexName, reindexOptions);
// Add users credentials for the worker to use
credentialStore.set(reindexOp, headers);
diff --git a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.test.ts
index df8b2fa80a25a..e739531e0e22c 100644
--- a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.test.ts
+++ b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.test.ts
@@ -261,7 +261,7 @@ describe('reindex API', () => {
describe('POST /api/upgrade_assistant/reindex/batch', () => {
const queueSettingsArg = {
- queueSettings: { queuedAt: expect.any(Number) },
+ enqueue: true,
};
it('creates a collection of index operations', async () => {
mockReindexService.createReindexOperation
diff --git a/x-pack/test/accessibility/apps/login_page.ts b/x-pack/test/accessibility/apps/login_page.ts
index 5b18b6be9e3a4..8c673bb332d91 100644
--- a/x-pack/test/accessibility/apps/login_page.ts
+++ b/x-pack/test/accessibility/apps/login_page.ts
@@ -28,14 +28,33 @@ export default function({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.security.forceLogout();
});
- it('meets a11y requirements', async () => {
+ it('login page meets a11y requirements', async () => {
await PageObjects.common.navigateToApp('login');
await retry.waitFor(
'login page visible',
async () => await testSubjects.exists('loginSubmit')
);
+ await a11y.testAppSnapshot();
+ });
+
+ it('User can login with a11y requirements', async () => {
+ await PageObjects.security.login();
+ await a11y.testAppSnapshot();
+ });
+
+ it('Wrong credentials message meets a11y requirements', async () => {
+ await PageObjects.security.loginPage.login('wrong-user', 'wrong-password', {
+ expectSuccess: false,
+ });
+ await PageObjects.security.loginPage.getErrorMessage();
+ await a11y.testAppSnapshot();
+ });
+ it('Logout message acknowledges a11y requirements', async () => {
+ await PageObjects.security.login();
+ await PageObjects.security.logout();
+ await testSubjects.getVisibleText('loginInfoMessage');
await a11y.testAppSnapshot();
});
});
diff --git a/x-pack/test/api_integration/apis/infra/metrics_alerting.ts b/x-pack/test/api_integration/apis/infra/metrics_alerting.ts
index 09f5a498ddc00..4f17f9db67483 100644
--- a/x-pack/test/api_integration/apis/infra/metrics_alerting.ts
+++ b/x-pack/test/api_integration/apis/infra/metrics_alerting.ts
@@ -13,11 +13,13 @@ import { FtrProviderContext } from '../../ftr_provider_context';
export default function({ getService }: FtrProviderContext) {
const client = getService('legacyEs');
const index = 'test-index';
- const baseParams = {
- metric: 'test.metric',
- timeUnit: 'm',
- timeSize: 5,
- };
+ const getSearchParams = (aggType: string) =>
+ ({
+ aggType,
+ timeUnit: 'm',
+ timeSize: 5,
+ ...(aggType !== 'count' ? { metric: 'test.metric' } : {}),
+ } as MetricExpressionParams);
describe('Metrics Threshold Alerts', () => {
before(async () => {
await client.index({
@@ -30,10 +32,7 @@ export default function({ getService }: FtrProviderContext) {
describe('querying the entire infrastructure', () => {
for (const aggType of aggs) {
it(`should work with the ${aggType} aggregator`, async () => {
- const searchBody = getElasticsearchMetricQuery({
- ...baseParams,
- aggType,
- } as MetricExpressionParams);
+ const searchBody = getElasticsearchMetricQuery(getSearchParams(aggType));
const result = await client.search({
index,
body: searchBody,
@@ -44,10 +43,7 @@ export default function({ getService }: FtrProviderContext) {
}
it('should work with a filterQuery', async () => {
const searchBody = getElasticsearchMetricQuery(
- {
- ...baseParams,
- aggType: 'avg',
- } as MetricExpressionParams,
+ getSearchParams('avg'),
undefined,
'{"bool":{"should":[{"match_phrase":{"agent.hostname":"foo"}}],"minimum_should_match":1}}'
);
@@ -62,13 +58,7 @@ export default function({ getService }: FtrProviderContext) {
describe('querying with a groupBy parameter', () => {
for (const aggType of aggs) {
it(`should work with the ${aggType} aggregator`, async () => {
- const searchBody = getElasticsearchMetricQuery(
- {
- ...baseParams,
- aggType,
- } as MetricExpressionParams,
- 'agent.id'
- );
+ const searchBody = getElasticsearchMetricQuery(getSearchParams(aggType), 'agent.id');
const result = await client.search({
index,
body: searchBody,
@@ -79,10 +69,7 @@ export default function({ getService }: FtrProviderContext) {
}
it('should work with a filterQuery', async () => {
const searchBody = getElasticsearchMetricQuery(
- {
- ...baseParams,
- aggType: 'avg',
- } as MetricExpressionParams,
+ getSearchParams('avg'),
'agent.id',
'{"bool":{"should":[{"match_phrase":{"agent.hostname":"foo"}}],"minimum_should_match":1}}'
);
diff --git a/x-pack/test/functional/apps/uptime/locations.ts b/x-pack/test/functional/apps/uptime/locations.ts
index 7f6932ab50319..96c7fad89a85d 100644
--- a/x-pack/test/functional/apps/uptime/locations.ts
+++ b/x-pack/test/functional/apps/uptime/locations.ts
@@ -10,7 +10,7 @@ import { FtrProviderContext } from '../../ftr_provider_context';
export default ({ getPageObjects, getService }: FtrProviderContext) => {
const pageObjects = getPageObjects(['uptime']);
- describe('location', () => {
+ describe.skip('location', () => {
const start = new Date().toISOString();
const end = new Date().toISOString();
diff --git a/x-pack/test/functional/apps/uptime/settings.ts b/x-pack/test/functional/apps/uptime/settings.ts
index 0e804dd161c6b..aafb145a1b9b0 100644
--- a/x-pack/test/functional/apps/uptime/settings.ts
+++ b/x-pack/test/functional/apps/uptime/settings.ts
@@ -16,7 +16,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const pageObjects = getPageObjects(['uptime']);
const es = getService('es');
- describe('uptime settings page', () => {
+ // Flaky https://github.com/elastic/kibana/issues/60866
+ describe.skip('uptime settings page', () => {
const settingsPage = () => pageObjects.uptime.settings;
beforeEach('navigate to clean app root', async () => {
// make 10 checks
diff --git a/x-pack/test/functional/apps/visualize/index.ts b/x-pack/test/functional/apps/visualize/index.ts
index 29b1ef9870d7d..4335690b6a70e 100644
--- a/x-pack/test/functional/apps/visualize/index.ts
+++ b/x-pack/test/functional/apps/visualize/index.ts
@@ -13,5 +13,6 @@ export default function visualize({ loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./feature_controls/visualize_security'));
loadTestFile(require.resolve('./feature_controls/visualize_spaces'));
loadTestFile(require.resolve('./hybrid_visualization'));
+ loadTestFile(require.resolve('./precalculated_histogram'));
});
}
diff --git a/x-pack/test/functional/apps/visualize/precalculated_histogram.ts b/x-pack/test/functional/apps/visualize/precalculated_histogram.ts
new file mode 100644
index 0000000000000..5d362d29b640c
--- /dev/null
+++ b/x-pack/test/functional/apps/visualize/precalculated_histogram.ts
@@ -0,0 +1,60 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import expect from '@kbn/expect';
+import { FtrProviderContext } from '../../ftr_provider_context';
+
+export default function({ getService, getPageObjects }: FtrProviderContext) {
+ const esArchiver = getService('esArchiver');
+ const PageObjects = getPageObjects(['common', 'visualize', 'discover', 'visChart', 'visEditor']);
+ const kibanaServer = getService('kibanaServer');
+ const log = getService('log');
+
+ describe('pre_calculated_histogram', function() {
+ before(async function() {
+ log.debug('Starting pre_calculated_histogram before method');
+ await esArchiver.load('pre_calculated_histogram');
+ await kibanaServer.uiSettings.replace({ defaultIndex: 'test-histogram' });
+ });
+
+ after(function() {
+ return esArchiver.unload('pre_calculated_histogram');
+ });
+
+ const initHistogramBarChart = async () => {
+ await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.clickVerticalBarChart();
+ await PageObjects.visualize.clickNewSearch('histogram-test');
+ await PageObjects.visChart.waitForVisualization();
+ };
+
+ const getFieldOptionsForAggregation = async (aggregation: string): Promise => {
+ await PageObjects.visEditor.clickBucket('Y-axis', 'metrics');
+ await PageObjects.visEditor.selectAggregation(aggregation, 'metrics');
+ const fieldValues = await PageObjects.visEditor.getField();
+ return fieldValues;
+ };
+
+ it('appears correctly in discover', async function() {
+ await PageObjects.common.navigateToApp('discover');
+ const rowData = await PageObjects.discover.getDocTableIndex(1);
+ expect(rowData.includes('"values": [ 0.3, 1, 3, 4.2, 4.8 ]')).to.be.ok();
+ });
+
+ it('appears in the field options of a Percentiles aggregation', async function() {
+ await initHistogramBarChart();
+ const fieldValues: string[] = await getFieldOptionsForAggregation('Percentiles');
+ log.debug('Percentiles Fields = ' + fieldValues);
+ expect(fieldValues[0]).to.be('histogram-content');
+ });
+
+ it('appears in the field options of a Percentile Ranks aggregation', async function() {
+ const fieldValues: string[] = await getFieldOptionsForAggregation('Percentile Ranks');
+ log.debug('Percentile Ranks Fields = ' + fieldValues);
+ expect(fieldValues[0]).to.be('histogram-content');
+ });
+ });
+}
diff --git a/x-pack/test/functional/es_archives/pre_calculated_histogram/data.json b/x-pack/test/functional/es_archives/pre_calculated_histogram/data.json
new file mode 100644
index 0000000000000..cab1dbdf84483
--- /dev/null
+++ b/x-pack/test/functional/es_archives/pre_calculated_histogram/data.json
@@ -0,0 +1,197 @@
+{
+ "type": "doc",
+ "value": {
+ "id": "index-pattern:histogram-test",
+ "index": ".kibana",
+ "source": {
+ "index-pattern": {
+ "title": "histogram-test",
+ "fields": "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"histogram-content\",\"type\":\"histogram\",\"esTypes\":[\"histogram\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"histogram-title\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]"
+ },
+ "type": "index-pattern"
+ }
+ }
+}
+
+{
+ "type": "doc",
+ "value": {
+ "id": "5e69404d93193e4074f0ec1a",
+ "index": "histogram-test",
+ "source": {
+ "histogram-title": "incididunt reprehenderit mollit",
+ "histogram-content": {
+ "values": [
+ 0.3,
+ 1,
+ 3,
+ 4.2,
+ 4.8
+ ],
+ "counts": [
+ 237,
+ 170,
+ 33,
+ 149,
+ 241
+ ]
+ }
+ }
+ }
+}
+
+{
+ "type": "doc",
+ "value": {
+ "id": "5e69408f2fc61f57fd5bc762",
+ "index": "histogram-test",
+ "source": {
+ "histogram-title": "culpa cillum ullamco",
+ "histogram-content": {
+ "values": [
+ 0.5,
+ 1,
+ 1.2,
+ 1.3,
+ 2.8,
+ 3.9,
+ 4.3
+ ],
+ "counts": [
+ 113,
+ 197,
+ 20,
+ 66,
+ 20,
+ 39,
+ 178
+ ]
+ }
+ }
+ }
+}
+
+{
+ "type": "doc",
+ "value": {
+ "id": "5e6940b979b57ad343114cc3",
+ "index": "histogram-test",
+ "source": {
+ "histogram-title": "enim veniam et",
+ "histogram-content": {
+ "values": [
+ 3.7,
+ 4.2
+ ],
+ "counts": [
+ 227,
+ 141
+ ]
+ }
+ }
+ }
+}
+
+{
+ "type": "doc",
+ "value": {
+ "id": "5e6940d3e95de786eeb7586d",
+ "index": "histogram-test",
+ "source": {
+ "histogram-title": "est incididunt sunt",
+ "histogram-content": {
+ "values": [
+ 1.8,
+ 2.4,
+ 2.6,
+ 4.9
+ ],
+ "counts": [
+ 92,
+ 101,
+ 122,
+ 244
+ ]
+ }
+ }
+ }
+}
+
+{
+ "type": "doc",
+ "value": {
+ "id": "5e694119fb2f956a822b93b9",
+ "index": "histogram-test",
+ "source": {
+ "histogram-title": "qui qui tempor",
+ "histogram-content": {
+ "values": [
+ 0.5,
+ 2.1,
+ 2.7,
+ 3,
+ 3.2,
+ 3.5,
+ 4.2,
+ 5
+ ],
+ "counts": [
+ 210,
+ 168,
+ 182,
+ 181,
+ 97,
+ 164,
+ 77,
+ 2
+ ]
+ }
+ }
+ }
+}
+
+{
+ "type": "doc",
+ "value": {
+ "id": "5e694145ad3c741aa12d6e8e",
+ "index": "histogram-test",
+ "source": {
+ "histogram-title": "ullamco nisi sunt",
+ "histogram-content": {
+ "values": [
+ 1.7,
+ 4.5,
+ 4.8
+ ],
+ "counts": [
+ 74,
+ 146,
+ 141
+ ]
+ }
+ }
+ }
+}
+
+{
+ "type": "doc",
+ "value": {
+ "id": "5e694159d909d9d99b5e12d1",
+ "index": "histogram-test",
+ "source": {
+ "histogram-title": "magna eu incididunt",
+ "histogram-content": {
+ "values": [
+ 1,
+ 3.4,
+ 4.8
+ ],
+ "counts": [
+ 103,
+ 205,
+ 11
+ ]
+ }
+ }
+ }
+}
diff --git a/x-pack/test/functional/es_archives/pre_calculated_histogram/mappings.json b/x-pack/test/functional/es_archives/pre_calculated_histogram/mappings.json
new file mode 100644
index 0000000000000..f616daf9d5ccb
--- /dev/null
+++ b/x-pack/test/functional/es_archives/pre_calculated_histogram/mappings.json
@@ -0,0 +1,29 @@
+{
+ "type": "index",
+ "value": {
+ "aliases": {},
+ "index": "histogram-test",
+ "mappings": {
+ "properties": {
+ "histogram-title": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "histogram-content": {
+ "type": "histogram"
+ }
+ }
+ },
+ "settings": {
+ "index": {
+ "number_of_replicas": "0",
+ "number_of_shards": "1"
+ }
+ }
+ }
+}
diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts
index 145b536a26f61..2c29954528bd5 100644
--- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts
@@ -254,12 +254,15 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
it('renders the active alert instances', async () => {
+ // refresh to ensure Api call and UI are looking at freshest output
+ await browser.refresh();
+
// Verify content
await testSubjects.existOrFail('alertInstancesList');
const { alertInstances } = await alerting.alerts.getAlertState(alert.id);
- const dateOnAllInstances = mapValues(
+ const dateOnAllInstancesFromApiResponse = mapValues>(
alertInstances,
({
meta: {
@@ -268,28 +271,32 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
}) => date
);
- log.debug(`API RESULT: ${JSON.stringify(dateOnAllInstances)}`);
+ log.debug(
+ `API RESULT: ${Object.entries(dateOnAllInstancesFromApiResponse)
+ .map(([id, date]) => `${id}: ${moment(date).utc()}`)
+ .join(', ')}`
+ );
const instancesList = await pageObjects.alertDetailsUI.getAlertInstancesList();
expect(instancesList.map(instance => omit(instance, 'duration'))).to.eql([
{
instance: 'us-central',
status: 'Active',
- start: moment(dateOnAllInstances['us-central'])
+ start: moment(dateOnAllInstancesFromApiResponse['us-central'])
.utc()
.format('D MMM YYYY @ HH:mm:ss'),
},
{
instance: 'us-east',
status: 'Active',
- start: moment(dateOnAllInstances['us-east'])
+ start: moment(dateOnAllInstancesFromApiResponse['us-east'])
.utc()
.format('D MMM YYYY @ HH:mm:ss'),
},
{
instance: 'us-west',
status: 'Active',
- start: moment(dateOnAllInstances['us-west'])
+ start: moment(dateOnAllInstancesFromApiResponse['us-west'])
.utc()
.format('D MMM YYYY @ HH:mm:ss'),
},
@@ -299,30 +306,41 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await pageObjects.alertDetailsUI.getAlertInstanceDurationEpoch()
).utc();
- const durationFromInstanceTillPageLoad = mapValues(dateOnAllInstances, date =>
- moment.duration(durationEpoch.diff(moment(date).utc()))
+ log.debug(`DURATION EPOCH is: ${durationEpoch}]`);
+
+ const durationFromInstanceInApiUntilPageLoad = mapValues(
+ dateOnAllInstancesFromApiResponse,
+ // time from Alert Instance until pageload (AKA durationEpoch)
+ date => {
+ const durationFromApiResuiltToEpoch = moment.duration(
+ durationEpoch.diff(moment(date).utc())
+ );
+ // The UI removes milliseconds, so lets do the same in the test so we can compare
+ return moment.duration({
+ hours: durationFromApiResuiltToEpoch.hours(),
+ minutes: durationFromApiResuiltToEpoch.minutes(),
+ seconds: durationFromApiResuiltToEpoch.seconds(),
+ });
+ }
);
+
instancesList
.map(alertInstance => ({
id: alertInstance.instance,
- duration: alertInstance.duration.split(':').map(part => parseInt(part, 10)),
+ // time from Alert Instance used to render the list until pageload (AKA durationEpoch)
+ duration: moment.duration(alertInstance.duration),
}))
- .map(({ id, duration: [hours, minutes, seconds] }) => ({
- id,
- duration: moment.duration({
- hours,
- minutes,
- seconds,
- }),
- }))
- .forEach(({ id, duration }) => {
- // make sure the duration is within a 10 second range which is
- // good enough as the alert interval is 1m, so we know it is a fresh value
- expect(duration.as('milliseconds')).to.greaterThan(
- durationFromInstanceTillPageLoad[id].subtract(1000 * 10).as('milliseconds')
+ .forEach(({ id, duration: durationAsItAppearsOnList }) => {
+ log.debug(
+ `DURATION of ${id} [From UI: ${durationAsItAppearsOnList.as(
+ 'seconds'
+ )} seconds] [From API: ${durationFromInstanceInApiUntilPageLoad[id].as(
+ 'seconds'
+ )} seconds]`
);
- expect(duration.as('milliseconds')).to.lessThan(
- durationFromInstanceTillPageLoad[id].add(1000 * 10).as('milliseconds')
+
+ expect(durationFromInstanceInApiUntilPageLoad[id].as('seconds')).to.equal(
+ durationAsItAppearsOnList.as('seconds')
);
});
});
diff --git a/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts b/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts
index 6c41c2cab801e..2a50c0117eae9 100644
--- a/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts
+++ b/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts
@@ -111,11 +111,11 @@ export function TriggersActionsPageProvider({ getService }: FtrProviderContext)
const table = await find.byCssSelector('[data-test-subj="alertsList"] table');
const $ = await table.parseDomContent();
const rows = $.findTestSubjects('alert-row').toArray();
- expect(rows.length).not.to.eql(0);
+ expect(rows.length).to.eql(0);
const emptyRow = await find.byCssSelector(
'[data-test-subj="alertsList"] table .euiTableRow'
);
- expect(await emptyRow.getVisibleText()).not.to.eql('No items found');
+ expect(await emptyRow.getVisibleText()).to.eql('No items found');
});
return true;
},
diff --git a/x-pack/test/siem_cypress/es_archives/closed_signals/data.json.gz b/x-pack/test/siem_cypress/es_archives/closed_signals/data.json.gz
new file mode 100644
index 0000000000000..117c829b31d6e
Binary files /dev/null and b/x-pack/test/siem_cypress/es_archives/closed_signals/data.json.gz differ
diff --git a/x-pack/test/siem_cypress/es_archives/closed_signals/mappings.json b/x-pack/test/siem_cypress/es_archives/closed_signals/mappings.json
new file mode 100644
index 0000000000000..94d89ed55dd8a
--- /dev/null
+++ b/x-pack/test/siem_cypress/es_archives/closed_signals/mappings.json
@@ -0,0 +1,7605 @@
+{
+ "type": "index",
+ "value": {
+ "aliases": {
+ ".siem-signals-default": {
+ "is_write_index": true
+ }
+ },
+ "index": ".siem-signals-default-000001",
+ "mappings": {
+ "dynamic": "false",
+ "properties": {
+ "@timestamp": {
+ "type": "date"
+ },
+ "agent": {
+ "properties": {
+ "ephemeral_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "as": {
+ "properties": {
+ "number": {
+ "type": "long"
+ },
+ "organization": {
+ "properties": {
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "client": {
+ "properties": {
+ "address": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "as": {
+ "properties": {
+ "number": {
+ "type": "long"
+ },
+ "organization": {
+ "properties": {
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "bytes": {
+ "type": "long"
+ },
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "geo": {
+ "properties": {
+ "city_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "location": {
+ "type": "geo_point"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "ip": {
+ "type": "ip"
+ },
+ "mac": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "nat": {
+ "properties": {
+ "ip": {
+ "type": "ip"
+ },
+ "port": {
+ "type": "long"
+ }
+ }
+ },
+ "packets": {
+ "type": "long"
+ },
+ "port": {
+ "type": "long"
+ },
+ "registered_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "top_level_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "user": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "email": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full_name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "cloud": {
+ "properties": {
+ "account": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "availability_zone": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "instance": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "machine": {
+ "properties": {
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "provider": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "container": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "image": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "tag": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "labels": {
+ "type": "object"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "runtime": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "destination": {
+ "properties": {
+ "address": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "as": {
+ "properties": {
+ "number": {
+ "type": "long"
+ },
+ "organization": {
+ "properties": {
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "bytes": {
+ "type": "long"
+ },
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "geo": {
+ "properties": {
+ "city_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "location": {
+ "type": "geo_point"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "ip": {
+ "type": "ip"
+ },
+ "mac": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "nat": {
+ "properties": {
+ "ip": {
+ "type": "ip"
+ },
+ "port": {
+ "type": "long"
+ }
+ }
+ },
+ "packets": {
+ "type": "long"
+ },
+ "port": {
+ "type": "long"
+ },
+ "registered_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "top_level_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "user": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "email": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full_name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "dns": {
+ "properties": {
+ "answers": {
+ "properties": {
+ "class": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "data": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ttl": {
+ "type": "long"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "header_flags": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "op_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "question": {
+ "properties": {
+ "class": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "registered_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subdomain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "top_level_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "resolved_ip": {
+ "type": "ip"
+ },
+ "response_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "ecs": {
+ "properties": {
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "error": {
+ "properties": {
+ "code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "message": {
+ "norms": false,
+ "type": "text"
+ },
+ "stack_trace": {
+ "doc_values": false,
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "index": false,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "event": {
+ "properties": {
+ "action": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "category": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "created": {
+ "type": "date"
+ },
+ "dataset": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "duration": {
+ "type": "long"
+ },
+ "end": {
+ "type": "date"
+ },
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ingested": {
+ "type": "date"
+ },
+ "kind": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "module": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "original": {
+ "doc_values": false,
+ "ignore_above": 1024,
+ "index": false,
+ "type": "keyword"
+ },
+ "outcome": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "provider": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "risk_score": {
+ "type": "float"
+ },
+ "risk_score_norm": {
+ "type": "float"
+ },
+ "sequence": {
+ "type": "long"
+ },
+ "severity": {
+ "type": "long"
+ },
+ "start": {
+ "type": "date"
+ },
+ "timezone": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "file": {
+ "properties": {
+ "accessed": {
+ "type": "date"
+ },
+ "attributes": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "created": {
+ "type": "date"
+ },
+ "ctime": {
+ "type": "date"
+ },
+ "device": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "directory": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "drive_letter": {
+ "ignore_above": 1,
+ "type": "keyword"
+ },
+ "extension": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "gid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "hash": {
+ "properties": {
+ "md5": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha1": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha512": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "inode": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "mode": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "mtime": {
+ "type": "date"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "owner": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "path": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "size": {
+ "type": "long"
+ },
+ "target_path": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "uid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "geo": {
+ "properties": {
+ "city_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "location": {
+ "type": "geo_point"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "group": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hash": {
+ "properties": {
+ "md5": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha1": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha512": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "host": {
+ "properties": {
+ "architecture": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "geo": {
+ "properties": {
+ "city_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "location": {
+ "type": "geo_point"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hostname": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ip": {
+ "type": "ip"
+ },
+ "mac": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "os": {
+ "properties": {
+ "family": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "kernel": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "platform": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "uptime": {
+ "type": "long"
+ },
+ "user": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "email": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full_name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "http": {
+ "properties": {
+ "request": {
+ "properties": {
+ "body": {
+ "properties": {
+ "bytes": {
+ "type": "long"
+ },
+ "content": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "bytes": {
+ "type": "long"
+ },
+ "method": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "referrer": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "response": {
+ "properties": {
+ "body": {
+ "properties": {
+ "bytes": {
+ "type": "long"
+ },
+ "content": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "bytes": {
+ "type": "long"
+ },
+ "status_code": {
+ "type": "long"
+ }
+ }
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "labels": {
+ "type": "object"
+ },
+ "log": {
+ "properties": {
+ "level": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "logger": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "origin": {
+ "properties": {
+ "file": {
+ "properties": {
+ "line": {
+ "type": "integer"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "function": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "original": {
+ "doc_values": false,
+ "ignore_above": 1024,
+ "index": false,
+ "type": "keyword"
+ },
+ "syslog": {
+ "properties": {
+ "facility": {
+ "properties": {
+ "code": {
+ "type": "long"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "priority": {
+ "type": "long"
+ },
+ "severity": {
+ "properties": {
+ "code": {
+ "type": "long"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "message": {
+ "norms": false,
+ "type": "text"
+ },
+ "network": {
+ "properties": {
+ "application": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "bytes": {
+ "type": "long"
+ },
+ "community_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "direction": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "forwarded_ip": {
+ "type": "ip"
+ },
+ "iana_number": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "packets": {
+ "type": "long"
+ },
+ "protocol": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "transport": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "observer": {
+ "properties": {
+ "geo": {
+ "properties": {
+ "city_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "location": {
+ "type": "geo_point"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hostname": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ip": {
+ "type": "ip"
+ },
+ "mac": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "os": {
+ "properties": {
+ "family": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "kernel": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "platform": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "product": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "serial_number": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "vendor": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "organization": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "os": {
+ "properties": {
+ "family": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "kernel": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "platform": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "package": {
+ "properties": {
+ "architecture": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "build_version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "checksum": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "description": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "install_scope": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "installed": {
+ "type": "date"
+ },
+ "license": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "path": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "size": {
+ "type": "long"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "process": {
+ "properties": {
+ "args": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "args_count": {
+ "type": "long"
+ },
+ "command_line": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "executable": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "exit_code": {
+ "type": "long"
+ },
+ "hash": {
+ "properties": {
+ "md5": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha1": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha512": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "parent": {
+ "properties": {
+ "args": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "args_count": {
+ "type": "long"
+ },
+ "command_line": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "executable": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "exit_code": {
+ "type": "long"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "pgid": {
+ "type": "long"
+ },
+ "pid": {
+ "type": "long"
+ },
+ "ppid": {
+ "type": "long"
+ },
+ "start": {
+ "type": "date"
+ },
+ "thread": {
+ "properties": {
+ "id": {
+ "type": "long"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "title": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "uptime": {
+ "type": "long"
+ },
+ "working_directory": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "pgid": {
+ "type": "long"
+ },
+ "pid": {
+ "type": "long"
+ },
+ "ppid": {
+ "type": "long"
+ },
+ "start": {
+ "type": "date"
+ },
+ "thread": {
+ "properties": {
+ "id": {
+ "type": "long"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "title": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "uptime": {
+ "type": "long"
+ },
+ "working_directory": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "registry": {
+ "properties": {
+ "data": {
+ "properties": {
+ "bytes": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "strings": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hive": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "key": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "path": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "value": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "related": {
+ "properties": {
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ip": {
+ "type": "ip"
+ },
+ "user": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "rule": {
+ "properties": {
+ "category": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "description": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ruleset": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "uuid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "server": {
+ "properties": {
+ "address": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "as": {
+ "properties": {
+ "number": {
+ "type": "long"
+ },
+ "organization": {
+ "properties": {
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "bytes": {
+ "type": "long"
+ },
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "geo": {
+ "properties": {
+ "city_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "location": {
+ "type": "geo_point"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "ip": {
+ "type": "ip"
+ },
+ "mac": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "nat": {
+ "properties": {
+ "ip": {
+ "type": "ip"
+ },
+ "port": {
+ "type": "long"
+ }
+ }
+ },
+ "packets": {
+ "type": "long"
+ },
+ "port": {
+ "type": "long"
+ },
+ "registered_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "top_level_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "user": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "email": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full_name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "service": {
+ "properties": {
+ "ephemeral_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "node": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "state": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "signal": {
+ "properties": {
+ "ancestors": {
+ "properties": {
+ "depth": {
+ "type": "long"
+ },
+ "id": {
+ "type": "keyword"
+ },
+ "rule": {
+ "type": "keyword"
+ },
+ "type": {
+ "type": "keyword"
+ }
+ }
+ },
+ "original_event": {
+ "properties": {
+ "action": {
+ "type": "keyword"
+ },
+ "category": {
+ "type": "keyword"
+ },
+ "code": {
+ "type": "keyword"
+ },
+ "created": {
+ "type": "date"
+ },
+ "dataset": {
+ "type": "keyword"
+ },
+ "duration": {
+ "type": "long"
+ },
+ "end": {
+ "type": "date"
+ },
+ "hash": {
+ "type": "keyword"
+ },
+ "id": {
+ "type": "keyword"
+ },
+ "kind": {
+ "type": "keyword"
+ },
+ "module": {
+ "type": "keyword"
+ },
+ "original": {
+ "doc_values": false,
+ "index": false,
+ "type": "keyword"
+ },
+ "outcome": {
+ "type": "keyword"
+ },
+ "provider": {
+ "type": "keyword"
+ },
+ "risk_score": {
+ "type": "float"
+ },
+ "risk_score_norm": {
+ "type": "float"
+ },
+ "sequence": {
+ "type": "long"
+ },
+ "severity": {
+ "type": "long"
+ },
+ "start": {
+ "type": "date"
+ },
+ "timezone": {
+ "type": "keyword"
+ },
+ "type": {
+ "type": "keyword"
+ }
+ }
+ },
+ "original_time": {
+ "type": "date"
+ },
+ "parent": {
+ "properties": {
+ "depth": {
+ "type": "long"
+ },
+ "id": {
+ "type": "keyword"
+ },
+ "index": {
+ "type": "keyword"
+ },
+ "rule": {
+ "type": "keyword"
+ },
+ "type": {
+ "type": "keyword"
+ }
+ }
+ },
+ "rule": {
+ "properties": {
+ "created_at": {
+ "type": "date"
+ },
+ "created_by": {
+ "type": "keyword"
+ },
+ "description": {
+ "type": "keyword"
+ },
+ "enabled": {
+ "type": "keyword"
+ },
+ "false_positives": {
+ "type": "keyword"
+ },
+ "filters": {
+ "type": "object"
+ },
+ "from": {
+ "type": "keyword"
+ },
+ "id": {
+ "type": "keyword"
+ },
+ "immutable": {
+ "type": "keyword"
+ },
+ "index": {
+ "type": "keyword"
+ },
+ "interval": {
+ "type": "keyword"
+ },
+ "language": {
+ "type": "keyword"
+ },
+ "max_signals": {
+ "type": "keyword"
+ },
+ "name": {
+ "type": "keyword"
+ },
+ "note": {
+ "type": "text"
+ },
+ "output_index": {
+ "type": "keyword"
+ },
+ "query": {
+ "type": "keyword"
+ },
+ "references": {
+ "type": "keyword"
+ },
+ "risk_score": {
+ "type": "keyword"
+ },
+ "rule_id": {
+ "type": "keyword"
+ },
+ "saved_id": {
+ "type": "keyword"
+ },
+ "severity": {
+ "type": "keyword"
+ },
+ "size": {
+ "type": "keyword"
+ },
+ "tags": {
+ "type": "keyword"
+ },
+ "threat": {
+ "properties": {
+ "framework": {
+ "type": "keyword"
+ },
+ "tactic": {
+ "properties": {
+ "id": {
+ "type": "keyword"
+ },
+ "name": {
+ "type": "keyword"
+ },
+ "reference": {
+ "type": "keyword"
+ }
+ }
+ },
+ "technique": {
+ "properties": {
+ "id": {
+ "type": "keyword"
+ },
+ "name": {
+ "type": "keyword"
+ },
+ "reference": {
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "timeline_id": {
+ "type": "keyword"
+ },
+ "timeline_title": {
+ "type": "keyword"
+ },
+ "to": {
+ "type": "keyword"
+ },
+ "type": {
+ "type": "keyword"
+ },
+ "updated_at": {
+ "type": "date"
+ },
+ "updated_by": {
+ "type": "keyword"
+ },
+ "version": {
+ "type": "keyword"
+ }
+ }
+ },
+ "status": {
+ "type": "keyword"
+ }
+ }
+ },
+ "source": {
+ "properties": {
+ "address": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "as": {
+ "properties": {
+ "number": {
+ "type": "long"
+ },
+ "organization": {
+ "properties": {
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "bytes": {
+ "type": "long"
+ },
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "geo": {
+ "properties": {
+ "city_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "location": {
+ "type": "geo_point"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "ip": {
+ "type": "ip"
+ },
+ "mac": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "nat": {
+ "properties": {
+ "ip": {
+ "type": "ip"
+ },
+ "port": {
+ "type": "long"
+ }
+ }
+ },
+ "packets": {
+ "type": "long"
+ },
+ "port": {
+ "type": "long"
+ },
+ "registered_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "top_level_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "user": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "email": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full_name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "tags": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "threat": {
+ "properties": {
+ "framework": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "tactic": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "technique": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "tls": {
+ "properties": {
+ "cipher": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "client": {
+ "properties": {
+ "certificate": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "certificate_chain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "hash": {
+ "properties": {
+ "md5": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha1": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "issuer": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ja3": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "not_after": {
+ "type": "date"
+ },
+ "not_before": {
+ "type": "date"
+ },
+ "server_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subject": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "supported_ciphers": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "curve": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "established": {
+ "type": "boolean"
+ },
+ "next_protocol": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "resumed": {
+ "type": "boolean"
+ },
+ "server": {
+ "properties": {
+ "certificate": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "certificate_chain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "hash": {
+ "properties": {
+ "md5": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha1": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "issuer": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ja3s": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "not_after": {
+ "type": "date"
+ },
+ "not_before": {
+ "type": "date"
+ },
+ "subject": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version_protocol": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "trace": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "transaction": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "url": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "extension": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "fragment": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "original": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "password": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "path": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "port": {
+ "type": "long"
+ },
+ "query": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "registered_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "scheme": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "top_level_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "username": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "user": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "email": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full_name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "user_agent": {
+ "properties": {
+ "device": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "original": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "os": {
+ "properties": {
+ "family": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "kernel": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "platform": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "vulnerability": {
+ "properties": {
+ "category": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "classification": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "description": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "enumeration": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "report_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "scanner": {
+ "properties": {
+ "vendor": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "score": {
+ "properties": {
+ "base": {
+ "type": "float"
+ },
+ "environmental": {
+ "type": "float"
+ },
+ "temporal": {
+ "type": "float"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "severity": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "settings": {
+ "index": {
+ "lifecycle": {
+ "name": ".siem-signals-default",
+ "rollover_alias": ".siem-signals-default"
+ },
+ "number_of_replicas": "1",
+ "number_of_shards": "1"
+ }
+ }
+ }
+}
+
+{
+ "type": "index",
+ "value": {
+ "aliases": {
+ "auditbeat-7.6.0": {
+ "is_write_index": true
+ }
+ },
+ "index": "auditbeat-7.6.0-2020.03.11-000001",
+ "mappings": {
+ "_meta": {
+ "beat": "auditbeat",
+ "version": "7.6.0"
+ },
+ "date_detection": false,
+ "dynamic_templates": [
+ {
+ "labels": {
+ "mapping": {
+ "type": "keyword"
+ },
+ "match_mapping_type": "string",
+ "path_match": "labels.*"
+ }
+ },
+ {
+ "container.labels": {
+ "mapping": {
+ "type": "keyword"
+ },
+ "match_mapping_type": "string",
+ "path_match": "container.labels.*"
+ }
+ },
+ {
+ "dns.answers": {
+ "mapping": {
+ "type": "keyword"
+ },
+ "match_mapping_type": "string",
+ "path_match": "dns.answers.*"
+ }
+ },
+ {
+ "log.syslog": {
+ "mapping": {
+ "type": "keyword"
+ },
+ "match_mapping_type": "string",
+ "path_match": "log.syslog.*"
+ }
+ },
+ {
+ "fields": {
+ "mapping": {
+ "type": "keyword"
+ },
+ "match_mapping_type": "string",
+ "path_match": "fields.*"
+ }
+ },
+ {
+ "docker.container.labels": {
+ "mapping": {
+ "type": "keyword"
+ },
+ "match_mapping_type": "string",
+ "path_match": "docker.container.labels.*"
+ }
+ },
+ {
+ "kubernetes.labels.*": {
+ "mapping": {
+ "type": "keyword"
+ },
+ "path_match": "kubernetes.labels.*"
+ }
+ },
+ {
+ "kubernetes.annotations.*": {
+ "mapping": {
+ "type": "keyword"
+ },
+ "path_match": "kubernetes.annotations.*"
+ }
+ },
+ {
+ "strings_as_keyword": {
+ "mapping": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "match_mapping_type": "string"
+ }
+ }
+ ],
+ "properties": {
+ "@timestamp": {
+ "type": "date"
+ },
+ "agent": {
+ "properties": {
+ "ephemeral_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "hostname": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "as": {
+ "properties": {
+ "number": {
+ "type": "long"
+ },
+ "organization": {
+ "properties": {
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "auditd": {
+ "properties": {
+ "data": {
+ "properties": {
+ "a0": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "a1": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "a2": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "a3": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "a[0-3]": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "acct": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "acl": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "action": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "added": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "addr": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "apparmor": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "arch": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "argc": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "audit_backlog_limit": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "audit_backlog_wait_time": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "audit_enabled": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "audit_failure": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "banners": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "bool": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "bus": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "cap_fe": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "cap_fi": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "cap_fp": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "cap_fver": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "cap_pe": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "cap_pi": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "cap_pp": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "capability": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "cgroup": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "changed": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "cipher": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "class": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "cmd": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "compat": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "daddr": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "data": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "default-context": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "device": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "dir": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "direction": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "dmac": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "dport": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "enforcing": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "entries": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "exit": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "fam": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "family": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "fd": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "fe": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "feature": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "fi": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "file": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "flags": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "format": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "fp": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "fver": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "grantors": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "grp": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "hook": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "hostname": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "icmp_type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "igid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "img-ctx": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "inif": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ino": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "inode_gid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "inode_uid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "invalid_context": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ioctlcmd": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ip": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ipid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ipx-net": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "items": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "iuid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "kernel": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "kind": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ksize": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "laddr": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "len": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "list": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "lport": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "mac": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "macproto": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "maj": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "major": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "minor": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "model": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "msg": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "nargs": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "net": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "new": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "new-chardev": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "new-disk": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "new-enabled": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "new-fs": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "new-level": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "new-log_passwd": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "new-mem": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "new-net": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "new-range": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "new-rng": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "new-role": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "new-seuser": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "new-vcpu": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "new_gid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "new_lock": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "new_pe": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "new_pi": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "new_pp": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "nlnk-fam": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "nlnk-grp": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "nlnk-pid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "oauid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "obj": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "obj_gid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "obj_uid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ocomm": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "oflag": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "old": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "old-auid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "old-chardev": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "old-disk": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "old-enabled": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "old-fs": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "old-level": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "old-log_passwd": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "old-mem": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "old-net": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "old-range": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "old-rng": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "old-role": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "old-ses": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "old-seuser": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "old-vcpu": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "old_enforcing": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "old_lock": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "old_pe": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "old_pi": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "old_pp": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "old_prom": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "old_val": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "op": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "opid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "oses": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "outif": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "parent": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "per": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "perm": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "perm_mask": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "permissive": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "pfs": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "printer": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "prom": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "proto": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "qbytes": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "range": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reason": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "removed": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "res": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "resrc": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "rport": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sauid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "scontext": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "selected-context": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "seperm": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "seperms": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "seqno": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "seresult": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ses": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "seuser": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sig": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sigev_signo": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "smac": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "socket": {
+ "properties": {
+ "addr": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "family": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "path": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "port": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "saddr": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "spid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sport": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "state": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subj": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "success": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "syscall": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "table": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "tclass": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "tcontext": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "terminal": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "tty": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "unit": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "uri": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "uuid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "val": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ver": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "virt": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "vm": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "vm-ctx": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "vm-pid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "watch": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "message_type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "paths": {
+ "properties": {
+ "dev": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "inode": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "item": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "mode": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "nametype": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "obj_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "obj_level": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "obj_role": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "obj_user": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "objtype": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ogid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ouid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "rdev": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "result": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sequence": {
+ "type": "long"
+ },
+ "session": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "summary": {
+ "properties": {
+ "actor": {
+ "properties": {
+ "primary": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "secondary": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "how": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "object": {
+ "properties": {
+ "primary": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "secondary": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "client": {
+ "properties": {
+ "address": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "as": {
+ "properties": {
+ "number": {
+ "type": "long"
+ },
+ "organization": {
+ "properties": {
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "bytes": {
+ "type": "long"
+ },
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "geo": {
+ "properties": {
+ "city_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "location": {
+ "type": "geo_point"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "ip": {
+ "type": "ip"
+ },
+ "mac": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "nat": {
+ "properties": {
+ "ip": {
+ "type": "ip"
+ },
+ "port": {
+ "type": "long"
+ }
+ }
+ },
+ "packets": {
+ "type": "long"
+ },
+ "port": {
+ "type": "long"
+ },
+ "registered_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "top_level_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "user": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "email": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full_name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "cloud": {
+ "properties": {
+ "account": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "availability_zone": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "image": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "instance": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "machine": {
+ "properties": {
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "project": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "provider": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "container": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "image": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "tag": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "labels": {
+ "type": "object"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "runtime": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "destination": {
+ "properties": {
+ "address": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "as": {
+ "properties": {
+ "number": {
+ "type": "long"
+ },
+ "organization": {
+ "properties": {
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "bytes": {
+ "type": "long"
+ },
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "geo": {
+ "properties": {
+ "city_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "location": {
+ "type": "geo_point"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "ip": {
+ "type": "ip"
+ },
+ "mac": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "nat": {
+ "properties": {
+ "ip": {
+ "type": "ip"
+ },
+ "port": {
+ "type": "long"
+ }
+ }
+ },
+ "packets": {
+ "type": "long"
+ },
+ "path": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "port": {
+ "type": "long"
+ },
+ "registered_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "top_level_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "user": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "email": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full_name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "dns": {
+ "properties": {
+ "answers": {
+ "properties": {
+ "class": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "data": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ttl": {
+ "type": "long"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "header_flags": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "op_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "question": {
+ "properties": {
+ "class": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "registered_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subdomain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "top_level_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "resolved_ip": {
+ "type": "ip"
+ },
+ "response_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "docker": {
+ "properties": {
+ "container": {
+ "properties": {
+ "labels": {
+ "type": "object"
+ }
+ }
+ }
+ }
+ },
+ "ecs": {
+ "properties": {
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "error": {
+ "properties": {
+ "code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "message": {
+ "norms": false,
+ "type": "text"
+ },
+ "stack_trace": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "event": {
+ "properties": {
+ "action": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "category": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "created": {
+ "type": "date"
+ },
+ "dataset": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "duration": {
+ "type": "long"
+ },
+ "end": {
+ "type": "date"
+ },
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ingested": {
+ "type": "date"
+ },
+ "kind": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "module": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "origin": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "original": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "outcome": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "provider": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "risk_score": {
+ "type": "float"
+ },
+ "risk_score_norm": {
+ "type": "float"
+ },
+ "sequence": {
+ "type": "long"
+ },
+ "severity": {
+ "type": "long"
+ },
+ "start": {
+ "type": "date"
+ },
+ "timezone": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "fields": {
+ "type": "object"
+ },
+ "file": {
+ "properties": {
+ "accessed": {
+ "type": "date"
+ },
+ "attributes": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "created": {
+ "type": "date"
+ },
+ "ctime": {
+ "type": "date"
+ },
+ "device": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "directory": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "drive_letter": {
+ "ignore_above": 1,
+ "type": "keyword"
+ },
+ "extension": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "gid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "hash": {
+ "properties": {
+ "md5": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha1": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha512": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "inode": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "mode": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "mtime": {
+ "type": "date"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "origin": {
+ "fields": {
+ "raw": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "owner": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "path": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "selinux": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "level": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "role": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "user": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "setgid": {
+ "type": "boolean"
+ },
+ "setuid": {
+ "type": "boolean"
+ },
+ "size": {
+ "type": "long"
+ },
+ "target_path": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "uid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "geo": {
+ "properties": {
+ "city_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "location": {
+ "type": "geo_point"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "geoip": {
+ "properties": {
+ "city_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "location": {
+ "type": "geo_point"
+ },
+ "region_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "group": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hash": {
+ "properties": {
+ "blake2b_256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "blake2b_384": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "blake2b_512": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "md5": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha1": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha224": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha384": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha3_224": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha3_256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha3_384": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha3_512": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha512": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha512_224": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha512_256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "xxh64": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "host": {
+ "properties": {
+ "architecture": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "containerized": {
+ "type": "boolean"
+ },
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "geo": {
+ "properties": {
+ "city_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "location": {
+ "type": "geo_point"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hostname": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ip": {
+ "type": "ip"
+ },
+ "mac": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "os": {
+ "properties": {
+ "build": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "codename": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "family": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "kernel": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "platform": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "uptime": {
+ "type": "long"
+ },
+ "user": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "email": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full_name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "http": {
+ "properties": {
+ "request": {
+ "properties": {
+ "body": {
+ "properties": {
+ "bytes": {
+ "type": "long"
+ },
+ "content": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "bytes": {
+ "type": "long"
+ },
+ "method": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "referrer": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "response": {
+ "properties": {
+ "body": {
+ "properties": {
+ "bytes": {
+ "type": "long"
+ },
+ "content": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "bytes": {
+ "type": "long"
+ },
+ "status_code": {
+ "type": "long"
+ }
+ }
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "jolokia": {
+ "properties": {
+ "agent": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "secured": {
+ "type": "boolean"
+ },
+ "server": {
+ "properties": {
+ "product": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "vendor": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "url": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "kubernetes": {
+ "properties": {
+ "annotations": {
+ "properties": {
+ "*": {
+ "type": "object"
+ }
+ }
+ },
+ "container": {
+ "properties": {
+ "image": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "deployment": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "labels": {
+ "properties": {
+ "*": {
+ "type": "object"
+ }
+ }
+ },
+ "namespace": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "node": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "pod": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "uid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "replicaset": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "statefulset": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "labels": {
+ "type": "object"
+ },
+ "log": {
+ "properties": {
+ "level": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "logger": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "origin": {
+ "properties": {
+ "file": {
+ "properties": {
+ "line": {
+ "type": "long"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "function": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "original": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "syslog": {
+ "properties": {
+ "facility": {
+ "properties": {
+ "code": {
+ "type": "long"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "priority": {
+ "type": "long"
+ },
+ "severity": {
+ "properties": {
+ "code": {
+ "type": "long"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "message": {
+ "norms": false,
+ "type": "text"
+ },
+ "network": {
+ "properties": {
+ "application": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "bytes": {
+ "type": "long"
+ },
+ "community_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "direction": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "forwarded_ip": {
+ "type": "ip"
+ },
+ "iana_number": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "packets": {
+ "type": "long"
+ },
+ "protocol": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "transport": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "observer": {
+ "properties": {
+ "geo": {
+ "properties": {
+ "city_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "location": {
+ "type": "geo_point"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hostname": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ip": {
+ "type": "ip"
+ },
+ "mac": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "os": {
+ "properties": {
+ "family": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "kernel": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "platform": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "product": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "serial_number": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "vendor": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "organization": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "os": {
+ "properties": {
+ "family": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "kernel": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "platform": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "package": {
+ "properties": {
+ "architecture": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "build_version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "checksum": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "description": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "install_scope": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "installed": {
+ "type": "date"
+ },
+ "license": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "path": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "size": {
+ "type": "long"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "process": {
+ "properties": {
+ "args": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "args_count": {
+ "type": "long"
+ },
+ "command_line": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "entity_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "executable": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "exit_code": {
+ "type": "long"
+ },
+ "hash": {
+ "properties": {
+ "blake2b_256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "blake2b_384": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "blake2b_512": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "md5": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha1": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha224": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha384": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha3_224": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha3_256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha3_384": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha3_512": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha512": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha512_224": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha512_256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "xxh64": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "parent": {
+ "properties": {
+ "args": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "args_count": {
+ "type": "long"
+ },
+ "command_line": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "executable": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "exit_code": {
+ "type": "long"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "pgid": {
+ "type": "long"
+ },
+ "pid": {
+ "type": "long"
+ },
+ "ppid": {
+ "type": "long"
+ },
+ "start": {
+ "type": "date"
+ },
+ "thread": {
+ "properties": {
+ "id": {
+ "type": "long"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "title": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "uptime": {
+ "type": "long"
+ },
+ "working_directory": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "pgid": {
+ "type": "long"
+ },
+ "pid": {
+ "type": "long"
+ },
+ "ppid": {
+ "type": "long"
+ },
+ "start": {
+ "type": "date"
+ },
+ "thread": {
+ "properties": {
+ "id": {
+ "type": "long"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "title": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "uptime": {
+ "type": "long"
+ },
+ "working_directory": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "registry": {
+ "properties": {
+ "data": {
+ "properties": {
+ "bytes": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "strings": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hive": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "key": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "path": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "value": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "related": {
+ "properties": {
+ "ip": {
+ "type": "ip"
+ },
+ "user": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "rule": {
+ "properties": {
+ "category": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "description": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ruleset": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "uuid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "server": {
+ "properties": {
+ "address": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "as": {
+ "properties": {
+ "number": {
+ "type": "long"
+ },
+ "organization": {
+ "properties": {
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "bytes": {
+ "type": "long"
+ },
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "geo": {
+ "properties": {
+ "city_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "location": {
+ "type": "geo_point"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "ip": {
+ "type": "ip"
+ },
+ "mac": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "nat": {
+ "properties": {
+ "ip": {
+ "type": "ip"
+ },
+ "port": {
+ "type": "long"
+ }
+ }
+ },
+ "packets": {
+ "type": "long"
+ },
+ "port": {
+ "type": "long"
+ },
+ "registered_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "top_level_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "user": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "email": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full_name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "service": {
+ "properties": {
+ "ephemeral_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "node": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "state": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "socket": {
+ "properties": {
+ "entity_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "source": {
+ "properties": {
+ "address": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "as": {
+ "properties": {
+ "number": {
+ "type": "long"
+ },
+ "organization": {
+ "properties": {
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "bytes": {
+ "type": "long"
+ },
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "geo": {
+ "properties": {
+ "city_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "location": {
+ "type": "geo_point"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "ip": {
+ "type": "ip"
+ },
+ "mac": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "nat": {
+ "properties": {
+ "ip": {
+ "type": "ip"
+ },
+ "port": {
+ "type": "long"
+ }
+ }
+ },
+ "packets": {
+ "type": "long"
+ },
+ "path": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "port": {
+ "type": "long"
+ },
+ "registered_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "top_level_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "user": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "email": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full_name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "system": {
+ "properties": {
+ "audit": {
+ "properties": {
+ "host": {
+ "properties": {
+ "architecture": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "boottime": {
+ "type": "date"
+ },
+ "containerized": {
+ "type": "boolean"
+ },
+ "hostname": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ip": {
+ "type": "ip"
+ },
+ "mac": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "os": {
+ "properties": {
+ "codename": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "family": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "kernel": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "platform": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "timezone": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "offset": {
+ "properties": {
+ "sec": {
+ "type": "long"
+ }
+ }
+ }
+ }
+ },
+ "uptime": {
+ "type": "long"
+ }
+ }
+ },
+ "package": {
+ "properties": {
+ "arch": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "entity_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "installtime": {
+ "type": "date"
+ },
+ "license": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "release": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "size": {
+ "type": "long"
+ },
+ "summary": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "url": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "user": {
+ "properties": {
+ "dir": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "gid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "type": "object"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "password": {
+ "properties": {
+ "last_changed": {
+ "type": "date"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "shell": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "uid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "user_information": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "threat": {
+ "properties": {
+ "framework": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "tactic": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "technique": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "timeseries": {
+ "properties": {
+ "instance": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "tls": {
+ "properties": {
+ "cipher": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "client": {
+ "properties": {
+ "certificate": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "certificate_chain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "hash": {
+ "properties": {
+ "md5": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha1": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "issuer": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ja3": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "not_after": {
+ "type": "date"
+ },
+ "not_before": {
+ "type": "date"
+ },
+ "server_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subject": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "supported_ciphers": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "curve": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "established": {
+ "type": "boolean"
+ },
+ "next_protocol": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "resumed": {
+ "type": "boolean"
+ },
+ "server": {
+ "properties": {
+ "certificate": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "certificate_chain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "hash": {
+ "properties": {
+ "md5": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha1": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "issuer": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ja3s": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "not_after": {
+ "type": "date"
+ },
+ "not_before": {
+ "type": "date"
+ },
+ "subject": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version_protocol": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "tracing": {
+ "properties": {
+ "trace": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "transaction": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "url": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "extension": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "fragment": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "original": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "password": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "path": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "port": {
+ "type": "long"
+ },
+ "query": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "registered_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "scheme": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "top_level_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "username": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "user": {
+ "properties": {
+ "audit": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "effective": {
+ "properties": {
+ "group": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "email": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "entity_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "filesystem": {
+ "properties": {
+ "group": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "full_name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name_map": {
+ "type": "object"
+ },
+ "saved": {
+ "properties": {
+ "group": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "selinux": {
+ "properties": {
+ "category": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "level": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "role": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "user": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "terminal": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "user_agent": {
+ "properties": {
+ "device": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "original": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "os": {
+ "properties": {
+ "family": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "kernel": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "platform": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "vulnerability": {
+ "properties": {
+ "category": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "classification": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "description": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "enumeration": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "report_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "scanner": {
+ "properties": {
+ "vendor": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "score": {
+ "properties": {
+ "base": {
+ "type": "float"
+ },
+ "environmental": {
+ "type": "float"
+ },
+ "temporal": {
+ "type": "float"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "severity": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "settings": {
+ "index": {
+ "lifecycle": {
+ "name": "auditbeat",
+ "rollover_alias": "auditbeat-7.6.0"
+ },
+ "mapping": {
+ "total_fields": {
+ "limit": "10000"
+ }
+ },
+ "number_of_replicas": "1",
+ "number_of_shards": "1",
+ "query": {
+ "default_field": [
+ "message",
+ "tags",
+ "agent.ephemeral_id",
+ "agent.id",
+ "agent.name",
+ "agent.type",
+ "agent.version",
+ "as.organization.name",
+ "client.address",
+ "client.as.organization.name",
+ "client.domain",
+ "client.geo.city_name",
+ "client.geo.continent_name",
+ "client.geo.country_iso_code",
+ "client.geo.country_name",
+ "client.geo.name",
+ "client.geo.region_iso_code",
+ "client.geo.region_name",
+ "client.mac",
+ "client.registered_domain",
+ "client.top_level_domain",
+ "client.user.domain",
+ "client.user.email",
+ "client.user.full_name",
+ "client.user.group.domain",
+ "client.user.group.id",
+ "client.user.group.name",
+ "client.user.hash",
+ "client.user.id",
+ "client.user.name",
+ "cloud.account.id",
+ "cloud.availability_zone",
+ "cloud.instance.id",
+ "cloud.instance.name",
+ "cloud.machine.type",
+ "cloud.provider",
+ "cloud.region",
+ "container.id",
+ "container.image.name",
+ "container.image.tag",
+ "container.name",
+ "container.runtime",
+ "destination.address",
+ "destination.as.organization.name",
+ "destination.domain",
+ "destination.geo.city_name",
+ "destination.geo.continent_name",
+ "destination.geo.country_iso_code",
+ "destination.geo.country_name",
+ "destination.geo.name",
+ "destination.geo.region_iso_code",
+ "destination.geo.region_name",
+ "destination.mac",
+ "destination.registered_domain",
+ "destination.top_level_domain",
+ "destination.user.domain",
+ "destination.user.email",
+ "destination.user.full_name",
+ "destination.user.group.domain",
+ "destination.user.group.id",
+ "destination.user.group.name",
+ "destination.user.hash",
+ "destination.user.id",
+ "destination.user.name",
+ "dns.answers.class",
+ "dns.answers.data",
+ "dns.answers.name",
+ "dns.answers.type",
+ "dns.header_flags",
+ "dns.id",
+ "dns.op_code",
+ "dns.question.class",
+ "dns.question.name",
+ "dns.question.registered_domain",
+ "dns.question.subdomain",
+ "dns.question.top_level_domain",
+ "dns.question.type",
+ "dns.response_code",
+ "dns.type",
+ "ecs.version",
+ "error.code",
+ "error.id",
+ "error.message",
+ "error.stack_trace",
+ "error.type",
+ "event.action",
+ "event.category",
+ "event.code",
+ "event.dataset",
+ "event.hash",
+ "event.id",
+ "event.kind",
+ "event.module",
+ "event.original",
+ "event.outcome",
+ "event.provider",
+ "event.timezone",
+ "event.type",
+ "file.device",
+ "file.directory",
+ "file.extension",
+ "file.gid",
+ "file.group",
+ "file.hash.md5",
+ "file.hash.sha1",
+ "file.hash.sha256",
+ "file.hash.sha512",
+ "file.inode",
+ "file.mode",
+ "file.name",
+ "file.owner",
+ "file.path",
+ "file.target_path",
+ "file.type",
+ "file.uid",
+ "geo.city_name",
+ "geo.continent_name",
+ "geo.country_iso_code",
+ "geo.country_name",
+ "geo.name",
+ "geo.region_iso_code",
+ "geo.region_name",
+ "group.domain",
+ "group.id",
+ "group.name",
+ "hash.md5",
+ "hash.sha1",
+ "hash.sha256",
+ "hash.sha512",
+ "host.architecture",
+ "host.geo.city_name",
+ "host.geo.continent_name",
+ "host.geo.country_iso_code",
+ "host.geo.country_name",
+ "host.geo.name",
+ "host.geo.region_iso_code",
+ "host.geo.region_name",
+ "host.hostname",
+ "host.id",
+ "host.mac",
+ "host.name",
+ "host.os.family",
+ "host.os.full",
+ "host.os.kernel",
+ "host.os.name",
+ "host.os.platform",
+ "host.os.version",
+ "host.type",
+ "host.user.domain",
+ "host.user.email",
+ "host.user.full_name",
+ "host.user.group.domain",
+ "host.user.group.id",
+ "host.user.group.name",
+ "host.user.hash",
+ "host.user.id",
+ "host.user.name",
+ "http.request.body.content",
+ "http.request.method",
+ "http.request.referrer",
+ "http.response.body.content",
+ "http.version",
+ "log.level",
+ "log.logger",
+ "log.origin.file.name",
+ "log.origin.function",
+ "log.original",
+ "log.syslog.facility.name",
+ "log.syslog.severity.name",
+ "network.application",
+ "network.community_id",
+ "network.direction",
+ "network.iana_number",
+ "network.name",
+ "network.protocol",
+ "network.transport",
+ "network.type",
+ "observer.geo.city_name",
+ "observer.geo.continent_name",
+ "observer.geo.country_iso_code",
+ "observer.geo.country_name",
+ "observer.geo.name",
+ "observer.geo.region_iso_code",
+ "observer.geo.region_name",
+ "observer.hostname",
+ "observer.mac",
+ "observer.name",
+ "observer.os.family",
+ "observer.os.full",
+ "observer.os.kernel",
+ "observer.os.name",
+ "observer.os.platform",
+ "observer.os.version",
+ "observer.product",
+ "observer.serial_number",
+ "observer.type",
+ "observer.vendor",
+ "observer.version",
+ "organization.id",
+ "organization.name",
+ "os.family",
+ "os.full",
+ "os.kernel",
+ "os.name",
+ "os.platform",
+ "os.version",
+ "package.architecture",
+ "package.checksum",
+ "package.description",
+ "package.install_scope",
+ "package.license",
+ "package.name",
+ "package.path",
+ "package.version",
+ "process.args",
+ "text",
+ "process.executable",
+ "process.hash.md5",
+ "process.hash.sha1",
+ "process.hash.sha256",
+ "process.hash.sha512",
+ "process.name",
+ "text",
+ "text",
+ "text",
+ "text",
+ "text",
+ "process.thread.name",
+ "process.title",
+ "process.working_directory",
+ "server.address",
+ "server.as.organization.name",
+ "server.domain",
+ "server.geo.city_name",
+ "server.geo.continent_name",
+ "server.geo.country_iso_code",
+ "server.geo.country_name",
+ "server.geo.name",
+ "server.geo.region_iso_code",
+ "server.geo.region_name",
+ "server.mac",
+ "server.registered_domain",
+ "server.top_level_domain",
+ "server.user.domain",
+ "server.user.email",
+ "server.user.full_name",
+ "server.user.group.domain",
+ "server.user.group.id",
+ "server.user.group.name",
+ "server.user.hash",
+ "server.user.id",
+ "server.user.name",
+ "service.ephemeral_id",
+ "service.id",
+ "service.name",
+ "service.node.name",
+ "service.state",
+ "service.type",
+ "service.version",
+ "source.address",
+ "source.as.organization.name",
+ "source.domain",
+ "source.geo.city_name",
+ "source.geo.continent_name",
+ "source.geo.country_iso_code",
+ "source.geo.country_name",
+ "source.geo.name",
+ "source.geo.region_iso_code",
+ "source.geo.region_name",
+ "source.mac",
+ "source.registered_domain",
+ "source.top_level_domain",
+ "source.user.domain",
+ "source.user.email",
+ "source.user.full_name",
+ "source.user.group.domain",
+ "source.user.group.id",
+ "source.user.group.name",
+ "source.user.hash",
+ "source.user.id",
+ "source.user.name",
+ "threat.framework",
+ "threat.tactic.id",
+ "threat.tactic.name",
+ "threat.tactic.reference",
+ "threat.technique.id",
+ "threat.technique.name",
+ "threat.technique.reference",
+ "tracing.trace.id",
+ "tracing.transaction.id",
+ "url.domain",
+ "url.extension",
+ "url.fragment",
+ "url.full",
+ "url.original",
+ "url.password",
+ "url.path",
+ "url.query",
+ "url.registered_domain",
+ "url.scheme",
+ "url.top_level_domain",
+ "url.username",
+ "user.domain",
+ "user.email",
+ "user.full_name",
+ "user.group.domain",
+ "user.group.id",
+ "user.group.name",
+ "user.hash",
+ "user.id",
+ "user.name",
+ "user_agent.device.name",
+ "user_agent.name",
+ "text",
+ "user_agent.original",
+ "user_agent.os.family",
+ "user_agent.os.full",
+ "user_agent.os.kernel",
+ "user_agent.os.name",
+ "user_agent.os.platform",
+ "user_agent.os.version",
+ "user_agent.version",
+ "text",
+ "agent.hostname",
+ "timeseries.instance",
+ "cloud.project.id",
+ "cloud.image.id",
+ "host.os.build",
+ "host.os.codename",
+ "kubernetes.pod.name",
+ "kubernetes.pod.uid",
+ "kubernetes.namespace",
+ "kubernetes.node.name",
+ "kubernetes.replicaset.name",
+ "kubernetes.deployment.name",
+ "kubernetes.statefulset.name",
+ "kubernetes.container.name",
+ "kubernetes.container.image",
+ "jolokia.agent.version",
+ "jolokia.agent.id",
+ "jolokia.server.product",
+ "jolokia.server.version",
+ "jolokia.server.vendor",
+ "jolokia.url",
+ "raw",
+ "file.origin",
+ "file.selinux.user",
+ "file.selinux.role",
+ "file.selinux.domain",
+ "file.selinux.level",
+ "user.audit.id",
+ "user.audit.name",
+ "user.effective.id",
+ "user.effective.name",
+ "user.effective.group.id",
+ "user.effective.group.name",
+ "user.filesystem.id",
+ "user.filesystem.name",
+ "user.filesystem.group.id",
+ "user.filesystem.group.name",
+ "user.saved.id",
+ "user.saved.name",
+ "user.saved.group.id",
+ "user.saved.group.name",
+ "user.selinux.user",
+ "user.selinux.role",
+ "user.selinux.domain",
+ "user.selinux.level",
+ "user.selinux.category",
+ "source.path",
+ "destination.path",
+ "auditd.message_type",
+ "auditd.session",
+ "auditd.result",
+ "auditd.summary.actor.primary",
+ "auditd.summary.actor.secondary",
+ "auditd.summary.object.type",
+ "auditd.summary.object.primary",
+ "auditd.summary.object.secondary",
+ "auditd.summary.how",
+ "auditd.paths.inode",
+ "auditd.paths.dev",
+ "auditd.paths.obj_user",
+ "auditd.paths.obj_role",
+ "auditd.paths.obj_domain",
+ "auditd.paths.obj_level",
+ "auditd.paths.objtype",
+ "auditd.paths.ouid",
+ "auditd.paths.rdev",
+ "auditd.paths.nametype",
+ "auditd.paths.ogid",
+ "auditd.paths.item",
+ "auditd.paths.mode",
+ "auditd.paths.name",
+ "auditd.data.action",
+ "auditd.data.minor",
+ "auditd.data.acct",
+ "auditd.data.addr",
+ "auditd.data.cipher",
+ "auditd.data.id",
+ "auditd.data.entries",
+ "auditd.data.kind",
+ "auditd.data.ksize",
+ "auditd.data.spid",
+ "auditd.data.arch",
+ "auditd.data.argc",
+ "auditd.data.major",
+ "auditd.data.unit",
+ "auditd.data.table",
+ "auditd.data.terminal",
+ "auditd.data.grantors",
+ "auditd.data.direction",
+ "auditd.data.op",
+ "auditd.data.tty",
+ "auditd.data.syscall",
+ "auditd.data.data",
+ "auditd.data.family",
+ "auditd.data.mac",
+ "auditd.data.pfs",
+ "auditd.data.items",
+ "auditd.data.a0",
+ "auditd.data.a1",
+ "auditd.data.a2",
+ "auditd.data.a3",
+ "auditd.data.hostname",
+ "auditd.data.lport",
+ "auditd.data.rport",
+ "auditd.data.exit",
+ "auditd.data.fp",
+ "auditd.data.laddr",
+ "auditd.data.sport",
+ "auditd.data.capability",
+ "auditd.data.nargs",
+ "auditd.data.new-enabled",
+ "auditd.data.audit_backlog_limit",
+ "auditd.data.dir",
+ "auditd.data.cap_pe",
+ "auditd.data.model",
+ "auditd.data.new_pp",
+ "auditd.data.old-enabled",
+ "auditd.data.oauid",
+ "auditd.data.old",
+ "auditd.data.banners",
+ "auditd.data.feature",
+ "auditd.data.vm-ctx",
+ "auditd.data.opid",
+ "auditd.data.seperms",
+ "auditd.data.seresult",
+ "auditd.data.new-rng",
+ "auditd.data.old-net",
+ "auditd.data.sigev_signo",
+ "auditd.data.ino",
+ "auditd.data.old_enforcing",
+ "auditd.data.old-vcpu",
+ "auditd.data.range",
+ "auditd.data.res",
+ "auditd.data.added",
+ "auditd.data.fam",
+ "auditd.data.nlnk-pid",
+ "auditd.data.subj",
+ "auditd.data.a[0-3]",
+ "auditd.data.cgroup",
+ "auditd.data.kernel",
+ "auditd.data.ocomm",
+ "auditd.data.new-net",
+ "auditd.data.permissive",
+ "auditd.data.class",
+ "auditd.data.compat",
+ "auditd.data.fi",
+ "auditd.data.changed",
+ "auditd.data.msg",
+ "auditd.data.dport",
+ "auditd.data.new-seuser",
+ "auditd.data.invalid_context",
+ "auditd.data.dmac",
+ "auditd.data.ipx-net",
+ "auditd.data.iuid",
+ "auditd.data.macproto",
+ "auditd.data.obj",
+ "auditd.data.ipid",
+ "auditd.data.new-fs",
+ "auditd.data.vm-pid",
+ "auditd.data.cap_pi",
+ "auditd.data.old-auid",
+ "auditd.data.oses",
+ "auditd.data.fd",
+ "auditd.data.igid",
+ "auditd.data.new-disk",
+ "auditd.data.parent",
+ "auditd.data.len",
+ "auditd.data.oflag",
+ "auditd.data.uuid",
+ "auditd.data.code",
+ "auditd.data.nlnk-grp",
+ "auditd.data.cap_fp",
+ "auditd.data.new-mem",
+ "auditd.data.seperm",
+ "auditd.data.enforcing",
+ "auditd.data.new-chardev",
+ "auditd.data.old-rng",
+ "auditd.data.outif",
+ "auditd.data.cmd",
+ "auditd.data.hook",
+ "auditd.data.new-level",
+ "auditd.data.sauid",
+ "auditd.data.sig",
+ "auditd.data.audit_backlog_wait_time",
+ "auditd.data.printer",
+ "auditd.data.old-mem",
+ "auditd.data.perm",
+ "auditd.data.old_pi",
+ "auditd.data.state",
+ "auditd.data.format",
+ "auditd.data.new_gid",
+ "auditd.data.tcontext",
+ "auditd.data.maj",
+ "auditd.data.watch",
+ "auditd.data.device",
+ "auditd.data.grp",
+ "auditd.data.bool",
+ "auditd.data.icmp_type",
+ "auditd.data.new_lock",
+ "auditd.data.old_prom",
+ "auditd.data.acl",
+ "auditd.data.ip",
+ "auditd.data.new_pi",
+ "auditd.data.default-context",
+ "auditd.data.inode_gid",
+ "auditd.data.new-log_passwd",
+ "auditd.data.new_pe",
+ "auditd.data.selected-context",
+ "auditd.data.cap_fver",
+ "auditd.data.file",
+ "auditd.data.net",
+ "auditd.data.virt",
+ "auditd.data.cap_pp",
+ "auditd.data.old-range",
+ "auditd.data.resrc",
+ "auditd.data.new-range",
+ "auditd.data.obj_gid",
+ "auditd.data.proto",
+ "auditd.data.old-disk",
+ "auditd.data.audit_failure",
+ "auditd.data.inif",
+ "auditd.data.vm",
+ "auditd.data.flags",
+ "auditd.data.nlnk-fam",
+ "auditd.data.old-fs",
+ "auditd.data.old-ses",
+ "auditd.data.seqno",
+ "auditd.data.fver",
+ "auditd.data.qbytes",
+ "auditd.data.seuser",
+ "auditd.data.cap_fe",
+ "auditd.data.new-vcpu",
+ "auditd.data.old-level",
+ "auditd.data.old_pp",
+ "auditd.data.daddr",
+ "auditd.data.old-role",
+ "auditd.data.ioctlcmd",
+ "auditd.data.smac",
+ "auditd.data.apparmor",
+ "auditd.data.fe",
+ "auditd.data.perm_mask",
+ "auditd.data.ses",
+ "auditd.data.cap_fi",
+ "auditd.data.obj_uid",
+ "auditd.data.reason",
+ "auditd.data.list",
+ "auditd.data.old_lock",
+ "auditd.data.bus",
+ "auditd.data.old_pe",
+ "auditd.data.new-role",
+ "auditd.data.prom",
+ "auditd.data.uri",
+ "auditd.data.audit_enabled",
+ "auditd.data.old-log_passwd",
+ "auditd.data.old-seuser",
+ "auditd.data.per",
+ "auditd.data.scontext",
+ "auditd.data.tclass",
+ "auditd.data.ver",
+ "auditd.data.new",
+ "auditd.data.val",
+ "auditd.data.img-ctx",
+ "auditd.data.old-chardev",
+ "auditd.data.old_val",
+ "auditd.data.success",
+ "auditd.data.inode_uid",
+ "auditd.data.removed",
+ "auditd.data.socket.port",
+ "auditd.data.socket.saddr",
+ "auditd.data.socket.addr",
+ "auditd.data.socket.family",
+ "auditd.data.socket.path",
+ "geoip.continent_name",
+ "geoip.city_name",
+ "geoip.region_name",
+ "geoip.country_iso_code",
+ "hash.blake2b_256",
+ "hash.blake2b_384",
+ "hash.blake2b_512",
+ "hash.md5",
+ "hash.sha1",
+ "hash.sha224",
+ "hash.sha256",
+ "hash.sha384",
+ "hash.sha3_224",
+ "hash.sha3_256",
+ "hash.sha3_384",
+ "hash.sha3_512",
+ "hash.sha512",
+ "hash.sha512_224",
+ "hash.sha512_256",
+ "hash.xxh64",
+ "event.origin",
+ "user.entity_id",
+ "user.terminal",
+ "process.entity_id",
+ "process.hash.blake2b_256",
+ "process.hash.blake2b_384",
+ "process.hash.blake2b_512",
+ "process.hash.sha224",
+ "process.hash.sha384",
+ "process.hash.sha3_224",
+ "process.hash.sha3_256",
+ "process.hash.sha3_384",
+ "process.hash.sha3_512",
+ "process.hash.sha512_224",
+ "process.hash.sha512_256",
+ "process.hash.xxh64",
+ "socket.entity_id",
+ "system.audit.host.timezone.name",
+ "system.audit.host.hostname",
+ "system.audit.host.id",
+ "system.audit.host.architecture",
+ "system.audit.host.mac",
+ "system.audit.host.os.codename",
+ "system.audit.host.os.platform",
+ "system.audit.host.os.name",
+ "system.audit.host.os.family",
+ "system.audit.host.os.version",
+ "system.audit.host.os.kernel",
+ "system.audit.package.entity_id",
+ "system.audit.package.name",
+ "system.audit.package.version",
+ "system.audit.package.release",
+ "system.audit.package.arch",
+ "system.audit.package.license",
+ "system.audit.package.summary",
+ "system.audit.package.url",
+ "system.audit.user.name",
+ "system.audit.user.uid",
+ "system.audit.user.gid",
+ "system.audit.user.dir",
+ "system.audit.user.shell",
+ "system.audit.user.user_information",
+ "system.audit.user.password.type",
+ "fields.*"
+ ]
+ },
+ "refresh_interval": "5s"
+ }
+ }
+ }
+}
\ No newline at end of file