From 9e7c169a0148acb225fdfc1842868e0a83d34393 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Sun, 7 Mar 2021 11:40:31 +0100 Subject: [PATCH 01/53] wip --- .../operations/definitions/percentile.tsx | 2 +- x-pack/plugins/observability/kibana.json | 2 +- .../public/application/index.tsx | 5 +- .../Exploratory_view/exploratory_view.tsx | 296 ++++++++++++++++++ x-pack/plugins/observability/public/plugin.ts | 34 +- .../observability/public/routes/index.tsx | 21 ++ x-pack/plugins/observability/tsconfig.json | 1 + 7 files changed, 349 insertions(+), 12 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/shared/Exploratory_view/exploratory_view.tsx diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx index e7654380bd85f1..acf33ee2297a0d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx @@ -105,7 +105,7 @@ export const percentileOperation: OperationDefinition { diff --git a/x-pack/plugins/observability/kibana.json b/x-pack/plugins/observability/kibana.json index 84aa1be9a8d870..7d7297e6a51747 100644 --- a/x-pack/plugins/observability/kibana.json +++ b/x-pack/plugins/observability/kibana.json @@ -4,7 +4,7 @@ "kibanaVersion": "kibana", "configPath": ["xpack", "observability"], "optionalPlugins": ["licensing", "home", "usageCollection"], - "requiredPlugins": ["data"], + "requiredPlugins": ["data", "lens"], "ui": true, "server": true, "requiredBundles": ["data", "kibanaReact", "kibanaUtils"] diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index 0185f9b4ef5bc0..f38a1d3642aa7c 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -18,9 +18,10 @@ import { import { PluginContext } from '../context/plugin_context'; import { usePluginContext } from '../hooks/use_plugin_context'; import { useRouteParams } from '../hooks/use_route_params'; -import { ObservabilityPluginSetupDeps } from '../plugin'; +import { ObservabilityClientPluginsSetup, ObservabilityClientPluginsStart } from '../plugin'; import { HasDataContextProvider } from '../context/has_data_context'; import { Breadcrumbs, routes } from '../routes'; +import { ExploratoryView } from '../components/shared/Exploratory_view/exploratory_view'; const observabilityLabelBreadcrumb = { text: i18n.translate('xpack.observability.observability.breadcrumb.', { @@ -61,7 +62,7 @@ function App() { export const renderApp = ( core: CoreStart, - plugins: ObservabilityPluginSetupDeps, + plugins: ObservabilityClientPluginsStart, appMountParameters: AppMountParameters ) => { const { element, history } = appMountParameters; diff --git a/x-pack/plugins/observability/public/components/shared/Exploratory_view/exploratory_view.tsx b/x-pack/plugins/observability/public/components/shared/Exploratory_view/exploratory_view.tsx new file mode 100644 index 00000000000000..3792c173e00201 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/Exploratory_view/exploratory_view.tsx @@ -0,0 +1,296 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiPageHeader, + EuiPageHeaderSection, + EuiTitle, +} from '@elastic/eui'; +import { DataPublicPluginStart, IndexPattern } from 'src/plugins/data/public'; +import { CoreStart } from 'kibana/public'; + +import { + LensPublicStart, + TypedLensByValueInput, + PersistedIndexPatternLayer, + XYState, +} from '../../../../../lens/public'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { ObservabilityClientPluginsStart } from '../../../plugin'; +import { useFetcher } from '../../..'; + +export interface StartDependencies { + data: DataPublicPluginStart; + lens: LensPublicStart; +} + +const st = { + type: 'lens', + id: '921a4d80-7dfc-11eb-99d2-0de5efac9392', + attributes: { + title: 'Prefilled from example app', + description: '', + visualizationType: 'lnsXY', + state: { + datasourceStates: { + indexpattern: { + layers: { + layer1: { + columnOrder: ['col1', 'col2'], + columns: { + col2: { + dataType: 'number', + isBucketed: false, + label: 'Count of records', + operationType: 'count', + scale: 'ratio', + sourceField: 'Records', + }, + col1: { + dataType: 'date', + isBucketed: true, + label: '@timestamp', + operationType: 'date_histogram', + params: { interval: 'auto' }, + scale: 'interval', + sourceField: '@timestamp', + }, + }, + }, + }, + }, + }, + visualization: { + axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + fittingFunction: 'None', + gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + layers: [ + { + accessors: ['col2'], + layerId: 'layer1', + seriesType: 'bar_stacked', + xAccessor: 'col1', + yConfig: [{ forAccessor: 'col2', color: 'green' }], + }, + ], + legend: { isVisible: true, position: 'right' }, + preferredSeriesType: 'bar_stacked', + tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true }, + valueLabels: 'hide', + }, + query: { language: 'kuery', query: '' }, + filters: [ + { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'transaction.type', + params: { query: 'page-load' }, + indexRefName: 'filter-index-pattern-0', + }, + query: { match_phrase: { 'transaction.type': 'page-load' } }, + $state: { store: 'appState' }, + }, + ], + }, + }, + references: [ + { + type: 'index-pattern', + id: 'apm_static_index_pattern_id', + name: 'indexpattern-datasource-current-indexpattern', + }, + { + type: 'index-pattern', + id: 'apm_static_index_pattern_id', + name: 'indexpattern-datasource-layer-layer1', + }, + { name: 'filter-index-pattern-0', type: 'index-pattern', id: 'apm_static_index_pattern_id' }, + { + type: 'tag', + id: '8e96a500-7dfc-11eb-99d2-0de5efac9392', + name: 'tag-ref-8e96a500-7dfc-11eb-99d2-0de5efac9392', + }, + ], + migrationVersion: { lens: '7.12.0' }, + coreMigrationVersion: '8.0.0', + updated_at: '2021-03-05T21:48:50.136Z', + version: 'WzM4MCwxXQ==', + namespaces: ['default'], +}; + +// Generate a Lens state based on some app-specific input parameters. +// `TypedLensByValueInput` can be used for type-safety - it uses the same interfaces as Lens-internal code. +function getLensAttributes( + defaultIndexPattern: IndexPattern, + color: string +): TypedLensByValueInput['attributes'] { + const dataLayer: PersistedIndexPatternLayer = { + columnOrder: ['col1', 'col2'], + columns: { + col2: { + dataType: 'number', + isBucketed: false, + label: 'Count of records', + operationType: 'count', + scale: 'ratio', + sourceField: 'Records', + }, + col1: { + dataType: 'date', + isBucketed: true, + label: '@timestamp', + operationType: 'date_histogram', + params: { interval: 'auto' }, + scale: 'interval', + sourceField: defaultIndexPattern.timeFieldName!, + }, + }, + }; + + const xyConfig: XYState = { + axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + fittingFunction: 'None', + gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + layers: [ + { + accessors: ['col2'], + layerId: 'layer1', + seriesType: 'bar_stacked', + xAccessor: 'col1', + yConfig: [{ forAccessor: 'col2', color }], + }, + ], + legend: { isVisible: true, position: 'right' }, + preferredSeriesType: 'bar_stacked', + tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true }, + valueLabels: 'hide', + }; + + return { + visualizationType: 'lnsXY', + title: 'Prefilled from example app', + references: [ + { + id: defaultIndexPattern.id!, + name: 'indexpattern-datasource-current-indexpattern', + type: 'index-pattern', + }, + { + id: defaultIndexPattern.id!, + name: 'indexpattern-datasource-layer-layer1', + type: 'index-pattern', + }, + ], + state: { + datasourceStates: { + indexpattern: { + layers: { + layer1: dataLayer, + }, + }, + }, + filters: [ + { + query: { match_phrase: { 'transaction.type': 'page-load' } }, + }, + ], + query: { language: 'kuery', query: '' }, + visualization: xyConfig, + }, + }; +} + +export const ExploratoryView = (props: { + core: CoreStart; + plugins: StartDependencies; + defaultIndexPattern: IndexPattern | null; +}) => { + const [color, setColor] = useState('green'); + + const { + services: { lens, data }, + } = useKibana(); + + const { data: defaultIndexPattern } = useFetcher(() => data.indexPatterns.getDefault(), []); + + const LensComponent = lens.EmbeddableComponent; + + return ( + + + + + +

Exploratory view

+
+
+
+ + + {defaultIndexPattern && defaultIndexPattern.isTimeBased() && ( + <> + + + { + // eslint-disable-next-line no-bitwise + const newColor = '#' + ((Math.random() * 0xffffff) << 0).toString(16); + setColor(newColor); + }} + > + Change color + + + + { + lens.navigateToPrefilledEditor({ + id: '', + timeRange: { + from: '2021-01-18T12:19:28.685Z', + to: '2021-01-18T12:26:20.767Z', + }, + attributes: getLensAttributes(defaultIndexPattern!, color), + }); + // eslint-disable-next-line no-bitwise + const newColor = '#' + ((Math.random() * 0xffffff) << 0).toString(16); + setColor(newColor); + }} + > + Edit + + + + + + )} + + +
+
+ ); +}; diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index a30d2a7411c23b..d8b3a2d802904f 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -7,7 +7,7 @@ import { BehaviorSubject } from 'rxjs'; import { i18n } from '@kbn/i18n'; -import { DataPublicPluginSetup } from '../../../../src/plugins/data/public'; +import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public'; import { AppMountParameters, AppUpdater, @@ -20,24 +20,42 @@ import { import { HomePublicPluginSetup } from '../../../../src/plugins/home/public'; import { registerDataHandler } from './data_handler'; import { toggleOverviewLinkInNav } from './toggle_overview_link_in_nav'; +import { LensPublicStart } from '../../lens/public'; export interface ObservabilityPluginSetup { dashboard: { register: typeof registerDataHandler }; } -export interface ObservabilityPluginSetupDeps { - home?: HomePublicPluginSetup; +export interface ObservabilityClientPluginsSetup { data: DataPublicPluginSetup; + home?: HomePublicPluginSetup; +} + +export interface ObservabilityClientPluginsStart { + home?: HomePublicPluginSetup; + data: DataPublicPluginStart; + lens: LensPublicStart; } -export type ObservabilityPluginStart = void; +export type ObservabilityClientSetup = void; +export type ObservabilityClientStart = void; -export class Plugin implements PluginClass { +export class Plugin + implements + PluginClass< + ObservabilityClientSetup, + ObservabilityClientStart, + ObservabilityClientPluginsSetup, + ObservabilityClientPluginsStart + > { private readonly appUpdater$ = new BehaviorSubject(() => ({})); constructor(context: PluginInitializerContext) {} - public setup(core: CoreSetup, plugins: ObservabilityPluginSetupDeps) { + public setup( + core: CoreSetup, + plugins: ObservabilityClientPluginsSetup + ) { core.application.register({ id: 'observability-overview', title: 'Overview', @@ -51,9 +69,9 @@ export class Plugin implements PluginClass = DecodeParams; @@ -72,4 +73,24 @@ export const routes = { }, ], }, + '/exploratory-view': { + handler: ({ query }: any) => { + return ; + }, + params: { + query: t.partial({ + rangeFrom: t.string, + rangeTo: t.string, + refreshPaused: jsonRt.pipe(t.boolean), + refreshInterval: jsonRt.pipe(t.number), + }), + }, + breadcrumb: [ + { + text: i18n.translate('xpack.observability.overview.breadcrumb', { + defaultMessage: 'Overview', + }), + }, + ], + }, }; diff --git a/x-pack/plugins/observability/tsconfig.json b/x-pack/plugins/observability/tsconfig.json index 62aecc1e0899f1..950ab79cc195ba 100644 --- a/x-pack/plugins/observability/tsconfig.json +++ b/x-pack/plugins/observability/tsconfig.json @@ -18,5 +18,6 @@ { "path": "../alerts/tsconfig.json" }, { "path": "../licensing/tsconfig.json" }, { "path": "../translations/tsconfig.json" } + { "path": "../lens/tsconfig.json" } ] } From b089a8719cdee1074b500bb3ee60c9c7c0570284 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 10 Mar 2021 16:18:27 +0100 Subject: [PATCH 02/53] Save progress --- .../public/application/index.tsx | 3 +- .../Exploratory_view/exploratory_view.tsx | 296 ------------------ .../exploratory_view/assets/axis_bottom.tsx | 31 ++ .../exploratory_view/assets/axis_left.tsx | 32 ++ .../exploratory_view/assets/axis_right.tsx | 32 ++ .../exploratory_view/assets/axis_top.tsx | 35 +++ .../exploratory_view/assets/chart_area.tsx | 31 ++ .../assets/chart_area_percentage.tsx | 35 +++ .../assets/chart_area_stacked.tsx | 35 +++ .../exploratory_view/assets/chart_bar.tsx | 31 ++ .../assets/chart_bar_horizontal.tsx | 35 +++ .../chart_bar_horizontal_percentage.tsx | 35 +++ .../assets/chart_bar_horizontal_stacked.tsx | 35 +++ .../assets/chart_bar_percentage.tsx | 35 +++ .../assets/chart_bar_stacked.tsx | 35 +++ .../assets/chart_datatable.tsx | 35 +++ .../exploratory_view/assets/chart_donut.tsx | 31 ++ .../exploratory_view/assets/chart_line.tsx | 31 ++ .../exploratory_view/assets/chart_metric.tsx | 31 ++ .../assets/chart_mixed_xy.tsx | 35 +++ .../exploratory_view/assets/chart_pie.tsx | 31 ++ .../exploratory_view/assets/chart_treemap.tsx | 35 +++ .../assets/drop_illustration.tsx | 49 +++ .../shared/exploratory_view/assets/legend.tsx | 40 +++ .../assets/lens_app_graphic_dark_2x.png | Bin 0 -> 82733 bytes .../assets/lens_app_graphic_light_2x.png | Bin 0 -> 94444 bytes .../chart_templates/chart_templates.tsx | 77 +++++ .../exploratory_view/chart_templates/tabs.tsx | 43 +++ .../configurations/constants.ts | 23 ++ .../configurations/default_configs.ts | 24 ++ .../configurations/lens_attributes.ts | 215 +++++++++++++ .../configurations/monitor_duration_config.ts | 32 ++ .../configurations/page_load_dist_config.ts | 40 +++ .../configurations/page_view_config.ts | 40 +++ .../exploratory_view/exploratory_view.tsx | 94 ++++++ .../shared/exploratory_view/header.tsx | 64 ++++ .../exploratory_view/header/chart_types.tsx | 164 ++++++++++ .../hooks/use_lens_attributes.ts | 54 ++++ .../hooks/use_url_strorage.tsx | 22 ++ .../shared/exploratory_view/index.tsx | 69 ++++ .../series_date_picker/index.tsx | 66 ++++ .../series_editor/columns/actions_col.tsx | 20 ++ .../series_editor/columns/breakdowns.tsx | 54 ++++ .../series_editor/columns/filter_expanded.tsx | 131 ++++++++ .../series_editor/columns/remove_series.tsx | 14 + .../series_editor/columns/series_filter.tsx | 102 ++++++ .../series_editor/selected_filters.tsx | 44 +++ .../series_editor/series_editor.tsx | 70 +++++ .../shared/exploratory_view/types.ts | 48 +++ .../public/hooks/use_breadcrumbs.ts | 71 +++++ .../hooks/use_default_index_pattern.tsx | 37 +++ .../observability/public/routes/index.tsx | 15 +- 52 files changed, 2383 insertions(+), 304 deletions(-) delete mode 100644 x-pack/plugins/observability/public/components/shared/Exploratory_view/exploratory_view.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_bottom.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_left.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_right.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_top.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_area.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_area_percentage.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_area_stacked.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_horizontal.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_horizontal_percentage.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_horizontal_stacked.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_percentage.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_stacked.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_datatable.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_donut.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_line.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_metric.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_mixed_xy.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_pie.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_treemap.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/drop_illustration.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/legend.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/lens_app_graphic_dark_2x.png create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/lens_app_graphic_light_2x.png create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/chart_templates/chart_templates.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/chart_templates/tabs.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_duration_config.ts create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_view_config.ts create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/header.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/header/chart_types.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/actions_col.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/remove_series.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts create mode 100644 x-pack/plugins/observability/public/hooks/use_breadcrumbs.ts create mode 100644 x-pack/plugins/observability/public/hooks/use_default_index_pattern.tsx diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index f38a1d3642aa7c..32739bffe397e1 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -18,10 +18,9 @@ import { import { PluginContext } from '../context/plugin_context'; import { usePluginContext } from '../hooks/use_plugin_context'; import { useRouteParams } from '../hooks/use_route_params'; -import { ObservabilityClientPluginsSetup, ObservabilityClientPluginsStart } from '../plugin'; +import { ObservabilityClientPluginsStart } from '../plugin'; import { HasDataContextProvider } from '../context/has_data_context'; import { Breadcrumbs, routes } from '../routes'; -import { ExploratoryView } from '../components/shared/Exploratory_view/exploratory_view'; const observabilityLabelBreadcrumb = { text: i18n.translate('xpack.observability.observability.breadcrumb.', { diff --git a/x-pack/plugins/observability/public/components/shared/Exploratory_view/exploratory_view.tsx b/x-pack/plugins/observability/public/components/shared/Exploratory_view/exploratory_view.tsx deleted file mode 100644 index 3792c173e00201..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/Exploratory_view/exploratory_view.tsx +++ /dev/null @@ -1,296 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useState } from 'react'; -import { - EuiButton, - EuiFlexGroup, - EuiFlexItem, - EuiPage, - EuiPageBody, - EuiPageContent, - EuiPageContentBody, - EuiPageHeader, - EuiPageHeaderSection, - EuiTitle, -} from '@elastic/eui'; -import { DataPublicPluginStart, IndexPattern } from 'src/plugins/data/public'; -import { CoreStart } from 'kibana/public'; - -import { - LensPublicStart, - TypedLensByValueInput, - PersistedIndexPatternLayer, - XYState, -} from '../../../../../lens/public'; -import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; -import { ObservabilityClientPluginsStart } from '../../../plugin'; -import { useFetcher } from '../../..'; - -export interface StartDependencies { - data: DataPublicPluginStart; - lens: LensPublicStart; -} - -const st = { - type: 'lens', - id: '921a4d80-7dfc-11eb-99d2-0de5efac9392', - attributes: { - title: 'Prefilled from example app', - description: '', - visualizationType: 'lnsXY', - state: { - datasourceStates: { - indexpattern: { - layers: { - layer1: { - columnOrder: ['col1', 'col2'], - columns: { - col2: { - dataType: 'number', - isBucketed: false, - label: 'Count of records', - operationType: 'count', - scale: 'ratio', - sourceField: 'Records', - }, - col1: { - dataType: 'date', - isBucketed: true, - label: '@timestamp', - operationType: 'date_histogram', - params: { interval: 'auto' }, - scale: 'interval', - sourceField: '@timestamp', - }, - }, - }, - }, - }, - }, - visualization: { - axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, - fittingFunction: 'None', - gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, - layers: [ - { - accessors: ['col2'], - layerId: 'layer1', - seriesType: 'bar_stacked', - xAccessor: 'col1', - yConfig: [{ forAccessor: 'col2', color: 'green' }], - }, - ], - legend: { isVisible: true, position: 'right' }, - preferredSeriesType: 'bar_stacked', - tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true }, - valueLabels: 'hide', - }, - query: { language: 'kuery', query: '' }, - filters: [ - { - meta: { - alias: null, - negate: false, - disabled: false, - type: 'phrase', - key: 'transaction.type', - params: { query: 'page-load' }, - indexRefName: 'filter-index-pattern-0', - }, - query: { match_phrase: { 'transaction.type': 'page-load' } }, - $state: { store: 'appState' }, - }, - ], - }, - }, - references: [ - { - type: 'index-pattern', - id: 'apm_static_index_pattern_id', - name: 'indexpattern-datasource-current-indexpattern', - }, - { - type: 'index-pattern', - id: 'apm_static_index_pattern_id', - name: 'indexpattern-datasource-layer-layer1', - }, - { name: 'filter-index-pattern-0', type: 'index-pattern', id: 'apm_static_index_pattern_id' }, - { - type: 'tag', - id: '8e96a500-7dfc-11eb-99d2-0de5efac9392', - name: 'tag-ref-8e96a500-7dfc-11eb-99d2-0de5efac9392', - }, - ], - migrationVersion: { lens: '7.12.0' }, - coreMigrationVersion: '8.0.0', - updated_at: '2021-03-05T21:48:50.136Z', - version: 'WzM4MCwxXQ==', - namespaces: ['default'], -}; - -// Generate a Lens state based on some app-specific input parameters. -// `TypedLensByValueInput` can be used for type-safety - it uses the same interfaces as Lens-internal code. -function getLensAttributes( - defaultIndexPattern: IndexPattern, - color: string -): TypedLensByValueInput['attributes'] { - const dataLayer: PersistedIndexPatternLayer = { - columnOrder: ['col1', 'col2'], - columns: { - col2: { - dataType: 'number', - isBucketed: false, - label: 'Count of records', - operationType: 'count', - scale: 'ratio', - sourceField: 'Records', - }, - col1: { - dataType: 'date', - isBucketed: true, - label: '@timestamp', - operationType: 'date_histogram', - params: { interval: 'auto' }, - scale: 'interval', - sourceField: defaultIndexPattern.timeFieldName!, - }, - }, - }; - - const xyConfig: XYState = { - axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, - fittingFunction: 'None', - gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, - layers: [ - { - accessors: ['col2'], - layerId: 'layer1', - seriesType: 'bar_stacked', - xAccessor: 'col1', - yConfig: [{ forAccessor: 'col2', color }], - }, - ], - legend: { isVisible: true, position: 'right' }, - preferredSeriesType: 'bar_stacked', - tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true }, - valueLabels: 'hide', - }; - - return { - visualizationType: 'lnsXY', - title: 'Prefilled from example app', - references: [ - { - id: defaultIndexPattern.id!, - name: 'indexpattern-datasource-current-indexpattern', - type: 'index-pattern', - }, - { - id: defaultIndexPattern.id!, - name: 'indexpattern-datasource-layer-layer1', - type: 'index-pattern', - }, - ], - state: { - datasourceStates: { - indexpattern: { - layers: { - layer1: dataLayer, - }, - }, - }, - filters: [ - { - query: { match_phrase: { 'transaction.type': 'page-load' } }, - }, - ], - query: { language: 'kuery', query: '' }, - visualization: xyConfig, - }, - }; -} - -export const ExploratoryView = (props: { - core: CoreStart; - plugins: StartDependencies; - defaultIndexPattern: IndexPattern | null; -}) => { - const [color, setColor] = useState('green'); - - const { - services: { lens, data }, - } = useKibana(); - - const { data: defaultIndexPattern } = useFetcher(() => data.indexPatterns.getDefault(), []); - - const LensComponent = lens.EmbeddableComponent; - - return ( - - - - - -

Exploratory view

-
-
-
- - - {defaultIndexPattern && defaultIndexPattern.isTimeBased() && ( - <> - - - { - // eslint-disable-next-line no-bitwise - const newColor = '#' + ((Math.random() * 0xffffff) << 0).toString(16); - setColor(newColor); - }} - > - Change color - - - - { - lens.navigateToPrefilledEditor({ - id: '', - timeRange: { - from: '2021-01-18T12:19:28.685Z', - to: '2021-01-18T12:26:20.767Z', - }, - attributes: getLensAttributes(defaultIndexPattern!, color), - }); - // eslint-disable-next-line no-bitwise - const newColor = '#' + ((Math.random() * 0xffffff) << 0).toString(16); - setColor(newColor); - }} - > - Edit - - - - - - )} - - -
-
- ); -}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_bottom.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_bottom.tsx new file mode 100644 index 00000000000000..309d41bf242214 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_bottom.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as React from 'react'; + +export const EuiIconAxisBottom = ({ + title, + titleId, + ...props +}: { + title: string; + titleId: string; +}) => ( + + {title ? {title} : null} + + + +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_left.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_left.tsx new file mode 100644 index 00000000000000..9a39a2f43a74df --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_left.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as React from 'react'; + +export const EuiIconAxisLeft = ({ + title, + titleId, + ...props +}: { + title: string; + titleId: string; +}) => ( + + {title ? {title} : null} + + + + +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_right.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_right.tsx new file mode 100644 index 00000000000000..4db6fc06d82fa7 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_right.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as React from 'react'; + +export const EuiIconAxisRight = ({ + title, + titleId, + ...props +}: { + title: string; + titleId: string; +}) => ( + + {title ? {title} : null} + + + + +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_top.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_top.tsx new file mode 100644 index 00000000000000..2c3dc0744c0684 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_top.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as React from 'react'; + +export const EuiIconAxisTop = ({ + title, + titleId, + ...props +}: { + title: string; + titleId: string; +}) => ( + + {title ? {title} : null} + + + + + + + +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_area.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_area.tsx new file mode 100644 index 00000000000000..664735205d97e0 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_area.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiIconProps } from '@elastic/eui'; + +export const LensIconChartArea = ({ title, titleId, ...props }: Omit) => ( + + {title ? {title} : null} + + + +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_area_percentage.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_area_percentage.tsx new file mode 100644 index 00000000000000..910d5dc8172899 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_area_percentage.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiIconProps } from '@elastic/eui'; + +export const LensIconChartAreaPercentage = ({ + title, + titleId, + ...props +}: Omit) => ( + + {title ? {title} : null} + + + +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_area_stacked.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_area_stacked.tsx new file mode 100644 index 00000000000000..16e1ff849b6091 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_area_stacked.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiIconProps } from '@elastic/eui'; + +export const LensIconChartAreaStacked = ({ + title, + titleId, + ...props +}: Omit) => ( + + {title ? {title} : null} + + + +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar.tsx new file mode 100644 index 00000000000000..991c9a6c74ced4 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiIconProps } from '@elastic/eui'; + +export const LensIconChartBar = ({ title, titleId, ...props }: Omit) => ( + + {title ? {title} : null} + + + +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_horizontal.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_horizontal.tsx new file mode 100644 index 00000000000000..dfd25158cc2958 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_horizontal.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiIconProps } from '@elastic/eui'; + +export const LensIconChartBarHorizontal = ({ + title, + titleId, + ...props +}: Omit) => ( + + {title ? {title} : null} + + + +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_horizontal_percentage.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_horizontal_percentage.tsx new file mode 100644 index 00000000000000..a3c79991f2affc --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_horizontal_percentage.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiIconProps } from '@elastic/eui'; + +export const LensIconChartBarHorizontalPercentage = ({ + title, + titleId, + ...props +}: Omit) => ( + + {title ? {title} : null} + + + +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_horizontal_stacked.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_horizontal_stacked.tsx new file mode 100644 index 00000000000000..d6abbaa37aaa0f --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_horizontal_stacked.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiIconProps } from '@elastic/eui'; + +export const LensIconChartBarHorizontalStacked = ({ + title, + titleId, + ...props +}: Omit) => ( + + {title ? {title} : null} + + + +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_percentage.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_percentage.tsx new file mode 100644 index 00000000000000..c297ee831f27cc --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_percentage.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiIconProps } from '@elastic/eui'; + +export const LensIconChartBarPercentage = ({ + title, + titleId, + ...props +}: Omit) => ( + + {title ? {title} : null} + + + +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_stacked.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_stacked.tsx new file mode 100644 index 00000000000000..c5775ce4bf859d --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_stacked.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiIconProps } from '@elastic/eui'; + +export const LensIconChartBarStacked = ({ + title, + titleId, + ...props +}: Omit) => ( + + {title ? {title} : null} + + + +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_datatable.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_datatable.tsx new file mode 100644 index 00000000000000..d6a5cb733e1e14 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_datatable.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiIconProps } from '@elastic/eui'; + +export const LensIconChartDatatable = ({ + title, + titleId, + ...props +}: Omit) => ( + + {title ? {title} : null} + + + +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_donut.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_donut.tsx new file mode 100644 index 00000000000000..f2ce65a1f2e196 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_donut.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiIconProps } from '@elastic/eui'; + +export const LensIconChartDonut = ({ title, titleId, ...props }: Omit) => ( + + {title ? {title} : null} + + + +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_line.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_line.tsx new file mode 100644 index 00000000000000..82df68d8fd43cb --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_line.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiIconProps } from '@elastic/eui'; + +export const LensIconChartLine = ({ title, titleId, ...props }: Omit) => ( + + {title ? {title} : null} + + + +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_metric.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_metric.tsx new file mode 100644 index 00000000000000..d15be760eec605 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_metric.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiIconProps } from '@elastic/eui'; + +export const LensIconChartMetric = ({ title, titleId, ...props }: Omit) => ( + + {title ? {title} : null} + + + +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_mixed_xy.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_mixed_xy.tsx new file mode 100644 index 00000000000000..bb213217a9fa9c --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_mixed_xy.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiIconProps } from '@elastic/eui'; + +export const LensIconChartMixedXy = ({ title, titleId, ...props }: Omit) => ( + + {title ? {title} : null} + + + + +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_pie.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_pie.tsx new file mode 100644 index 00000000000000..56a18b9d616240 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_pie.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiIconProps } from '@elastic/eui'; + +export const LensIconChartPie = ({ title, titleId, ...props }: Omit) => ( + + {title ? {title} : null} + + + +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_treemap.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_treemap.tsx new file mode 100644 index 00000000000000..c56fdedfebdf7c --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_treemap.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiIconProps } from '@elastic/eui'; + +export const LensIconChartTreemap = ({ title, titleId, ...props }: Omit) => ( + + {title ? {title} : null} + + + + +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/drop_illustration.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/drop_illustration.tsx new file mode 100644 index 00000000000000..acd6d7bf313241 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/drop_illustration.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as React from 'react'; +import { EuiIconProps } from '@elastic/eui'; + +export const DropIllustration = ({ title, titleId, ...props }: Omit) => ( + + {title ? {title} : null} + + + + + + + + +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/legend.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/legend.tsx new file mode 100644 index 00000000000000..cde39b222cdd40 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/legend.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as React from 'react'; + +export const EuiIconLegend = ({ title, titleId, ...props }: { title: string; titleId: string }) => ( + + {title ? {title} : null} + + + + + + + +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/lens_app_graphic_dark_2x.png b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/lens_app_graphic_dark_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..2c2c71b82180a7574427c284cfffa91771a2296a GIT binary patch literal 82733 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

D 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

s4&bcDbHgys`Q8|Ssnb#9qa?* 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 setIsFlyoutVisible(false)} aria-labelledby="flyoutTitle"> + + +

Choose chart

+ + + + + + + + } + title={`Page load distribution`} + description="Example of a card's description. Stick to one or two sentences." + href="/app/observability/exploratory-view/page-load-dist" + /> + + + } + title={`Page views`} + description="Example of a card's description. Stick to one or two sentences." + href="/app/observability/exploratory-view/page-views" + /> + + + } + title={`Monitor duration`} + description="Uptime monitor duration, slice and dice by location etc" + href="/app/observability/exploratory-view/uptime-duration" + /> + + + + + ); + } + + return ( +
+ setIsFlyoutVisible(true)}>Chart templates + {flyout} +
+ ); +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/chart_templates/tabs.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/chart_templates/tabs.tsx new file mode 100644 index 00000000000000..4643bc4ee7f99c --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/chart_templates/tabs.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; + +import { EuiTabs, EuiTab } from '@elastic/eui'; + +const tabs = [ + { + id: 'savedCharts', + name: 'Saved charts', + }, + { + id: 'predefinedCharts', + name: 'Predefined charts', + }, +]; + +export function SelectionTabs() { + const [selectedTabId, setSelectedTabId] = useState('predefinedCharts'); + + const onSelectedTabChanged = (id: string) => { + setSelectedTabId(id); + }; + + const renderTabs = () => { + return tabs.map((tab, index) => ( + onSelectedTabChanged(tab.id)} + isSelected={tab.id === selectedTabId} + key={index} + > + {tab.name} + + )); + }; + + return {renderTabs()}; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts new file mode 100644 index 00000000000000..f1dce5421345be --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataViewType } from '../types'; + +export const FieldLabels: Record = { + 'user_agent.name': 'Browser family', + 'user_agent.os.name': 'Operating system', + 'client.geo.country_name': 'Location', + 'user_agent.device.name': 'Device', + 'observer.geo.name': 'Observer location', +}; + +export const DataViewLabels: Record = { + 'page-load-dist': 'Page load distribution', + 'page-views': 'Page views', + 'uptime-duration': 'Uptime monitor duration', + 'uptime-pings': 'Uptime pings', +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts new file mode 100644 index 00000000000000..bbc4ae9739a939 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataViewType } from '../types'; +import { getPageLoadDistLensConfig } from './page_load_dist_config'; +import { getPageViewLensConfig } from './page_view_config'; +import { getMonitorDurationConfig } from './monitor_duration_config'; + +export const getDefaultConfigs = ({ dataViewType }: { dataViewType: DataViewType }) => { + switch (dataViewType) { + case 'page-load-dist': + return getPageLoadDistLensConfig(); + case 'page-views': + return getPageViewLensConfig(); + case 'uptime-duration': + return getMonitorDurationConfig(); + default: + return getPageViewLensConfig(); + } +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts new file mode 100644 index 00000000000000..78cc6f903a590f --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts @@ -0,0 +1,215 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + PersistedIndexPatternLayer, + TypedLensByValueInput, + XYState, +} from '../../../../../../lens/public'; +import { IIndexPattern } from '../../../../../../../../src/plugins/data/common'; +import { FieldLabels } from './constants'; +import { DataSeries, UrlFilter } from '../types'; + +export class LensAttributes { + indexPattern: IIndexPattern; + layers: Record; + visualization: XYState; + filters: UrlFilter[]; + seriesType: string; + dataViewConfig: DataSeries; + + constructor( + indexPattern: IIndexPattern, + dataViewConfig: DataSeries, + seriesType: string, + filters: UrlFilter[] + ) { + this.indexPattern = indexPattern; + this.layers = {}; + this.filters = filters ?? []; + this.seriesType = seriesType ?? dataViewConfig.defaultSeriesType; + this.dataViewConfig = dataViewConfig; + this.addLayer(); + this.visualization = this.getXyState(); + } + + addBreakdown(sourceField: string) { + this.layers.layer1.columns['break-down-column'] = { + sourceField, + label: `Top values of user_agent.name${FieldLabels[sourceField]}`, + dataType: 'string', + operationType: 'terms', + scale: 'ordinal', + isBucketed: true, + params: { + size: 3, + orderBy: { type: 'column', columnId: 'y-axis-column' }, + orderDirection: 'desc', + otherBucket: true, + missingBucket: false, + }, + }; + + this.layers.layer1.columnOrder = ['x-axis-column', 'break-down-column', 'y-axis-column']; + + this.visualization.layers[0].splitAccessor = 'break-down-column'; + } + + removeBreakdown() { + this.layers.layer1.columns['break-down-column'] = undefined; + + this.layers.layer1.columnOrder = ['x-axis-column', 'y-axis-column']; + + this.visualization.layers[0].splitAccessor = undefined; + } + + getNumberColumn(sourceField: string) { + return { + sourceField, + label: FieldLabels[sourceField], + dataType: 'number', + operationType: 'range', + isBucketed: true, + scale: 'interval', + params: { + type: 'histogram', + ranges: [{ from: 0, to: 1000, label: '' }], + maxBars: 'auto', + }, + }; + } + + getDateHistogramColumn(sourceField: string) { + return { + sourceField, + dataType: 'date', + isBucketed: true, + label: '@timestamp', + operationType: 'date_histogram', + params: { interval: 'auto' }, + scale: 'interval', + }; + } + + getXAxis() { + const { xAxisColumn } = this.dataViewConfig; + + if (xAxisColumn.sourceField) { + const fieldMeta = this.indexPattern.fields.find( + (field) => field.name === xAxisColumn.sourceField + ); + + if (fieldMeta?.type === 'date') { + return this.getDateHistogramColumn(xAxisColumn.sourceField); + } + } + + if (xAxisColumn) + return { + label: 'Page load duration', + dataType: 'number', + operationType: 'range', + sourceField: 'transaction.duration.us', + isBucketed: true, + scale: 'interval', + params: { + type: 'histogram', + ranges: [{ from: 0, to: 1000, label: '' }], + maxBars: 'auto', + }, + }; + } + + getMainYAxis() { + return { + dataType: 'number', + isBucketed: false, + label: 'Count of records', + operationType: 'count', + scale: 'ratio', + sourceField: 'Records', + ...this.dataViewConfig.yAxisColumn, + }; + } + + addLayer() { + this.layers.layer1 = { + columnOrder: ['x-axis-column', 'y-axis-column'], + columns: { + 'x-axis-column': this.getXAxis(), + 'y-axis-column': this.getMainYAxis(), + }, + incompleteColumns: {}, + }; + } + + getXyState(): XYState { + return { + legend: { isVisible: true, position: 'right' }, + valueLabels: 'hide', + fittingFunction: 'Linear', + // fittingFunction: 'None', + axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true }, + gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + preferredSeriesType: 'line', + // preferredSeriesType: 'bar_stacked', + layers: [ + { + accessors: ['y-axis-column'], + layerId: 'layer1', + seriesType: this.seriesType ?? 'line', + yConfig: [{ forAccessor: 'y-axis-column', color: 'green' }], + xAccessor: 'x-axis-column', + }, + ], + }; + } + + parseFilters() { + const defaultFilters = this.dataViewConfig.filters ?? {}; + const parsedFilters = this.dataViewConfig.filters ? [defaultFilters] : []; + + this.filters.forEach(({ field, values = [], notValues = [] }) => { + values?.forEach((value) => { + parsedFilters.push({ query: { match_phrase: { [field]: value } } }); + }); + }); + + return parsedFilters; + } + + getJSON(): TypedLensByValueInput['attributes'] { + return { + title: 'Prefilled from example app', + description: '', + visualizationType: 'lnsXY', + references: [ + { + id: this.indexPattern.id, + name: 'indexpattern-datasource-current-indexpattern', + type: 'index-pattern', + }, + { + id: this.indexPattern.id, + name: 'indexpattern-datasource-layer-layer1', + type: 'index-pattern', + }, + ], + state: { + datasourceStates: { + indexpattern: { + layers: this.layers, + }, + }, + visualization: this.visualization, + query: { query: '', language: 'kuery' }, + filters: this.parseFilters(), + }, + }; + } +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_duration_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_duration_config.ts new file mode 100644 index 00000000000000..82a3ff98c62c4b --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_duration_config.ts @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataSeries } from '../types'; + +export function getMonitorDurationConfig(): DataSeries { + return { + name: 'Android homepage', + id: 'elastic-co', + dataViewType: 'uptime-duration', + defaultSeriesType: 'line', + indexPattern: 'df32db00-819e-11eb-87f5-d7da22b1dde3', + seriesTypes: ['bar', 'bar_stacked'], + xAxisColumn: { + sourceField: '@timestamp', + }, + yAxisColumn: { + operationType: 'avg', + sourceField: 'monitor.duration.us', + label: 'Monitor duration', + }, + defaultFilters: ['observer.geo.name'], + breakdowns: ['observer.geo.name'], + filters: { + query: { match_phrase: { 'monitor.id': 'android-homepage' } }, + }, + }; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts new file mode 100644 index 00000000000000..4c97bb0471ccfe --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataSeries } from '../types'; + +export function getPageLoadDistLensConfig(): DataSeries { + return { + name: 'elastic.co', + id: 'elastic-co', + dataViewType: 'page-load-dist', + defaultSeriesType: 'line', + indexPattern: 'apm_static_index_pattern_id', + seriesTypes: ['line', 'bar'], + xAxisColumn: { + sourceField: 'transaction.duration.us', + }, + yAxisColumn: { + operationType: 'count', + }, + defaultFilters: [ + 'user_agent.name', + 'user_agent.os.name', + 'client.geo.country_name', + 'user_agent.device.name', + ], + breakdowns: [ + 'user_agent.name', + 'user_agent.os.name', + 'client.geo.country_name', + 'user_agent.device.name', + ], + filters: { + query: { match_phrase: { 'transaction.type': 'page-load' } }, + }, + }; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_view_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_view_config.ts new file mode 100644 index 00000000000000..e8028fc2406ef1 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_view_config.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataSeries } from '../types'; + +export function getPageViewLensConfig(): DataSeries { + return { + name: 'elastic.co', + id: 'elastic-co', + defaultSeriesType: 'bar', + dataViewType: 'page-views', + indexPattern: 'apm_static_index_pattern_id', + seriesTypes: ['bar', 'bar_stacked'], + xAxisColumn: { + sourceField: '@timestamp', + }, + yAxisColumn: { + operationType: 'count', + }, + defaultFilters: [ + 'user_agent.name', + 'user_agent.os.name', + 'client.geo.country_name', + 'user_agent.device.name', + ], + breakdowns: [ + 'user_agent.name', + 'user_agent.os.name', + 'client.geo.country_name', + 'user_agent.device.name', + ], + filters: { + query: { match_phrase: { 'transaction.type': 'page-load' } }, + }, + }; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx new file mode 100644 index 00000000000000..f7ca5877c9ca55 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { + EuiLoadingSpinner, + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiPageHeader, + EuiPageHeaderSection, + EuiTitle, +} from '@elastic/eui'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { ObservabilityClientPluginsStart } from '../../../plugin'; +import { IndexPattern } from '../../../../../../../src/plugins/data/common'; +import { ExploratoryViewHeader } from './header'; +import { SeriesEditor } from './series_editor/series_editor'; +import { useUrlStorage } from './hooks/use_url_strorage'; +import { SeriesUrl } from './types'; +import { useLensAttributes } from './hooks/use_lens_attributes'; +import styled from 'styled-components'; + +export interface Props { + seriesId: string; + defaultIndexPattern?: IndexPattern | null; +} + +export const ExploratoryView = ({ seriesId, defaultIndexPattern }: Props) => { + const { + services: { lens }, + } = useKibana(); + + const LensComponent = lens.EmbeddableComponent; + + const storage = useUrlStorage(); + + const series = storage.get('elastic-co'); + + const lensAttributes = useLensAttributes({ + seriesId: 'elastic-co', + }); + + return ( + + + + + +

Exploratory view

+
+
+
+ + + {defaultIndexPattern ? ( + <> + + + + + ) : ( + + + + )} + + +
+
+ ); +}; + +const SpinnerWrap = styled.div` + height: 100vh; + display: flex; + justify-content: center; + align-items: center; +`; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header.tsx new file mode 100644 index 00000000000000..834bda562e0239 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { useParams } from 'react-router-dom'; +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { TypedLensByValueInput } from '../../../../../lens/public'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { ObservabilityClientPluginsStart } from '../../../plugin'; +import { ChartTemplates } from './chart_templates/chart_templates'; +import { ChartTypes } from './header/chart_types'; +import { DataViewType } from './types'; +import { DataViewLabels } from './configurations/constants'; + +interface Props { + lensAttributes: TypedLensByValueInput['attributes']; +} + +export function ExploratoryViewHeader({ lensAttributes }: Props) { + const { + services: { lens }, + } = useKibana(); + + const { dataViewType } = useParams<{ dataViewType: DataViewType }>(); + + return ( + + + +

{DataViewLabels[dataViewType]}

+
+
+ + + + + { + lens.navigateToPrefilledEditor({ + id: '', + timeRange: { + from: '2021-01-18T12:19:28.685Z', + to: '2021-01-18T12:26:20.767Z', + }, + attributes: lensAttributes, + }); + }} + > + Open in Lens + + + + + +
+ ); +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/chart_types.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/chart_types.tsx new file mode 100644 index 00000000000000..580869d4989d0e --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/chart_types.tsx @@ -0,0 +1,164 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiButtonGroup } from '@elastic/eui'; +import { VisualizationType } from '../../../../../../lens/public/types'; +import { i18n } from '@kbn/i18n'; + +import { LensIconChartBar } from '../assets/chart_bar'; +import { LensIconChartBarHorizontal } from '../assets/chart_bar_horizontal'; +import { LensIconChartBarStacked } from '../assets/chart_bar_stacked'; +import { LensIconChartBarPercentage } from '../assets/chart_bar_percentage'; +import { LensIconChartBarHorizontalStacked } from '../assets/chart_bar_horizontal_stacked'; +import { LensIconChartBarHorizontalPercentage } from '../assets/chart_bar_horizontal_percentage'; +import { LensIconChartArea } from '../assets/chart_area'; +import { LensIconChartAreaPercentage } from '../assets/chart_area_percentage'; +import { LensIconChartLine } from '../assets/chart_line'; +import { LensIconChartAreaStacked } from '../assets/chart_area_stacked'; +import styled from 'styled-components'; +import { useUrlStorage } from '../hooks/use_url_strorage'; +import { SeriesUrl } from '../types'; + +const ButtonGroup = styled(EuiButtonGroup)` + &&& { + .euiButtonGroupButton-isSelected { + background-color: #a5a9b1 !important; + } + } +`; + +export const ChartTypes = () => { + const storage = useUrlStorage(); + + const series = storage.get('elastic-co'); + + return ( + ({ + id: t.id, + label: t.label, + iconType: t.icon || 'empty', + 'data-test-subj': `lnsXY_seriesType-${t.id}`, + }))} + idSelected={series?.seriesType ?? 'line'} + onChange={(seriesType: string) => { + storage.set('elastic-co', { ...series, seriesType }); + }} + /> + ); +}; + +const groupLabelForBar = i18n.translate('xpack.lens.xyVisualization.barGroupLabel', { + defaultMessage: 'Bar', +}); + +const groupLabelForLineAndArea = i18n.translate('xpack.lens.xyVisualization.lineGroupLabel', { + defaultMessage: 'Line and area', +}); + +export const visualizationTypes: VisualizationType[] = [ + { + id: 'bar', + icon: LensIconChartBar, + label: i18n.translate('xpack.lens.xyVisualization.barLabel', { + defaultMessage: 'Bar vertical', + }), + groupLabel: groupLabelForBar, + }, + { + id: 'bar_horizontal', + icon: LensIconChartBarHorizontal, + label: i18n.translate('xpack.lens.xyVisualization.barHorizontalLabel', { + defaultMessage: 'H. Bar', + }), + fullLabel: i18n.translate('xpack.lens.xyVisualization.barHorizontalFullLabel', { + defaultMessage: 'Bar horizontal', + }), + groupLabel: groupLabelForBar, + }, + { + id: 'bar_stacked', + icon: LensIconChartBarStacked, + label: i18n.translate('xpack.lens.xyVisualization.stackedBarLabel', { + defaultMessage: 'Bar vertical stacked', + }), + groupLabel: groupLabelForBar, + }, + { + id: 'bar_percentage_stacked', + icon: LensIconChartBarPercentage, + label: i18n.translate('xpack.lens.xyVisualization.stackedPercentageBarLabel', { + defaultMessage: 'Bar vertical percentage', + }), + groupLabel: groupLabelForBar, + }, + { + id: 'bar_horizontal_stacked', + icon: LensIconChartBarHorizontalStacked, + label: i18n.translate('xpack.lens.xyVisualization.stackedBarHorizontalLabel', { + defaultMessage: 'H. Stacked bar', + }), + fullLabel: i18n.translate('xpack.lens.xyVisualization.stackedBarHorizontalFullLabel', { + defaultMessage: 'Bar horizontal stacked', + }), + groupLabel: groupLabelForBar, + }, + { + id: 'bar_horizontal_percentage_stacked', + icon: LensIconChartBarHorizontalPercentage, + label: i18n.translate('xpack.lens.xyVisualization.stackedPercentageBarHorizontalLabel', { + defaultMessage: 'H. Percentage bar', + }), + fullLabel: i18n.translate( + 'xpack.lens.xyVisualization.stackedPercentageBarHorizontalFullLabel', + { + defaultMessage: 'Bar horizontal percentage', + } + ), + groupLabel: groupLabelForBar, + }, + { + id: 'area', + icon: LensIconChartArea, + label: i18n.translate('xpack.lens.xyVisualization.areaLabel', { + defaultMessage: 'Area', + }), + groupLabel: groupLabelForLineAndArea, + }, + { + id: 'area_stacked', + icon: LensIconChartAreaStacked, + label: i18n.translate('xpack.lens.xyVisualization.stackedAreaLabel', { + defaultMessage: 'Area stacked', + }), + groupLabel: groupLabelForLineAndArea, + }, + { + id: 'area_percentage_stacked', + icon: LensIconChartAreaPercentage, + label: i18n.translate('xpack.lens.xyVisualization.stackedPercentageAreaLabel', { + defaultMessage: 'Area percentage', + }), + groupLabel: groupLabelForLineAndArea, + }, + { + id: 'line', + icon: LensIconChartLine, + label: i18n.translate('xpack.lens.xyVisualization.lineLabel', { + defaultMessage: 'Line', + }), + groupLabel: groupLabelForLineAndArea, + }, +]; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts new file mode 100644 index 00000000000000..be0568f2cdc224 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { TypedLensByValueInput } from '../../../../../../lens/public'; +import { useIndexPatternContext } from '../../../../hooks/use_default_index_pattern'; +import { LensAttributes } from '../configurations/lens_attributes'; +import { useUrlStorage } from './use_url_strorage'; +import { DataViewType, SeriesUrl } from '../types'; +import { useMemo } from 'react'; +import { useParams } from 'react-router-dom'; +import { getDefaultConfigs } from '../configurations/default_configs'; + +interface Props { + seriesId: string; +} + +export const useLensAttributes = ({ + seriesId, +}: Props): TypedLensByValueInput['attributes'] | null => { + const { dataViewType } = useParams<{ dataViewType: DataViewType }>(); + + const dataViewConfig = getDefaultConfigs({ dataViewType }); + + const { indexPattern: defaultIndexPattern } = useIndexPatternContext(dataViewConfig.indexPattern); + + const storage = useUrlStorage(); + + const series = storage.get(seriesId); + + const { filters = [] } = series ?? {}; + + return useMemo(() => { + if (!defaultIndexPattern) { + return null; + } + + const lensAttributes = new LensAttributes( + defaultIndexPattern, + dataViewConfig, + series?.seriesType!, + filters + ); + + if (series?.breakdown) { + lensAttributes.addBreakdown(series.breakdown); + } + + return lensAttributes.getJSON(); + }, [defaultIndexPattern, series?.breakdown, series?.seriesType, filters]); +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx new file mode 100644 index 00000000000000..177d90d906ccfd --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { createContext, useContext, Context } from 'react'; +import { IKbnUrlStateStorage } from '../../../../../../../../src/plugins/kibana_utils/public'; + +export const UrlStorageContext = createContext(null); + +interface ProviderProps { + storage: IKbnUrlStateStorage; +} + +export const UrlStorageContextProvider: React.FC = ({ children, storage }) => { + return {children}; +}; + +export const useUrlStorage = () => + useContext((UrlStorageContext as unknown) as Context); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx new file mode 100644 index 00000000000000..7a8b65c72f45da --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { ExploratoryView } from './exploratory_view'; +import { useFetcher } from '../../..'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { ObservabilityClientPluginsStart } from '../../../plugin'; +import { useBreadcrumbs } from '../../../hooks/use_breadcrumbs'; +import { i18n } from '@kbn/i18n'; +import { IndexPatternContextProvider } from '../../../hooks/use_default_index_pattern'; +import { useHistory } from 'react-router-dom'; +import { + createKbnUrlStateStorage, + withNotifyOnErrors, +} from '../../../../../../../src/plugins/kibana_utils/public/'; +import { UrlStorageContextProvider } from './hooks/use_url_strorage'; +import { DataViewType } from './types'; +import { getDefaultConfigs } from './configurations/default_configs'; + +export interface Props { + dataViewType: DataViewType; +} + +export const ExploratoryViewPage = ({ dataViewType }: Props) => { + useBreadcrumbs([ + { + text: i18n.translate('xpack.observability.overview.exploratoryView', { + defaultMessage: 'Exploratory view', + }), + }, + { + text: i18n.translate('xpack.observability.overview.exploratoryView.dataViewType', { + defaultMessage: dataViewType, + }), + }, + ]); + + const { + services: { data, uiSettings, notifications }, + } = useKibana(); + + const history = useHistory(); + + const kbnUrlStateStorage = createKbnUrlStateStorage({ + history, + useHash: uiSettings!.get('state:storeInSessionStorage'), + ...withNotifyOnErrors(notifications!.toasts), + }); + + const dataViewConfig = getDefaultConfigs({ dataViewType }); + + const { data: defaultIndexPattern } = useFetcher( + () => data.indexPatterns.get(dataViewConfig.indexPattern), + [dataViewConfig.indexPattern] + ); + + return ( + + + + + + ); +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx new file mode 100644 index 00000000000000..dc6dc803d22ec4 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiSuperDatePicker } from '@elastic/eui'; +import React, { useEffect } from 'react'; +import { useHasData } from '../../../../hooks/use_has_data'; +import { UI_SETTINGS, useKibanaUISettings } from '../../../../hooks/use_kibana_ui_settings'; +import { useUrlStorage } from '../hooks/use_url_strorage'; +import { SeriesUrl } from '../types'; + +export interface TimePickerTime { + from: string; + to: string; +} + +export interface TimePickerQuickRange extends TimePickerTime { + display: string; +} + +interface Props { + seriesId: string; +} + +export function SeriesDatePicker({ seriesId }: Props) { + const { onRefreshTimeRange } = useHasData(); + + const timePickerQuickRanges = useKibanaUISettings( + UI_SETTINGS.TIMEPICKER_QUICK_RANGES + ); + + const commonlyUsedRanges = timePickerQuickRanges.map(({ from, to, display }) => ({ + start: from, + end: to, + label: display, + })); + + const storage = useUrlStorage(); + + const series = storage.get(seriesId); + + function onTimeChange({ start, end }: { start: string; end: string }) { + onRefreshTimeRange(); + storage.set(seriesId, { ...series, time: { from: start, to: end } }); + } + + useEffect(() => { + if (!series || !series.time) { + storage.set(seriesId, { ...series, time: { from: 'now-15m', to: 'now' } }); + } + }, []); + + return ( + + ); +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/actions_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/actions_col.tsx new file mode 100644 index 00000000000000..902529f827e132 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/actions_col.tsx @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { SeriesDatePicker } from '../../series_date_picker'; + +interface Props { + series: any; +} +export const ActionsCol = ({ series }: Props) => { + return ( +
+ +
+ ); +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx new file mode 100644 index 00000000000000..bb9febf47f6f7b --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect, useState } from 'react'; +import { EuiButton, EuiSpacer } from '@elastic/eui'; +import { FieldLabels } from '../../configurations/constants'; +import { useUrlStorage } from '../../hooks/use_url_strorage'; +import { SeriesUrl } from '../../types'; + +interface Props { + seriesId: string; + breakdowns: string[]; +} + +export const Breakdowns = ({ seriesId, breakdowns = [] }: Props) => { + const options = breakdowns.map((breakdown) => ({ id: breakdown, label: FieldLabels[breakdown] })); + + const [selectedBreakdown, setSelectedBreakdown] = useState(); + + const onClick = (optionId: string) => { + setSelectedBreakdown((prevState) => (prevState === optionId ? undefined : optionId)); + }; + + const storage = useUrlStorage(); + const series = storage.get(seriesId) ?? {}; + + useEffect(() => { + storage.set(seriesId, { ...series, breakdown: selectedBreakdown }); + }, [selectedBreakdown]); + + return ( +
+ {options.map(({ id, label }) => ( +
+ onClick(id)} + > + {label} + + +
+ ))} +
+ ); +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx new file mode 100644 index 00000000000000..e1315dc785be99 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState, Fragment } from 'react'; +import { + EuiFieldSearch, + EuiSpacer, + EuiButtonEmpty, + EuiLoadingSpinner, + EuiFilterButton, + EuiFilterGroup, +} from '@elastic/eui'; +import { ObservabilityClientPluginsStart } from '../../../../../plugin'; +import { useFetcher } from '../../../../..'; +import { useKibana } from '../../../../../../../../../src/plugins/kibana_react/public'; +import { useIndexPatternContext } from '../../../../../hooks/use_default_index_pattern'; +import { useUrlStorage } from '../../hooks/use_url_strorage'; +import { SeriesUrl, UrlFilter } from '../../types'; + +interface Props { + label: string; + field: string; + goBack: () => void; +} + +export const FilterExpanded = ({ field, label, goBack }: Props) => { + const { indexPattern } = useIndexPatternContext(); + + const [value, setValue] = useState(''); + + const { + services: { data }, + } = useKibana(); + + const storage = useUrlStorage(); + + const { data: values, status } = useFetcher(() => { + return data.autocomplete.getValueSuggestions({ + indexPattern, + query: '', + useTimeRange: false, + field: { name: field, type: 'string', aggregatable: true }, + }); + }, [field]); + + const series = storage.get('elastic-co'); + + const filters = series?.filters ?? []; + + let currFilter: UrlFilter | undefined = filters.find(({ field: fd }) => field === fd); + + const onChange = (id: string, not?: boolean) => { + if (!currFilter && filters.length === 0) { + currFilter = { field }; + if (not) { + currFilter.notValues = [id]; + } else { + currFilter.values = [id]; + } + storage.set('elastic-co', { ...series, filters: [currFilter] }); + return; + } + + if (currFilter) { + const currNotValues = currFilter.notValues ?? []; + const currValues = currFilter.values ?? []; + + const notValues = currNotValues.filter((val) => val !== id); + const values = currValues.filter((val) => val !== id); + + if (not && !currNotValues.includes(id)) { + notValues.push(id); + } else if (!currValues.includes(id)) { + values.push(id); + } + + currFilter.notValues = notValues.length > 0 ? notValues : undefined; + currFilter.values = values.length > 0 ? values : undefined; + + if (notValues.length > 0 || values.length > 0) { + storage.set('elastic-co', { ...series, filters: [currFilter] }); + } else { + storage.set('elastic-co', { ...series, filters: undefined }); + } + } + }; + + return ( + <> + goBack()}> + {label} + + { + setValue(evt.target.value); + }} + /> + + {status === 'loading' && } + {(values || []) + .filter((opt) => opt.toLowerCase().includes(value.toLowerCase())) + .map((opt) => ( + + + onChange(opt, true)} + color="danger" + > + Not {opt} + + onChange(opt)} + color="primary" + > + {opt} + + + + + ))} + + ); +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/remove_series.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/remove_series.tsx new file mode 100644 index 00000000000000..677804405b8d67 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/remove_series.tsx @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiButtonIcon } from '@elastic/eui'; + +interface Props {} +export const RemoveSeries = (props: Props) => { + return ; +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx new file mode 100644 index 00000000000000..dfd4b6969be5ca --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState, Fragment } from 'react'; +import { + EuiButton, + EuiPopover, + EuiSpacer, + EuiButtonEmpty, + EuiFlexItem, + EuiFlexGroup, +} from '@elastic/eui'; +import { FilterExpanded } from './filter_expanded'; +import { DataSeries } from '../../types'; +import { FieldLabels } from '../../configurations/constants'; +import { SelectedFilters } from '../selected_filters'; + +interface Props { + defaultFilters: DataSeries['defaultFilters']; +} + +export interface Field { + label: string; + field: string; +} + +export const SeriesFilter = ({ defaultFilters = [] }: Props) => { + const [isPopoverVisible, setIsPopoverVisible] = useState(false); + + const [selectedField, setSelectedField] = useState(null); + + const options = defaultFilters.map((field) => ({ label: FieldLabels[field], field })); + + const button = ( + { + setIsPopoverVisible(true); + }} + > + Add filter + + ); + + const mainPanel = ( + <> + + {options.map((opt) => ( + + { + setSelectedField(opt); + }} + > + {opt.label} + + + + ))} + + ); + + const childPanel = selectedField ? ( + { + setSelectedField(null); + }} + /> + ) : null; + + const closePopover = () => { + setIsPopoverVisible(false); + setSelectedField(null); + }; + + return ( + + + + {!selectedField ? mainPanel : childPanel} + + + + + + + ); +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx new file mode 100644 index 00000000000000..a1d4981d620b98 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { Fragment } from 'react'; +import { useUrlStorage } from '../hooks/use_url_strorage'; +import { SeriesUrl } from '../types'; +import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { FieldLabels } from '../configurations/constants'; + +interface Props {} +export const SelectedFilters = (props: Props) => { + const storage = useUrlStorage(); + + const { filters = [] } = storage.get('elastic-co') ?? {}; + + const style = { maxWidth: 250 }; + + return ( + + {filters.map(({ field, values, notValues }) => ( + + {(values ?? []).map((val) => ( + + + {FieldLabels[field]}: {val} + + + ))} + {(notValues ?? []).map((val) => ( + + + {FieldLabels[field]}: {val} + + + ))} + + ))} + + ); +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx new file mode 100644 index 00000000000000..dff1921763782a --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { useParams } from 'react-router-dom'; +import { EuiBasicTable, EuiButton, EuiIcon, EuiSpacer, EuiText } from '@elastic/eui'; +import { SeriesFilter } from './columns/series_filter'; +import { ActionsCol } from './columns/actions_col'; +import { Breakdowns } from './columns/breakdowns'; +import { DataSeries, DataViewType } from '../types'; +import { getDefaultConfigs } from '../configurations/default_configs'; + +export const SeriesEditor = () => { + const columns = [ + { + name: 'Name', + field: 'name', + width: '20%', + render: (val: string) => ( + + {val} + + ), + }, + { + name: 'Filter', + field: 'defaultFilters', + width: '30%', + render: (defaultFilters: string[]) => , + }, + { + name: 'Breakdowns', + field: 'breakdowns', + width: '25%', + render: (val: string[], item: DataSeries) => ( + + ), + }, + { + name: 'Time', + width: '25%', + field: 'id', + render: (val: string, item: DataSeries) => , + }, + ]; + + const { dataViewType } = useParams<{ dataViewType: DataViewType }>(); + + const items: DataSeries[] = [getDefaultConfigs({ dataViewType })]; + + return ( + <> + + ({ height: 100 })} + /> + + + Add series + + + ); +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts new file mode 100644 index 00000000000000..ed6cf901115c52 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + AvgIndexPatternColumn, + CountIndexPatternColumn, + DateHistogramIndexPatternColumn, + LastValueIndexPatternColumn, +} from '../../../../../lens/public'; + +export type DataViewType = 'page-load-dist' | 'page-views' | 'uptime-duration' | 'uptime-pings'; + +export interface DataSeries { + dataViewType: DataViewType; + indexPattern: string; + name: string; + id: string; + xAxisColumn: Partial | Partial; + yAxisColumn: + | Partial + | Partial + | Partial; + breakdowns: string[]; + defaultSeriesType: string; + defaultFilters: string[]; + seriesTypes?: string[]; + filters?: Record; +} + +export interface SeriesUrl { + time: { + to: string; + from: string; + }; + breakdown: string; + filters: UrlFilter[]; + seriesType: string; +} + +export interface UrlFilter { + field: string; + values?: string[]; + notValues?: string[]; +} diff --git a/x-pack/plugins/observability/public/hooks/use_breadcrumbs.ts b/x-pack/plugins/observability/public/hooks/use_breadcrumbs.ts new file mode 100644 index 00000000000000..f34809ce4f8eb6 --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/use_breadcrumbs.ts @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ChromeBreadcrumb } from 'kibana/public'; +import { i18n } from '@kbn/i18n'; +import { MouseEvent, useEffect } from 'react'; +import { EuiBreadcrumb } from '@elastic/eui'; +import { useKibana } from '../../../../../src/plugins/kibana_react/public'; +import { useQueryParams } from './use_query_params'; +import { stringify } from 'query-string'; + +const EMPTY_QUERY = '?'; + +function handleBreadcrumbClick( + breadcrumbs: ChromeBreadcrumb[], + navigateToHref?: (url: string) => Promise +) { + return breadcrumbs.map((bc) => ({ + ...bc, + ...(bc.href + ? { + onClick: (event: MouseEvent) => { + if (navigateToHref && bc.href) { + event.preventDefault(); + navigateToHref(bc.href); + } + }, + } + : {}), + })); +} + +export const makeBaseBreadcrumb = (href: string, params?: any): EuiBreadcrumb => { + if (params) { + const crumbParams = { ...params }; + + delete crumbParams.statusFilter; + const query = stringify(crumbParams, true); + href += query === EMPTY_QUERY ? '' : query; + } + return { + text: i18n.translate('xpack.uptime.breadcrumbs.observability', { + defaultMessage: 'Observability', + }), + href, + }; +}; + +export const useBreadcrumbs = (extraCrumbs: ChromeBreadcrumb[]) => { + const params = useQueryParams(); + + const { + services: { chrome, application }, + } = useKibana(); + + const setBreadcrumbs = chrome?.setBreadcrumbs; + const appPath = application?.getUrlForApp('observability-overview') ?? ''; + const navigate = application?.navigateToUrl; + + useEffect(() => { + if (setBreadcrumbs) { + setBreadcrumbs( + handleBreadcrumbClick([makeBaseBreadcrumb(appPath, params)].concat(extraCrumbs), navigate) + ); + } + }, [appPath, extraCrumbs, navigate, params, setBreadcrumbs]); +}; diff --git a/x-pack/plugins/observability/public/hooks/use_default_index_pattern.tsx b/x-pack/plugins/observability/public/hooks/use_default_index_pattern.tsx new file mode 100644 index 00000000000000..45c886535290be --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/use_default_index_pattern.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { createContext, useContext, Context } from 'react'; +import { IIndexPattern } from '../../../../../src/plugins/data/common'; + +export interface IIndexPatternContext { + indexPattern: IIndexPattern; +} + +export const IndexPatternContext = createContext>({}); + +interface ProviderProps { + indexPattern: IIndexPattern; +} + +export const IndexPatternContextProvider: React.FC = ({ + children, + indexPattern, +}) => { + return ( + + {children} + + ); +}; + +export const useIndexPatternContext = () => + useContext((IndexPatternContext as unknown) as Context); diff --git a/x-pack/plugins/observability/public/routes/index.tsx b/x-pack/plugins/observability/public/routes/index.tsx index 753e357ae98c28..04ae8df15fe9dd 100644 --- a/x-pack/plugins/observability/public/routes/index.tsx +++ b/x-pack/plugins/observability/public/routes/index.tsx @@ -8,11 +8,13 @@ import React from 'react'; import * as t from 'io-ts'; import { i18n } from '@kbn/i18n'; +import { useParams } from 'react-router-dom'; import { HomePage } from '../pages/home'; import { LandingPage } from '../pages/landing'; import { OverviewPage } from '../pages/overview'; import { jsonRt } from './json_rt'; -import { ExploratoryView } from '../components/shared/Exploratory_view/exploratory_view'; +import { ExploratoryViewPage } from '../components/shared/exploratory_view'; +import { DataViewType } from '../components/shared/exploratory_view/types'; export type RouteParams = DecodeParams; @@ -73,9 +75,10 @@ export const routes = { }, ], }, - '/exploratory-view': { - handler: ({ query }: any) => { - return ; + '/exploratory-view/:dataViewType': { + handler: () => { + const { dataViewType } = useParams<{ dataViewType: DataViewType }>(); + return ; }, params: { query: t.partial({ @@ -87,8 +90,8 @@ export const routes = { }, breadcrumb: [ { - text: i18n.translate('xpack.observability.overview.breadcrumb', { - defaultMessage: 'Overview', + text: i18n.translate('xpack.observability.overview.exploratoryView', { + defaultMessage: 'Exploratory view', }), }, ], From 3ed7c82b3a8613304619aaf73230a1fade8c5d50 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Thu, 11 Mar 2021 14:42:31 +0100 Subject: [PATCH 03/53] breakpoint --- .../chart_templates/chart_templates.tsx | 18 ++- .../configurations/constants.ts | 2 + .../configurations/default_configs.ts | 5 +- .../configurations/lens_attributes.ts | 4 +- .../configurations/page_load_dist_config.ts | 17 ++- .../configurations/service_latency_config.ts | 42 +++++ .../exploratory_view/exploratory_view.tsx | 18 +-- .../shared/exploratory_view/header.tsx | 4 +- .../exploratory_view/header/chart_types.tsx | 77 +++++----- .../hooks/use_lens_attributes.ts | 4 +- .../hooks/use_url_strorage.tsx | 31 +++- .../series_builder/columns/data_types_col.tsx | 43 ++++++ .../columns/report_breakdowns.tsx | 14 ++ .../columns/report_definition_col.tsx | 42 +++++ .../series_builder/columns/report_filters.tsx | 13 ++ .../columns/report_types_col.tsx | 38 +++++ .../series_builder/series_builder.tsx | 144 ++++++++++++++++++ .../series_date_picker/index.tsx | 9 +- .../series_editor/columns/breakdowns.tsx | 5 +- .../series_editor/columns/filter_expanded.tsx | 15 +- .../series_editor/columns/remove_series.tsx | 16 +- .../series_editor/columns/series_filter.tsx | 5 +- .../series_editor/selected_filters.tsx | 48 +++++- .../series_editor/series_editor.tsx | 37 ++++- .../shared/exploratory_view/types.ts | 15 +- .../shared/field_value_selection.tsx | 118 ++++++++++++++ .../public/hooks/use_values_list.ts | 34 +++++ 27 files changed, 705 insertions(+), 113 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/field_value_selection.tsx create mode 100644 x-pack/plugins/observability/public/hooks/use_values_list.ts diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/chart_templates/chart_templates.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/chart_templates/chart_templates.tsx index 20c41b20c6b971..2bad254c33d7b4 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/chart_templates/chart_templates.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/chart_templates/chart_templates.tsx @@ -26,6 +26,8 @@ export function ChartTemplates() { let flyout; + const style = { minWidth: 200 }; + if (isFlyoutVisible) { flyout = ( setIsFlyoutVisible(false)} aria-labelledby="flyoutTitle"> @@ -37,8 +39,8 @@ export function ChartTemplates() { - - + + } title={`Page load distribution`} @@ -46,7 +48,7 @@ export function ChartTemplates() { href="/app/observability/exploratory-view/page-load-dist" /> - + } title={`Page views`} @@ -54,7 +56,7 @@ export function ChartTemplates() { href="/app/observability/exploratory-view/page-views" /> - + } title={`Monitor duration`} @@ -62,6 +64,14 @@ export function ChartTemplates() { href="/app/observability/exploratory-view/uptime-duration" /> + + } + title={`APM Service latency`} + description="Uptime monitor duration, slice and dice by location etc" + href="/app/observability/exploratory-view/service-latency" + /> + diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts index f1dce5421345be..85dd3cc6019ba1 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts @@ -13,6 +13,7 @@ export const FieldLabels: Record = { 'client.geo.country_name': 'Location', 'user_agent.device.name': 'Device', 'observer.geo.name': 'Observer location', + 'service.name': 'Service', }; export const DataViewLabels: Record = { @@ -20,4 +21,5 @@ export const DataViewLabels: Record = { 'page-views': 'Page views', 'uptime-duration': 'Uptime monitor duration', 'uptime-pings': 'Uptime pings', + 'service-latency': 'APM Service latency', }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts index bbc4ae9739a939..b1b3cce83593c1 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts @@ -9,15 +9,18 @@ import { DataViewType } from '../types'; import { getPageLoadDistLensConfig } from './page_load_dist_config'; import { getPageViewLensConfig } from './page_view_config'; import { getMonitorDurationConfig } from './monitor_duration_config'; +import { getServiceLatencyLensConfig } from './service_latency_config'; export const getDefaultConfigs = ({ dataViewType }: { dataViewType: DataViewType }) => { switch (dataViewType) { case 'page-load-dist': - return getPageLoadDistLensConfig(); + return getPageLoadDistLensConfig({}); case 'page-views': return getPageViewLensConfig(); case 'uptime-duration': return getMonitorDurationConfig(); + case 'service-latency': + return getServiceLatencyLensConfig(); default: return getPageViewLensConfig(); } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts index 78cc6f903a590f..3e649fcbcb76ff 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts @@ -171,8 +171,8 @@ export class LensAttributes { } parseFilters() { - const defaultFilters = this.dataViewConfig.filters ?? {}; - const parsedFilters = this.dataViewConfig.filters ? [defaultFilters] : []; + const defaultFilters = this.dataViewConfig.filters ?? []; + const parsedFilters = this.dataViewConfig.filters ? [...defaultFilters] : []; this.filters.forEach(({ field, values = [], notValues = [] }) => { values?.forEach((value) => { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts index 4c97bb0471ccfe..2a7dcbc83bd1e9 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts @@ -7,10 +7,14 @@ import { DataSeries } from '../types'; -export function getPageLoadDistLensConfig(): DataSeries { +interface Props { + seriesId: string; + serviceName?: string; +} + +export function getPageLoadDistLensConfig({ seriesId, serviceName }: Props): DataSeries { return { - name: 'elastic.co', - id: 'elastic-co', + id: seriesId ?? 'unique-key', dataViewType: 'page-load-dist', defaultSeriesType: 'line', indexPattern: 'apm_static_index_pattern_id', @@ -33,8 +37,9 @@ export function getPageLoadDistLensConfig(): DataSeries { 'client.geo.country_name', 'user_agent.device.name', ], - filters: { - query: { match_phrase: { 'transaction.type': 'page-load' } }, - }, + filters: [ + { query: { match_phrase: { 'transaction.type': 'page-load' } } }, + ...(serviceName ? [{ query: { match_phrase: { 'service.type': serviceName } } }] : []), + ], }; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts new file mode 100644 index 00000000000000..9c15dda1c41f0f --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataSeries } from '../types'; + +export function getServiceLatencyLensConfig(): DataSeries { + return { + name: 'elastic.co', + id: 'elastic-co', + dataViewType: 'service-latency', + defaultSeriesType: 'line', + indexPattern: 'apm_static_index_pattern_id', + seriesTypes: ['line', 'bar'], + xAxisColumn: { + sourceField: '@timestamp', + }, + yAxisColumn: { + operationType: 'avg', + sourceField: 'transaction.duration.us', + label: 'Latency', + }, + defaultFilters: [ + 'user_agent.name', + 'user_agent.os.name', + 'client.geo.country_name', + 'user_agent.device.name', + ], + breakdowns: [ + 'user_agent.name', + 'user_agent.os.name', + 'client.geo.country_name', + 'user_agent.device.name', + ], + filters: { + query: { match_phrase: { 'transaction.type': 'request' } }, + }, + }; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx index f7ca5877c9ca55..e70a9838b72fd0 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx @@ -22,7 +22,6 @@ import { IndexPattern } from '../../../../../../../src/plugins/data/common'; import { ExploratoryViewHeader } from './header'; import { SeriesEditor } from './series_editor/series_editor'; import { useUrlStorage } from './hooks/use_url_strorage'; -import { SeriesUrl } from './types'; import { useLensAttributes } from './hooks/use_lens_attributes'; import styled from 'styled-components'; @@ -31,31 +30,24 @@ export interface Props { defaultIndexPattern?: IndexPattern | null; } -export const ExploratoryView = ({ seriesId, defaultIndexPattern }: Props) => { +export const ExploratoryView = ({ defaultIndexPattern }: Props) => { const { services: { lens }, } = useKibana(); const LensComponent = lens.EmbeddableComponent; - const storage = useUrlStorage(); + const { firstSeries: seriesId } = useUrlStorage(); - const series = storage.get('elastic-co'); + const { series } = useUrlStorage(seriesId); const lensAttributes = useLensAttributes({ - seriesId: 'elastic-co', + seriesId, }); return ( - - - -

Exploratory view

-
-
-
{defaultIndexPattern ? ( @@ -70,7 +62,7 @@ export const ExploratoryView = ({ seriesId, defaultIndexPattern }: Props) => { to: 'now', } } - attributes={lensAttributes} + attributes={seriesId ? lensAttributes : undefined} /> diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header.tsx index 834bda562e0239..eef54c9654cfe5 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/header.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header.tsx @@ -29,12 +29,12 @@ export function ExploratoryViewHeader({ lensAttributes }: Props) { return ( - +

{DataViewLabels[dataViewType]}

- + diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/chart_types.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/chart_types.tsx index 580869d4989d0e..9656dba2c67d42 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/chart_types.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/chart_types.tsx @@ -13,16 +13,15 @@ import { i18n } from '@kbn/i18n'; import { LensIconChartBar } from '../assets/chart_bar'; import { LensIconChartBarHorizontal } from '../assets/chart_bar_horizontal'; import { LensIconChartBarStacked } from '../assets/chart_bar_stacked'; -import { LensIconChartBarPercentage } from '../assets/chart_bar_percentage'; +// import { LensIconChartBarPercentage } from '../assets/chart_bar_percentage'; import { LensIconChartBarHorizontalStacked } from '../assets/chart_bar_horizontal_stacked'; -import { LensIconChartBarHorizontalPercentage } from '../assets/chart_bar_horizontal_percentage'; +// import { LensIconChartBarHorizontalPercentage } from '../assets/chart_bar_horizontal_percentage'; import { LensIconChartArea } from '../assets/chart_area'; -import { LensIconChartAreaPercentage } from '../assets/chart_area_percentage'; +// import { LensIconChartAreaPercentage } from '../assets/chart_area_percentage'; import { LensIconChartLine } from '../assets/chart_line'; import { LensIconChartAreaStacked } from '../assets/chart_area_stacked'; import styled from 'styled-components'; import { useUrlStorage } from '../hooks/use_url_strorage'; -import { SeriesUrl } from '../types'; const ButtonGroup = styled(EuiButtonGroup)` &&& { @@ -33,9 +32,7 @@ const ButtonGroup = styled(EuiButtonGroup)` `; export const ChartTypes = () => { - const storage = useUrlStorage(); - - const series = storage.get('elastic-co'); + const { series, setSeries, allSeries } = useUrlStorage(); return ( { }))} idSelected={series?.seriesType ?? 'line'} onChange={(seriesType: string) => { - storage.set('elastic-co', { ...series, seriesType }); + Object.keys(allSeries).forEach((seriesKey) => { + const series = allSeries[seriesKey]; + + setSeries(seriesKey, { ...series, seriesType }); + }); }} /> ); @@ -96,14 +97,14 @@ export const visualizationTypes: VisualizationType[] = [ }), groupLabel: groupLabelForBar, }, - { - id: 'bar_percentage_stacked', - icon: LensIconChartBarPercentage, - label: i18n.translate('xpack.lens.xyVisualization.stackedPercentageBarLabel', { - defaultMessage: 'Bar vertical percentage', - }), - groupLabel: groupLabelForBar, - }, + // { + // id: 'bar_percentage_stacked', + // icon: LensIconChartBarPercentage, + // label: i18n.translate('xpack.lens.xyVisualization.stackedPercentageBarLabel', { + // defaultMessage: 'Bar vertical percentage', + // }), + // groupLabel: groupLabelForBar, + // }, { id: 'bar_horizontal_stacked', icon: LensIconChartBarHorizontalStacked, @@ -115,20 +116,20 @@ export const visualizationTypes: VisualizationType[] = [ }), groupLabel: groupLabelForBar, }, - { - id: 'bar_horizontal_percentage_stacked', - icon: LensIconChartBarHorizontalPercentage, - label: i18n.translate('xpack.lens.xyVisualization.stackedPercentageBarHorizontalLabel', { - defaultMessage: 'H. Percentage bar', - }), - fullLabel: i18n.translate( - 'xpack.lens.xyVisualization.stackedPercentageBarHorizontalFullLabel', - { - defaultMessage: 'Bar horizontal percentage', - } - ), - groupLabel: groupLabelForBar, - }, + // { + // id: 'bar_horizontal_percentage_stacked', + // icon: LensIconChartBarHorizontalPercentage, + // label: i18n.translate('xpack.lens.xyVisualization.stackedPercentageBarHorizontalLabel', { + // defaultMessage: 'H. Percentage bar', + // }), + // fullLabel: i18n.translate( + // 'xpack.lens.xyVisualization.stackedPercentageBarHorizontalFullLabel', + // { + // defaultMessage: 'Bar horizontal percentage', + // } + // ), + // groupLabel: groupLabelForBar, + // }, { id: 'area', icon: LensIconChartArea, @@ -145,14 +146,14 @@ export const visualizationTypes: VisualizationType[] = [ }), groupLabel: groupLabelForLineAndArea, }, - { - id: 'area_percentage_stacked', - icon: LensIconChartAreaPercentage, - label: i18n.translate('xpack.lens.xyVisualization.stackedPercentageAreaLabel', { - defaultMessage: 'Area percentage', - }), - groupLabel: groupLabelForLineAndArea, - }, + // { + // id: 'area_percentage_stacked', + // icon: LensIconChartAreaPercentage, + // label: i18n.translate('xpack.lens.xyVisualization.stackedPercentageAreaLabel', { + // defaultMessage: 'Area percentage', + // }), + // groupLabel: groupLabelForLineAndArea, + // }, { id: 'line', icon: LensIconChartLine, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts index be0568f2cdc224..e781cc2d03cdfa 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts @@ -27,9 +27,7 @@ export const useLensAttributes = ({ const { indexPattern: defaultIndexPattern } = useIndexPatternContext(dataViewConfig.indexPattern); - const storage = useUrlStorage(); - - const series = storage.get(seriesId); + const { series } = useUrlStorage(seriesId); const { filters = [] } = series ?? {}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx index 177d90d906ccfd..9b4ad7c4e1a708 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx @@ -7,6 +7,7 @@ import React, { createContext, useContext, Context } from 'react'; import { IKbnUrlStateStorage } from '../../../../../../../../src/plugins/kibana_utils/public'; +import { SeriesUrl } from '../types'; export const UrlStorageContext = createContext(null); @@ -18,5 +19,31 @@ export const UrlStorageContextProvider: React.FC = ({ children, s return {children}; }; -export const useUrlStorage = () => - useContext((UrlStorageContext as unknown) as Context); +type AllSeries = Record; + +export const useUrlStorage = (seriesId?: string) => { + const allSeriesKey = 'sr'; + const storage = useContext((UrlStorageContext as unknown) as Context); + let series: SeriesUrl = {} as SeriesUrl; + const allSeries = storage.get(allSeriesKey) ?? {}; + + if (seriesId) { + series = allSeries?.[seriesId] ?? ({} as SeriesUrl); + } + + const setSeries = (seriesId: string, newValue: SeriesUrl) => { + allSeries[seriesId] = newValue; + storage.set(allSeriesKey, allSeries); + }; + + const removeSeries = (seriesId: string) => { + delete allSeries[seriesId]; + storage.set(allSeriesKey, allSeries); + }; + + const allSeriesIds = Object.keys(allSeries); + + const firstSeries = allSeriesIds?.[0]; + + return { storage, setSeries, removeSeries, series, firstSeries, allSeries, allSeriesIds }; +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx new file mode 100644 index 00000000000000..e112905a7f4f9a --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { Dispatch, SetStateAction } from 'react'; +import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { AppDataType } from '../../types'; + +interface Props { + selectedDataType: AppDataType | null; + onChange: Dispatch>; +} + +const dataTypes: { id: AppDataType; label: string }[] = [ + { id: 'synthetics', label: 'Synthetic Monitoring' }, + { id: 'rum', label: 'User Experience(RUM)' }, + { id: 'logs', label: 'Logs' }, + { id: 'metrics', label: 'Logs' }, + { id: 'apm', label: 'APM' }, +]; +export const DataTypesCol = ({ selectedDataType, onChange }: Props) => { + return ( + + {dataTypes.map(({ id: dt, label }) => ( + + onChange(dt === selectedDataType ? null : dt)} + > + {label} + + + ))} + + ); +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.tsx new file mode 100644 index 00000000000000..eced44f7475a98 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.tsx @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +interface Props {} + +export const ReportBreakdowns = (props: Props) => { + return
; +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx new file mode 100644 index 00000000000000..eb265df8b1f0ce --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { Dispatch, SetStateAction } from 'react'; +import { FieldValueSelection } from '../../../field_value_selection'; +import { useIndexPatternContext } from '../../../../../hooks/use_default_index_pattern'; +import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; + +interface Props { + selectedServiceName: string | null; + reportType: string; + onChange: Dispatch>; +} + +export const ReportDefinitionCol = ({ selectedServiceName, onChange }: Props) => { + const { indexPattern } = useIndexPatternContext(); + + return ( +
+ {selectedServiceName && ( + + + + Web App: {selectedServiceName} + + + + )} + + onChange(val)} + /> +
+ ); +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx new file mode 100644 index 00000000000000..35e6fa68a0e035 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +interface Props {} +export const ReportFilters = (props: Props) => { + return
; +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx new file mode 100644 index 00000000000000..05441cc93927e7 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { Dispatch, SetStateAction } from 'react'; +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; + +interface Props { + selectedReportType: string | null; + reportTypes: string[]; + onChange: Dispatch>; +} + +export const ReportTypesCol = ({ selectedReportType, reportTypes, onChange }: Props) => { + return reportTypes?.length > 0 ? ( + + {reportTypes.map((reportType) => ( + + onChange(reportType === selectedReportType ? null : reportType)} + > + {reportType} + + + ))} + + ) : ( + Select a data type to start building a series. + ); +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx new file mode 100644 index 00000000000000..57a9ce714a2fbb --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx @@ -0,0 +1,144 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; + +import { EuiButton, EuiBasicTable, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import styled from 'styled-components'; +import { AppDataType, SeriesUrl } from '../types'; +import { DataTypesCol } from './columns/data_types_col'; +import { ReportTypesCol } from './columns/report_types_col'; +import { ReportDefinitionCol } from './columns/report_definition_col'; +import { ReportFilters } from './columns/report_filters'; +import { ReportBreakdowns } from './columns/report_breakdowns'; +import { useUrlStorage } from '../hooks/use_url_strorage'; + +export const ReportTypes = { + synthetics: ['Monitor duration', 'Pings histogram'], + rum: ['Performance distribution', 'Page views', 'KPI over time'], + apm: ['Latency', 'Throughput'], + logs: [], + metrics: [], +}; + +export const SeriesBuilder = () => { + const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); + + const [dataType, setDataType] = useState(null); + const [reportType, setReportType] = useState(null); + const [serviceName, setServiceName] = useState(null); + + const columns = [ + { + name: 'DataType', + field: 'dataType', + width: '20%', + render: (val: string) => , + }, + { + name: 'Report', + field: 'defaultFilters', + width: '30%', + render: (val: string) => ( + + ), + }, + { + name: 'Definition', + field: 'defaultFilters', + width: '30%', + render: (val: string) => + reportType ? ( + + ) : null, + }, + { + name: 'Filters', + field: 'filters', + width: '25%', + render: (val: string[]) => (reportType ? : null), + }, + { + name: 'Breakdowns', + width: '25%', + field: 'id', + render: (val: string[]) => (reportType ? : null), + }, + ]; + + const { setSeries, allSeriesIds } = useUrlStorage(); + + const addSeries = () => { + const newSeriesId = `${serviceName!}-pd`; + + const newSeries = { reportType: 'pd', serviceName } as SeriesUrl; + setSeries(newSeriesId, newSeries); + + // reset state + setDataType(null); + setReportType(null); + setServiceName(null); + setIsFlyoutVisible(false); + }; + + const items = [{ dataTypes: ['APM'] }]; + + let flyout; + + if (isFlyoutVisible) { + flyout = ( + + + + + + + Add + + + + + Cancel + + + + + ); + } + + return ( +
+ setIsFlyoutVisible((prevState) => !prevState)} + disabled={allSeriesIds.length > 0} + > + {'Add series'} + + + {flyout} +
+ ); +}; + +const BottomFlyout = styled.div` + height: 300px; +`; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx index dc6dc803d22ec4..d81823f51205a7 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx @@ -10,7 +10,6 @@ import React, { useEffect } from 'react'; import { useHasData } from '../../../../hooks/use_has_data'; import { UI_SETTINGS, useKibanaUISettings } from '../../../../hooks/use_kibana_ui_settings'; import { useUrlStorage } from '../hooks/use_url_strorage'; -import { SeriesUrl } from '../types'; export interface TimePickerTime { from: string; @@ -38,18 +37,16 @@ export function SeriesDatePicker({ seriesId }: Props) { label: display, })); - const storage = useUrlStorage(); - - const series = storage.get(seriesId); + const { series, setSeries } = useUrlStorage(seriesId); function onTimeChange({ start, end }: { start: string; end: string }) { onRefreshTimeRange(); - storage.set(seriesId, { ...series, time: { from: start, to: end } }); + setSeries(seriesId, { ...series, time: { from: start, to: end } }); } useEffect(() => { if (!series || !series.time) { - storage.set(seriesId, { ...series, time: { from: 'now-15m', to: 'now' } }); + setSeries(seriesId, { ...series, time: { from: 'now-15m', to: 'now' } }); } }, []); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx index bb9febf47f6f7b..0b8f6f2fef1282 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx @@ -25,11 +25,10 @@ export const Breakdowns = ({ seriesId, breakdowns = [] }: Props) => { setSelectedBreakdown((prevState) => (prevState === optionId ? undefined : optionId)); }; - const storage = useUrlStorage(); - const series = storage.get(seriesId) ?? {}; + const { setSeries, series } = useUrlStorage(seriesId); useEffect(() => { - storage.set(seriesId, { ...series, breakdown: selectedBreakdown }); + setSeries(seriesId, { ...series, breakdown: selectedBreakdown }); }, [selectedBreakdown]); return ( diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx index e1315dc785be99..336e771f09fe2d 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx @@ -19,15 +19,16 @@ import { useFetcher } from '../../../../..'; import { useKibana } from '../../../../../../../../../src/plugins/kibana_react/public'; import { useIndexPatternContext } from '../../../../../hooks/use_default_index_pattern'; import { useUrlStorage } from '../../hooks/use_url_strorage'; -import { SeriesUrl, UrlFilter } from '../../types'; +import { UrlFilter } from '../../types'; interface Props { + seriesId: string; label: string; field: string; goBack: () => void; } -export const FilterExpanded = ({ field, label, goBack }: Props) => { +export const FilterExpanded = ({ seriesId, field, label, goBack }: Props) => { const { indexPattern } = useIndexPatternContext(); const [value, setValue] = useState(''); @@ -36,7 +37,7 @@ export const FilterExpanded = ({ field, label, goBack }: Props) => { services: { data }, } = useKibana(); - const storage = useUrlStorage(); + const { series, setSeries } = useUrlStorage(seriesId); const { data: values, status } = useFetcher(() => { return data.autocomplete.getValueSuggestions({ @@ -47,8 +48,6 @@ export const FilterExpanded = ({ field, label, goBack }: Props) => { }); }, [field]); - const series = storage.get('elastic-co'); - const filters = series?.filters ?? []; let currFilter: UrlFilter | undefined = filters.find(({ field: fd }) => field === fd); @@ -61,7 +60,7 @@ export const FilterExpanded = ({ field, label, goBack }: Props) => { } else { currFilter.values = [id]; } - storage.set('elastic-co', { ...series, filters: [currFilter] }); + setSeries(seriesId, { ...series, filters: [currFilter] }); return; } @@ -82,9 +81,9 @@ export const FilterExpanded = ({ field, label, goBack }: Props) => { currFilter.values = values.length > 0 ? values : undefined; if (notValues.length > 0 || values.length > 0) { - storage.set('elastic-co', { ...series, filters: [currFilter] }); + setSeries(seriesId, { ...series, filters: [currFilter] }); } else { - storage.set('elastic-co', { ...series, filters: undefined }); + setSeries(seriesId, { ...series, filters: undefined }); } } }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/remove_series.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/remove_series.tsx index 677804405b8d67..c55c0388357bbb 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/remove_series.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/remove_series.tsx @@ -7,8 +7,18 @@ import React from 'react'; import { EuiButtonIcon } from '@elastic/eui'; +import { DataSeries } from '../../types'; +import { useUrlStorage } from '../../hooks/use_url_strorage'; -interface Props {} -export const RemoveSeries = (props: Props) => { - return ; +interface Props { + series: DataSeries; +} + +export const RemoveSeries = ({ series }: Props) => { + const { removeSeries } = useUrlStorage(); + + const onClick = () => { + removeSeries(series.id); + }; + return ; }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx index dfd4b6969be5ca..0ea8ab4686e4d4 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx @@ -20,6 +20,7 @@ import { FieldLabels } from '../../configurations/constants'; import { SelectedFilters } from '../selected_filters'; interface Props { + seriesId: string; defaultFilters: DataSeries['defaultFilters']; } @@ -28,7 +29,7 @@ export interface Field { field: string; } -export const SeriesFilter = ({ defaultFilters = [] }: Props) => { +export const SeriesFilter = ({ seriesId, defaultFilters = [] }: Props) => { const [isPopoverVisible, setIsPopoverVisible] = useState(false); const [selectedField, setSelectedField] = useState(null); @@ -95,7 +96,7 @@ export const SeriesFilter = ({ defaultFilters = [] }: Props) => {
- +
); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx index a1d4981d620b98..83f54a39131947 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx @@ -7,33 +7,65 @@ import React, { Fragment } from 'react'; import { useUrlStorage } from '../hooks/use_url_strorage'; -import { SeriesUrl } from '../types'; import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FieldLabels } from '../configurations/constants'; -interface Props {} -export const SelectedFilters = (props: Props) => { - const storage = useUrlStorage(); +interface Props { + seriesId: string; +} +export const SelectedFilters = ({ seriesId }: Props) => { + const { series, setSeries } = useUrlStorage(seriesId); - const { filters = [] } = storage.get('elastic-co') ?? {}; + const filters = series?.filters ?? []; const style = { maxWidth: 250 }; + const removeFilter = (field: string, value: string, notVal: boolean) => { + const filtersN = filters.map((filter) => { + if (filter.field === field) { + if (notVal) { + const notValuesN = filter.notValues?.filter((val) => val !== value); + return { ...filter, notValues: notValuesN }; + } else { + const valuesN = filter.values?.filter((val) => val !== value); + return { ...filter, values: valuesN }; + } + } + + return filter; + }); + setSeries(seriesId, { ...series, filters: filtersN }); + }; + return ( {filters.map(({ field, values, notValues }) => ( {(values ?? []).map((val) => ( - + removeFilter(field, val, false)} + > {FieldLabels[field]}: {val} ))} {(notValues ?? []).map((val) => ( - - {FieldLabels[field]}: {val} + removeFilter(field, val, true)} + > + Not {FieldLabels[field]}: {val} ))} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx index dff1921763782a..de34f3f86ffdd2 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx @@ -7,18 +7,21 @@ import React from 'react'; import { useParams } from 'react-router-dom'; -import { EuiBasicTable, EuiButton, EuiIcon, EuiSpacer, EuiText } from '@elastic/eui'; +import { EuiBasicTable, EuiIcon, EuiSpacer, EuiText } from '@elastic/eui'; import { SeriesFilter } from './columns/series_filter'; import { ActionsCol } from './columns/actions_col'; import { Breakdowns } from './columns/breakdowns'; import { DataSeries, DataViewType } from '../types'; -import { getDefaultConfigs } from '../configurations/default_configs'; +import { SeriesBuilder } from '../series_builder/series_builder'; +import { useUrlStorage } from '../hooks/use_url_strorage'; +import { getPageLoadDistLensConfig } from '../configurations/page_load_dist_config'; +import { RemoveSeries } from './columns/remove_series'; export const SeriesEditor = () => { const columns = [ { name: 'Name', - field: 'name', + field: 'id', width: '20%', render: (val: string) => ( @@ -30,7 +33,9 @@ export const SeriesEditor = () => { name: 'Filter', field: 'defaultFilters', width: '30%', - render: (defaultFilters: string[]) => , + render: (defaultFilters: string[], series: DataSeries) => ( + + ), }, { name: 'Breakdowns', @@ -46,11 +51,29 @@ export const SeriesEditor = () => { field: 'id', render: (val: string, item: DataSeries) => , }, + { + name: 'Remove', + width: '5%', + field: 'id', + render: (val: string, item: DataSeries) => , + }, ]; const { dataViewType } = useParams<{ dataViewType: DataViewType }>(); - const items: DataSeries[] = [getDefaultConfigs({ dataViewType })]; + const { allSeries } = useUrlStorage(); + + console.log(allSeries); + + const allSeriesKeys = Object.keys(allSeries); + + const items: DataSeries[] = allSeriesKeys.map((seriesKey) => { + const series = allSeries[seriesKey]; + return getPageLoadDistLensConfig({ + seriesId: seriesKey, + serviceName: series.serviceName, + }); + }); return ( <> @@ -62,9 +85,7 @@ export const SeriesEditor = () => { rowProps={() => ({ height: 100 })} /> - - Add series - + ); }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts index ed6cf901115c52..2d9464dc8600b3 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts @@ -12,12 +12,16 @@ import { LastValueIndexPatternColumn, } from '../../../../../lens/public'; -export type DataViewType = 'page-load-dist' | 'page-views' | 'uptime-duration' | 'uptime-pings'; +export type DataViewType = + | 'page-load-dist' + | 'page-views' + | 'uptime-duration' + | 'uptime-pings' + | 'service-latency'; export interface DataSeries { dataViewType: DataViewType; indexPattern: string; - name: string; id: string; xAxisColumn: Partial | Partial; yAxisColumn: @@ -32,12 +36,13 @@ export interface DataSeries { } export interface SeriesUrl { + id: string; time: { to: string; from: string; }; - breakdown: string; - filters: UrlFilter[]; + breakdown?: string; + filters?: UrlFilter[]; seriesType: string; } @@ -46,3 +51,5 @@ export interface UrlFilter { values?: string[]; notValues?: string[]; } + +export type AppDataType = 'synthetics' | 'rum' | 'logs' | 'metrics' | 'apm'; diff --git a/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx b/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx new file mode 100644 index 00000000000000..bab924e8750952 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FormEvent, Fragment, useEffect, useState } from 'react'; +import { + EuiButton, + EuiPopover, + EuiPopoverFooter, + EuiPopoverTitle, + EuiSelectable, +} from '@elastic/eui'; +import { useValuesList } from '../../hooks/use_values_list'; +import { IIndexPattern } from '../../../../../../src/plugins/data/common'; +import { FieldLabels } from './exploratory_view/configurations/constants'; + +interface Option { + id: string; + label: string; + checked?: 'on'; +} + +interface Props { + value?: string; + indexPattern: IIndexPattern; + sourceField: string; + onChange: (val: string) => void; +} + +export const FieldValueSelection = ({ + sourceField, + indexPattern, + value, + onChange: onSelectionChange, +}: Props) => { + const [query, setQuery] = useState(''); + const { values, loading } = useValuesList({ indexPattern, query, sourceField }); + + const [options, setOptions] = useState([]); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + useEffect(() => { + setOptions( + (values ?? []).map((val) => ({ id: val, label: val, ...(value ? { checked: 'on' } : {}) })) + ); + }, [values]); + + const onButtonClick = () => { + setIsPopoverOpen(!isPopoverOpen); + }; + + const closePopover = () => { + setIsPopoverOpen(false); + }; + + const onChange = (options: Option[]) => { + setOptions(options); + }; + + const onValueChange = (evt: FormEvent) => { + setQuery(evt.target.value); + }; + + const button = ( + + Web Application + + ); + + return ( + + + + {(list, search) => ( +
+ {search} + {list} + + opt?.checked === 'on')} + onClick={() => { + const selected = options.find((opt) => opt?.checked === 'on')!; + onSelectionChange(selected.label); + setIsPopoverOpen(false); + }} + > + Apply + + +
+ )} +
+
+
+ ); +}; diff --git a/x-pack/plugins/observability/public/hooks/use_values_list.ts b/x-pack/plugins/observability/public/hooks/use_values_list.ts new file mode 100644 index 00000000000000..f795f121250f29 --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/use_values_list.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IIndexPattern } from '../../../../../src/plugins/data/common'; +import { useKibana } from '../../../../../src/plugins/kibana_react/public'; +import { ObservabilityClientPluginsStart } from '../plugin'; +import { useFetcher } from './use_fetcher'; + +interface Props { + sourceField: string; + query?: string; + indexPattern: IIndexPattern; +} + +export const useValuesList = ({ sourceField, indexPattern, query }: Props) => { + const { + services: { data }, + } = useKibana(); + + const { data: values, status } = useFetcher(() => { + return data.autocomplete.getValueSuggestions({ + indexPattern, + query: query || '', + useTimeRange: false, + field: indexPattern.fields.find(({ name }) => name === sourceField)!, + }); + }, [sourceField, query]); + + return { values, loading: status === 'loading' || status === 'pending' }; +}; From 73b2c41ae20d9370acc84a6f2f66b1a9a70ef484 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 15 Mar 2021 09:44:33 +0100 Subject: [PATCH 04/53] more views --- src/plugins/data/public/index.ts | 4 +- .../data/public/ui/filter_bar/filter_item.tsx | 166 ++++++++++-------- .../data/public/ui/filter_bar/index.tsx | 9 + .../app/RumDashboard/PageViewsTrend/index.tsx | 29 ++- .../projections/rum_page_load_transactions.ts | 11 -- .../public/xy_visualization/expression.tsx | 8 +- .../public/assets/kibana_dashboard_dark.svg | 116 ++++++++++++ .../public/assets/kibana_dashboard_light.svg | 116 ++++++++++++ .../public/components/app/header/index.tsx | 8 +- .../public/components/app/header/tabs.tsx | 41 +++++ .../components/empty_view.tsx | 29 +++ .../components/filter_label.tsx | 62 +++++++ .../configurations/constants.ts | 12 +- .../configurations/default_configs.ts | 26 ++- .../configurations/lens_attributes.ts | 23 ++- .../configurations/monitor_pings_config.ts | 39 ++++ .../configurations/page_load_dist_config.ts | 7 +- .../configurations/page_view_config.ts | 23 ++- .../configurations/service_latency_config.ts | 19 +- .../service_throughput_config.ts | 47 +++++ .../exploratory_view/exploratory_view.tsx | 45 +++-- .../shared/exploratory_view/header.tsx | 64 ------- .../exploratory_view/header/chart_types.tsx | 9 +- .../shared/exploratory_view/header/header.tsx | 71 ++++++++ .../header/metric_selection.tsx | 56 ++++++ .../hooks/use_init_exploratory_view.ts | 43 +++++ .../hooks/use_lens_attributes.ts | 47 +++-- .../hooks/use_series_filters.ts | 46 +++++ .../hooks/use_url_strorage.tsx | 17 +- .../shared/exploratory_view/index.tsx | 45 ++--- .../series_builder/columns/data_types_col.tsx | 14 +- .../columns/report_definition_col.tsx | 63 +++++-- .../columns/report_types_col.tsx | 11 +- .../series_builder/series_builder.tsx | 48 +++-- .../series_editor/columns/breakdowns.tsx | 5 +- .../series_editor/columns/filter_expanded.tsx | 20 ++- .../series_editor/columns/series_filter.tsx | 49 ++++-- .../series_editor/default_filters.tsx | 43 +++++ .../series_editor/selected_filters.tsx | 49 +++--- .../series_editor/series_editor.tsx | 16 +- .../shared/exploratory_view/types.ts | 35 +++- .../shared/field_value_selection.tsx | 2 +- .../hooks/use_default_index_pattern.tsx | 47 +++-- .../observability/public/routes/index.tsx | 7 +- .../common/charts/ping_histogram.tsx | 44 +++-- 45 files changed, 1294 insertions(+), 397 deletions(-) create mode 100644 x-pack/plugins/observability/public/assets/kibana_dashboard_dark.svg create mode 100644 x-pack/plugins/observability/public/assets/kibana_dashboard_light.svg create mode 100644 x-pack/plugins/observability/public/components/app/header/tabs.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_pings_config.ts create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/header.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/header/metric_selection.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_filters.ts create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/default_filters.tsx diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 1ab8e29ff2fd14..dfc38787531765 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -54,6 +54,7 @@ import { // Filter helpers namespace: export const esFilters = { FilterLabel, + FilterItem, FILTERS, FilterStateStore, @@ -92,7 +93,7 @@ export const esFilters = { extractTimeRange, }; -export { +export type { RangeFilter, RangeFilterMeta, RangeFilterParams, @@ -484,6 +485,7 @@ export { APPLY_FILTER_TRIGGER } from './triggers'; */ import { DataPublicPlugin } from './plugin'; +import { FilterItem } from './ui/filter_bar'; export function plugin(initializerContext: PluginInitializerContext) { return new DataPublicPlugin(initializerContext); diff --git a/src/plugins/data/public/ui/filter_bar/filter_item.tsx b/src/plugins/data/public/ui/filter_bar/filter_item.tsx index 05ae4e74535f47..d935316f247090 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_item.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_item.tsx @@ -25,7 +25,9 @@ import { } from '../../../common'; import { getIndexPatterns } from '../../services'; -interface Props { +type PanelOptions = 'pinFilter' | 'editFilter' | 'negateFilter' | 'disableFilter' | 'deleteFilter'; + +export interface FilterItemProps { id: string; filter: Filter; indexPatterns: IIndexPattern[]; @@ -34,6 +36,7 @@ interface Props { onRemove: () => void; intl: InjectedIntl; uiSettings: IUiSettingsClient; + hiddenPanelOptions?: Array; } interface LabelOptions { @@ -53,10 +56,10 @@ export type FilterLabelStatus = export const FILTER_EDITOR_WIDTH = 800; -export function FilterItem(props: Props) { +export function FilterItem(props: FilterItemProps) { const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [indexPatternExists, setIndexPatternExists] = useState(undefined); - const { id, filter, indexPatterns } = props; + const { id, filter, indexPatterns, hiddenPanelOptions } = props; useEffect(() => { const index = props.filter.meta.index; @@ -154,83 +157,90 @@ export function FilterItem(props: Props) { function getPanels() { const { negate, disabled } = filter.meta; - return [ + let mainPanelItems = [ { - id: 0, - items: [ - { - name: isFilterPinned(filter) - ? props.intl.formatMessage({ - id: 'data.filter.filterBar.unpinFilterButtonLabel', - defaultMessage: 'Unpin', - }) - : props.intl.formatMessage({ - id: 'data.filter.filterBar.pinFilterButtonLabel', - defaultMessage: 'Pin across all apps', - }), - icon: 'pin', - onClick: () => { - setIsPopoverOpen(false); - onTogglePinned(); - }, - 'data-test-subj': 'pinFilter', - }, - { - name: props.intl.formatMessage({ - id: 'data.filter.filterBar.editFilterButtonLabel', - defaultMessage: 'Edit filter', + name: isFilterPinned(filter) + ? props.intl.formatMessage({ + id: 'data.filter.filterBar.unpinFilterButtonLabel', + defaultMessage: 'Unpin', + }) + : props.intl.formatMessage({ + id: 'data.filter.filterBar.pinFilterButtonLabel', + defaultMessage: 'Pin across all apps', }), - icon: 'pencil', - panel: 1, - 'data-test-subj': 'editFilter', - }, - { - name: negate - ? props.intl.formatMessage({ - id: 'data.filter.filterBar.includeFilterButtonLabel', - defaultMessage: 'Include results', - }) - : props.intl.formatMessage({ - id: 'data.filter.filterBar.excludeFilterButtonLabel', - defaultMessage: 'Exclude results', - }), - icon: negate ? 'plusInCircle' : 'minusInCircle', - onClick: () => { - setIsPopoverOpen(false); - onToggleNegated(); - }, - 'data-test-subj': 'negateFilter', - }, - { - name: disabled - ? props.intl.formatMessage({ - id: 'data.filter.filterBar.enableFilterButtonLabel', - defaultMessage: 'Re-enable', - }) - : props.intl.formatMessage({ - id: 'data.filter.filterBar.disableFilterButtonLabel', - defaultMessage: 'Temporarily disable', - }), - icon: `${disabled ? 'eye' : 'eyeClosed'}`, - onClick: () => { - setIsPopoverOpen(false); - onToggleDisabled(); - }, - 'data-test-subj': 'disableFilter', - }, - { - name: props.intl.formatMessage({ - id: 'data.filter.filterBar.deleteFilterButtonLabel', - defaultMessage: 'Delete', + icon: 'pin', + onClick: () => { + setIsPopoverOpen(false); + onTogglePinned(); + }, + 'data-test-subj': 'pinFilter', + }, + { + name: props.intl.formatMessage({ + id: 'data.filter.filterBar.editFilterButtonLabel', + defaultMessage: 'Edit filter', + }), + icon: 'pencil', + panel: 1, + 'data-test-subj': 'editFilter', + }, + { + name: negate + ? props.intl.formatMessage({ + id: 'data.filter.filterBar.includeFilterButtonLabel', + defaultMessage: 'Include results', + }) + : props.intl.formatMessage({ + id: 'data.filter.filterBar.excludeFilterButtonLabel', + defaultMessage: 'Exclude results', + }), + icon: negate ? 'plusInCircle' : 'minusInCircle', + onClick: () => { + setIsPopoverOpen(false); + onToggleNegated(); + }, + 'data-test-subj': 'negateFilter', + }, + { + name: disabled + ? props.intl.formatMessage({ + id: 'data.filter.filterBar.enableFilterButtonLabel', + defaultMessage: 'Re-enable', + }) + : props.intl.formatMessage({ + id: 'data.filter.filterBar.disableFilterButtonLabel', + defaultMessage: 'Temporarily disable', }), - icon: 'trash', - onClick: () => { - setIsPopoverOpen(false); - props.onRemove(); - }, - 'data-test-subj': 'deleteFilter', - }, - ], + icon: `${disabled ? 'eye' : 'eyeClosed'}`, + onClick: () => { + setIsPopoverOpen(false); + onToggleDisabled(); + }, + 'data-test-subj': 'disableFilter', + }, + { + name: props.intl.formatMessage({ + id: 'data.filter.filterBar.deleteFilterButtonLabel', + defaultMessage: 'Delete', + }), + icon: 'trash', + onClick: () => { + setIsPopoverOpen(false); + props.onRemove(); + }, + 'data-test-subj': 'deleteFilter', + }, + ]; + + if (hiddenPanelOptions && hiddenPanelOptions.length > 0) { + mainPanelItems = mainPanelItems.filter( + (pItem) => !hiddenPanelOptions.includes(pItem['data-test-subj'] as PanelOptions) + ); + } + return [ + { + id: 0, + items: mainPanelItems, }, { id: 1, @@ -363,3 +373,5 @@ export function FilterItem(props: Props) { ); } + +export default FilterItem; diff --git a/src/plugins/data/public/ui/filter_bar/index.tsx b/src/plugins/data/public/ui/filter_bar/index.tsx index f8c2f46424c387..9fb352c64aa5d8 100644 --- a/src/plugins/data/public/ui/filter_bar/index.tsx +++ b/src/plugins/data/public/ui/filter_bar/index.tsx @@ -17,3 +17,12 @@ export const FilterLabel = (props: FilterLabelProps) => ( ); + +import type { FilterItemProps } from './filter_item'; + +const LazyFilterItem = React.lazy(() => import('./filter_item')); +export const FilterItem = (props: FilterItemProps) => ( + }> + + +); diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx index 85df933bcb9e29..74e5974fa34604 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx @@ -6,7 +6,14 @@ */ import React, { useState } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import rison from 'rison-node'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { useFetcher } from '../../../../hooks/use_fetcher'; import { I18LABELS } from '../translations'; @@ -17,7 +24,7 @@ import { BreakdownItem } from '../../../../../typings/ui_filters'; export function PageViewsTrend() { const { urlParams, uiFilters } = useUrlParams(); - const { start, end, searchTerm } = urlParams; + const { start, end, searchTerm, serviceName } = urlParams; const [breakdown, setBreakdown] = useState(null); @@ -48,6 +55,14 @@ export function PageViewsTrend() { [end, start, uiFilters, breakdown, searchTerm] ); + const analyzeHref = { + [serviceName + '-pgv']: { + rt: 'pgv', + serviceName, + time: { from: start, to: end }, + }, + }; + return (
}> - - - ); -}; diff --git a/x-pack/plugins/lens/public/shared_components/chart_types/xy_chart_types.tsx b/x-pack/plugins/lens/public/shared_components/chart_types/xy_chart_types.tsx deleted file mode 100644 index 67b6dca4cdca77..00000000000000 --- a/x-pack/plugins/lens/public/shared_components/chart_types/xy_chart_types.tsx +++ /dev/null @@ -1,101 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useState } from 'react'; -import { EuiButton, EuiButtonGroup, EuiButtonIcon, EuiPopover } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import styled from 'styled-components'; -import { visualizationTypes } from '../../xy_visualization/types'; - -const ButtonGroup = styled(EuiButtonGroup)` - &&& { - .euiButtonGroupButton-isSelected { - background-color: #a5a9b1 !important; - } - } -`; -export interface XYChartTypesProps { - onChange: (value: string) => void; - value: string; - label?: string; - includeChartTypes?: string[]; - excludeChartTypes?: string[]; -} - -function XYChartTypes({ - onChange, - value, - label, - includeChartTypes, - excludeChartTypes, -}: XYChartTypesProps) { - const [isOpen, setIsOpen] = useState(false); - - let vizTypes = visualizationTypes; - - if ((excludeChartTypes ?? []).length > 0) { - vizTypes = visualizationTypes.filter(({ id }) => !excludeChartTypes?.includes(id)); - } - - if ((includeChartTypes ?? []).length > 0) { - vizTypes = visualizationTypes.filter(({ id }) => includeChartTypes?.includes(id)); - } - - return ( - id === value)?.icon} - onClick={() => { - setIsOpen((prevState) => !prevState); - }} - > - {label} - - ) : ( - id === value)?.icon!} - onClick={() => { - setIsOpen((prevState) => !prevState); - }} - /> - ) - } - closePopover={() => setIsOpen(false)} - > - ({ - id: t.id, - label: t.label, - title: t.label, - iconType: t.icon || 'empty', - 'data-test-subj': `lnsXY_seriesType-${t.id}`, - }))} - idSelected={value} - onChange={(valueN: string) => { - onChange(valueN); - }} - /> - - ); -} - -// eslint-disable-next-line import/no-default-export -export default XYChartTypes; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx index 047b68473fba46..b0ced2593d151f 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx @@ -5,10 +5,15 @@ * 2.0. */ -import React from 'react'; +import React, { useState } from 'react'; +import { EuiButton, EuiButtonGroup, EuiButtonIcon, EuiPopover } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import styled from 'styled-components'; +import { useKibana } from '../../../../../../../../../src/plugins/kibana_react/public'; +import { ObservabilityClientPluginsStart } from '../../../../../plugin'; +import { useFetcher } from '../../../../..'; import { useUrlStorage } from '../../hooks/use_url_strorage'; -import { XYChartTypes } from '../../../../../../../lens/public'; export function SeriesChartTypes({ seriesId, @@ -38,3 +43,95 @@ export function SeriesChartTypes({ /> ); } + +export interface XYChartTypesProps { + onChange: (value: string) => void; + value: string; + label?: string; + includeChartTypes?: string[]; + excludeChartTypes?: string[]; +} + +function XYChartTypes({ + onChange, + value, + label, + includeChartTypes, + excludeChartTypes, +}: XYChartTypesProps) { + const [isOpen, setIsOpen] = useState(false); + + const { + services: { lens }, + } = useKibana(); + + const { data = [] } = useFetcher(() => lens.getXyVisTypes(), [lens]); + + let vizTypes = data ?? []; + + if ((excludeChartTypes ?? []).length > 0) { + vizTypes = vizTypes.filter(({ id }) => !excludeChartTypes?.includes(id)); + } + + if ((includeChartTypes ?? []).length > 0) { + vizTypes = vizTypes.filter(({ id }) => includeChartTypes?.includes(id)); + } + + return ( + id === value)?.icon} + onClick={() => { + setIsOpen((prevState) => !prevState); + }} + > + {label} + + ) : ( + id === value)?.icon!} + onClick={() => { + setIsOpen((prevState) => !prevState); + }} + /> + ) + } + closePopover={() => setIsOpen(false)} + > + ({ + id: t.id, + label: t.label, + title: t.label, + iconType: t.icon || 'empty', + 'data-test-subj': `lnsXY_seriesType-${t.id}`, + }))} + idSelected={value} + onChange={(valueN: string) => { + onChange(valueN); + }} + /> + + ); +} + +const ButtonGroup = styled(EuiButtonGroup)` + &&& { + .euiButtonGroupButton-isSelected { + background-color: #a5a9b1 !important; + } + } +`; From 68282c63ab72724ec41f79b720c87d4926fac870 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 24 Mar 2021 18:21:09 +0100 Subject: [PATCH 23/53] fix types --- x-pack/plugins/observability/public/index.ts | 22 ++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/observability/public/index.ts b/x-pack/plugins/observability/public/index.ts index dfe454ccc7b871..a14990e7ba3b17 100644 --- a/x-pack/plugins/observability/public/index.ts +++ b/x-pack/plugins/observability/public/index.ts @@ -6,12 +6,22 @@ */ import { PluginInitializerContext, PluginInitializer } from 'kibana/public'; -import { Plugin, ObservabilityPluginSetup, ObservabilityPluginStart } from './plugin'; -export type { ObservabilityPluginSetup, ObservabilityPluginStart }; - -export const plugin: PluginInitializer = ( - context: PluginInitializerContext -) => { +import { + Plugin, + ObservabilityPluginSetup, + ObservabilityClientPluginsStart, + ObservabilityClientPluginsSetup, + ObservabilityClientStart, + ObservabilityClientSetup, +} from './plugin'; +export type { ObservabilityPluginSetup, ObservabilityClientPluginsStart }; + +export const plugin: PluginInitializer< + ObservabilityClientSetup, + ObservabilityClientStart, + ObservabilityClientPluginsSetup, + ObservabilityClientPluginsStart +> = (context: PluginInitializerContext) => { return new Plugin(context); }; From a2121ebdaad03ad1d245799d2f493b7800dd5265 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Thu, 25 Mar 2021 08:17:17 +0100 Subject: [PATCH 24/53] update types --- x-pack/plugins/apm/public/plugin.ts | 4 ++-- x-pack/plugins/infra/public/types.ts | 8 +++---- .../public/application/application.test.tsx | 4 ++-- .../public/application/index.tsx | 4 ++-- .../components/app/section/apm/index.test.tsx | 4 ++-- .../components/app/section/ux/index.test.tsx | 4 ++-- .../public/context/plugin_context.tsx | 4 ++-- .../public/hooks/use_time_range.test.ts | 6 ++--- x-pack/plugins/observability/public/index.ts | 24 +++++++++++-------- .../pages/overview/overview.stories.tsx | 4 ++-- x-pack/plugins/observability/public/plugin.ts | 21 ++++++++-------- .../public/utils/test_helper.tsx | 4 ++-- x-pack/plugins/uptime/public/apps/plugin.ts | 4 ++-- 13 files changed, 49 insertions(+), 46 deletions(-) diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index 4ddd10ecc14764..5740e47d0076f2 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -9,7 +9,7 @@ import { ConfigSchema } from '.'; import { FetchDataParams, HasDataParams, - ObservabilityPluginSetup, + ObservabilityPublicSetup, } from '../../observability/public'; import { AppMountParameters, @@ -52,7 +52,7 @@ export interface ApmPluginSetupDeps { home?: HomePublicPluginSetup; licensing: LicensingPluginSetup; triggersActionsUi: TriggersAndActionsUIPublicPluginSetup; - observability?: ObservabilityPluginSetup; + observability?: ObservabilityPublicSetup; } export interface ApmPluginStartDeps { diff --git a/x-pack/plugins/infra/public/types.ts b/x-pack/plugins/infra/public/types.ts index 4d70676d25e40f..068abd0e0f20f3 100644 --- a/x-pack/plugins/infra/public/types.ts +++ b/x-pack/plugins/infra/public/types.ts @@ -19,8 +19,8 @@ import type { } from '../../../plugins/triggers_actions_ui/public'; import type { DataEnhancedSetup, DataEnhancedStart } from '../../data_enhanced/public'; import type { - ObservabilityPluginSetup, - ObservabilityPluginStart, + ObservabilityPublicSetup, + ObservabilityPublicStart, } from '../../observability/public'; import type { SpacesPluginStart } from '../../spaces/public'; import { MlPluginStart, MlPluginSetup } from '../../ml/public'; @@ -33,7 +33,7 @@ export type InfraClientStartExports = void; export interface InfraClientSetupDeps { dataEnhanced: DataEnhancedSetup; home?: HomePublicPluginSetup; - observability: ObservabilityPluginSetup; + observability: ObservabilityPublicSetup; triggersActionsUi: TriggersAndActionsUIPublicPluginSetup; usageCollection: UsageCollectionSetup; ml: MlPluginSetup; @@ -43,7 +43,7 @@ export interface InfraClientSetupDeps { export interface InfraClientStartDeps { data: DataPublicPluginStart; dataEnhanced: DataEnhancedStart; - observability: ObservabilityPluginStart; + observability: ObservabilityPublicStart; spaces: SpacesPluginStart; triggersActionsUi: TriggersAndActionsUIPublicPluginStart; usageCollection: UsageCollectionStart; diff --git a/x-pack/plugins/observability/public/application/application.test.tsx b/x-pack/plugins/observability/public/application/application.test.tsx index 0eac688cd3e4bf..d06b3822c25710 100644 --- a/x-pack/plugins/observability/public/application/application.test.tsx +++ b/x-pack/plugins/observability/public/application/application.test.tsx @@ -9,7 +9,7 @@ import { createMemoryHistory } from 'history'; import React from 'react'; import { Observable } from 'rxjs'; import { AppMountParameters, CoreStart } from 'src/core/public'; -import { ObservabilityClientPluginsStart } from '../plugin'; +import { ObservabilityPublicPluginsStart } from '../plugin'; import { renderApp } from './'; describe('renderApp', () => { @@ -32,7 +32,7 @@ describe('renderApp', () => { }, }, }, - } as unknown) as ObservabilityClientPluginsStart; + } as unknown) as ObservabilityPublicPluginsStart; const core = ({ application: { currentAppId$: new Observable(), navigateToUrl: () => {} }, chrome: { diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index f711cfbb82b338..c8a8d877380e39 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -18,7 +18,7 @@ import { import { PluginContext } from '../context/plugin_context'; import { usePluginContext } from '../hooks/use_plugin_context'; import { useRouteParams } from '../hooks/use_route_params'; -import { ObservabilityClientPluginsStart } from '../plugin'; +import { ObservabilityPublicPluginsStart } from '../plugin'; import { HasDataContextProvider } from '../context/has_data_context'; import { Breadcrumbs, routes } from '../routes'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; @@ -68,7 +68,7 @@ function App() { export const renderApp = ( core: CoreStart, - plugins: ObservabilityClientPluginsStart, + plugins: ObservabilityPublicPluginsStart, appMountParameters: AppMountParameters ) => { const { element, history } = appMountParameters; diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx index 6873dd33e6f78e..e5f100be285e13 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx @@ -14,7 +14,7 @@ import * as hasDataHook from '../../../../hooks/use_has_data'; import * as pluginContext from '../../../../hooks/use_plugin_context'; import { HasDataContextValue } from '../../../../context/has_data_context'; import { AppMountParameters, CoreStart } from 'kibana/public'; -import { ObservabilityClientPluginsStart } from '../../../../plugin'; +import { ObservabilityPublicPluginsStart } from '../../../../plugin'; jest.mock('react-router-dom', () => ({ useLocation: () => ({ @@ -53,7 +53,7 @@ describe('APMSection', () => { }, }, }, - } as unknown) as ObservabilityClientPluginsStart, + } as unknown) as ObservabilityPublicPluginsStart, })); }); it('renders with transaction series and stats', () => { diff --git a/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx b/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx index a586a8aa87bc49..d76e6d1b3e5514 100644 --- a/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx @@ -11,7 +11,7 @@ import { HasDataContextValue } from '../../../../context/has_data_context'; import * as fetcherHook from '../../../../hooks/use_fetcher'; import * as hasDataHook from '../../../../hooks/use_has_data'; import * as pluginContext from '../../../../hooks/use_plugin_context'; -import { ObservabilityClientPluginsStart } from '../../../../plugin'; +import { ObservabilityPublicPluginsStart } from '../../../../plugin'; import { render } from '../../../../utils/test_helper'; import { UXSection } from './'; import { response } from './mock_data/ux.mock'; @@ -52,7 +52,7 @@ describe('UXSection', () => { }, }, }, - } as unknown) as ObservabilityClientPluginsStart, + } as unknown) as ObservabilityPublicPluginsStart, })); }); it('renders with core web vitals', () => { diff --git a/x-pack/plugins/observability/public/context/plugin_context.tsx b/x-pack/plugins/observability/public/context/plugin_context.tsx index e0eed2a1470a3b..771968861a6bbf 100644 --- a/x-pack/plugins/observability/public/context/plugin_context.tsx +++ b/x-pack/plugins/observability/public/context/plugin_context.tsx @@ -7,12 +7,12 @@ import { createContext } from 'react'; import { AppMountParameters, CoreStart } from 'kibana/public'; -import { ObservabilityClientPluginsStart } from '../plugin'; +import { ObservabilityPublicPluginsStart } from '../plugin'; export interface PluginContextValue { appMountParameters: AppMountParameters; core: CoreStart; - plugins: ObservabilityClientPluginsStart; + plugins: ObservabilityPublicPluginsStart; } export const PluginContext = createContext({} as PluginContextValue); diff --git a/x-pack/plugins/observability/public/hooks/use_time_range.test.ts b/x-pack/plugins/observability/public/hooks/use_time_range.test.ts index 833221ebdefbd0..184ec4f3390f4e 100644 --- a/x-pack/plugins/observability/public/hooks/use_time_range.test.ts +++ b/x-pack/plugins/observability/public/hooks/use_time_range.test.ts @@ -8,7 +8,7 @@ import { useTimeRange } from './use_time_range'; import * as pluginContext from './use_plugin_context'; import { AppMountParameters, CoreStart } from 'kibana/public'; -import { ObservabilityClientPluginsStart } from '../plugin'; +import { ObservabilityPublicPluginsStart } from '../plugin'; import * as kibanaUISettings from './use_kibana_ui_settings'; jest.mock('react-router-dom', () => ({ @@ -36,7 +36,7 @@ describe('useTimeRange', () => { }, }, }, - } as unknown) as ObservabilityClientPluginsStart, + } as unknown) as ObservabilityPublicPluginsStart, })); jest.spyOn(kibanaUISettings, 'useKibanaUISettings').mockImplementation(() => ({ from: '2020-10-08T05:00:00.000Z', @@ -76,7 +76,7 @@ describe('useTimeRange', () => { }, }, }, - } as unknown) as ObservabilityClientPluginsStart, + } as unknown) as ObservabilityPublicPluginsStart, })); }); it('returns ranges and absolute times from kibana default settings', () => { diff --git a/x-pack/plugins/observability/public/index.ts b/x-pack/plugins/observability/public/index.ts index a14990e7ba3b17..f473ed963c75a7 100644 --- a/x-pack/plugins/observability/public/index.ts +++ b/x-pack/plugins/observability/public/index.ts @@ -8,19 +8,23 @@ import { PluginInitializerContext, PluginInitializer } from 'kibana/public'; import { Plugin, - ObservabilityPluginSetup, - ObservabilityClientPluginsStart, - ObservabilityClientPluginsSetup, - ObservabilityClientStart, - ObservabilityClientSetup, + ObservabilityPublicPluginsStart, + ObservabilityPublicPluginsSetup, + ObservabilityPublicStart, + ObservabilityPublicSetup, } from './plugin'; -export type { ObservabilityPluginSetup, ObservabilityClientPluginsStart }; +export type { + ObservabilityPublicSetup, + ObservabilityPublicStart, + ObservabilityPublicPluginsSetup, + ObservabilityPublicPluginsStart, +}; export const plugin: PluginInitializer< - ObservabilityClientSetup, - ObservabilityClientStart, - ObservabilityClientPluginsSetup, - ObservabilityClientPluginsStart + ObservabilityPublicSetup, + ObservabilityPublicStart, + ObservabilityPublicPluginsSetup, + ObservabilityPublicPluginsStart > = (context: PluginInitializerContext) => { return new Plugin(context); }; diff --git a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx index 6c09d4cde4add6..56019eeccfd5ae 100644 --- a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx +++ b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx @@ -15,7 +15,7 @@ import { UI_SETTINGS } from '../../../../../../src/plugins/data/public'; import { HasDataContextProvider } from '../../context/has_data_context'; import { PluginContext } from '../../context/plugin_context'; import { registerDataHandler, unregisterDataHandler } from '../../data_handler'; -import { ObservabilityClientPluginsStart } from '../../plugin'; +import { ObservabilityPublicPluginsStart } from '../../plugin'; import { OverviewPage } from './'; import { alertsFetchData } from './mock/alerts.mock'; import { emptyResponse as emptyAPMResponse, fetchApmData } from './mock/apm.mock'; @@ -51,7 +51,7 @@ const withCore = makeDecorator({ timefilter: { timefilter: { setTime: () => {}, getTime: () => ({}) } }, }, }, - } as unknown) as ObservabilityClientPluginsStart, + } as unknown) as ObservabilityPublicPluginsStart, }} > diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index cd6ebc661045da..11b78a30381887 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -22,39 +22,38 @@ import { registerDataHandler } from './data_handler'; import { toggleOverviewLinkInNav } from './toggle_overview_link_in_nav'; import { LensPublicStart } from '../../lens/public'; -export interface ObservabilityPluginSetup { +export interface ObservabilityPublicSetup { dashboard: { register: typeof registerDataHandler }; } -export interface ObservabilityClientPluginsSetup { +export interface ObservabilityPublicPluginsSetup { data: DataPublicPluginSetup; home?: HomePublicPluginSetup; } -export interface ObservabilityClientPluginsStart { +export interface ObservabilityPublicPluginsStart { home?: HomePublicPluginStart; data: DataPublicPluginStart; lens: LensPublicStart; } -export type ObservabilityClientSetup = void; -export type ObservabilityClientStart = void; +export type ObservabilityPublicStart = void; export class Plugin implements PluginClass< - ObservabilityClientSetup, - ObservabilityClientStart, - ObservabilityClientPluginsSetup, - ObservabilityClientPluginsStart + ObservabilityPublicSetup, + ObservabilityPublicStart, + ObservabilityPublicPluginsSetup, + ObservabilityPublicPluginsStart > { private readonly appUpdater$ = new BehaviorSubject(() => ({})); constructor(context: PluginInitializerContext) {} public setup( - core: CoreSetup, - plugins: ObservabilityClientPluginsSetup + core: CoreSetup, + plugins: ObservabilityPublicPluginsSetup ) { const category = DEFAULT_APP_CATEGORIES.observability; const euiIconType = 'logo-observability'; diff --git a/x-pack/plugins/observability/public/utils/test_helper.tsx b/x-pack/plugins/observability/public/utils/test_helper.tsx index 7c516f9b5fb34a..885303ea0c54b3 100644 --- a/x-pack/plugins/observability/public/utils/test_helper.tsx +++ b/x-pack/plugins/observability/public/utils/test_helper.tsx @@ -13,7 +13,7 @@ import { of } from 'rxjs'; import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; import translations from '../../../translations/translations/ja-JP.json'; import { PluginContext } from '../context/plugin_context'; -import { ObservabilityClientPluginsStart } from '../plugin'; +import { ObservabilityPublicPluginsStart } from '../plugin'; import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common'; const appMountParameters = ({ setHeaderActionMenu: () => {} } as unknown) as AppMountParameters; @@ -32,7 +32,7 @@ export const core = ({ const plugins = ({ data: { query: { timefilter: { timefilter: { setTime: jest.fn() } } } }, -} as unknown) as ObservabilityClientPluginsStart; +} as unknown) as ObservabilityPublicPluginsStart; export const render = (component: React.ReactNode) => { return testLibRender( diff --git a/x-pack/plugins/uptime/public/apps/plugin.ts b/x-pack/plugins/uptime/public/apps/plugin.ts index e7a22a080d79ad..e3457884594a98 100644 --- a/x-pack/plugins/uptime/public/apps/plugin.ts +++ b/x-pack/plugins/uptime/public/apps/plugin.ts @@ -27,13 +27,13 @@ import { DataPublicPluginStart, } from '../../../../../src/plugins/data/public'; import { alertTypeInitializers } from '../lib/alert_types'; -import { FetchDataParams, ObservabilityPluginSetup } from '../../../observability/public'; +import { FetchDataParams, ObservabilityPublicSetup } from '../../../observability/public'; import { PLUGIN } from '../../common/constants/plugin'; export interface ClientPluginsSetup { data: DataPublicPluginSetup; home?: HomePublicPluginSetup; - observability: ObservabilityPluginSetup; + observability: ObservabilityPublicSetup; triggersActionsUi: TriggersAndActionsUIPublicPluginSetup; } From 52cbefa25c2f8b75c8d3f27c1bf556f1c0c994cb Mon Sep 17 00:00:00 2001 From: Shahzad Date: Thu, 25 Mar 2021 09:43:27 +0100 Subject: [PATCH 25/53] fix type --- .../exploratory_view/series_editor/columns/chart_types.tsx | 2 +- x-pack/plugins/observability/tsconfig.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx index b0ced2593d151f..8dad22339a61a4 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx @@ -39,7 +39,7 @@ export function SeriesChartTypes({ onChange={onChange} value={seriesType} excludeChartTypes={['bar_percentage_stacked']} - label={'Chart types'} + label={'Chart type'} /> ); } diff --git a/x-pack/plugins/observability/tsconfig.json b/x-pack/plugins/observability/tsconfig.json index 975b122a5bbba5..1293be4fbb51d5 100644 --- a/x-pack/plugins/observability/tsconfig.json +++ b/x-pack/plugins/observability/tsconfig.json @@ -17,7 +17,7 @@ { "path": "../../../src/plugins/usage_collection/tsconfig.json" }, { "path": "../alerting/tsconfig.json" }, { "path": "../licensing/tsconfig.json" }, - { "path": "../translations/tsconfig.json" } + { "path": "../translations/tsconfig.json" }, { "path": "../lens/tsconfig.json" } ] } From a6dcd151cc1c4ed52af08adeb77f5b4fa18ecd5a Mon Sep 17 00:00:00 2001 From: Shahzad Date: Thu, 25 Mar 2021 10:43:42 +0100 Subject: [PATCH 26/53] fix types --- .../app/RumDashboard/PageViewsTrend/index.tsx | 8 +- .../components/filter_label.tsx | 2 +- .../exploratory_view/exploratory_view.tsx | 3 +- .../hooks/use_default_index_pattern.tsx | 30 ++-- .../hooks/use_init_exploratory_view.ts | 13 +- .../hooks/use_lens_attributes.ts | 2 +- .../shared/exploratory_view/index.tsx | 2 +- .../series_builder/columns/data_types_col.tsx | 2 +- .../columns/report_definition_col.tsx | 26 ++-- .../series_date_picker/index.tsx | 2 +- .../series_editor/columns/chart_types.tsx | 2 +- .../series_editor/columns/filter_expanded.tsx | 25 +--- .../columns/filter_value_btn.tsx | 6 +- .../series_editor/selected_filters.tsx | 17 ++- .../shared/field_value_selection.tsx | 137 ------------------ .../field_value_selection.tsx | 14 +- .../shared/field_value_suggestions/index.tsx | 17 ++- .../public/hooks/use_values_list.ts | 10 +- ...rns.ts => observability_index_patterns.ts} | 0 19 files changed, 100 insertions(+), 218 deletions(-) rename x-pack/plugins/observability/public/{ => components/shared/exploratory_view}/hooks/use_default_index_pattern.tsx (68%) delete mode 100644 x-pack/plugins/observability/public/components/shared/field_value_selection.tsx rename x-pack/plugins/observability/public/utils/{observability_Index_patterns.ts => observability_index_patterns.ts} (100%) diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx index be0284f8295417..c728e601b17991 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx @@ -24,14 +24,14 @@ import { BreakdownItem } from '../../../../../typings/ui_filters'; export function PageViewsTrend() { const { urlParams, uiFilters } = useUrlParams(); - const { start, end, searchTerm, serviceName } = urlParams; + const { start, end, searchTerm } = urlParams; + + const { serviceName } = uiFilters; const [breakdown, setBreakdown] = useState(null); const { data, status } = useFetcher( (callApmApi) => { - const { serviceName } = uiFilters; - if (start && end && serviceName) { return callApmApi({ endpoint: 'GET /api/apm/rum-client/page-view-trends', @@ -52,7 +52,7 @@ export function PageViewsTrend() { } return Promise.resolve(undefined); }, - [end, start, uiFilters, breakdown, searchTerm] + [end, start, uiFilters, breakdown, searchTerm, serviceName] ); const analyzeHref = { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx index 7052492cc1a3dd..e4592b60de827b 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { injectI18n } from '@kbn/i18n/react'; import { esFilters, Filter } from '../../../../../../../../src/plugins/data/public'; -import { useIndexPatternContext } from '../../../../hooks/use_default_index_pattern'; +import { useIndexPatternContext } from '../hooks/use_default_index_pattern'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { useSeriesFilters } from '../hooks/use_series_filters'; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx index f90651f1d943fd..d1f3662d7a83a2 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx @@ -15,7 +15,7 @@ import { SeriesEditor } from './series_editor/series_editor'; import { useUrlStorage } from './hooks/use_url_strorage'; import { useLensAttributes } from './hooks/use_lens_attributes'; import { EmptyView } from './components/empty_view'; -import { useIndexPatternContext } from '../../../hooks/use_default_index_pattern'; +import { useIndexPatternContext } from './hooks/use_default_index_pattern'; import { TypedLensByValueInput } from '../../../../../lens/public'; export function ExploratoryView() { @@ -40,6 +40,7 @@ export function ExploratoryView() { useEffect(() => { setLensAttributes(lensAttributesT); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify(lensAttributesT ?? {}), series?.reportType, series?.time?.from]); return ( diff --git a/x-pack/plugins/observability/public/hooks/use_default_index_pattern.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_default_index_pattern.tsx similarity index 68% rename from x-pack/plugins/observability/public/hooks/use_default_index_pattern.tsx rename to x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_default_index_pattern.tsx index 0b744d1ae4e52a..b41e07e75caa40 100644 --- a/x-pack/plugins/observability/public/hooks/use_default_index_pattern.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_default_index_pattern.tsx @@ -5,12 +5,12 @@ * 2.0. */ -import React, { createContext, useContext, Context, useState, useMemo, useEffect } from 'react'; -import { IIndexPattern } from '../../../../../src/plugins/data/common'; -import { AppDataType } from '../components/shared/exploratory_view/types'; -import { useKibana } from '../../../../../src/plugins/kibana_react/public'; -import { ObservabilityClientPluginsStart } from '../plugin'; -import { ObservabilityIndexPatterns } from '../utils/observability_Index_patterns'; +import React, { createContext, useContext, Context, useState, useEffect } from 'react'; +import { IIndexPattern } from '../../../../../../../../src/plugins/data/common'; +import { AppDataType } from '../types'; +import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; +import { ObservabilityClientPluginsStart } from '../../../../plugin'; +import { ObservabilityIndexPatterns } from '../../../../utils/observability_index_patterns'; export interface IIndexPatternContext { indexPattern: IIndexPattern; @@ -44,14 +44,16 @@ export function IndexPatternContextProvider({ setIndexPattern(indPattern!); }; - const value = useMemo(() => { - return { - indexPattern, - loadIndexPattern, - }; - }, [indexPattern]); - - return {children}; + return ( + + {children} + + ); } export const useIndexPatternContext = () => { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts index 6efa420e8db6f0..9749e439926139 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts @@ -4,8 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { useMemo } from 'react'; import { useFetcher } from '../../../..'; import { IKbnUrlStateStorage } from '../../../../../../../../src/plugins/kibana_utils/public'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; @@ -32,20 +30,15 @@ export const useInitExploratoryView = (storage: IKbnUrlStateStorage) => { const firstSeries = allSeries[firstSeriesId]; - const { data: indexPattern, status } = useFetcher(() => { + const { data: indexPattern } = useFetcher(() => { const obsvIndexP = new ObservabilityIndexPatterns(data); let reportType: DataType = 'apm'; if (firstSeries?.rt) { reportType = ReportToDataTypeMap[firstSeries?.rt]; } - if (firstSeries?.reportType) { - reportType = ReportToDataTypeMap[firstSeries?.reportType]; - } return obsvIndexP.getIndexPattern(reportType); - }, [firstSeries?.rt]); + }, [firstSeries?.rt, data]); - return useMemo(() => { - return indexPattern; - }, [status]); + return indexPattern; }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts index 7b79d96b269138..293d2df4f42d4f 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts @@ -53,7 +53,7 @@ export const useLensAttributes = ({ const filters: UrlFilter[] = useMemo(() => { return (series.filters ?? []).concat(getFiltersFromDefs(reportDefinitions, dataViewConfig)); - }, [series.filters, reportDefinitions]); + }, [series.filters, reportDefinitions, dataViewConfig]); return useMemo(() => { if (!indexPattern || !reportType) { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx index ae475e21e47cc0..355bef35b4496b 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx @@ -13,7 +13,7 @@ import { ExploratoryView } from './exploratory_view'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { ObservabilityClientPluginsStart } from '../../../plugin'; import { useBreadcrumbs } from '../../../hooks/use_breadcrumbs'; -import { IndexPatternContextProvider } from '../../../hooks/use_default_index_pattern'; +import { IndexPatternContextProvider } from './hooks/use_default_index_pattern'; import { createKbnUrlStateStorage, withNotifyOnErrors, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx index d8694415446e40..301d529a6d7f0e 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { AppDataType } from '../../types'; -import { useIndexPatternContext } from '../../../../../hooks/use_default_index_pattern'; +import { useIndexPatternContext } from '../../hooks/use_default_index_pattern'; import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_strorage'; const dataTypes: Array<{ id: AppDataType; label: string }> = [ diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx index da94ea5b786ac7..58f47bd30aefb2 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx @@ -7,11 +7,11 @@ import React from 'react'; import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { FieldValueSelection } from '../../../field_value_selection'; -import { useIndexPatternContext } from '../../../../../hooks/use_default_index_pattern'; +import { useIndexPatternContext } from '../../hooks/use_default_index_pattern'; import { getDefaultConfigs } from '../../configurations/default_configs'; import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_strorage'; import { CustomReportField } from '../custom_report_field'; +import FieldValueSuggestions from '../../../field_value_suggestions'; export function ReportDefinitionCol() { const { indexPattern } = useIndexPatternContext(); @@ -25,11 +25,19 @@ export function ReportDefinitionCol() { seriesId: 'newSeries', }); - const onChange = (field: string, value: string) => { - setSeries(NEW_SERIES_KEY, { - ...series, - reportDefinitions: { ...rtd, [field]: value }, - }); + const onChange = (field: string, value?: string) => { + if (!value) { + delete rtd[field]; + setSeries(NEW_SERIES_KEY, { + ...series, + reportDefinitions: { ...rtd }, + }); + } else { + setSeries(NEW_SERIES_KEY, { + ...series, + reportDefinitions: { ...rtd, [field]: value }, + }); + } }; const onRemove = (field: string) => { @@ -48,12 +56,12 @@ export function ReportDefinitionCol() { {!custom ? ( - onChange(field, val)} + onChange={(val?: string) => onChange(field, val)} filters={(filters ?? []).map(({ query }) => query)} time={series.time} /> diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx index c4ab9d4410a5e4..61f1249928597e 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx @@ -48,7 +48,7 @@ export function SeriesDatePicker({ seriesId }: Props) { if (!series || !series.time) { setSeries(seriesId, { ...series, time: { from: 'now-5h', to: 'now' } }); } - }, []); + }, [seriesId, series, setSeries]); return ( (); - const { series } = useUrlStorage(seriesId); - const { data: values, status } = useFetcher>(() => { - return data.autocomplete.getValueSuggestions({ - indexPattern, - query: '', - useTimeRange: false, - field: { name: field, type: 'string', aggregatable: true }, - }); - }, [field]); + const { values, loading } = useValuesList({ + sourceField: field, + time: series.time, + indexPattern, + }); const filters = series?.filters ?? []; @@ -68,7 +59,7 @@ export function FilterExpanded({ seriesId, field, label, goBack, nestedField }: }} /> - {status === 'loading' && ( + {loading && (
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx index 7ae79f7d010e0b..6f975ef3fffb87 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx @@ -7,11 +7,11 @@ import React from 'react'; import { EuiFilterButton, hexToRgb } from '@elastic/eui'; -import { useIndexPatternContext } from '../../../../../hooks/use_default_index_pattern'; +import { useIndexPatternContext } from '../../hooks/use_default_index_pattern'; import { useUrlStorage } from '../../hooks/use_url_strorage'; -import { FieldValueSelection } from '../../../field_value_selection'; import { useSeriesFilters } from '../../hooks/use_series_filters'; import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common'; +import FieldValueSuggestions from '../../../field_value_suggestions'; interface Props { value: string; @@ -73,7 +73,7 @@ export function FilterValueButton({ }; return nestedField ? ( - { - if (seriesId === NEW_SERIES_KEY && isNew) { - return series.filters ?? []; - } - return (series.filters ?? []).concat(getFiltersFromDefs(reportDefinitions, dataSeries)); - }, [series.filters, reportDefinitions]); + let filters: UrlFilter[] = []; + filters = (series.filters ?? []).concat(getFiltersFromDefs(reportDefinitions, dataSeries)); + + // we don't want to display report definition filters in new series view + if (seriesId === NEW_SERIES_KEY && isNew) { + filters = series.filters ?? []; + } const { removeFilter } = useSeriesFilters({ seriesId }); diff --git a/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx b/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx deleted file mode 100644 index 839545c8b96a4c..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx +++ /dev/null @@ -1,137 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { FormEvent, useEffect, useState } from 'react'; -import { - EuiButton, - EuiPopover, - EuiPopoverFooter, - EuiPopoverTitle, - EuiSelectable, -} from '@elastic/eui'; -import { PopoverAnchorPosition } from '@elastic/eui/src/components/popover/popover'; -import { useValuesList } from '../../hooks/use_values_list'; -import { IIndexPattern } from '../../../../../../src/plugins/data/common'; -import { ESFilter } from '../../../../../../typings/elasticsearch'; - -interface Option { - id: string; - label: string; - checked?: 'on'; -} - -interface Props { - value?: string; - label: string; - indexPattern: IIndexPattern; - sourceField: string; - onChange: (val?: string) => void; - filters: ESFilter[]; - anchorPosition?: PopoverAnchorPosition; - time?: { from: string; to: string }; - forceOpen?: boolean; - button?: JSX.Element; -} - -export function FieldValueSelection({ - sourceField, - indexPattern, - value, - label, - filters, - button, - time, - forceOpen, - anchorPosition, - onChange: onSelectionChange, -}: Props) { - const [query, setQuery] = useState(''); - const { values, loading } = useValuesList({ indexPattern, query, sourceField, filters, time }); - - const [options, setOptions] = useState([]); - const [isPopoverOpen, setIsPopoverOpen] = useState(false); - - useEffect(() => { - setOptions( - (values ?? []).map((val) => ({ id: val, label: val, ...(value ? { checked: 'on' } : {}) })) - ); - }, [values]); - - const onButtonClick = () => { - setIsPopoverOpen(!isPopoverOpen); - }; - - const closePopover = () => { - setIsPopoverOpen(false); - }; - - const onChange = (options: Option[]) => { - setOptions(options); - }; - - const onValueChange = (evt: FormEvent) => { - setQuery((evt.target as HTMLInputElement).value); - }; - - const anchorButton = ( - - {label} - - ); - - return ( - - - {(list, search) => ( -
- {search} - {list} - - opt?.checked === 'on')} - onClick={() => { - const selected = options.find((opt) => opt?.checked === 'on'); - onSelectionChange(selected?.label); - setIsPopoverOpen(false); - }} - > - Apply - - -
- )} -
-
- ); -} diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx index b2c682dc58937f..198254df001b64 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx @@ -15,6 +15,7 @@ import { EuiSelectableOption, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { PopoverAnchorPosition } from '@elastic/eui/src/components/popover/popover'; export interface FieldValueSelectionProps { value?: string; @@ -23,6 +24,9 @@ export interface FieldValueSelectionProps { onChange: (val?: string) => void; values?: string[]; setQuery: Dispatch>; + anchorPosition?: PopoverAnchorPosition; + forceOpen?: boolean; + button?: JSX.Element; } const formatOptions = (values?: string[], value?: string): EuiSelectableOption[] => { @@ -38,6 +42,9 @@ export function FieldValueSelection({ loading, values, setQuery, + button, + forceOpen, + anchorPosition, onChange: onSelectionChange, }: FieldValueSelectionProps) { const [options, setOptions] = useState(formatOptions(values, value)); @@ -63,7 +70,7 @@ export function FieldValueSelection({ setQuery((evt.target as HTMLInputElement).value); }; - const button = ( + const anchorButton = ( void; + filters: ESFilter[]; + anchorPosition?: PopoverAnchorPosition; + time?: { from: string; to: string }; + forceOpen?: boolean; + button?: JSX.Element; } export function FieldValueSuggestions({ @@ -25,12 +32,17 @@ export function FieldValueSuggestions({ label, indexPattern, value, + filters, + button, + time, + forceOpen, + anchorPosition, onChange: onSelectionChange, }: FieldValueSuggestionsProps) { const [query, setQuery] = useState(''); const [debouncedValue, setDebouncedValue] = useState(''); - const { values, loading } = useValuesList({ indexPattern, query, sourceField }); + const { values, loading } = useValuesList({ indexPattern, query, sourceField, filters, time }); useDebounce( () => { @@ -48,6 +60,9 @@ export function FieldValueSuggestions({ setQuery={setDebouncedValue} loading={loading} value={value} + button={button} + forceOpen={forceOpen} + anchorPosition={anchorPosition} /> ); } diff --git a/x-pack/plugins/observability/public/hooks/use_values_list.ts b/x-pack/plugins/observability/public/hooks/use_values_list.ts index 0373f54cfe98ad..75ac16610e0f31 100644 --- a/x-pack/plugins/observability/public/hooks/use_values_list.ts +++ b/x-pack/plugins/observability/public/hooks/use_values_list.ts @@ -15,22 +15,22 @@ interface Props { sourceField: string; query?: string; indexPattern: IIndexPattern; - filters: ESFilter[]; + filters?: ESFilter[]; time?: { from: string; to: string }; } export const useValuesList = ({ sourceField, indexPattern, - query, + query = '', filters = [], time, -}: Props): { values: string[]; loading: boolean } => { +}: Props): { values: string[]; loading?: boolean } => { const { services: { data }, } = useKibana<{ data: DataPublicPluginStart }>(); - const { data: values, status } = useFetcher(() => { + const { data: values, loading } = useFetcher(() => { if (!sourceField || !indexPattern) { return []; } @@ -55,5 +55,5 @@ export const useValuesList = ({ }); }, [sourceField, query, time, data.autocomplete, indexPattern, filters]); - return { values: values as string[], loading: status === 'loading' || status === 'pending' }; + return { values: values as string[], loading }; }; diff --git a/x-pack/plugins/observability/public/utils/observability_Index_patterns.ts b/x-pack/plugins/observability/public/utils/observability_index_patterns.ts similarity index 100% rename from x-pack/plugins/observability/public/utils/observability_Index_patterns.ts rename to x-pack/plugins/observability/public/utils/observability_index_patterns.ts From 7af9e258c4bf46da4ec424b403eaa1fb06efa463 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Thu, 25 Mar 2021 10:45:00 +0100 Subject: [PATCH 27/53] fix types --- .../shared/exploratory_view/hooks/use_init_exploratory_view.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts index 9749e439926139..945d0ebb58b5db 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts @@ -13,7 +13,7 @@ import { ReportToDataTypeMap } from '../configurations/constants'; import { DataType, ObservabilityIndexPatterns, -} from '../../../../utils/observability_Index_patterns'; +} from '../../../../utils/observability_index_patterns'; export const useInitExploratoryView = (storage: IKbnUrlStateStorage) => { const { From 6c1a330c416966a64ac815d37014c72f46445eb2 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Thu, 25 Mar 2021 13:13:32 +0100 Subject: [PATCH 28/53] fix types --- .../app/RumDashboard/PageViewsTrend/index.tsx | 30 ++++++----- .../components/filter_label.tsx | 15 +++++- .../configurations/constants.ts | 2 +- .../exploratory_view/configurations/utils.ts | 50 +++++++++++++++++++ .../hooks/use_url_strorage.tsx | 21 +++----- .../series_editor/selected_filters.tsx | 29 +++++++++-- x-pack/plugins/observability/public/index.ts | 1 + 7 files changed, 110 insertions(+), 38 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx index c728e601b17991..83ce2bc0320397 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx @@ -13,20 +13,18 @@ import { EuiSpacer, EuiTitle, } from '@elastic/eui'; -import rison from 'rison-node'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { useFetcher } from '../../../../hooks/use_fetcher'; import { I18LABELS } from '../translations'; import { BreakdownFilter } from '../Breakdowns/BreakdownFilter'; import { PageViewsChart } from '../Charts/PageViewsChart'; import { BreakdownItem } from '../../../../../typings/ui_filters'; +import { createExploratoryViewUrl } from '../../../../../../observability/public'; export function PageViewsTrend() { const { urlParams, uiFilters } = useUrlParams(); - const { start, end, searchTerm } = urlParams; - - const { serviceName } = uiFilters; + const { start, end, rangeFrom, rangeTo, searchTerm, serviceName } = urlParams; const [breakdown, setBreakdown] = useState(null); @@ -55,13 +53,18 @@ export function PageViewsTrend() { [end, start, uiFilters, breakdown, searchTerm, serviceName] ); - const analyzeHref = { - [serviceName + '-kpi']: { - rt: 'kpi', - serviceName, - time: { from: start, to: end }, + const analyzeHref = createExploratoryViewUrl( + { + 'page-views': { + reportType: 'kpi', + reportDefinitions: { + 'service.name': serviceName as string, + }, + time: { from: rangeFrom!, to: rangeTo! }, + }, }, - }; + '' + ); return (
@@ -79,12 +82,7 @@ export function PageViewsTrend() { /> - + Analyze diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx index e4592b60de827b..f6e28c4ef038bd 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx @@ -18,9 +18,18 @@ interface Props { value: string; seriesId: string; negate: boolean; + definitionFilter?: boolean; removeFilter: (field: string, value: string, notVal: boolean) => void; } -export function FilterLabel({ label, seriesId, field, value, negate, removeFilter }: Props) { +export function FilterLabel({ + label, + seriesId, + field, + value, + negate, + removeFilter, + definitionFilter, +}: Props) { const FilterItem = injectI18n(esFilters.FilterItem); const { indexPattern } = useIndexPatternContext(); @@ -51,7 +60,9 @@ export function FilterLabel({ label, seriesId, field, value, negate, removeFilte removeFilter(field, value, false); }} onUpdate={(filterN: Filter) => { - if (filterN.meta.negate !== negate) { + if (definitionFilter) { + // FIXME handle this use case + } else if (filterN.meta.negate !== negate) { invertFilter({ field, value, negate }); } }} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts index c7864c8df52432..94ace1f2b9d131 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts @@ -45,7 +45,7 @@ export const DataViewLabels: Record = { upd: 'Uptime monitor duration', upp: 'Uptime pings', svl: 'APM Service latency', - kpi: 'Business KPI over time', + kpi: 'KPI over time', tpt: 'APM Service throughput', cpu: 'System CPU Usage', logs: 'Logs Frequency', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts new file mode 100644 index 00000000000000..bfa69e658750ab --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import rison from 'rison-node'; +import type { AllSeries, AllShortSeries } from '../hooks/use_url_strorage'; +import type { SeriesUrl } from '../types'; + +const METRIC_TYPE = 'mt'; +const REPORT_TYPE = 'rt'; +const SERIES_TYPE = 'st'; +const BREAK_DOWN = 'bd'; +const FILTERS = 'ft'; +const REPORT_DEFINITIONS = 'rdf'; + +export function convertToShortUrl(series: SeriesUrl) { + const { + metric, + seriesType, + reportType, + breakdown, + filters, + reportDefinitions, + ...restSeries + } = series; + + return { + [METRIC_TYPE]: metric, + [REPORT_TYPE]: reportType, + [SERIES_TYPE]: seriesType, + [BREAK_DOWN]: breakdown, + [FILTERS]: filters, + [REPORT_DEFINITIONS]: reportDefinitions, + ...restSeries, + }; +} + +export function createExploratoryViewUrl(allSeries: AllSeries, baseHref = '') { + const allSeriesIds = Object.keys(allSeries); + + const allShortSeries: AllShortSeries = {}; + + allSeriesIds.forEach((seriesKey) => { + allShortSeries[seriesKey] = convertToShortUrl(allSeries[seriesKey]); + }); + + return baseHref + `/app/observability/exploratory-view#?sr=${rison.encode(allShortSeries)}`; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx index d32d4d4bf3d0aa..cf8256a5f92cfe 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx @@ -7,7 +7,8 @@ import React, { createContext, useContext, Context } from 'react'; import { IKbnUrlStateStorage } from '../../../../../../../../src/plugins/kibana_utils/public'; -import { AppDataType, ReportViewTypeId, SeriesUrl, UrlFilter } from '../types'; +import type { AppDataType, ReportViewTypeId, SeriesUrl, UrlFilter } from '../types'; +import { convertToShortUrl } from '../configurations/utils'; export const UrlStorageContext = createContext(null); @@ -19,25 +20,14 @@ const REPORT_TYPE = 'rt'; const SERIES_TYPE = 'st'; const BREAK_DOWN = 'bd'; const FILTERS = 'ft'; +const REPORT_DEFINITIONS = 'rdf'; export function UrlStorageContextProvider({ children, storage }: React.FC) { return {children}; } -function convertToShortUrl(newValue: SeriesUrl): ShortUrlSeries { - const { metric, seriesType, reportType, breakdown, filters, ...restSeries } = newValue; - return { - [METRIC_TYPE]: metric, - [REPORT_TYPE]: reportType, - [SERIES_TYPE]: seriesType, - [BREAK_DOWN]: breakdown, - [FILTERS]: filters, - ...restSeries, - }; -} - function convertFromShortUrl(newValue: ShortUrlSeries): SeriesUrl { - const { mt, st, rt, bd, ft, time, ...restSeries } = newValue; + const { mt, st, rt, bd, ft, time, rdf, ...restSeries } = newValue; return { metric: mt, reportType: rt!, @@ -45,6 +35,7 @@ function convertFromShortUrl(newValue: ShortUrlSeries): SeriesUrl { breakdown: bd, filters: ft!, time: time!, + reportDefinitions: rdf, ...restSeries, }; } @@ -55,12 +46,12 @@ interface ShortUrlSeries { [SERIES_TYPE]?: string; [BREAK_DOWN]?: string; [FILTERS]?: UrlFilter[]; + [REPORT_DEFINITIONS]?: Record; time?: { to: string; from: string; }; dataType?: AppDataType; - reportDefinitions?: Record; } export type AllShortSeries = Record; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx index 2012d783b9dbd0..be8b1feb4d7236 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx @@ -26,19 +26,20 @@ export function SelectedFilters({ seriesId, isNew, series: dataSeries }: Props) const { labels } = dataSeries; - let filters: UrlFilter[] = []; - filters = (series.filters ?? []).concat(getFiltersFromDefs(reportDefinitions, dataSeries)); + const filters: UrlFilter[] = series.filters ?? []; + + let definitionFilters: UrlFilter[] = getFiltersFromDefs(reportDefinitions, dataSeries); // we don't want to display report definition filters in new series view if (seriesId === NEW_SERIES_KEY && isNew) { - filters = series.filters ?? []; + definitionFilters = []; } const { removeFilter } = useSeriesFilters({ seriesId }); const { indexPattern } = useIndexPatternContext(); - return filters.length > 0 && indexPattern ? ( + return (filters.length > 0 || definitionFilters.length > 0) && indexPattern ? ( {filters.map(({ field, values, notValues }) => ( @@ -69,6 +70,26 @@ export function SelectedFilters({ seriesId, isNew, series: dataSeries }: Props) ))} ))} + + {definitionFilters.map(({ field, values }) => ( + + {(values ?? []).map((val) => ( + + { + // FIXME handle this use case + }} + negate={false} + definitionFilter={true} + /> + + ))} + + ))} ) : null; diff --git a/x-pack/plugins/observability/public/index.ts b/x-pack/plugins/observability/public/index.ts index dfe454ccc7b871..57d16f451a8f18 100644 --- a/x-pack/plugins/observability/public/index.ts +++ b/x-pack/plugins/observability/public/index.ts @@ -40,3 +40,4 @@ export * from './typings'; export { useChartTheme } from './hooks/use_chart_theme'; export { useTheme } from './hooks/use_theme'; export { getApmTraceUrl } from './utils/get_apm_trace_url'; +export { createExploratoryViewUrl } from './components/shared/exploratory_view/configurations/utils'; From 3c670f21be277ddfa5192a18050436a39966e940 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Thu, 25 Mar 2021 19:30:54 +0100 Subject: [PATCH 29/53] fix test --- .../public/context/has_data_context.test.tsx | 9 ++++++++- .../observability/public/hooks/use_breadcrumbs.ts | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/observability/public/context/has_data_context.test.tsx b/x-pack/plugins/observability/public/context/has_data_context.test.tsx index 5e48860a9b0492..01655c0d7b2d77 100644 --- a/x-pack/plugins/observability/public/context/has_data_context.test.tsx +++ b/x-pack/plugins/observability/public/context/has_data_context.test.tsx @@ -17,12 +17,19 @@ import { HasData, ObservabilityFetchDataPlugins } from '../typings/fetch_overvie import { HasDataContextProvider } from './has_data_context'; import * as pluginContext from '../hooks/use_plugin_context'; import { PluginContextValue } from './plugin_context'; +import { Router } from 'react-router-dom'; +import { createMemoryHistory } from 'history'; const relativeStart = '2020-10-08T06:00:00.000Z'; const relativeEnd = '2020-10-08T07:00:00.000Z'; function wrapper({ children }: { children: React.ReactElement }) { - return {children}; + const history = createMemoryHistory(); + return ( + + {children} + + ); } function unregisterAll() { diff --git a/x-pack/plugins/observability/public/hooks/use_breadcrumbs.ts b/x-pack/plugins/observability/public/hooks/use_breadcrumbs.ts index 5ce71438d0a241..a354ac8a07f050 100644 --- a/x-pack/plugins/observability/public/hooks/use_breadcrumbs.ts +++ b/x-pack/plugins/observability/public/hooks/use_breadcrumbs.ts @@ -39,11 +39,11 @@ export const makeBaseBreadcrumb = (href: string, params?: any): EuiBreadcrumb => const crumbParams = { ...params }; delete crumbParams.statusFilter; - const query = stringify(crumbParams, true); + const query = stringify(crumbParams, { skipEmptyString: true, skipNull: true }); href += query === EMPTY_QUERY ? '' : query; } return { - text: i18n.translate('xpack.uptime.breadcrumbs.observability', { + text: i18n.translate('xpack.observability.breadcrumbs.observability', { defaultMessage: 'Observability', }), href, From d26f306d2532b4c094e2cf4ae0612376aecbd45e Mon Sep 17 00:00:00 2001 From: Shahzad Date: Thu, 25 Mar 2021 20:53:12 +0100 Subject: [PATCH 30/53] update test --- .../embeddable/embeddable.tsx | 24 ------------------- .../operations/definitions/percentile.tsx | 2 +- .../public/xy_visualization/expression.tsx | 8 +------ 3 files changed, 2 insertions(+), 32 deletions(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx index 29b18c502e2234..bfeb645d7bd5ff 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx @@ -45,9 +45,6 @@ import { isLensBrushEvent, isLensFilterEvent, isLensTableRowContextMenuClickEvent, - LensBrushEvent, - LensFilterEvent, - LensTableRowContextMenuEvent, } from '../../types'; import { IndexPatternsContract } from '../../../../../../src/plugins/data/public'; @@ -66,10 +63,6 @@ interface LensBaseEmbeddableInput extends EmbeddableInput { renderMode?: RenderMode; style?: React.CSSProperties; className?: string; - onBrushEnd?: (data: LensBrushEvent['data']) => void; - onLoad?: (isLoading: boolean) => void; - onFilter?: (data: LensFilterEvent['data']) => void; - onTableRowClick?: (data: LensTableRowContextMenuEvent['data']) => void; } export type LensByValueInput = { @@ -275,10 +268,6 @@ export class Embeddable inspectorAdapters?: Partial | undefined ) => { this.activeData = inspectorAdapters; - if (this.input.onLoad) { - // once onData$ is get's called from expression renderer, loading becomes false - this.input.onLoad(false); - } }; /** @@ -291,9 +280,6 @@ export class Embeddable if (!this.savedVis || !this.isInitialized) { return; } - if (this.input.onLoad) { - this.input.onLoad(true); - } const input = this.getInput(); render( { diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index d55f4944e247c4..0bf5c139e24033 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -24,7 +24,6 @@ import { HorizontalAlignment, ElementClickListener, BrushEndListener, - CurveType, } from '@elastic/charts'; import { I18nProvider } from '@kbn/i18n/react'; import { @@ -777,12 +776,7 @@ export function XYChart({ switch (seriesType) { case 'line': return ( - + ); case 'bar': case 'bar_stacked': From 43887f1a494610e6eac7c12e420dcdbed99ddf5c Mon Sep 17 00:00:00 2001 From: Shahzad Date: Fri, 26 Mar 2021 08:01:06 +0100 Subject: [PATCH 31/53] update types --- .../exploratory_view/configurations/kpi_trends_config.ts | 3 ++- .../configurations/performance_dist_config.ts | 5 +++-- .../configurations/service_latency_config.ts | 3 ++- .../configurations/service_throughput_config.ts | 3 ++- .../components/shared/exploratory_view/exploratory_view.tsx | 4 ++-- .../components/shared/exploratory_view/header/header.tsx | 6 +++--- .../exploratory_view/hooks/use_default_index_pattern.tsx | 4 ++-- .../exploratory_view/hooks/use_init_exploratory_view.ts | 4 ++-- .../shared/exploratory_view/hooks/use_url_strorage.tsx | 5 ++++- .../public/components/shared/exploratory_view/index.tsx | 4 ++-- .../exploratory_view/series_builder/series_builder.tsx | 2 +- .../series_editor/columns/filter_value_btn.tsx | 2 +- .../field_value_suggestions/field_value_selection.tsx | 2 +- 13 files changed, 27 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts index a6bda3e4f139e1..82ef2ea503a4b3 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { QueryContainer } from '@elastic/elasticsearch/api/types'; import { DataSeries } from '../types'; import { FieldLabels } from './constants'; @@ -44,7 +45,7 @@ export function getKPITrendsLensConfig({ seriesId }: Props): DataSeries { filters: [ { query: { match_phrase: { 'transaction.type': 'page-load' } }, - }, + } as QueryContainer, ], labels: { ...FieldLabels, 'service.name': 'Web Application' }, reportDefinitions: [ diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/performance_dist_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/performance_dist_config.ts index e349cf819b067e..835c82168ed869 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/performance_dist_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/performance_dist_config.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { QueryContainer } from '@elastic/elasticsearch/api/types'; import { DataSeries } from '../types'; import { FieldLabels } from './constants'; @@ -71,8 +72,8 @@ export function getPerformanceDistLensConfig({ seriesId }: Props): DataSeries { }, ], filters: [ - { query: { match_phrase: { 'transaction.type': 'page-load' } } }, - { query: { match_phrase: { 'processor.event': 'transaction' } } }, + { query: { match_phrase: { 'transaction.type': 'page-load' } } } as QueryContainer, + { query: { match_phrase: { 'processor.event': 'transaction' } } } as QueryContainer, ], labels: { ...FieldLabels, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts index a90dd2852e2bdf..75d7d1c0b541fb 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { QueryContainer } from '@elastic/elasticsearch/api/types'; import { DataSeries } from '../types'; import { FieldLabels } from './constants'; @@ -39,7 +40,7 @@ export function getServiceLatencyLensConfig({ seriesId }: Props): DataSeries { 'client.geo.country_name', 'user_agent.device.name', ], - filters: [{ query: { match_phrase: { 'transaction.type': 'request' } } }], + filters: [{ query: { match_phrase: { 'transaction.type': 'request' } } } as QueryContainer], labels: { ...FieldLabels }, reportDefinitions: [ { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts index 1ffacfd8a18ce6..5e7b61449846cc 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { QueryContainer } from '@elastic/elasticsearch/api/types'; import { DataSeries } from '../types'; import { FieldLabels } from './constants'; @@ -39,7 +40,7 @@ export function getServiceThroughputLensConfig({ seriesId }: Props): DataSeries 'client.geo.country_name', 'user_agent.device.name', ], - filters: [{ query: { match_phrase: { 'transaction.type': 'request' } } }], + filters: [{ query: { match_phrase: { 'transaction.type': 'request' } } } as QueryContainer], labels: { ...FieldLabels }, reportDefinitions: [ { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx index d1f3662d7a83a2..c94cdaa6148925 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx @@ -9,7 +9,7 @@ import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import { EuiLoadingSpinner, EuiPanel } from '@elastic/eui'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; -import { ObservabilityClientPluginsStart } from '../../../plugin'; +import { ObservabilityPublicPluginsStart } from '../../../plugin'; import { ExploratoryViewHeader } from './header/header'; import { SeriesEditor } from './series_editor/series_editor'; import { useUrlStorage } from './hooks/use_url_strorage'; @@ -21,7 +21,7 @@ import { TypedLensByValueInput } from '../../../../../lens/public'; export function ExploratoryView() { const { services: { lens }, - } = useKibana(); + } = useKibana(); const [lensAttributes, setLensAttributes] = useState( null diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx index daf9577f3fc1f5..25011e2713125c 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx @@ -9,8 +9,8 @@ import React from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { TypedLensByValueInput } from '../../../../../../lens/public'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; -import { ObservabilityClientPluginsStart } from '../../../../plugin'; -import { DataViewLabels, REPORT_TYPE } from '../configurations/constants'; +import { ObservabilityPublicPluginsStart } from '../../../../plugin'; +import { DataViewLabels } from '../configurations/constants'; import { useUrlStorage } from '../hooks/use_url_strorage'; interface Props { @@ -21,7 +21,7 @@ interface Props { export function ExploratoryViewHeader({ seriesId, lensAttributes }: Props) { const { services: { lens }, - } = useKibana(); + } = useKibana(); const { series } = useUrlStorage(seriesId); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_default_index_pattern.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_default_index_pattern.tsx index b41e07e75caa40..cc82f21016631b 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_default_index_pattern.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_default_index_pattern.tsx @@ -9,7 +9,7 @@ import React, { createContext, useContext, Context, useState, useEffect } from ' import { IIndexPattern } from '../../../../../../../../src/plugins/data/common'; import { AppDataType } from '../types'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; -import { ObservabilityClientPluginsStart } from '../../../../plugin'; +import { ObservabilityPublicPluginsStart } from '../../../../plugin'; import { ObservabilityIndexPatterns } from '../../../../utils/observability_index_patterns'; export interface IIndexPatternContext { @@ -36,7 +36,7 @@ export function IndexPatternContextProvider({ const { services: { data }, - } = useKibana(); + } = useKibana(); const loadIndexPattern = async (dataType: AppDataType) => { const obsvIndexP = new ObservabilityIndexPatterns(data); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts index 945d0ebb58b5db..9f462790e8d37f 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts @@ -7,7 +7,7 @@ import { useFetcher } from '../../../..'; import { IKbnUrlStateStorage } from '../../../../../../../../src/plugins/kibana_utils/public'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; -import { ObservabilityClientPluginsStart } from '../../../../plugin'; +import { ObservabilityPublicPluginsStart } from '../../../../plugin'; import { AllShortSeries } from './use_url_strorage'; import { ReportToDataTypeMap } from '../configurations/constants'; import { @@ -18,7 +18,7 @@ import { export const useInitExploratoryView = (storage: IKbnUrlStateStorage) => { const { services: { data }, - } = useKibana(); + } = useKibana(); const allSeriesKey = 'sr'; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx index cf8256a5f92cfe..8d76252f458185 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx @@ -22,7 +22,10 @@ const BREAK_DOWN = 'bd'; const FILTERS = 'ft'; const REPORT_DEFINITIONS = 'rdf'; -export function UrlStorageContextProvider({ children, storage }: React.FC) { +export function UrlStorageContextProvider({ + children, + storage, +}: ProviderProps & { children: JSX.Element }) { return {children}; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx index 355bef35b4496b..a5d9c88abe4e67 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx @@ -11,7 +11,7 @@ import { useHistory } from 'react-router-dom'; import { ThemeContext } from 'styled-components'; import { ExploratoryView } from './exploratory_view'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; -import { ObservabilityClientPluginsStart } from '../../../plugin'; +import { ObservabilityPublicPluginsStart } from '../../../plugin'; import { useBreadcrumbs } from '../../../hooks/use_breadcrumbs'; import { IndexPatternContextProvider } from './hooks/use_default_index_pattern'; import { @@ -35,7 +35,7 @@ export function ExploratoryViewPage() { const { services: { uiSettings, notifications }, - } = useKibana(); + } = useKibana(); const history = useHistory(); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx index 6955f2bf9fe78e..26db8178d163f3 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx @@ -9,7 +9,7 @@ import React, { useState } from 'react'; import { EuiButton, EuiBasicTable, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import styled from 'styled-components'; -import { AppDataType, ReportViewTypeId, ReportViewTypes, SeriesUrl, UrlFilter } from '../types'; +import { AppDataType, ReportViewTypeId, ReportViewTypes, SeriesUrl } from '../types'; import { DataTypesCol } from './columns/data_types_col'; import { ReportTypesCol } from './columns/report_types_col'; import { ReportDefinitionCol } from './columns/report_definition_col'; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx index 6f975ef3fffb87..e93bc3faeb5ed6 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx @@ -68,7 +68,7 @@ export function FilterValueButton({ ); const onNestedChange = (val?: string) => { - setFilter({ field: nestedField!, value: val }); + setFilter({ field: nestedField!, value: val! }); setIsOpen({ value: '', negate }); }; diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx index 198254df001b64..d47708037c936e 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx @@ -20,7 +20,7 @@ import { PopoverAnchorPosition } from '@elastic/eui/src/components/popover/popov export interface FieldValueSelectionProps { value?: string; label: string; - loading: boolean; + loading?: boolean; onChange: (val?: string) => void; values?: string[]; setQuery: Dispatch>; From 967888aad7fbc399dec798bbd5597c45090df5b4 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Fri, 26 Mar 2021 12:15:11 +0100 Subject: [PATCH 32/53] fix types --- .../configurations/default_configs.ts | 14 +-- .../configurations/kpi_trends_config.ts | 16 +--- .../configurations/lens_attributes.ts | 92 ++++++++++--------- .../configurations/performance_dist_config.ts | 14 +-- .../configurations/service_latency_config.ts | 12 +-- .../service_throughput_config.ts | 15 ++- .../exploratory_view/configurations/utils.ts | 14 ++- .../hooks/use_lens_attributes.ts | 25 ++--- .../hooks/use_url_strorage.tsx | 5 +- .../columns/report_breakdowns.tsx | 16 +--- .../columns/report_definition_col.tsx | 16 ++-- .../series_builder/columns/report_filters.tsx | 17 +--- .../columns/report_types_col.tsx | 5 +- .../series_builder/series_builder.tsx | 25 ++++- .../series_editor/columns/chart_types.tsx | 15 +-- .../columns/metric_selection.tsx | 5 +- .../series_editor/columns/series_filter.tsx | 8 +- .../series_editor/series_editor.tsx | 7 +- .../shared/exploratory_view/types.ts | 34 ++++--- x-pack/plugins/observability/tsconfig.json | 2 +- 20 files changed, 189 insertions(+), 168 deletions(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts index 71564df9297835..85d48ef638d448 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts @@ -16,26 +16,28 @@ import { getCPUUsageLensConfig } from './cpu_usage_config'; import { getMemoryUsageLensConfig } from './memory_usage_config'; import { getNetworkActivityLensConfig } from './network_activity_config'; import { getLogsFrequencyLensConfig } from './logs_frequency_config'; +import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns'; interface Props { reportType: keyof typeof ReportViewTypes; seriesId: string; + indexPattern: IIndexPattern; } -export const getDefaultConfigs = ({ reportType, seriesId }: Props) => { +export const getDefaultConfigs = ({ reportType, seriesId, indexPattern }: Props) => { switch (ReportViewTypes[reportType]) { case 'page-load-dist': - return getPerformanceDistLensConfig({ seriesId }); + return getPerformanceDistLensConfig({ seriesId, indexPattern }); case 'kpi-trends': - return getKPITrendsLensConfig({ seriesId }); + return getKPITrendsLensConfig({ seriesId, indexPattern }); case 'uptime-duration': return getMonitorDurationConfig({ seriesId }); case 'uptime-pings': return getMonitorPingsConfig({ seriesId }); case 'service-latency': - return getServiceLatencyLensConfig({ seriesId }); + return getServiceLatencyLensConfig({ seriesId, indexPattern }); case 'service-throughput': - return getServiceThroughputLensConfig({ seriesId }); + return getServiceThroughputLensConfig({ seriesId, indexPattern }); case 'cpu-usage': return getCPUUsageLensConfig({ seriesId }); case 'memory-usage': @@ -45,6 +47,6 @@ export const getDefaultConfigs = ({ reportType, seriesId }: Props) => { case 'logs-frequency': return getLogsFrequencyLensConfig({ seriesId }); default: - return getKPITrendsLensConfig({ seriesId }); + return getKPITrendsLensConfig({ seriesId, indexPattern }); } }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts index 82ef2ea503a4b3..4df4dfb4fe27ad 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts @@ -5,15 +5,11 @@ * 2.0. */ -import { QueryContainer } from '@elastic/elasticsearch/api/types'; -import { DataSeries } from '../types'; +import { ConfigProps, DataSeries } from '../types'; import { FieldLabels } from './constants'; +import { buildPhraseFilter } from './utils'; -interface Props { - seriesId: string; -} - -export function getKPITrendsLensConfig({ seriesId }: Props): DataSeries { +export function getKPITrendsLensConfig({ seriesId, indexPattern }: ConfigProps): DataSeries { return { id: seriesId, defaultSeriesType: 'bar_stacked', @@ -42,11 +38,7 @@ export function getKPITrendsLensConfig({ seriesId }: Props): DataSeries { 'client.geo.country_name', 'user_agent.device.name', ], - filters: [ - { - query: { match_phrase: { 'transaction.type': 'page-load' } }, - } as QueryContainer, - ], + filters: [buildPhraseFilter('transaction.type', 'page-load', indexPattern)], labels: { ...FieldLabels, 'service.name': 'Web Application' }, reportDefinitions: [ { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts index 66159ca5fa21c1..d47b455d4294f7 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts @@ -6,7 +6,13 @@ */ import { + CountIndexPatternColumn, + DateHistogramIndexPatternColumn, + LastValueIndexPatternColumn, + OperationType, PersistedIndexPatternLayer, + RangeIndexPatternColumn, + SeriesType, TypedLensByValueInput, XYState, } from '../../../../../../lens/public'; @@ -17,23 +23,26 @@ import { } from '../../../../../../../../src/plugins/data/common'; import { FieldLabels } from './constants'; import { DataSeries, UrlFilter } from '../types'; -import { ESFilter } from '../../../../../../../../typings/elasticsearch'; + +function getLayerReferenceName(layerId: string) { + return `indexpattern-datasource-layer-${layerId}`; +} export class LensAttributes { indexPattern: IIndexPattern; layers: Record; visualization: XYState; filters: UrlFilter[]; - seriesType: string; + seriesType: SeriesType; reportViewConfig: DataSeries; reportDefinitions: Record; constructor( indexPattern: IIndexPattern, reportViewConfig: DataSeries, - seriesType: string, - filters: UrlFilter[], - metricType?: string, + seriesType?: SeriesType, + filters?: UrlFilter[], + metricType?: OperationType, reportDefinitions?: Record ) { this.indexPattern = indexPattern; @@ -73,14 +82,14 @@ export class LensAttributes { } removeBreakdown() { - this.layers.layer1.columns['break-down-column'] = undefined; + delete this.layers.layer1.columns['break-down-column']; this.layers.layer1.columnOrder = ['x-axis-column', 'y-axis-column']; this.visualization.layers[0].splitAccessor = undefined; } - getNumberColumn(sourceField: string) { + getNumberColumn(sourceField: string): RangeIndexPatternColumn { return { sourceField, label: this.reportViewConfig.labels[sourceField], @@ -96,7 +105,7 @@ export class LensAttributes { }; } - getDateHistogramColumn(sourceField: string) { + getDateHistogramColumn(sourceField: string): DateHistogramIndexPatternColumn { return { sourceField, dataType: 'date', @@ -108,7 +117,28 @@ export class LensAttributes { }; } - getXAxis() { + getXAxis(): + | LastValueIndexPatternColumn + | DateHistogramIndexPatternColumn + | RangeIndexPatternColumn { + const { xAxisColumn } = this.reportViewConfig; + + const xAxisField = xAxisColumn.sourceField!; + + const fieldType = this.getFieldType(); + + if (fieldType === 'date') { + return this.getDateHistogramColumn(xAxisField); + } + if (fieldType === 'number') { + return this.getNumberColumn(xAxisField); + } + + // FIXME review my approach again + return this.getDateHistogramColumn(xAxisField); + } + + getFieldType() { const { xAxisColumn } = this.reportViewConfig; let xAxisField = xAxisColumn.sourceField; @@ -124,28 +154,8 @@ export class LensAttributes { const fieldMeta = this.indexPattern.fields.find((field) => field.name === xAxisField); - if (fieldMeta?.type === 'date') { - return this.getDateHistogramColumn(xAxisField); - } - if (fieldMeta?.type === 'number') { - return this.getNumberColumn(xAxisField); - } + return fieldMeta?.type; } - - if (xAxisColumn) - return { - label: 'Page load duration (Seconds)', - dataType: 'number', - operationType: 'range', - sourceField: 'transaction.duration.us', - isBucketed: true, - scale: 'interval', - params: { - type: 'histogram', - ranges: [{ from: 0, to: 1000, label: '' }], - maxBars: 'auto', - }, - }; } getMainYAxis() { @@ -153,11 +163,11 @@ export class LensAttributes { dataType: 'number', isBucketed: false, label: 'Count of records', - operationType: 'count', + operationType: 'count' as any, scale: 'ratio', sourceField: 'Records', ...this.reportViewConfig.yAxisColumn, - }; + } as CountIndexPatternColumn; } addLayer() { @@ -187,7 +197,7 @@ export class LensAttributes { accessors: ['y-axis-column'], layerId: 'layer1', seriesType: this.seriesType ?? 'line', - palette: this.reportViewConfig.palette ?? undefined, + palette: this.reportViewConfig.palette, yConfig: [{ forAccessor: 'y-axis-column', color: 'green' }], xAccessor: 'x-axis-column', }, @@ -205,10 +215,10 @@ export class LensAttributes { if (values?.length > 0) { if (values?.length > 1) { const multiFilter = buildPhrasesFilter(fieldMeta, values, this.indexPattern); - parsedFilters.push(multiFilter as ESFilter); + parsedFilters.push(multiFilter); } else { const filter = buildPhraseFilter(fieldMeta, values[0], this.indexPattern); - parsedFilters.push(filter as ESFilter); + parsedFilters.push(filter); } } @@ -216,11 +226,11 @@ export class LensAttributes { if (notValues?.length > 1) { const multiFilter = buildPhrasesFilter(fieldMeta, notValues, this.indexPattern); multiFilter.meta.negate = true; - parsedFilters.push(multiFilter as ESFilter); + parsedFilters.push(multiFilter); } else { const filter = buildPhraseFilter(fieldMeta, notValues[0], this.indexPattern); filter.meta.negate = true; - parsedFilters.push(filter as ESFilter); + parsedFilters.push(filter); } } }); @@ -230,18 +240,18 @@ export class LensAttributes { getJSON(): TypedLensByValueInput['attributes'] { return { - title: 'Prefilled from example app', + title: 'Prefilled from exploratory view app', description: '', visualizationType: 'lnsXY', references: [ { - id: this.indexPattern.id, + id: this.indexPattern.id!, name: 'indexpattern-datasource-current-indexpattern', type: 'index-pattern', }, { - id: this.indexPattern.id, - name: 'indexpattern-datasource-layer-layer1', + id: this.indexPattern.id!, + name: getLayerReferenceName('layer1'), type: 'index-pattern', }, ], diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/performance_dist_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/performance_dist_config.ts index 835c82168ed869..5ae9f3bb8c56a4 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/performance_dist_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/performance_dist_config.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { QueryContainer } from '@elastic/elasticsearch/api/types'; -import { DataSeries } from '../types'; +import { ConfigProps, DataSeries } from '../types'; import { FieldLabels } from './constants'; +import { buildPhraseFilter } from './utils'; export const TRANSACTION_DURATION = 'transaction.duration.us'; export const FCP_FIELD = 'transaction.marks.agent.firstContentfulPaint'; @@ -16,11 +16,7 @@ export const TBT_FIELD = 'transaction.experience.tbt'; export const FID_FIELD = 'transaction.experience.fid'; export const CLS_FIELD = 'transaction.experience.cls'; -interface Props { - seriesId: string; -} - -export function getPerformanceDistLensConfig({ seriesId }: Props): DataSeries { +export function getPerformanceDistLensConfig({ seriesId, indexPattern }: ConfigProps): DataSeries { return { id: seriesId ?? 'unique-key', reportType: 'page-load-dist', @@ -72,8 +68,8 @@ export function getPerformanceDistLensConfig({ seriesId }: Props): DataSeries { }, ], filters: [ - { query: { match_phrase: { 'transaction.type': 'page-load' } } } as QueryContainer, - { query: { match_phrase: { 'processor.event': 'transaction' } } } as QueryContainer, + buildPhraseFilter('transaction.type', 'page-load', indexPattern), + buildPhraseFilter('processor.event', 'transaction', indexPattern), ], labels: { ...FieldLabels, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts index 75d7d1c0b541fb..5de00a98169fbb 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts @@ -5,15 +5,11 @@ * 2.0. */ -import { QueryContainer } from '@elastic/elasticsearch/api/types'; -import { DataSeries } from '../types'; +import { ConfigProps, DataSeries } from '../types'; import { FieldLabels } from './constants'; +import { buildPhraseFilter } from './utils'; -interface Props { - seriesId: string; -} - -export function getServiceLatencyLensConfig({ seriesId }: Props): DataSeries { +export function getServiceLatencyLensConfig({ seriesId, indexPattern }: ConfigProps): DataSeries { return { id: seriesId, reportType: 'service-latency', @@ -40,7 +36,7 @@ export function getServiceLatencyLensConfig({ seriesId }: Props): DataSeries { 'client.geo.country_name', 'user_agent.device.name', ], - filters: [{ query: { match_phrase: { 'transaction.type': 'request' } } } as QueryContainer], + filters: [buildPhraseFilter('transaction.type', 'request', indexPattern)], labels: { ...FieldLabels }, reportDefinitions: [ { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts index 5e7b61449846cc..2eeb9090a84ec7 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts @@ -5,15 +5,14 @@ * 2.0. */ -import { QueryContainer } from '@elastic/elasticsearch/api/types'; -import { DataSeries } from '../types'; +import { ConfigProps, DataSeries } from '../types'; import { FieldLabels } from './constants'; +import { buildPhraseFilter } from './utils'; -interface Props { - seriesId: string; -} - -export function getServiceThroughputLensConfig({ seriesId }: Props): DataSeries { +export function getServiceThroughputLensConfig({ + seriesId, + indexPattern, +}: ConfigProps): DataSeries { return { id: seriesId, reportType: 'service-latency', @@ -40,7 +39,7 @@ export function getServiceThroughputLensConfig({ seriesId }: Props): DataSeries 'client.geo.country_name', 'user_agent.device.name', ], - filters: [{ query: { match_phrase: { 'transaction.type': 'request' } } } as QueryContainer], + filters: [buildPhraseFilter('transaction.type', 'request', indexPattern)], labels: { ...FieldLabels }, reportDefinitions: [ { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts index bfa69e658750ab..ad563db61f82a2 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts @@ -4,9 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import rison from 'rison-node'; +import rison, { RisonValue } from 'rison-node'; import type { AllSeries, AllShortSeries } from '../hooks/use_url_strorage'; import type { SeriesUrl } from '../types'; +import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns'; +import { esFilters } from '../../../../../../../../src/plugins/data/public'; const METRIC_TYPE = 'mt'; const REPORT_TYPE = 'rt'; @@ -46,5 +48,13 @@ export function createExploratoryViewUrl(allSeries: AllSeries, baseHref = '') { allShortSeries[seriesKey] = convertToShortUrl(allSeries[seriesKey]); }); - return baseHref + `/app/observability/exploratory-view#?sr=${rison.encode(allShortSeries)}`; + return ( + baseHref + + `/app/observability/exploratory-view#?sr=${rison.encode(allShortSeries as RisonValue)}` + ); +} + +export function buildPhraseFilter(field: string, value: any, indexPattern: IIndexPattern) { + const fieldMeta = indexPattern.fields.find((fieldT) => fieldT.name === field)!; + return esFilters.buildPhraseFilter(fieldMeta, value, indexPattern); } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts index 293d2df4f42d4f..f7022a6aa34130 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts @@ -46,24 +46,25 @@ export const useLensAttributes = ({ const { breakdown, seriesType, metric: metricType, reportType, reportDefinitions = {} } = series ?? {}; - const dataViewConfig = getDefaultConfigs({ - seriesId, - reportType, - }); - - const filters: UrlFilter[] = useMemo(() => { - return (series.filters ?? []).concat(getFiltersFromDefs(reportDefinitions, dataViewConfig)); - }, [series.filters, reportDefinitions, dataViewConfig]); - return useMemo(() => { if (!indexPattern || !reportType) { return null; } + const dataViewConfig = getDefaultConfigs({ + seriesId, + reportType, + indexPattern, + }); + + const filters: UrlFilter[] = (series.filters ?? []).concat( + getFiltersFromDefs(reportDefinitions, dataViewConfig) + ); + const lensAttributes = new LensAttributes( indexPattern, dataViewConfig, - seriesType!, + seriesType, filters, metricType, reportDefinitions @@ -78,10 +79,10 @@ export const useLensAttributes = ({ indexPattern, breakdown, seriesType, - filters, metricType, reportType, reportDefinitions, - dataViewConfig, + seriesId, + series.filters, ]); }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx index 8d76252f458185..c6ef99b38ea248 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx @@ -9,6 +9,7 @@ import React, { createContext, useContext, Context } from 'react'; import { IKbnUrlStateStorage } from '../../../../../../../../src/plugins/kibana_utils/public'; import type { AppDataType, ReportViewTypeId, SeriesUrl, UrlFilter } from '../types'; import { convertToShortUrl } from '../configurations/utils'; +import { OperationType, SeriesType } from '../../../../../../lens/public'; export const UrlStorageContext = createContext(null); @@ -44,9 +45,9 @@ function convertFromShortUrl(newValue: ShortUrlSeries): SeriesUrl { } interface ShortUrlSeries { - [METRIC_TYPE]?: string; + [METRIC_TYPE]?: OperationType; [REPORT_TYPE]?: ReportViewTypeId; - [SERIES_TYPE]?: string; + [SERIES_TYPE]?: SeriesType; [BREAK_DOWN]?: string; [FILTERS]?: UrlFilter[]; [REPORT_DEFINITIONS]?: Record; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.tsx index e22e34ab36ea5c..7667cea417a52a 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.tsx @@ -7,17 +7,9 @@ import React from 'react'; import { Breakdowns } from '../../series_editor/columns/breakdowns'; -import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_strorage'; -import { getDefaultConfigs } from '../../configurations/default_configs'; +import { NEW_SERIES_KEY } from '../../hooks/use_url_strorage'; +import { DataSeries } from '../../types'; -export function ReportBreakdowns() { - const { - series: { reportType }, - } = useUrlStorage(NEW_SERIES_KEY); - - const dataSeries = getDefaultConfigs({ - reportType: reportType!, - seriesId: NEW_SERIES_KEY, - }); - return ; +export function ReportBreakdowns({ dataViewSeries }: { dataViewSeries: DataSeries }) { + return ; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx index 58f47bd30aefb2..0691687e632889 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx @@ -8,22 +8,19 @@ import React from 'react'; import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { useIndexPatternContext } from '../../hooks/use_default_index_pattern'; -import { getDefaultConfigs } from '../../configurations/default_configs'; import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_strorage'; import { CustomReportField } from '../custom_report_field'; import FieldValueSuggestions from '../../../field_value_suggestions'; +import { DataSeries } from '../../types'; -export function ReportDefinitionCol() { +export function ReportDefinitionCol({ dataViewSeries }: { dataViewSeries: DataSeries }) { const { indexPattern } = useIndexPatternContext(); const { series, setSeries } = useUrlStorage(NEW_SERIES_KEY); - const { reportType, reportDefinitions: rtd = {} } = series; + const { reportDefinitions: rtd = {} } = series; - const { reportDefinitions, labels, filters } = getDefaultConfigs({ - reportType: reportType!, - seriesId: 'newSeries', - }); + const { reportDefinitions, labels, filters } = dataViewSeries; const onChange = (field: string, value?: string) => { if (!value) { @@ -60,7 +57,7 @@ export function ReportDefinitionCol() { label={labels[field]} sourceField={field} indexPattern={indexPattern} - value={reportDefinitions?.[field]} + value={rtd?.[field]} onChange={(val?: string) => onChange(field, val)} filters={(filters ?? []).map(({ query }) => query)} time={series.time} @@ -73,6 +70,9 @@ export function ReportDefinitionCol() { iconType="cross" color="hollow" onClick={() => onRemove(field)} + iconOnClick={() => onRemove(field)} + iconOnClickAriaLabel={''} + onClickAriaLabel={''} > {rtd?.[field]} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx index d08614e0675b7f..903dda549aeee3 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx @@ -8,22 +8,13 @@ import React from 'react'; import { SeriesFilter } from '../../series_editor/columns/series_filter'; import { NEW_SERIES_KEY } from '../../hooks/use_url_strorage'; -import { getDefaultConfigs } from '../../configurations/default_configs'; -import { ReportViewTypeId } from '../../types'; - -interface Props { - reportType: ReportViewTypeId; -} -export function ReportFilters({ reportType }: Props) { - const dataSeries = getDefaultConfigs({ - reportType: reportType!, - seriesId: NEW_SERIES_KEY, - }); +import { DataSeries } from '../../types'; +export function ReportFilters({ dataViewSeries }: { dataViewSeries: DataSeries }) { return ( diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx index e125c0e1de0920..32bb439315263d 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; -import { ReportViewTypeId } from '../../types'; +import { ReportViewTypeId, SeriesUrl } from '../../types'; import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_strorage'; interface Props { @@ -34,8 +34,7 @@ export function ReportTypesCol({ reportTypes }: Props) { if (reportType === selectedReportType) { setSeries(NEW_SERIES_KEY, { dataType: restSeries.dataType, - reportType: undefined, - }); + } as SeriesUrl); } else { setSeries(NEW_SERIES_KEY, { ...restSeries, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx index 26db8178d163f3..70c0bdf045bbf0 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx @@ -16,6 +16,8 @@ import { ReportDefinitionCol } from './columns/report_definition_col'; import { ReportFilters } from './columns/report_filters'; import { ReportBreakdowns } from './columns/report_breakdowns'; import { NEW_SERIES_KEY, useUrlStorage } from '../hooks/use_url_strorage'; +import { useIndexPatternContext } from '../hooks/use_default_index_pattern'; +import { getDefaultConfigs } from '../configurations/default_configs'; export const ReportTypes: Record> = { synthetics: [ @@ -50,6 +52,16 @@ export function SeriesBuilder() { const [isFlyoutVisible, setIsFlyoutVisible] = useState(!!series.dataType); + const { indexPattern } = useIndexPatternContext(); + + const getDataViewSeries = () => { + return getDefaultConfigs({ + indexPattern, + reportType: reportType!, + seriesId: NEW_SERIES_KEY, + }); + }; + const columns = [ { name: 'DataType', @@ -66,18 +78,25 @@ export function SeriesBuilder() { { name: 'Definition', width: '30%', - render: (val: string) => (reportType ? : null), + render: (val: string) => + reportType && indexPattern ? ( + + ) : null, }, { name: 'Filters', width: '25%', - render: (val: string) => (reportType ? : null), + render: (val: string) => + reportType && indexPattern ? : null, }, { name: 'Breakdowns', width: '25%', field: 'id', - render: (val: string) => (reportType ? : null), + render: (val: string) => + reportType && indexPattern ? ( + + ) : null, }, ]; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx index 768251005c316a..85504bf0df5be8 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx @@ -11,22 +11,23 @@ import { EuiButton, EuiButtonGroup, EuiButtonIcon, EuiPopover } from '@elastic/e import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; import { useKibana } from '../../../../../../../../../src/plugins/kibana_react/public'; -import { ObservabilityClientPluginsStart } from '../../../../../plugin'; +import { ObservabilityPublicPluginsStart } from '../../../../../plugin'; import { useFetcher } from '../../../../..'; import { useUrlStorage } from '../../hooks/use_url_strorage'; +import { SeriesType } from '../../../../../../../lens/public'; export function SeriesChartTypes({ seriesId, defaultChartType, }: { seriesId: string; - defaultChartType: string; + defaultChartType: SeriesType; }) { const { series, setSeries, allSeries } = useUrlStorage(seriesId); const seriesType = series?.seriesType ?? defaultChartType; - const onChange = (value: string) => { + const onChange = (value: SeriesType) => { Object.keys(allSeries).forEach((seriesKey) => { const seriesN = allSeries[seriesKey]; @@ -45,8 +46,8 @@ export function SeriesChartTypes({ } export interface XYChartTypesProps { - onChange: (value: string) => void; - value: string; + onChange: (value: SeriesType) => void; + value: SeriesType; label?: string; includeChartTypes?: string[]; excludeChartTypes?: string[]; @@ -63,7 +64,7 @@ function XYChartTypes({ const { services: { lens }, - } = useKibana(); + } = useKibana(); const { data = [] } = useFetcher(() => lens.getXyVisTypes(), [lens]); @@ -121,7 +122,7 @@ function XYChartTypes({ }))} idSelected={value} onChange={(valueN: string) => { - onChange(valueN); + onChange(valueN as SeriesType); }} /> diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.tsx index 934a291b1f9948..eaf4a48879781d 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.tsx @@ -8,6 +8,7 @@ import React, { useState } from 'react'; import { EuiButton, EuiButtonGroup, EuiPopover } from '@elastic/eui'; import { useUrlStorage } from '../../hooks/use_url_strorage'; +import { OperationType } from '../../../../../../../lens/public'; const toggleButtons = [ { @@ -41,7 +42,7 @@ export function MetricSelection({ const [toggleIdSelected, setToggleIdSelected] = useState(series?.metric ?? 'avg'); - const onChange = (optionId: string) => { + const onChange = (optionId: OperationType) => { setToggleIdSelected(optionId); Object.keys(allSeries).forEach((seriesKey) => { @@ -69,7 +70,7 @@ export function MetricSelection({ legend="Chart metric group" options={toggleButtons} idSelected={toggleIdSelected} - onChange={(id) => onChange(id)} + onChange={(id) => onChange(id as OperationType)} /> ); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx index 3c59e1fc29e021..6109322052d93b 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx @@ -30,13 +30,13 @@ interface Props { export interface Field { label: string; field: string; - nested: string; + nested?: string; } export function SeriesFilter({ series, isNew, seriesId, defaultFilters = [] }: Props) { const [isPopoverVisible, setIsPopoverVisible] = useState(false); - const [selectedField, setSelectedField] = useState(null); + const [selectedField, setSelectedField] = useState(); const options = defaultFilters.map((field) => { if (typeof field === 'string') { @@ -90,14 +90,14 @@ export function SeriesFilter({ series, isNew, seriesId, defaultFilters = [] }: P label={selectedField.label} nestedField={selectedField.nested} goBack={() => { - setSelectedField(null); + setSelectedField(undefined); }} /> ) : null; const closePopover = () => { setIsPopoverVisible(false); - setSelectedField(null); + setSelectedField(undefined); }; return ( diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx index 59f318a76e436c..7603e944a5bdd4 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx @@ -16,6 +16,7 @@ import { NEW_SERIES_KEY, useUrlStorage } from '../hooks/use_url_strorage'; import { getDefaultConfigs } from '../configurations/default_configs'; import { DatePickerCol } from './columns/date_picker_col'; import { RemoveSeries } from './columns/remove_series'; +import { useIndexPatternContext } from '../hooks/use_default_index_pattern'; export function SeriesEditor() { const { allSeries, firstSeriesId } = useUrlStorage(); @@ -83,11 +84,15 @@ export function SeriesEditor() { const allSeriesKeys = Object.keys(allSeries); const items: DataSeries[] = []; + + const { indexPattern } = useIndexPatternContext(); + allSeriesKeys.forEach((seriesKey) => { const series = allSeries[seriesKey]; - if (series.reportType) { + if (series.reportType && indexPattern) { items.push( getDefaultConfigs({ + indexPattern, reportType: series.reportType, seriesId: seriesKey, }) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts index 41d885de5edb41..8331962be0b236 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts @@ -5,14 +5,17 @@ * 2.0. */ +import { PaletteOutput } from 'src/plugins/charts/public'; import { - AvgIndexPatternColumn, - CountIndexPatternColumn, - DateHistogramIndexPatternColumn, LastValueIndexPatternColumn, + DateHistogramIndexPatternColumn, + SeriesType, + OperationType, + IndexPatternColumn, } from '../../../../../lens/public'; -import { ESFilter } from '../../../../../../../typings/elasticsearch'; +import { PersistableFilter } from '../../../../../lens/common'; +import { IIndexPattern } from '../../../../../../../src/plugins/data/common/index_patterns'; export const ReportViewTypes = { pld: 'page-load-dist', @@ -45,19 +48,17 @@ export interface DataSeries { reportType: ReportViewType; id: string; xAxisColumn: Partial | Partial; - yAxisColumn: - | Partial - | Partial - | Partial; + yAxisColumn: Partial; + breakdowns: string[]; - defaultSeriesType: string; + defaultSeriesType: SeriesType; defaultFilters: Array; - seriesTypes: string[]; - filters?: ESFilter[]; + seriesTypes: SeriesType[]; + filters?: PersistableFilter[]; reportDefinitions: ReportDefinition[]; labels: Record; metricType: boolean; - palette?: Record; + palette?: PaletteOutput; } export interface SeriesUrl { @@ -67,9 +68,9 @@ export interface SeriesUrl { }; breakdown?: string; filters?: UrlFilter[]; - seriesType?: string; + seriesType?: SeriesType; reportType: ReportViewTypeId; - metric?: string; + metric?: OperationType; dataType?: AppDataType; reportDefinitions?: Record; } @@ -80,4 +81,9 @@ export interface UrlFilter { notValues?: string[]; } +export interface ConfigProps { + seriesId: string; + indexPattern: IIndexPattern; +} + export type AppDataType = 'synthetics' | 'rum' | 'logs' | 'metrics' | 'apm'; diff --git a/x-pack/plugins/observability/tsconfig.json b/x-pack/plugins/observability/tsconfig.json index 1293be4fbb51d5..eeb216802387e9 100644 --- a/x-pack/plugins/observability/tsconfig.json +++ b/x-pack/plugins/observability/tsconfig.json @@ -7,7 +7,7 @@ "declaration": true, "declarationMap": true }, - "include": ["common/**/*", "public/**/*", "server/**/*", "typings/**/*"], + "include": ["common/**/*", "public/**/*", "server/**/*", "typings/**/*", "../../../typings/**/*"], "references": [ { "path": "../../../src/core/tsconfig.json" }, { "path": "../../../src/plugins/data/tsconfig.json" }, From 9acecc133d127d5375bc6ac72915c9196a459870 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Fri, 26 Mar 2021 14:52:23 +0100 Subject: [PATCH 33/53] fix issue --- .../configurations/kpi_trends_config.ts | 5 +++- .../configurations/lens_attributes.ts | 24 +++++++++---------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts index 4df4dfb4fe27ad..c40a71960a5ac4 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts @@ -38,7 +38,10 @@ export function getKPITrendsLensConfig({ seriesId, indexPattern }: ConfigProps): 'client.geo.country_name', 'user_agent.device.name', ], - filters: [buildPhraseFilter('transaction.type', 'page-load', indexPattern)], + filters: [ + buildPhraseFilter('transaction.type', 'page-load', indexPattern), + buildPhraseFilter('processor.event', 'transaction', indexPattern), + ], labels: { ...FieldLabels, 'service.name': 'Web Application' }, reportDefinitions: [ { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts index d47b455d4294f7..e4ca4a7ab44f8e 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts @@ -121,21 +121,17 @@ export class LensAttributes { | LastValueIndexPatternColumn | DateHistogramIndexPatternColumn | RangeIndexPatternColumn { - const { xAxisColumn } = this.reportViewConfig; - - const xAxisField = xAxisColumn.sourceField!; - - const fieldType = this.getFieldType(); + const { type: fieldType, name: fieldName } = this.getFieldType()!; if (fieldType === 'date') { - return this.getDateHistogramColumn(xAxisField); + return this.getDateHistogramColumn(fieldName); } if (fieldType === 'number') { - return this.getNumberColumn(xAxisField); + return this.getNumberColumn(fieldName); } // FIXME review my approach again - return this.getDateHistogramColumn(xAxisField); + return this.getDateHistogramColumn(fieldName); } getFieldType() { @@ -148,13 +144,15 @@ export class LensAttributes { const customField = rdf.find(({ field }) => field === xAxisField); - if (customField && this.reportDefinitions[xAxisField]) { - xAxisField = this.reportDefinitions[xAxisField]; + if (customField) { + if (this.reportDefinitions[xAxisField]) { + xAxisField = this.reportDefinitions[xAxisField]; + } else if (customField.options?.[0].field) { + xAxisField = customField.options?.[0].field; + } } - const fieldMeta = this.indexPattern.fields.find((field) => field.name === xAxisField); - - return fieldMeta?.type; + return this.indexPattern.fields.find((field) => field.name === xAxisField); } } From 211716b797f49b9aa523f3a8810fa24b2feab8fc Mon Sep 17 00:00:00 2001 From: Shahzad Date: Fri, 26 Mar 2021 16:26:12 +0100 Subject: [PATCH 34/53] fix types --- .../shared/exploratory_view/configurations/cpu_usage_config.ts | 3 ++- .../exploratory_view/configurations/memory_usage_config.ts | 3 ++- .../exploratory_view/configurations/monitor_duration_config.ts | 3 ++- .../exploratory_view/configurations/network_activity_config.ts | 3 ++- .../exploratory_view/configurations/service_latency_config.ts | 3 ++- .../configurations/service_throughput_config.ts | 3 ++- .../series_builder/columns/report_definition_col.tsx | 1 + .../shared/field_value_suggestions/field_value_selection.tsx | 3 +++ .../public/components/shared/field_value_suggestions/index.tsx | 3 +++ 9 files changed, 19 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/cpu_usage_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/cpu_usage_config.ts index 0e68b691ca2467..7c210ebc7a0034 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/cpu_usage_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/cpu_usage_config.ts @@ -7,6 +7,7 @@ import { DataSeries } from '../types'; import { FieldLabels } from './constants'; +import { OperationType } from '../../../../../../lens/public'; interface Props { seriesId: string; @@ -22,7 +23,7 @@ export function getCPUUsageLensConfig({ seriesId }: Props): DataSeries { sourceField: '@timestamp', }, yAxisColumn: { - operationType: 'avg', + operationType: 'avg' as OperationType, sourceField: 'system.cpu.user.pct', label: 'CPU Usage %', }, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/memory_usage_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/memory_usage_config.ts index 7bb18b0dbf3169..247e4909fd6067 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/memory_usage_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/memory_usage_config.ts @@ -7,6 +7,7 @@ import { DataSeries } from '../types'; import { FieldLabels } from './constants'; +import { OperationType } from '../../../../../../lens/public'; interface Props { seriesId: string; @@ -22,7 +23,7 @@ export function getMemoryUsageLensConfig({ seriesId }: Props): DataSeries { sourceField: '@timestamp', }, yAxisColumn: { - operationType: 'avg', + operationType: 'avg' as OperationType, sourceField: 'system.memory.used.pct', label: 'Memory Usage %', }, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_duration_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_duration_config.ts index 9e20a8ac92050e..c07cb5e12b806b 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_duration_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_duration_config.ts @@ -7,6 +7,7 @@ import { DataSeries } from '../types'; import { FieldLabels } from './constants'; +import { OperationType } from '../../../../../../lens/public'; interface Props { seriesId: string; @@ -22,7 +23,7 @@ export function getMonitorDurationConfig({ seriesId }: Props): DataSeries { sourceField: '@timestamp', }, yAxisColumn: { - operationType: 'avg', + operationType: 'avg' as OperationType, sourceField: 'monitor.duration.us', label: 'Monitor duration (ms)', }, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/network_activity_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/network_activity_config.ts index 3d9cb5f67c5129..8e317697822a76 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/network_activity_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/network_activity_config.ts @@ -7,6 +7,7 @@ import { DataSeries } from '../types'; import { FieldLabels } from './constants'; +import { OperationType } from '../../../../../../lens/public'; interface Props { seriesId: string; @@ -22,7 +23,7 @@ export function getNetworkActivityLensConfig({ seriesId }: Props): DataSeries { sourceField: '@timestamp', }, yAxisColumn: { - operationType: 'avg', + operationType: 'avg' as OperationType, sourceField: 'system.memory.used.pct', }, metricType: true, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts index 5de00a98169fbb..3883e82f93c419 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts @@ -8,6 +8,7 @@ import { ConfigProps, DataSeries } from '../types'; import { FieldLabels } from './constants'; import { buildPhraseFilter } from './utils'; +import { OperationType } from '../../../../../../lens/public'; export function getServiceLatencyLensConfig({ seriesId, indexPattern }: ConfigProps): DataSeries { return { @@ -19,7 +20,7 @@ export function getServiceLatencyLensConfig({ seriesId, indexPattern }: ConfigPr sourceField: '@timestamp', }, yAxisColumn: { - operationType: 'avg', + operationType: 'avg' as OperationType, sourceField: 'transaction.duration.us', label: 'Latency', }, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts index 2eeb9090a84ec7..f0b0626a27c44d 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts @@ -8,6 +8,7 @@ import { ConfigProps, DataSeries } from '../types'; import { FieldLabels } from './constants'; import { buildPhraseFilter } from './utils'; +import { OperationType } from '../../../../../../lens/public'; export function getServiceThroughputLensConfig({ seriesId, @@ -22,7 +23,7 @@ export function getServiceThroughputLensConfig({ sourceField: '@timestamp', }, yAxisColumn: { - operationType: 'avg', + operationType: 'avg' as OperationType, sourceField: 'transaction.duration.us', label: 'Throughput', }, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx index 0691687e632889..9bde3759a5756c 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx @@ -61,6 +61,7 @@ export function ReportDefinitionCol({ dataViewSeries }: { dataViewSeries: DataSe onChange={(val?: string) => onChange(field, val)} filters={(filters ?? []).map(({ query }) => query)} time={series.time} + width={200} /> {rtd?.[field] && ( diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx index d47708037c936e..a44aab2da85be7 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx @@ -27,6 +27,7 @@ export interface FieldValueSelectionProps { anchorPosition?: PopoverAnchorPosition; forceOpen?: boolean; button?: JSX.Element; + width?: number; } const formatOptions = (values?: string[], value?: string): EuiSelectableOption[] => { @@ -43,6 +44,7 @@ export function FieldValueSelection({ values, setQuery, button, + width, forceOpen, anchorPosition, onChange: onSelectionChange, @@ -72,6 +74,7 @@ export function FieldValueSelection({ const anchorButton = ( ); } From 635be8e39a988acd7f2d73ac8645070408add511 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Fri, 26 Mar 2021 17:54:17 +0100 Subject: [PATCH 35/53] test --- .../exploratory_view.test.tsx.snap | 590 ++++++++++++++++++ .../components/empty_view.tsx | 8 +- .../configurations/lens_attributes.ts | 1 + .../exploratory_view.test.tsx | 18 + .../shared/exploratory_view/rtl_helpers.tsx | 188 ++++++ 5 files changed, 802 insertions(+), 3 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/__snapshots__/exploratory_view.test.tsx.snap create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/__snapshots__/exploratory_view.test.tsx.snap b/x-pack/plugins/observability/public/components/shared/exploratory_view/__snapshots__/exploratory_view.test.tsx.snap new file mode 100644 index 00000000000000..c6b07e90c32aaa --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/__snapshots__/exploratory_view.test.tsx.snap @@ -0,0 +1,590 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ExploratoryView renders a label for button 1`] = ` +Object { + "asFragment": [Function], + "baseElement": .c0 { + text-align: center; + opacity: 0.4; + height: 550pz; +} + + +
+
+
+
+
+

+ Exploratory view +

+
+
+
+ +
+
+
+
+ Visulization +
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + +
+
+
+ + Name + +
+
+
+ + Filter + +
+
+
+ + Breakdowns + +
+
+
+ +
+
+
+ +
+ Time +
+
+
+
+
+ + Actions + +
+
+
+ + No series found, please add a series. + +
+
+
+
+
+
+ +
+
+
+
+ , + "container":
+
+
+
+
+

+ Exploratory view +

+
+
+
+ +
+
+
+
+ Visulization +
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + +
+
+
+ + Name + +
+
+
+ + Filter + +
+
+
+ + Breakdowns + +
+
+
+ +
+
+
+ +
+ Time +
+
+
+
+
+ + Actions + +
+
+
+ + No series found, please add a series. + +
+
+
+
+
+
+ +
+
+
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "history": Object { + "action": "POP", + "block": [Function], + "canGo": [Function], + "createHref": [Function], + "entries": Array [ + Object { + "hash": "", + "key": "ngqvzn", + "pathname": "/", + "search": "", + "state": undefined, + }, + ], + "go": [Function], + "goBack": [Function], + "goForward": [Function], + "index": 0, + "length": 1, + "listen": [Function], + "location": Object { + "hash": "", + "key": "ngqvzn", + "pathname": "/", + "search": "", + "state": undefined, + }, + "push": [Function], + "replace": [Function], + }, + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx index 2d4d42733ba5b5..0f7505d9bbef5f 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx @@ -8,16 +8,18 @@ import React from 'react'; import { EuiImage } from '@elastic/eui'; import styled from 'styled-components'; -import { usePluginContext } from '../../../../hooks/use_plugin_context'; +import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; export function EmptyView() { - const { core } = usePluginContext(); + const { + services: { http }, + } = useKibana(); return ( ); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts index e4ca4a7ab44f8e..88299e4d0f1bbd 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts @@ -185,6 +185,7 @@ export class LensAttributes { valueLabels: 'hide', fittingFunction: 'Linear', // fittingFunction: 'None', + axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true }, gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx new file mode 100644 index 00000000000000..f2bcdd7e3cee0c --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from './rtl_helpers'; +import { ExploratoryView } from './exploratory_view'; + +describe('ExploratoryView', () => { + it('renders a label for button', async () => { + const wrapper = render(); + + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx new file mode 100644 index 00000000000000..b7c2685f132496 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx @@ -0,0 +1,188 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { of } from 'rxjs'; +import React, { ReactElement } from 'react'; +import { stringify } from 'query-string'; +import { render as reactTestLibRender, RenderOptions } from '@testing-library/react'; +import { Router } from 'react-router-dom'; +import { createMemoryHistory, History } from 'history'; +import { CoreStart } from 'kibana/public'; +import { I18nProvider } from '@kbn/i18n/react'; +import { coreMock } from 'src/core/public/mocks'; +import { configure } from '@testing-library/dom'; +import { + KibanaServices, + KibanaContextProvider, +} from '../../../../../../../src/plugins/kibana_react/public'; +import { ObservabilityPublicPluginsStart } from '../../../plugin'; +import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; +import { lensPluginMock } from '../../../../../lens/public/mocks'; +import { IndexPatternContextProvider } from './hooks/use_default_index_pattern'; +import { UrlStorageContextProvider } from './hooks/use_url_strorage'; +import { + withNotifyOnErrors, + createKbnUrlStateStorage, +} from '../../../../../../../src/plugins/kibana_utils/public'; + +interface KibanaProps { + services?: KibanaServices; +} + +export interface KibanaProviderOptions { + core?: Partial & ExtraCore; + kibanaProps?: KibanaProps; +} + +interface MockKibanaProviderProps extends KibanaProviderOptions { + children: ReactElement; + history: History; +} + +interface MockRouterProps extends MockKibanaProviderProps { + history?: History; +} + +type Url = + | string + | { + path: string; + queryParams: Record; + }; + +interface RenderRouterOptions extends KibanaProviderOptions { + history?: History; + renderOptions?: Omit; + url?: Url; +} + +function getSetting(key: string): T { + return ('MMM D, YYYY @ HH:mm:ss.SSS' as unknown) as T; +} + +function setSetting$(key: string): T { + return (of('MMM D, YYYY @ HH:mm:ss.SSS') as unknown) as T; +} + +/* default mock core */ +const defaultCore = coreMock.createStart(); +const mockCore: () => Partial = () => { + const core: Partial = { + ...defaultCore, + application: { + ...defaultCore.application, + getUrlForApp: () => '/app/uptime', + navigateToUrl: jest.fn(), + capabilities: { + ...defaultCore.application.capabilities, + uptime: { + 'alerting:save': true, + configureSettings: true, + save: true, + show: true, + }, + }, + }, + uiSettings: { + ...defaultCore.uiSettings, + get: getSetting, + get$: setSetting$, + }, + lens: lensPluginMock.createStartContract(), + }; + + return core; +}; + +/* Mock Provider Components */ +export function MockKibanaProvider({ + children, + core, + history, + kibanaProps, +}: MockKibanaProviderProps) { + const coreOptions = { + ...mockCore(), + ...core, + }; + + const { uiSettings, notifications } = coreOptions; + + const kbnUrlStateStorage = createKbnUrlStateStorage({ + history, + useHash: uiSettings!.get('state:storeInSessionStorage'), + ...withNotifyOnErrors(notifications!.toasts), + }); + + return ( + + + + + + {children} + + + + + + ); +} + +export function MockRouter({ + children, + core, + history = createMemoryHistory(), + kibanaProps, +}: MockRouterProps) { + return ( + + + {children} + + + ); +} +configure({ testIdAttribute: 'data-test-subj' }); + +/* Custom react testing library render */ +export function render( + ui: ReactElement, + { + history = createMemoryHistory(), + core, + kibanaProps, + renderOptions, + url, + }: RenderRouterOptions = {} +) { + if (url) { + history = getHistoryFromUrl(url); + } + + return { + ...reactTestLibRender( + + {ui} + , + renderOptions + ), + history, + }; +} + +const getHistoryFromUrl = (url: Url) => { + if (typeof url === 'string') { + return createMemoryHistory({ + initialEntries: [url], + }); + } + + return createMemoryHistory({ + initialEntries: [url.path + stringify(url.queryParams)], + }); +}; From dbb98c74a03bbead991c7c3b02386644284d8cb5 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 29 Mar 2021 17:04:03 +0200 Subject: [PATCH 36/53] update tests --- .../lens/public/{mocks.ts => mocks.tsx} | 8 +- .../exploratory_view.test.tsx.snap | 590 ------------------ .../components/filter_label.test.tsx | 119 ++++ .../components/filter_label.tsx | 43 +- .../data/elasticsearch_fieldnames.ts | 143 +++++ .../configurations/data/index_pattern.json | 11 + .../configurations/data/sample_attribute.ts | 74 +++ .../configurations/lens_attributes.test.ts | 387 ++++++++++++ .../configurations/lens_attributes.ts | 36 +- .../configurations/performance_dist_config.ts | 1 + .../exploratory_view.test.tsx | 87 ++- .../exploratory_view/header/header.test.tsx | 51 ++ .../hooks/use_default_index_pattern.tsx | 6 +- .../hooks/use_init_exploratory_view.ts | 7 +- .../shared/exploratory_view/index.tsx | 12 +- .../shared/exploratory_view/rtl_helpers.tsx | 161 ++++- .../columns/data_types_col.test.tsx | 58 ++ .../series_builder/columns/data_types_col.tsx | 2 +- .../columns/report_breakdowns.test.tsx | 75 +++ .../columns/report_definition_col.test.tsx | 75 +++ .../columns/report_filters.test.tsx | 28 + .../columns/report_types_col.test.tsx | 65 ++ .../columns/report_types_col.tsx | 8 +- .../series_date_picker/index.tsx | 12 +- .../series_date_picker.test.tsx | 76 +++ .../columns/filter_expanded.test.tsx | 75 +++ .../series_editor/columns/filter_expanded.tsx | 8 +- .../columns/filter_value_btn.test.tsx | 231 +++++++ .../columns/filter_value_btn.tsx | 26 +- .../columns/metric_selection.test.tsx | 112 ++++ .../series_editor/selected_filters.test.tsx | 33 + .../public/hooks/use_quick_time_ranges.tsx | 22 + .../public/hooks/use_values_list.ts | 4 +- .../utils/observability_index_patterns.ts | 7 +- x-pack/plugins/observability/tsconfig.json | 9 +- 35 files changed, 1975 insertions(+), 687 deletions(-) rename x-pack/plugins/lens/public/{mocks.ts => mocks.tsx} (68%) delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/__snapshots__/exploratory_view.test.tsx.snap create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.test.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/data/elasticsearch_fieldnames.ts create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/data/index_pattern.json create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/data/sample_attribute.ts create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.test.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.test.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.test.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.test.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.test.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.test.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/series_date_picker.test.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.test.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.test.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.test.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.test.tsx create mode 100644 x-pack/plugins/observability/public/hooks/use_quick_time_ranges.tsx diff --git a/x-pack/plugins/lens/public/mocks.ts b/x-pack/plugins/lens/public/mocks.tsx similarity index 68% rename from x-pack/plugins/lens/public/mocks.ts rename to x-pack/plugins/lens/public/mocks.tsx index fd1e38db242a83..743846d81213c0 100644 --- a/x-pack/plugins/lens/public/mocks.ts +++ b/x-pack/plugins/lens/public/mocks.tsx @@ -5,16 +5,20 @@ * 2.0. */ +import React from 'react'; import { LensPublicStart } from '.'; +import { visualizationTypes } from './xy_visualization/types'; export type Start = jest.Mocked; const createStartContract = (): Start => { const startContract: Start = { - EmbeddableComponent: jest.fn(() => null), + EmbeddableComponent: jest.fn(() => { + return Lens Embeddable Component; + }), canUseEditor: jest.fn(() => true), navigateToPrefilledEditor: jest.fn(), - getXyVisTypes: jest.fn(), + getXyVisTypes: jest.fn().mockReturnValue(new Promise(() => visualizationTypes)), }; return startContract; }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/__snapshots__/exploratory_view.test.tsx.snap b/x-pack/plugins/observability/public/components/shared/exploratory_view/__snapshots__/exploratory_view.test.tsx.snap deleted file mode 100644 index c6b07e90c32aaa..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/__snapshots__/exploratory_view.test.tsx.snap +++ /dev/null @@ -1,590 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ExploratoryView renders a label for button 1`] = ` -Object { - "asFragment": [Function], - "baseElement": .c0 { - text-align: center; - opacity: 0.4; - height: 550pz; -} - - -
-
-
-
-
-

- Exploratory view -

-
-
-
- -
-
-
-
- Visulization -
-
-
-
-
-
-
-
-
-
-
- - - - - - - - - - - - - - - - -
-
-
- - Name - -
-
-
- - Filter - -
-
-
- - Breakdowns - -
-
-
- -
-
-
- -
- Time -
-
-
-
-
- - Actions - -
-
-
- - No series found, please add a series. - -
-
-
-
-
-
- -
-
-
-
- , - "container":
-
-
-
-
-

- Exploratory view -

-
-
-
- -
-
-
-
- Visulization -
-
-
-
-
-
-
-
-
-
-
- - - - - - - - - - - - - - - - -
-
-
- - Name - -
-
-
- - Filter - -
-
-
- - Breakdowns - -
-
-
- -
-
-
- -
- Time -
-
-
-
-
- - Actions - -
-
-
- - No series found, please add a series. - -
-
-
-
-
-
- -
-
-
-
, - "debug": [Function], - "findAllByAltText": [Function], - "findAllByDisplayValue": [Function], - "findAllByLabelText": [Function], - "findAllByPlaceholderText": [Function], - "findAllByRole": [Function], - "findAllByTestId": [Function], - "findAllByText": [Function], - "findAllByTitle": [Function], - "findByAltText": [Function], - "findByDisplayValue": [Function], - "findByLabelText": [Function], - "findByPlaceholderText": [Function], - "findByRole": [Function], - "findByTestId": [Function], - "findByText": [Function], - "findByTitle": [Function], - "getAllByAltText": [Function], - "getAllByDisplayValue": [Function], - "getAllByLabelText": [Function], - "getAllByPlaceholderText": [Function], - "getAllByRole": [Function], - "getAllByTestId": [Function], - "getAllByText": [Function], - "getAllByTitle": [Function], - "getByAltText": [Function], - "getByDisplayValue": [Function], - "getByLabelText": [Function], - "getByPlaceholderText": [Function], - "getByRole": [Function], - "getByTestId": [Function], - "getByText": [Function], - "getByTitle": [Function], - "history": Object { - "action": "POP", - "block": [Function], - "canGo": [Function], - "createHref": [Function], - "entries": Array [ - Object { - "hash": "", - "key": "ngqvzn", - "pathname": "/", - "search": "", - "state": undefined, - }, - ], - "go": [Function], - "goBack": [Function], - "goForward": [Function], - "index": 0, - "length": 1, - "listen": [Function], - "location": Object { - "hash": "", - "key": "ngqvzn", - "pathname": "/", - "search": "", - "state": undefined, - }, - "push": [Function], - "replace": [Function], - }, - "queryAllByAltText": [Function], - "queryAllByDisplayValue": [Function], - "queryAllByLabelText": [Function], - "queryAllByPlaceholderText": [Function], - "queryAllByRole": [Function], - "queryAllByTestId": [Function], - "queryAllByText": [Function], - "queryAllByTitle": [Function], - "queryByAltText": [Function], - "queryByDisplayValue": [Function], - "queryByLabelText": [Function], - "queryByPlaceholderText": [Function], - "queryByRole": [Function], - "queryByTestId": [Function], - "queryByText": [Function], - "queryByTitle": [Function], - "rerender": [Function], - "unmount": [Function], -} -`; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.test.tsx new file mode 100644 index 00000000000000..708793bbba0006 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.test.tsx @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { fireEvent, screen, waitFor } from '@testing-library/react'; +import { mockIndexPattern, render } from '../rtl_helpers'; +import { buildFilterLabel, FilterLabel } from './filter_label'; +import * as useSeriesHook from '../hooks/use_series_filters'; + +describe('FilterLabel', function () { + const invertFilter = jest.fn(); + jest.spyOn(useSeriesHook, 'useSeriesFilters').mockReturnValue({ + invertFilter, + } as any); + it('should render properly', async function () { + render( + + ); + + await waitFor(() => { + screen.getByText('elastic-co'); + screen.getByText(/web application:/i); + screen.getByTitle('Delete Web Application: elastic-co'); + screen.getByRole('button', { + name: /delete web application: elastic-co/i, + }); + }); + }); + + it('should invert/delete filter', async function () { + const removeFilter = jest.fn(); + render( + + ); + + await waitFor(() => { + fireEvent.click(screen.getByLabelText('Filter actions')); + }); + + fireEvent.click(screen.getByTestId('deleteFilter')); + expect(removeFilter).toHaveBeenCalledTimes(1); + expect(removeFilter).toHaveBeenCalledWith('service.name', 'elastic-co', false); + + fireEvent.click(screen.getByTestId('negateFilter')); + expect(invertFilter).toHaveBeenCalledTimes(1); + expect(invertFilter).toHaveBeenCalledWith({ + field: 'service.name', + negate: false, + value: 'elastic-co', + }); + }); + + it('should display invert filter', async function () { + render( + + ); + + await waitFor(() => { + screen.getByText('elastic-co'); + screen.getByText(/web application:/i); + screen.getByTitle('Delete NOT Web Application: elastic-co'); + screen.getByRole('button', { + name: /delete not web application: elastic-co/i, + }); + }); + }); + + it('should build filter meta', function () { + expect( + buildFilterLabel({ + field: 'user_agent.name', + label: 'Browser family', + indexPattern: mockIndexPattern, + value: 'Firefox', + negate: false, + }) + ).toEqual({ + meta: { + alias: null, + disabled: false, + index: 'apm-*', + key: 'Browser family', + negate: false, + type: 'phrase', + value: 'Firefox', + }, + query: { + match_phrase: { + 'user_agent.name': 'Firefox', + }, + }, + }); + }); +}); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx index f6e28c4ef038bd..c4d1c6958fd8cb 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { injectI18n } from '@kbn/i18n/react'; -import { esFilters, Filter } from '../../../../../../../../src/plugins/data/public'; +import { esFilters, Filter, IndexPattern } from '../../../../../../../../src/plugins/data/public'; import { useIndexPatternContext } from '../hooks/use_default_index_pattern'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { useSeriesFilters } from '../hooks/use_series_filters'; @@ -21,6 +21,32 @@ interface Props { definitionFilter?: boolean; removeFilter: (field: string, value: string, notVal: boolean) => void; } +export function buildFilterLabel({ + field, + value, + label, + indexPattern, + negate, +}: { + label: string; + value: string; + negate: boolean; + field: string; + indexPattern: IndexPattern; +}) { + const indexField = indexPattern.getFieldByName(field)!; + + const filter = esFilters.buildPhraseFilter(indexField, value, indexPattern); + + filter.meta.value = value; + filter.meta.key = label; + filter.meta.alias = null; + filter.meta.negate = negate; + filter.meta.disabled = false; + filter.meta.type = 'phrase'; + + return filter; +} export function FilterLabel({ label, seriesId, @@ -34,16 +60,7 @@ export function FilterLabel({ const { indexPattern } = useIndexPatternContext(); - const indexField = indexPattern.fields.find((fd) => fd.name === field)!; - - const filter = esFilters.buildPhraseFilter(indexField, value, indexPattern); - - filter.meta.value = value; - filter.meta.key = label; - filter.meta.alias = null; - filter.meta.negate = negate; - filter.meta.disabled = false; - filter.meta.type = 'phrase'; + const filter = buildFilterLabel({ field, value, label, indexPattern, negate }); const { invertFilter } = useSeriesFilters({ seriesId }); @@ -51,7 +68,7 @@ export function FilterLabel({ services: { uiSettings }, } = useKibana(); - return ( + return indexPattern ? ( - ); + ) : null; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/data/elasticsearch_fieldnames.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/data/elasticsearch_fieldnames.ts new file mode 100644 index 00000000000000..8a3218d64a01ee --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/data/elasticsearch_fieldnames.ts @@ -0,0 +1,143 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const CLOUD = 'cloud'; +export const CLOUD_AVAILABILITY_ZONE = 'cloud.availability_zone'; +export const CLOUD_PROVIDER = 'cloud.provider'; +export const CLOUD_REGION = 'cloud.region'; +export const CLOUD_MACHINE_TYPE = 'cloud.machine.type'; + +export const SERVICE = 'service'; +export const SERVICE_NAME = 'service.name'; +export const SERVICE_ENVIRONMENT = 'service.environment'; +export const SERVICE_FRAMEWORK_NAME = 'service.framework.name'; +export const SERVICE_FRAMEWORK_VERSION = 'service.framework.version'; +export const SERVICE_LANGUAGE_NAME = 'service.language.name'; +export const SERVICE_LANGUAGE_VERSION = 'service.language.version'; +export const SERVICE_RUNTIME_NAME = 'service.runtime.name'; +export const SERVICE_RUNTIME_VERSION = 'service.runtime.version'; +export const SERVICE_NODE_NAME = 'service.node.name'; +export const SERVICE_VERSION = 'service.version'; + +export const AGENT = 'agent'; +export const AGENT_NAME = 'agent.name'; +export const AGENT_VERSION = 'agent.version'; + +export const URL_FULL = 'url.full'; +export const HTTP_REQUEST_METHOD = 'http.request.method'; +export const HTTP_RESPONSE_STATUS_CODE = 'http.response.status_code'; +export const USER_ID = 'user.id'; +export const USER_AGENT_ORIGINAL = 'user_agent.original'; +export const USER_AGENT_NAME = 'user_agent.name'; +export const USER_AGENT_VERSION = 'user_agent.version'; + +export const DESTINATION_ADDRESS = 'destination.address'; + +export const OBSERVER_HOSTNAME = 'observer.hostname'; +export const OBSERVER_VERSION_MAJOR = 'observer.version_major'; +export const OBSERVER_LISTENING = 'observer.listening'; +export const PROCESSOR_EVENT = 'processor.event'; + +export const TRANSACTION_DURATION = 'transaction.duration.us'; +export const TRANSACTION_DURATION_HISTOGRAM = 'transaction.duration.histogram'; +export const TRANSACTION_TYPE = 'transaction.type'; +export const TRANSACTION_RESULT = 'transaction.result'; +export const TRANSACTION_NAME = 'transaction.name'; +export const TRANSACTION_ID = 'transaction.id'; +export const TRANSACTION_SAMPLED = 'transaction.sampled'; +export const TRANSACTION_BREAKDOWN_COUNT = 'transaction.breakdown.count'; +export const TRANSACTION_PAGE_URL = 'transaction.page.url'; +// for transaction metrics +export const TRANSACTION_ROOT = 'transaction.root'; + +export const EVENT_OUTCOME = 'event.outcome'; + +export const TRACE_ID = 'trace.id'; + +export const SPAN_DURATION = 'span.duration.us'; +export const SPAN_TYPE = 'span.type'; +export const SPAN_SUBTYPE = 'span.subtype'; +export const SPAN_SELF_TIME_SUM = 'span.self_time.sum.us'; +export const SPAN_ACTION = 'span.action'; +export const SPAN_NAME = 'span.name'; +export const SPAN_ID = 'span.id'; +export const SPAN_DESTINATION_SERVICE_RESOURCE = 'span.destination.service.resource'; +export const SPAN_DESTINATION_SERVICE_RESPONSE_TIME_COUNT = + 'span.destination.service.response_time.count'; + +export const SPAN_DESTINATION_SERVICE_RESPONSE_TIME_SUM = + 'span.destination.service.response_time.sum.us'; + +// Parent ID for a transaction or span +export const PARENT_ID = 'parent.id'; + +export const ERROR_GROUP_ID = 'error.grouping_key'; +export const ERROR_CULPRIT = 'error.culprit'; +export const ERROR_LOG_LEVEL = 'error.log.level'; +export const ERROR_LOG_MESSAGE = 'error.log.message'; +export const ERROR_EXC_MESSAGE = 'error.exception.message'; // only to be used in es queries, since error.exception is now an array +export const ERROR_EXC_HANDLED = 'error.exception.handled'; // only to be used in es queries, since error.exception is now an array +export const ERROR_EXC_TYPE = 'error.exception.type'; +export const ERROR_PAGE_URL = 'error.page.url'; + +// METRICS +export const METRIC_SYSTEM_FREE_MEMORY = 'system.memory.actual.free'; +export const METRIC_SYSTEM_TOTAL_MEMORY = 'system.memory.total'; +export const METRIC_SYSTEM_CPU_PERCENT = 'system.cpu.total.norm.pct'; +export const METRIC_PROCESS_CPU_PERCENT = 'system.process.cpu.total.norm.pct'; +export const METRIC_CGROUP_MEMORY_LIMIT_BYTES = 'system.process.cgroup.memory.mem.limit.bytes'; +export const METRIC_CGROUP_MEMORY_USAGE_BYTES = 'system.process.cgroup.memory.mem.usage.bytes'; + +export const METRIC_JAVA_HEAP_MEMORY_MAX = 'jvm.memory.heap.max'; +export const METRIC_JAVA_HEAP_MEMORY_COMMITTED = 'jvm.memory.heap.committed'; +export const METRIC_JAVA_HEAP_MEMORY_USED = 'jvm.memory.heap.used'; +export const METRIC_JAVA_NON_HEAP_MEMORY_MAX = 'jvm.memory.non_heap.max'; +export const METRIC_JAVA_NON_HEAP_MEMORY_COMMITTED = 'jvm.memory.non_heap.committed'; +export const METRIC_JAVA_NON_HEAP_MEMORY_USED = 'jvm.memory.non_heap.used'; +export const METRIC_JAVA_THREAD_COUNT = 'jvm.thread.count'; +export const METRIC_JAVA_GC_COUNT = 'jvm.gc.count'; +export const METRIC_JAVA_GC_TIME = 'jvm.gc.time'; + +export const LABEL_NAME = 'labels.name'; + +export const HOST = 'host'; +export const HOST_NAME = 'host.hostname'; +export const HOST_OS_PLATFORM = 'host.os.platform'; +export const CONTAINER_ID = 'container.id'; +export const KUBERNETES = 'kubernetes'; +export const POD_NAME = 'kubernetes.pod.name'; + +export const CLIENT_GEO_COUNTRY_ISO_CODE = 'client.geo.country_iso_code'; + +// RUM Labels +export const TRANSACTION_URL = 'url.full'; +export const CLIENT_GEO = 'client.geo'; +export const USER_AGENT_DEVICE = 'user_agent.device.name'; +export const USER_AGENT_OS = 'user_agent.os.name'; + +export const TRANSACTION_TIME_TO_FIRST_BYTE = 'transaction.marks.agent.timeToFirstByte'; +export const TRANSACTION_DOM_INTERACTIVE = 'transaction.marks.agent.domInteractive'; + +export const FCP_FIELD = 'transaction.marks.agent.firstContentfulPaint'; +export const LCP_FIELD = 'transaction.marks.agent.largestContentfulPaint'; +export const TBT_FIELD = 'transaction.experience.tbt'; +export const FID_FIELD = 'transaction.experience.fid'; +export const CLS_FIELD = 'transaction.experience.cls'; + +export const PROFILE_ID = 'profile.id'; +export const PROFILE_DURATION = 'profile.duration'; +export const PROFILE_TOP_ID = 'profile.top.id'; +export const PROFILE_STACK = 'profile.stack'; + +export const PROFILE_SAMPLES_COUNT = 'profile.samples.count'; +export const PROFILE_CPU_NS = 'profile.cpu.ns'; +export const PROFILE_WALL_US = 'profile.wall.us'; + +export const PROFILE_ALLOC_OBJECTS = 'profile.alloc_objects.count'; +export const PROFILE_ALLOC_SPACE = 'profile.alloc_space.bytes'; +export const PROFILE_INUSE_OBJECTS = 'profile.inuse_objects.count'; +export const PROFILE_INUSE_SPACE = 'profile.inuse_space.bytes'; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/data/index_pattern.json b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/data/index_pattern.json new file mode 100644 index 00000000000000..31fec1fe8d4f46 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/data/index_pattern.json @@ -0,0 +1,11 @@ +{ + "attributes": { + "fieldFormatMap": "{\"client.bytes\":{\"id\":\"bytes\"},\"client.nat.port\":{\"id\":\"string\"},\"client.port\":{\"id\":\"string\"},\"destination.bytes\":{\"id\":\"bytes\"},\"destination.nat.port\":{\"id\":\"string\"},\"destination.port\":{\"id\":\"string\"},\"event.duration\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"nanoseconds\",\"outputFormat\":\"asMilliseconds\",\"outputPrecision\":1}},\"event.sequence\":{\"id\":\"string\"},\"event.severity\":{\"id\":\"string\"},\"http.request.body.bytes\":{\"id\":\"bytes\"},\"http.request.bytes\":{\"id\":\"bytes\"},\"http.response.body.bytes\":{\"id\":\"bytes\"},\"http.response.bytes\":{\"id\":\"bytes\"},\"http.response.status_code\":{\"id\":\"string\"},\"log.syslog.facility.code\":{\"id\":\"string\"},\"log.syslog.priority\":{\"id\":\"string\"},\"network.bytes\":{\"id\":\"bytes\"},\"package.size\":{\"id\":\"string\"},\"process.parent.pgid\":{\"id\":\"string\"},\"process.parent.pid\":{\"id\":\"string\"},\"process.parent.ppid\":{\"id\":\"string\"},\"process.parent.thread.id\":{\"id\":\"string\"},\"process.pgid\":{\"id\":\"string\"},\"process.pid\":{\"id\":\"string\"},\"process.ppid\":{\"id\":\"string\"},\"process.thread.id\":{\"id\":\"string\"},\"server.bytes\":{\"id\":\"bytes\"},\"server.nat.port\":{\"id\":\"string\"},\"server.port\":{\"id\":\"string\"},\"source.bytes\":{\"id\":\"bytes\"},\"source.nat.port\":{\"id\":\"string\"},\"source.port\":{\"id\":\"string\"},\"system.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.memory.actual.free\":{\"id\":\"bytes\"},\"system.memory.total\":{\"id\":\"bytes\"},\"system.process.cgroup.memory.mem.limit.bytes\":{\"id\":\"bytes\"},\"system.process.cgroup.memory.mem.usage.bytes\":{\"id\":\"bytes\"},\"system.process.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.process.memory.rss.bytes\":{\"id\":\"bytes\"},\"system.process.memory.size\":{\"id\":\"bytes\"},\"url.port\":{\"id\":\"string\"}}", + "fields": "[{\"name\":\"@timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.build.original\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.ephemeral_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"as.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"as.organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"as.organization.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"as.organization.name\"}}},{\"name\":\"child.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.as.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.as.organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.as.organization.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"client.as.organization.name\"}}},{\"name\":\"client.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.nat.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.nat.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.registered_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.subdomain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.top_level_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.full_name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"client.user.full_name\"}}},{\"name\":\"client.user.group.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"client.user.name\"}}},{\"name\":\"client.user.roles\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.account.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.account.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.availability_zone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.image.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.instance.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.instance.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.machine.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.project.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.project.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.provider\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.region\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.service.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clr.gc.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clr.gc.gen0size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clr.gc.gen1size\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clr.gc.gen2size\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clr.gc.gen3size\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"code_signature.exists\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"code_signature.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"code_signature.subject_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"code_signature.trusted\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"code_signature.valid\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.image.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.image.tag\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.runtime\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.as.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.as.organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.as.organization.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"destination.as.organization.name\"}}},{\"name\":\"destination.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.nat.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.nat.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.registered_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.subdomain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.top_level_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.full_name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"destination.user.full_name\"}}},{\"name\":\"destination.user.group.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"destination.user.name\"}}},{\"name\":\"destination.user.roles\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.code_signature.exists\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.code_signature.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.code_signature.subject_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.code_signature.trusted\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.code_signature.valid\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.hash.sha512\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.pe.architecture\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.pe.company\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.pe.description\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.pe.file_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.pe.imphash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.pe.original_file_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dll.pe.product\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.answers.class\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.answers.data\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.answers.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.answers.ttl\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.answers.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.header_flags\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.op_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.class\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.registered_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.subdomain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.top_level_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.resolved_ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.response_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ecs.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.culprit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.exception.code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.exception.handled\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.exception.message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"error.exception.module\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.exception.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.grouping_key\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.log.level\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.log.logger_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.log.message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"error.log.param_message\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"error.stack_trace\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.stack_trace.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"error.stack_trace\"}}},{\"name\":\"error.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.action\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.category\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.created\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.dataset\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.duration\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.end\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.ingested\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.kind\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.module\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.original\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.outcome\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.provider\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.reason\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.reference\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.risk_score\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.risk_score_norm\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.sequence\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.severity\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.start\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.timezone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.url\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.accessed\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.attributes\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.code_signature.exists\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.code_signature.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.code_signature.subject_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.code_signature.trusted\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.code_signature.valid\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.created\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.ctime\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.device\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.directory\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.drive_letter\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.extension\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.gid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.group\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.hash.sha512\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.inode\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.mime_type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.mode\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.mtime\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.owner\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.path.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"file.path\"}}},{\"name\":\"file.pe.architecture\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.pe.company\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.pe.description\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.pe.file_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.pe.imphash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.pe.original_file_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.pe.product\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.target_path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.target_path.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"file.target_path\"}}},{\"name\":\"file.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.uid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.alternative_names\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.issuer.common_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.issuer.country\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.issuer.distinguished_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.issuer.locality\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.issuer.organization\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.issuer.organizational_unit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.issuer.state_or_province\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.not_after\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.not_before\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.public_key_algorithm\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.public_key_curve\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.public_key_exponent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.public_key_size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.serial_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.signature_algorithm\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.subject.common_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.subject.country\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.subject.distinguished_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.subject.locality\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.subject.organization\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.subject.organizational_unit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.subject.state_or_province\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.x509.version_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.goroutines\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.active\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.allocated\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.frees\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.idle\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.mallocs\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.objects\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.total\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.cpu_fraction\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.next_gc_limit\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.total_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.total_pause.ns\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.system.obtained\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.system.released\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.system.stack\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.system.total\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"group.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hash.sha512\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.architecture\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.containerized\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.build\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.codename\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.family\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.full\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.full.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"host.os.full\"}}},{\"name\":\"host.os.kernel\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"host.os.name\"}}},{\"name\":\"host.os.platform\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.uptime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.full_name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"host.user.full_name\"}}},{\"name\":\"host.user.group.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"host.user.name\"}}},{\"name\":\"host.user.roles\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.body.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.body.content\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.body.content.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"http.request.body.content\"}}},{\"name\":\"http.request.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.method\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.mime_type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.referrer\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.body.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.body.content\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.body.content.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"http.response.body.content\"}}},{\"name\":\"http.response.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.finished\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.mime_type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.status_code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"interface.alias\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"interface.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"interface.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.gc.alloc\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.gc.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.gc.time\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.heap.committed\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.heap.max\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.heap.pool.committed\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.heap.pool.max\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.heap.pool.used\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.heap.used\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.non_heap.committed\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.non_heap.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.memory.non_heap.used\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jvm.thread.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.image\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.deployment.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.namespace\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.uid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.replicaset.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.statefulset.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.city\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.company\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.country_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.customer_email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.customer_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.customer_tier\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.env\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.events_encoded\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.events_failed\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.events_original\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.events_published\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.foo\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.git_rev\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.in_eu\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.ip\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.kibana_uuid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.lang\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.lorem\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.multi-line\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.plugin\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.productId\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.request_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.served_from_cache\",\"type\":\"conflict\",\"esTypes\":[\"boolean\",\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false,\"conflictDescriptions\":{\"boolean\":[\"apm-8.0.0-transaction-000001\"],\"keyword\":[\"apm-8.0.0-transaction-000002\"]}},{\"name\":\"labels.taskType\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.this-is-a-very-long-tag-name-without-any-spaces\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.u\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"labels.worker\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.file.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.level\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.logger\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.origin.file.line\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.origin.file.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.origin.function\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.original\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.syslog.facility.code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.syslog.facility.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.syslog.priority\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.syslog.severity.code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.syslog.severity.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"metricset.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"metricset.period\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.application\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.community_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.direction\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.forwarded_ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.iana_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.inner.vlan.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.inner.vlan.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.protocol\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.transport\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.vlan.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.vlan.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nodejs.eventloop.delay.avg.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nodejs.eventloop.delay.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nodejs.handles.active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nodejs.memory.arrayBuffers.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nodejs.memory.external.bytes\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nodejs.memory.heap.allocated.bytes\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nodejs.memory.heap.used.bytes\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nodejs.requests.active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.egress.interface.alias\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.egress.interface.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.egress.interface.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.egress.vlan.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.egress.vlan.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.egress.zone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.ingress.interface.alias\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.ingress.interface.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.ingress.interface.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.ingress.vlan.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.ingress.vlan.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.ingress.zone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.listening\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.family\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.full\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.full.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"observer.os.full\"}}},{\"name\":\"observer.os.kernel\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"observer.os.name\"}}},{\"name\":\"observer.os.platform\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.product\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.serial_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.vendor\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.version_major\",\"type\":\"number\",\"esTypes\":[\"byte\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"organization.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"organization.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"organization.name\"}}},{\"name\":\"os.family\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.full\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.full.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"os.full\"}}},{\"name\":\"os.kernel\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"os.name\"}}},{\"name\":\"os.platform\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.architecture\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.build_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.checksum\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.description\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.install_scope\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.installed\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.license\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.reference\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"package.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"parent.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"pe.architecture\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"pe.company\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"pe.description\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"pe.file_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"pe.imphash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"pe.original_file_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"pe.product\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.args\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.args_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.code_signature.exists\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.code_signature.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.code_signature.subject_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.code_signature.trusted\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.code_signature.valid\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.command_line\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.command_line.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.command_line\"}}},{\"name\":\"process.entity_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.executable\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.executable.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.executable\"}}},{\"name\":\"process.exit_code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.hash.sha512\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.name\"}}},{\"name\":\"process.parent.args\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.args_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.code_signature.exists\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.code_signature.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.code_signature.subject_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.code_signature.trusted\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.code_signature.valid\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.command_line\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.command_line.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.parent.command_line\"}}},{\"name\":\"process.parent.entity_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.executable\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.executable.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.parent.executable\"}}},{\"name\":\"process.parent.exit_code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.hash.sha512\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.parent.name\"}}},{\"name\":\"process.parent.pe.architecture\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.pe.company\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.pe.description\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.pe.file_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.pe.imphash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.pe.original_file_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.pe.product\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.pgid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.pid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.ppid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.start\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.thread.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.thread.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.title\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.title.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.parent.title\"}}},{\"name\":\"process.parent.uptime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.working_directory\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.parent.working_directory.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.parent.working_directory\"}}},{\"name\":\"process.pe.architecture\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pe.company\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pe.description\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pe.file_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pe.imphash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pe.original_file_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pe.product\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pgid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.ppid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.start\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.thread.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.thread.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.title\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.uptime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.working_directory\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.working_directory.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"process.working_directory\"}}},{\"name\":\"processor.event\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"processor.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.alloc_objects.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.alloc_space.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.cpu.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.duration\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.inuse_objects.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.inuse_space.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.samples.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.stack.filename\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.stack.function\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.stack.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.stack.line\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.top.filename\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.top.function\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.top.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"profile.top.line\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"registry.data.bytes\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"registry.data.strings\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"registry.data.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"registry.hive\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"registry.key\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"registry.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"registry.value\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"related.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"related.hosts\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"related.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"related.user\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ruby.gc.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ruby.heap.allocations.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ruby.heap.slots.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ruby.heap.slots.live\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ruby.threads\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.author\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.category\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.description\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.license\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.reference\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.ruleset\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.uuid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rule.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.as.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.as.organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.as.organization.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"server.as.organization.name\"}}},{\"name\":\"server.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.nat.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.nat.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.registered_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.subdomain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.top_level_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.full_name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"server.user.full_name\"}}},{\"name\":\"server.user.group.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"server.user.name\"}}},{\"name\":\"server.user.roles\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.environment\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.ephemeral_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.framework.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.framework.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.language.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.language.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.node.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.runtime.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.runtime.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.as.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.as.organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.as.organization.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"source.as.organization.name\"}}},{\"name\":\"source.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.nat.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.nat.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.registered_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.subdomain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.top_level_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.full_name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"source.user.full_name\"}}},{\"name\":\"source.user.group.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"source.user.name\"}}},{\"name\":\"source.user.roles\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"sourcemap.bundle_filepath\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"sourcemap.service.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"sourcemap.service.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.action\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.db.link\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.db.rows_affected\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.destination.service.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.destination.service.resource\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.destination.service.response_time.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.destination.service.response_time.sum.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.destination.service.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.duration.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.message.age.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.message.queue.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.self_time.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.self_time.sum.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.start.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.subtype\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.sync\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"span.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.total.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.actual.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.mem.limit.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.mem.usage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.inactive_file.bytes\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cpu.system.norm.pct\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cpu.total.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cpu.user.norm.pct\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.memory.rss.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.memory.size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tags\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.framework\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.tactic.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.tactic.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.tactic.reference\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.technique.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.technique.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.technique.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"threat.technique.name\"}}},{\"name\":\"threat.technique.reference\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.technique.subtechnique.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.technique.subtechnique.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"threat.technique.subtechnique.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"threat.technique.subtechnique.name\"}}},{\"name\":\"threat.technique.subtechnique.reference\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"timeseries.instance\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"timestamp.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.cipher\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.certificate\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.certificate_chain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.issuer\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.ja3\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.not_after\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.not_before\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.server_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.subject\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.supported_ciphers\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.alternative_names\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.issuer.common_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.issuer.country\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.issuer.distinguished_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.issuer.locality\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.issuer.organization\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.issuer.organizational_unit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.issuer.state_or_province\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.not_after\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.not_before\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.public_key_algorithm\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.public_key_curve\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.public_key_exponent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.public_key_size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.serial_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.signature_algorithm\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.subject.common_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.subject.country\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.subject.distinguished_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.subject.locality\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.subject.organization\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.subject.organizational_unit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.subject.state_or_province\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.client.x509.version_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.curve\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.established\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.next_protocol\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.resumed\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.certificate\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.certificate_chain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.issuer\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.ja3s\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.not_after\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.not_before\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.subject\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.alternative_names\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.issuer.common_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.issuer.country\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.issuer.distinguished_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.issuer.locality\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.issuer.organization\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.issuer.organizational_unit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.issuer.state_or_province\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.not_after\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.not_before\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.public_key_algorithm\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.public_key_curve\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.public_key_exponent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.public_key_size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.serial_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.signature_algorithm\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.subject.common_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.subject.country\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.subject.distinguished_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.subject.locality\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.subject.organization\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.subject.organizational_unit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.subject.state_or_province\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.server.x509.version_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tls.version_protocol\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"trace.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.breakdown.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.duration.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.duration.histogram\",\"type\":\"histogram\",\"esTypes\":[\"histogram\"],\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.duration.sum.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.duration.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.experience.cls\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.experience.fid\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.experience.longtask.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.experience.longtask.max\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.experience.longtask.sum\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.experience.tbt\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.agent.domComplete\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.agent.domInteractive\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.agent.firstContentfulPaint\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.agent.largestContentfulPaint\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.agent.timeToFirstByte\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.connectEnd\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.connectStart\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.domComplete\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.domContentLoadedEventEnd\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.domContentLoadedEventStart\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.domInteractive\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.domLoading\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.domainLookupEnd\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.domainLookupStart\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.fetchStart\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.loadEventEnd\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.loadEventStart\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.requestStart\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.responseEnd\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.marks.navigationTiming.responseStart\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.message.age.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.message.queue.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"transaction.name\"}}},{\"name\":\"transaction.result\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.root\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.sampled\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.self_time.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.self_time.sum.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.span_count.dropped\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"transaction.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.extension\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.fragment\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.full\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.original\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.original.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"url.original\"}}},{\"name\":\"url.password\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.query\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.registered_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.scheme\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.subdomain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.top_level_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.username\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.full_name.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"user.full_name\"}}},{\"name\":\"user.group.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.roles\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.device.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.original\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.original.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"user_agent.original\"}}},{\"name\":\"user_agent.os.family\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.full\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.kernel\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.platform\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vlan.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vlan.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.category\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.classification\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.description\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.description.text\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false,\"subType\":{\"multi\":{\"parent\":\"vulnerability.description\"}}},{\"name\":\"vulnerability.enumeration\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.reference\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.report_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.scanner.vendor\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.score.base\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.score.environmental\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.score.temporal\",\"type\":\"number\",\"esTypes\":[\"float\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.score.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vulnerability.severity\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.alternative_names\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.issuer.common_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.issuer.country\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.issuer.distinguished_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.issuer.locality\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.issuer.organization\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.issuer.organizational_unit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.issuer.state_or_province\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.not_after\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.not_before\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.public_key_algorithm\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.public_key_curve\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.public_key_exponent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.public_key_size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.serial_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.signature_algorithm\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.subject.common_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.subject.country\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.subject.distinguished_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.subject.locality\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.subject.organization\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.subject.organizational_unit\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.subject.state_or_province\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"x509.version_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", + "sourceFilters": "[{\"value\":\"sourcemap.sourcemap\"}]", + "timeFieldName": "@timestamp" + }, + "id": "apm-*", + "type": "index-pattern", + "version": "1" +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/data/sample_attribute.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/data/sample_attribute.ts new file mode 100644 index 00000000000000..9b299e7d70bcc4 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/data/sample_attribute.ts @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export const sampleAttribute = { + title: 'Prefilled from exploratory view app', + description: '', + visualizationType: 'lnsXY', + references: [ + { id: 'apm-*', name: 'indexpattern-datasource-current-indexpattern', type: 'index-pattern' }, + { id: 'apm-*', name: 'indexpattern-datasource-layer-layer1', type: 'index-pattern' }, + ], + state: { + datasourceStates: { + indexpattern: { + layers: { + layer1: { + columnOrder: ['x-axis-column', 'y-axis-column'], + columns: { + 'x-axis-column': { + sourceField: 'transaction.duration.us', + label: 'Page load time', + dataType: 'number', + operationType: 'range', + isBucketed: true, + scale: 'interval', + params: { + type: 'histogram', + ranges: [{ from: 0, to: 1000, label: '' }], + maxBars: 'auto', + }, + }, + 'y-axis-column': { + dataType: 'number', + isBucketed: false, + label: 'Pages loaded', + operationType: 'count', + scale: 'ratio', + sourceField: 'Records', + }, + }, + incompleteColumns: {}, + }, + }, + }, + }, + visualization: { + legend: { isVisible: true, position: 'right' }, + valueLabels: 'hide', + fittingFunction: 'Linear', + curveType: 'CURVE_MONOTONE_X', + axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true }, + gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + preferredSeriesType: 'line', + layers: [ + { + accessors: ['y-axis-column'], + layerId: 'layer1', + seriesType: 'line', + yConfig: [{ forAccessor: 'y-axis-column', color: 'green' }], + xAccessor: 'x-axis-column', + }, + ], + }, + query: { query: '', language: 'kuery' }, + filters: [ + { meta: { index: 'apm-*' }, query: { match_phrase: { 'transaction.type': 'page-load' } } }, + { meta: { index: 'apm-*' }, query: { match_phrase: { 'processor.event': 'transaction' } } }, + ], + }, +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts new file mode 100644 index 00000000000000..37b3178affa2db --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts @@ -0,0 +1,387 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { LensAttributes } from './lens_attributes'; +import { mockIndexPattern } from '../rtl_helpers'; +import { getDefaultConfigs } from './default_configs'; +import { sampleAttribute } from './data/sample_attribute'; +import { LCP_FIELD, SERVICE_NAME } from './performance_dist_config'; +import { USER_AGENT_NAME } from './data/elasticsearch_fieldnames'; + +describe('Lens Attribute', () => { + const reportViewConfig = getDefaultConfigs({ + reportType: 'pld', + indexPattern: mockIndexPattern, + seriesId: 'series-id', + }); + + let lnsAttr: LensAttributes; + + beforeEach(() => { + lnsAttr = new LensAttributes(mockIndexPattern, reportViewConfig, 'line', [], 'count', {}); + }); + + it('should return expected json', function () { + expect(lnsAttr.getJSON()).toEqual(sampleAttribute); + }); + + it('should return main y axis', function () { + expect(lnsAttr.getMainYAxis()).toEqual({ + dataType: 'number', + isBucketed: false, + label: 'Pages loaded', + operationType: 'count', + scale: 'ratio', + sourceField: 'Records', + }); + }); + + it('should return expected field type', function () { + expect(JSON.stringify(lnsAttr.getFieldMeta('transaction.type'))).toEqual( + JSON.stringify({ + count: 0, + name: 'transaction.type', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }) + ); + }); + + it('should return expected field type for custom field with default value', function () { + expect(JSON.stringify(lnsAttr.getFieldMeta('performance.metric'))).toEqual( + JSON.stringify({ + count: 0, + name: 'transaction.duration.us', + type: 'number', + esTypes: ['long'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }) + ); + }); + + it('should return expected field type for custom field with passed value', function () { + lnsAttr = new LensAttributes(mockIndexPattern, reportViewConfig, 'line', [], 'count', { + 'performance.metric': LCP_FIELD, + }); + + expect(JSON.stringify(lnsAttr.getFieldMeta('performance.metric'))).toEqual( + JSON.stringify({ + count: 0, + name: LCP_FIELD, + type: 'number', + esTypes: ['scaled_float'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }) + ); + }); + + it('should return expected number column', function () { + expect(lnsAttr.getNumberColumn('transaction.duration.us')).toEqual({ + dataType: 'number', + isBucketed: true, + label: 'Page load time', + operationType: 'range', + params: { + maxBars: 'auto', + ranges: [ + { + from: 0, + label: '', + to: 1000, + }, + ], + type: 'histogram', + }, + scale: 'interval', + sourceField: 'transaction.duration.us', + }); + }); + + it('should return expected date histogram column', function () { + expect(lnsAttr.getDateHistogramColumn('@timestamp')).toEqual({ + dataType: 'date', + isBucketed: true, + label: '@timestamp', + operationType: 'date_histogram', + params: { + interval: 'auto', + }, + scale: 'interval', + sourceField: '@timestamp', + }); + }); + + it('should return main x axis', function () { + expect(lnsAttr.getXAxis()).toEqual({ + dataType: 'number', + isBucketed: true, + label: 'Page load time', + operationType: 'range', + params: { + maxBars: 'auto', + ranges: [ + { + from: 0, + label: '', + to: 1000, + }, + ], + type: 'histogram', + }, + scale: 'interval', + sourceField: 'transaction.duration.us', + }); + }); + + it('should return first layer', function () { + expect(lnsAttr.getLayer()).toEqual({ + columnOrder: ['x-axis-column', 'y-axis-column'], + columns: { + 'x-axis-column': { + dataType: 'number', + isBucketed: true, + label: 'Page load time', + operationType: 'range', + params: { + maxBars: 'auto', + ranges: [ + { + from: 0, + label: '', + to: 1000, + }, + ], + type: 'histogram', + }, + scale: 'interval', + sourceField: 'transaction.duration.us', + }, + 'y-axis-column': { + dataType: 'number', + isBucketed: false, + label: 'Pages loaded', + operationType: 'count', + scale: 'ratio', + sourceField: 'Records', + }, + }, + incompleteColumns: {}, + }); + }); + + it('should return expected XYState', function () { + expect(lnsAttr.getXyState()).toEqual({ + axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + curveType: 'CURVE_MONOTONE_X', + fittingFunction: 'Linear', + gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + layers: [ + { + accessors: ['y-axis-column'], + layerId: 'layer1', + palette: undefined, + seriesType: 'line', + xAccessor: 'x-axis-column', + yConfig: [{ color: 'green', forAccessor: 'y-axis-column' }], + }, + ], + legend: { isVisible: true, position: 'right' }, + preferredSeriesType: 'line', + tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true }, + valueLabels: 'hide', + }); + }); + + describe('ParseFilters function', function () { + it('should parse default filters', function () { + expect(lnsAttr.parseFilters()).toEqual([ + { meta: { index: 'apm-*' }, query: { match_phrase: { 'transaction.type': 'page-load' } } }, + { meta: { index: 'apm-*' }, query: { match_phrase: { 'processor.event': 'transaction' } } }, + ]); + }); + + it('should parse default and ui filters', function () { + lnsAttr = new LensAttributes( + mockIndexPattern, + reportViewConfig, + 'line', + [ + { field: SERVICE_NAME, values: ['elastic-co', 'kibana-front'] }, + { field: USER_AGENT_NAME, values: ['Firefox'], notValues: ['Chrome'] }, + ], + 'count', + {} + ); + + expect(lnsAttr.parseFilters()).toEqual([ + { meta: { index: 'apm-*' }, query: { match_phrase: { 'transaction.type': 'page-load' } } }, + { meta: { index: 'apm-*' }, query: { match_phrase: { 'processor.event': 'transaction' } } }, + { + meta: { + index: 'apm-*', + key: 'service.name', + params: ['elastic-co', 'kibana-front'], + type: 'phrases', + value: 'elastic-co, kibana-front', + }, + query: { + bool: { + minimum_should_match: 1, + should: [ + { + match_phrase: { + 'service.name': 'elastic-co', + }, + }, + { + match_phrase: { + 'service.name': 'kibana-front', + }, + }, + ], + }, + }, + }, + { + meta: { + index: 'apm-*', + }, + query: { + match_phrase: { + 'user_agent.name': 'Firefox', + }, + }, + }, + { + meta: { + index: 'apm-*', + negate: true, + }, + query: { + match_phrase: { + 'user_agent.name': 'Chrome', + }, + }, + }, + ]); + }); + }); + + describe('Layer breakdowns', function () { + it('should add breakdown column', function () { + lnsAttr.addBreakdown(USER_AGENT_NAME); + + expect(lnsAttr.visualization.layers).toEqual([ + { + accessors: ['y-axis-column'], + layerId: 'layer1', + palette: undefined, + seriesType: 'line', + splitAccessor: 'break-down-column', + xAccessor: 'x-axis-column', + yConfig: [{ color: 'green', forAccessor: 'y-axis-column' }], + }, + ]); + + expect(lnsAttr.layers.layer1).toEqual({ + columnOrder: ['x-axis-column', 'break-down-column', 'y-axis-column'], + columns: { + 'break-down-column': { + dataType: 'string', + isBucketed: true, + label: 'Top values of Browser family', + operationType: 'terms', + params: { + missingBucket: false, + orderBy: { columnId: 'y-axis-column', type: 'column' }, + orderDirection: 'desc', + otherBucket: true, + size: 3, + }, + scale: 'ordinal', + sourceField: 'user_agent.name', + }, + 'x-axis-column': { + dataType: 'number', + isBucketed: true, + label: 'Page load time', + operationType: 'range', + params: { + maxBars: 'auto', + ranges: [{ from: 0, label: '', to: 1000 }], + type: 'histogram', + }, + scale: 'interval', + sourceField: 'transaction.duration.us', + }, + 'y-axis-column': { + dataType: 'number', + isBucketed: false, + label: 'Pages loaded', + operationType: 'count', + scale: 'ratio', + sourceField: 'Records', + }, + }, + incompleteColumns: {}, + }); + }); + + it('should remove breakdown column', function () { + lnsAttr.addBreakdown(USER_AGENT_NAME); + + lnsAttr.removeBreakdown(); + + expect(lnsAttr.visualization.layers).toEqual([ + { + accessors: ['y-axis-column'], + layerId: 'layer1', + palette: undefined, + seriesType: 'line', + xAccessor: 'x-axis-column', + yConfig: [{ color: 'green', forAccessor: 'y-axis-column' }], + }, + ]); + + expect(lnsAttr.layers.layer1.columnOrder).toEqual(['x-axis-column', 'y-axis-column']); + + expect(lnsAttr.layers.layer1.columns).toEqual({ + 'x-axis-column': { + dataType: 'number', + isBucketed: true, + label: 'Page load time', + operationType: 'range', + params: { + maxBars: 'auto', + ranges: [{ from: 0, label: '', to: 1000 }], + type: 'histogram', + }, + scale: 'interval', + sourceField: 'transaction.duration.us', + }, + 'y-axis-column': { + dataType: 'number', + isBucketed: false, + label: 'Pages loaded', + operationType: 'count', + scale: 'ratio', + sourceField: 'Records', + }, + }); + }); + }); +}); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts index 88299e4d0f1bbd..596fb12de38572 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts @@ -19,17 +19,19 @@ import { import { buildPhraseFilter, buildPhrasesFilter, - IIndexPattern, + IndexPattern, } from '../../../../../../../../src/plugins/data/common'; import { FieldLabels } from './constants'; import { DataSeries, UrlFilter } from '../types'; +import { XYCurveType } from '../../../../../../lens/public/xy_visualization/types'; +import { DataType } from '../../../../../../lens/public/types'; function getLayerReferenceName(layerId: string) { return `indexpattern-datasource-layer-${layerId}`; } export class LensAttributes { - indexPattern: IIndexPattern; + indexPattern: IndexPattern; layers: Record; visualization: XYState; filters: UrlFilter[]; @@ -38,7 +40,7 @@ export class LensAttributes { reportDefinitions: Record; constructor( - indexPattern: IIndexPattern, + indexPattern: IndexPattern, reportViewConfig: DataSeries, seriesType?: SeriesType, filters?: UrlFilter[], @@ -55,15 +57,17 @@ export class LensAttributes { } this.seriesType = seriesType ?? reportViewConfig.defaultSeriesType; this.reportViewConfig = reportViewConfig; - this.addLayer(); + this.layers.layer1 = this.getLayer(); this.visualization = this.getXyState(); } addBreakdown(sourceField: string) { + const fieldMeta = this.indexPattern.getFieldByName(sourceField); + this.layers.layer1.columns['break-down-column'] = { sourceField, - label: `Top values of user_agent.name${FieldLabels[sourceField]}`, - dataType: 'string', + label: `Top values of ${FieldLabels[sourceField]}`, + dataType: fieldMeta?.type as DataType, operationType: 'terms', scale: 'ordinal', isBucketed: true, @@ -121,7 +125,9 @@ export class LensAttributes { | LastValueIndexPatternColumn | DateHistogramIndexPatternColumn | RangeIndexPatternColumn { - const { type: fieldType, name: fieldName } = this.getFieldType()!; + const { xAxisColumn } = this.reportViewConfig; + + const { type: fieldType, name: fieldName } = this.getFieldMeta(xAxisColumn.sourceField)!; if (fieldType === 'date') { return this.getDateHistogramColumn(fieldName); @@ -134,10 +140,8 @@ export class LensAttributes { return this.getDateHistogramColumn(fieldName); } - getFieldType() { - const { xAxisColumn } = this.reportViewConfig; - - let xAxisField = xAxisColumn.sourceField; + getFieldMeta(sourceField?: string) { + let xAxisField = sourceField; if (xAxisField) { const rdf = this.reportViewConfig.reportDefinitions ?? []; @@ -147,12 +151,14 @@ export class LensAttributes { if (customField) { if (this.reportDefinitions[xAxisField]) { xAxisField = this.reportDefinitions[xAxisField]; + } else if (customField.defaultValue) { + xAxisField = customField.defaultValue; } else if (customField.options?.[0].field) { xAxisField = customField.options?.[0].field; } } - return this.indexPattern.fields.find((field) => field.name === xAxisField); + return this.indexPattern.getFieldByName(xAxisField); } } @@ -168,8 +174,8 @@ export class LensAttributes { } as CountIndexPatternColumn; } - addLayer() { - this.layers.layer1 = { + getLayer() { + return { columnOrder: ['x-axis-column', 'y-axis-column'], columns: { 'x-axis-column': this.getXAxis(), @@ -185,7 +191,7 @@ export class LensAttributes { valueLabels: 'hide', fittingFunction: 'Linear', // fittingFunction: 'None', - + curveType: 'CURVE_MONOTONE_X' as XYCurveType, axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true }, gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/performance_dist_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/performance_dist_config.ts index 5ae9f3bb8c56a4..1320d3453d99c8 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/performance_dist_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/performance_dist_config.ts @@ -15,6 +15,7 @@ export const LCP_FIELD = 'transaction.marks.agent.largestContentfulPaint'; export const TBT_FIELD = 'transaction.experience.tbt'; export const FID_FIELD = 'transaction.experience.fid'; export const CLS_FIELD = 'transaction.experience.cls'; +export const SERVICE_NAME = 'service.name'; export function getPerformanceDistLensConfig({ seriesId, indexPattern }: ConfigProps): DataSeries { return { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx index f2bcdd7e3cee0c..fb2099b9538116 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx @@ -6,13 +6,92 @@ */ import React from 'react'; -import { render } from './rtl_helpers'; +import { act, within } from '@testing-library/react'; +import { fireEvent, screen, waitFor } from '@testing-library/dom'; +import { render, mockUrlStorage, mockCore } from './rtl_helpers'; import { ExploratoryView } from './exploratory_view'; +import { getStubIndexPattern } from '../../../../../../../src/plugins/data/public/test_utils'; +import * as obsvInd from '../../../utils/observability_index_patterns'; describe('ExploratoryView', () => { - it('renders a label for button', async () => { - const wrapper = render(); + beforeEach(() => { + const indexPattern = getStubIndexPattern( + 'apm-*', + () => {}, + '@timestamp', + [ + { + name: '@timestamp', + type: 'date', + esTypes: ['date'], + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + ], + mockCore() as any + ); - expect(wrapper).toMatchSnapshot(); + jest.spyOn(obsvInd, 'ObservabilityIndexPatterns').mockReturnValue({ + getIndexPattern: jest.fn().mockReturnValue(indexPattern), + } as any); + }); + + it('renders exploratory view', async () => { + render(); + + await waitFor(() => { + screen.getByText(/open in lens/i); + screen.getByRole('heading', { name: /exploratory view/i }); + screen.getByRole('img', { name: /visulization/i }); + screen.getByText(/add series/i); + screen.getByText(/no series found, please add a series\./i); + }); + }); + + it('can add, cancel new series', async () => { + render(); + + await act(async () => { + await fireEvent.click(screen.getByText(/add series/i)); + }); + + await waitFor(() => { + screen.getByText(/open in lens/i); + screen.getByText(/select a data type to start building a series\./i); + screen.getByRole('table', { name: /this table contains 1 rows\./i }); + const button = screen.getByRole('button', { name: /add/i }); + within(button).getByText(/add/i); + }); + + await act(async () => { + await fireEvent.click(screen.getByText(/cancel/i)); + }); + + await waitFor(() => { + screen.getByText(/add series/i); + }); + }); + + it('renders lens component when there is series', async () => { + mockUrlStorage({ + data: { + 'uptime-pings-histogram': { + reportType: 'upp', + breakdown: 'monitor.status', + time: { from: 'now-15m', to: 'now' }, + }, + }, + }); + + render(); + + await waitFor(() => { + screen.getByText(/open in lens/i); + screen.getByRole('heading', { name: /uptime pings/i }); + screen.getByText(/uptime-pings-histogram/i); + screen.getByText(/Lens Embeddable Component/i); + screen.getByRole('table', { name: /this table contains 1 rows\./i }); + }); }); }); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.test.tsx new file mode 100644 index 00000000000000..337a3c7f6b6944 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { mockUrlStorage, render } from '../rtl_helpers'; +import { ExploratoryViewHeader } from './header'; +import { fireEvent } from '@testing-library/dom'; + +describe('ExploratoryViewHeader', function () { + it('should render properly', function () { + const { getByText } = render( + + ); + getByText('Open in Lens'); + }); + + it('should be able to click open in lens', function () { + mockUrlStorage({ + 'uptime-pings-histogram': { + reportType: 'upp', + breakdown: 'monitor.status', + time: { from: 'now-15m', to: 'now' }, + }, + }); + + const { getByText, core } = render( + + ); + fireEvent.click(getByText('Open in Lens')); + + expect(core?.lens?.navigateToPrefilledEditor).toHaveBeenCalledTimes(1); + expect(core?.lens?.navigateToPrefilledEditor).toHaveBeenCalledWith({ + attributes: { title: 'Performance distribution' }, + id: '', + timeRange: { + from: 'now-15m', + to: 'now', + }, + }); + }); +}); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_default_index_pattern.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_default_index_pattern.tsx index cc82f21016631b..04cbb4a4ddb18d 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_default_index_pattern.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_default_index_pattern.tsx @@ -6,21 +6,21 @@ */ import React, { createContext, useContext, Context, useState, useEffect } from 'react'; -import { IIndexPattern } from '../../../../../../../../src/plugins/data/common'; +import { IndexPattern } from '../../../../../../../../src/plugins/data/common'; import { AppDataType } from '../types'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { ObservabilityPublicPluginsStart } from '../../../../plugin'; import { ObservabilityIndexPatterns } from '../../../../utils/observability_index_patterns'; export interface IIndexPatternContext { - indexPattern: IIndexPattern; + indexPattern: IndexPattern; loadIndexPattern: (dataType: AppDataType) => void; } export const IndexPatternContext = createContext>({}); interface ProviderProps { - indexPattern: IIndexPattern; + indexPattern?: IndexPattern; children: JSX.Element; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts index 9f462790e8d37f..132f7a42c5d529 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { useMemo } from 'react'; import { useFetcher } from '../../../..'; import { IKbnUrlStateStorage } from '../../../../../../../../src/plugins/kibana_utils/public'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; @@ -30,7 +31,7 @@ export const useInitExploratoryView = (storage: IKbnUrlStateStorage) => { const firstSeries = allSeries[firstSeriesId]; - const { data: indexPattern } = useFetcher(() => { + const { data: indexPattern, loading } = useFetcher(() => { const obsvIndexP = new ObservabilityIndexPatterns(data); let reportType: DataType = 'apm'; if (firstSeries?.rt) { @@ -40,5 +41,7 @@ export const useInitExploratoryView = (storage: IKbnUrlStateStorage) => { return obsvIndexP.getIndexPattern(reportType); }, [firstSeries?.rt, data]); - return indexPattern; + return useMemo(() => { + return indexPattern; + }, [loading]); }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx index a5d9c88abe4e67..dc47a0f075fe69 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx @@ -52,11 +52,13 @@ export function ExploratoryViewPage() { headerColor={theme.eui.euiColorEmptyShade} bodyColor={theme.eui.euiPageBackgroundColor} > - - - - - + {indexPattern ? ( + + + + + + ) : null} ); } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx index b7c2685f132496..c57c8d9608c26d 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx @@ -23,29 +23,41 @@ import { ObservabilityPublicPluginsStart } from '../../../plugin'; import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; import { lensPluginMock } from '../../../../../lens/public/mocks'; import { IndexPatternContextProvider } from './hooks/use_default_index_pattern'; -import { UrlStorageContextProvider } from './hooks/use_url_strorage'; +import { AllSeries, UrlStorageContextProvider } from './hooks/use_url_strorage'; import { withNotifyOnErrors, createKbnUrlStateStorage, } from '../../../../../../../src/plugins/kibana_utils/public'; +import * as fetcherHook from '../../../hooks/use_fetcher'; +import * as useUrlHook from './hooks/use_url_strorage'; +import * as useSeriesFilterHook from './hooks/use_series_filters'; +import * as useHasDataHook from '../../../hooks/use_has_data'; +import * as useValuesListHook from '../../../hooks/use_values_list'; +import { getStubIndexPattern } from '../../../../../../../src/plugins/data/public/index_patterns/index_pattern.stub'; + +import indexPatternData from './configurations/data/index_pattern.json'; +import { setIndexPatterns } from '../../../../../../../src/plugins/data/public/services'; +import { IndexPatternsContract } from '../../../../../../../src/plugins/data/common/index_patterns/index_patterns'; +import { UrlFilter } from './types'; +import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; interface KibanaProps { services?: KibanaServices; } export interface KibanaProviderOptions { - core?: Partial & ExtraCore; + core?: ExtraCore & Partial; kibanaProps?: KibanaProps; } -interface MockKibanaProviderProps extends KibanaProviderOptions { +interface MockKibanaProviderProps> + extends KibanaProviderOptions { children: ReactElement; history: History; } -interface MockRouterProps extends MockKibanaProviderProps { - history?: History; -} +interface MockRouterProps> + extends MockKibanaProviderProps {} type Url = | string @@ -61,6 +73,15 @@ interface RenderRouterOptions extends KibanaProviderOptions(key: string): T { + if (key === 'timepicker:quickRanges') { + return ([ + { + display: 'Today', + from: 'now/d', + to: 'now/d', + }, + ] as unknown) as T; + } return ('MMM D, YYYY @ HH:mm:ss.SSS' as unknown) as T; } @@ -70,16 +91,16 @@ function setSetting$(key: string): T { /* default mock core */ const defaultCore = coreMock.createStart(); -const mockCore: () => Partial = () => { +export const mockCore: () => Partial = () => { const core: Partial = { ...defaultCore, application: { ...defaultCore.application, - getUrlForApp: () => '/app/uptime', + getUrlForApp: () => '/app/observability', navigateToUrl: jest.fn(), capabilities: { ...defaultCore.application.capabilities, - uptime: { + observability: { 'alerting:save': true, configureSettings: true, save: true, @@ -93,36 +114,39 @@ const mockCore: () => Partial = () => { get$: setSetting$, }, lens: lensPluginMock.createStartContract(), + data: dataPluginMock.createStartContract(), }; return core; }; /* Mock Provider Components */ -export function MockKibanaProvider({ +export function MockKibanaProvider>({ children, core, history, kibanaProps, }: MockKibanaProviderProps) { - const coreOptions = { - ...mockCore(), - ...core, - }; - - const { uiSettings, notifications } = coreOptions; + const { notifications } = core!; const kbnUrlStateStorage = createKbnUrlStateStorage({ history, - useHash: uiSettings!.get('state:storeInSessionStorage'), + useHash: false, ...withNotifyOnErrors(notifications!.toasts), }); + const indexPattern = mockIndexPattern; + + setIndexPatterns(({ + ...[indexPattern], + get: async () => indexPattern, + } as unknown) as IndexPatternsContract); + return ( - + - + {children} @@ -154,7 +178,7 @@ export function render( ui: ReactElement, { history = createMemoryHistory(), - core, + core: customCore, kibanaProps, renderOptions, url, @@ -164,6 +188,11 @@ export function render( history = getHistoryFromUrl(url); } + const core = { + ...mockCore(), + ...customCore, + }; + return { ...reactTestLibRender( @@ -172,6 +201,7 @@ export function render( renderOptions ), history, + core, }; } @@ -186,3 +216,94 @@ const getHistoryFromUrl = (url: Url) => { initialEntries: [url.path + stringify(url.queryParams)], }); }; + +export const mockFetcher = (data: any) => { + return jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ + data: data, + status: fetcherHook.FETCH_STATUS.SUCCESS, + refetch: jest.fn(), + }); +}; + +export const mockUseHasData = () => { + const onRefreshTimeRange = jest.fn(); + const spy = jest.spyOn(useHasDataHook, 'useHasData').mockReturnValue({ + onRefreshTimeRange, + } as any); + return { spy, onRefreshTimeRange }; +}; + +export const mockUseValuesList = (values?: string[]) => { + const onRefreshTimeRange = jest.fn(); + const spy = jest.spyOn(useValuesListHook, 'useValuesList').mockReturnValue({ + values: values ?? [], + } as any); + return { spy, onRefreshTimeRange }; +}; + +export const mockUrlStorage = ({ data, filters }: { data?: AllSeries; filters?: UrlFilter[] }) => { + let mockDataSeries = data || { + 'performance-distribution': { + reportType: 'pld', + breakdown: 'user_agent.name', + time: { from: 'now-15m', to: 'now' }, + ...(filters ? { filters } : {}), + }, + }; + const allSeriesIds = Object.keys(mockDataSeries); + const firstSeriesId = allSeriesIds?.[0]; + + const series = mockDataSeries[firstSeriesId]; + + const removeSeries = jest.fn(); + const setSeries = jest.fn(); + + const spy = jest.spyOn(useUrlHook, 'useUrlStorage').mockReturnValue({ + firstSeriesId, + allSeriesIds, + removeSeries, + setSeries, + series, + firstSeries: mockDataSeries[firstSeriesId], + allSeries: data, + } as any); + + return { spy, removeSeries, setSeries }; +}; + +export function mockUseSeriesFilter() { + const removeFilter = jest.fn(); + const invertFilter = jest.fn(); + const setFilter = jest.fn(); + const spy = jest.spyOn(useSeriesFilterHook, 'useSeriesFilters').mockReturnValue({ + removeFilter, + invertFilter, + setFilter, + }); + + return { + spy, + removeFilter, + invertFilter, + setFilter, + }; +} + +const hist = createMemoryHistory(); +export const mockHistory = { + ...hist, + createHref: jest.fn(({ pathname }) => `/observability${pathname}`), + push: jest.fn(), + location: { + ...hist.location, + pathname: '/current-path', + }, +}; + +export const mockIndexPattern = getStubIndexPattern( + 'apm-*', + () => {}, + '@timestamp', + JSON.parse(indexPatternData.attributes.fields), + mockCore() as any +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.test.tsx new file mode 100644 index 00000000000000..b44f2d8c593809 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.test.tsx @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { fireEvent, screen } from '@testing-library/react'; +import { mockUrlStorage, render } from '../../rtl_helpers'; +import { dataTypes, DataTypesCol } from './data_types_col'; +import { NEW_SERIES_KEY } from '../../hooks/use_url_strorage'; + +describe('DataTypesCol', function () { + it('should render properly', function () { + const { getByText } = render(); + + dataTypes.forEach(({ label }) => { + getByText(label); + }); + }); + + it('should set series on change', function () { + const { setSeries } = mockUrlStorage({}); + + const { getByText } = render(); + + fireEvent.click(getByText(/user experience\(rum\)/i)); + + expect(setSeries).toHaveBeenCalledTimes(1); + expect(setSeries).toHaveBeenCalledWith('newSeriesKey', { dataType: 'rum' }); + }); + + it('should set series on change', function () { + const { setSeries } = mockUrlStorage({ + data: { + [NEW_SERIES_KEY]: { + dataType: 'synthetics', + reportType: 'upp', + breakdown: 'monitor.status', + time: { from: 'now-15m', to: 'now' }, + }, + }, + }); + + render(); + + const button = screen.getByRole('button', { + name: /Synthetic Monitoring/i, + }); + + expect(button.classList).toContain('euiButton--fill'); + fireEvent.click(button); + + // undefined on click selected + expect(setSeries).toHaveBeenCalledWith('newSeriesKey', { dataType: undefined }); + }); +}); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx index 301d529a6d7f0e..05ae20b07288a5 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx @@ -11,7 +11,7 @@ import { AppDataType } from '../../types'; import { useIndexPatternContext } from '../../hooks/use_default_index_pattern'; import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_strorage'; -const dataTypes: Array<{ id: AppDataType; label: string }> = [ +export const dataTypes: Array<{ id: AppDataType; label: string }> = [ { id: 'synthetics', label: 'Synthetic Monitoring' }, { id: 'rum', label: 'User Experience(RUM)' }, { id: 'logs', label: 'Logs' }, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.test.tsx new file mode 100644 index 00000000000000..ee2357e77c3926 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.test.tsx @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { fireEvent, screen } from '@testing-library/react'; +import { render } from '../../../../../utils/test_helper'; +import { getDefaultConfigs } from '../../configurations/default_configs'; +import { mockIndexPattern, mockUrlStorage } from '../../rtl_helpers'; +import { NEW_SERIES_KEY } from '../../hooks/use_url_strorage'; +import { ReportBreakdowns } from './report_breakdowns'; +import { USER_AGENT_NAME, USER_AGENT_OS } from '../../configurations/data/elasticsearch_fieldnames'; + +describe('Series Builder ReportBreakdowns', function () { + const dataViewSeries = getDefaultConfigs({ + reportType: 'pld', + indexPattern: mockIndexPattern, + seriesId: NEW_SERIES_KEY, + }); + + it('should render properly', function () { + mockUrlStorage({}); + + render(); + + screen.getByText('Select an option: , is selected'); + screen.getAllByText('Browser family'); + }); + + it('should set new series breakdown on change', function () { + const { setSeries } = mockUrlStorage({}); + + render(); + + const btn = screen.getByRole('button', { + name: /select an option: Browser family , is selected/i, + hidden: true, + }); + + fireEvent.click(btn); + + fireEvent.click(screen.getByText(/operating system/i)); + + expect(setSeries).toHaveBeenCalledTimes(1); + expect(setSeries).toHaveBeenCalledWith('newSeriesKey', { + breakdown: USER_AGENT_OS, + reportType: 'pld', + time: { from: 'now-15m', to: 'now' }, + }); + }); + it('should set undefined on new series on no select breakdown', function () { + const { setSeries } = mockUrlStorage({}); + + render(); + + const btn = screen.getByRole('button', { + name: /select an option: Browser family , is selected/i, + hidden: true, + }); + + fireEvent.click(btn); + + fireEvent.click(screen.getByText(/no breakdown/i)); + + expect(setSeries).toHaveBeenCalledTimes(1); + expect(setSeries).toHaveBeenCalledWith('newSeriesKey', { + breakdown: undefined, + reportType: 'pld', + time: { from: 'now-15m', to: 'now' }, + }); + }); +}); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.test.tsx new file mode 100644 index 00000000000000..d09421641555af --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.test.tsx @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { fireEvent, screen } from '@testing-library/react'; +import { getDefaultConfigs } from '../../configurations/default_configs'; +import { mockIndexPattern, mockUrlStorage, mockUseValuesList, render } from '../../rtl_helpers'; +import { NEW_SERIES_KEY } from '../../hooks/use_url_strorage'; +import { ReportDefinitionCol } from './report_definition_col'; +import { SERVICE_NAME } from '../../configurations/data/elasticsearch_fieldnames'; + +describe('Series Builder ReportDefinitionCol', function () { + const dataViewSeries = getDefaultConfigs({ + reportType: 'pld', + indexPattern: mockIndexPattern, + seriesId: NEW_SERIES_KEY, + }); + + const { setSeries } = mockUrlStorage({ + data: { + 'performance-dist': { + dataType: 'rum', + reportType: 'pld', + time: { from: 'now-30d', to: 'now' }, + reportDefinitions: { [SERVICE_NAME]: 'elastic-co' }, + }, + }, + }); + + it('should render properly', async function () { + render(); + + screen.getByText('Web Application'); + screen.getByText('Environment'); + screen.getByText('Select an option: Page load time, is selected'); + screen.getByText('Page load time'); + }); + + it('should render selected report definitions', function () { + render(); + + screen.getByText('elastic-co'); + }); + + it('should be able to remove selected definition', function () { + render(); + + const removeBtn = screen.getByRole('button', { name: /elastic-co/i }); + + fireEvent.click(removeBtn); + + expect(setSeries).toHaveBeenCalledTimes(1); + expect(setSeries).toHaveBeenCalledWith('newSeriesKey', { + dataType: 'rum', + reportDefinitions: {}, + reportType: 'pld', + time: { from: 'now-30d', to: 'now' }, + }); + }); + + it('should be able to unselected selected definition', async function () { + mockUseValuesList(['elastic-co']); + render(); + + const definitionBtn = screen.getByText(/web application/i); + + fireEvent.click(definitionBtn); + + screen.getByText('Apply'); + }); +}); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.test.tsx new file mode 100644 index 00000000000000..674f5e6f49bded --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.test.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { screen } from '@testing-library/react'; +import { render } from '../../../../../utils/test_helper'; +import { ReportFilters } from './report_filters'; +import { getDefaultConfigs } from '../../configurations/default_configs'; +import { mockIndexPattern, mockUrlStorage } from '../../rtl_helpers'; +import { NEW_SERIES_KEY } from '../../hooks/use_url_strorage'; + +describe('Series Builder ReportFilters', function () { + const dataViewSeries = getDefaultConfigs({ + reportType: 'pld', + indexPattern: mockIndexPattern, + seriesId: NEW_SERIES_KEY, + }); + mockUrlStorage({}); + it('should render properly', function () { + render(); + + screen.getByText('Add filter'); + }); +}); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.test.tsx new file mode 100644 index 00000000000000..41f234f76554ed --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.test.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { fireEvent, screen } from '@testing-library/react'; +import { mockUrlStorage, render } from '../../rtl_helpers'; +import { ReportTypesCol, SELECTED_DATA_TYPE_FOR_REPORT } from './report_types_col'; +import { ReportTypes } from '../series_builder'; + +describe('ReportTypesCol', function () { + it('should render properly', function () { + render(); + screen.getByText('Performance distribution'); + screen.getByText('KPI over time'); + }); + + it('should display empty message', function () { + render(); + screen.getByText(SELECTED_DATA_TYPE_FOR_REPORT); + }); + + it('should set series on change', function () { + const { setSeries } = mockUrlStorage({}); + render(); + + fireEvent.click(screen.getByText(/monitor duration/i)); + + expect(setSeries).toHaveBeenCalledWith('newSeriesKey', { + breakdown: 'user_agent.name', + reportDefinitions: {}, + reportType: 'upd', + time: { from: 'now-15m', to: 'now' }, + }); + expect(setSeries).toHaveBeenCalledTimes(1); + }); + + it('should set selected as filled', function () { + const { setSeries } = mockUrlStorage({ + data: { + newSeriesKey: { + dataType: 'synthetics', + reportType: 'upp', + breakdown: 'monitor.status', + time: { from: 'now-15m', to: 'now' }, + }, + }, + }); + + render(); + + const button = screen.getByRole('button', { + name: /pings histogram/i, + }); + + expect(button.classList).toContain('euiButton--fill'); + fireEvent.click(button); + + // undefined on click selected + expect(setSeries).toHaveBeenCalledWith('newSeriesKey', { dataType: 'synthetics' }); + }); +}); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx index 32bb439315263d..8bcc2c09a1426f 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { ReportViewTypeId, SeriesUrl } from '../../types'; import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_strorage'; +import { i18n } from '@kbn/i18n'; interface Props { reportTypes: Array<{ id: ReportViewTypeId; label: string }>; @@ -50,6 +51,11 @@ export function ReportTypesCol({ reportTypes }: Props) { ))} ) : ( - Select a data type to start building a series. + {SELECTED_DATA_TYPE_FOR_REPORT} ); } + +export const SELECTED_DATA_TYPE_FOR_REPORT = i18n.translate( + 'xpack.observability.expView.reportType.noDataType', + { defaultMessage: 'Select a data type to start building a series.' } +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx index 61f1249928597e..71e3317ad6db86 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx @@ -8,8 +8,8 @@ import { EuiSuperDatePicker } from '@elastic/eui'; import React, { useEffect } from 'react'; import { useHasData } from '../../../../hooks/use_has_data'; -import { UI_SETTINGS, useKibanaUISettings } from '../../../../hooks/use_kibana_ui_settings'; import { useUrlStorage } from '../hooks/use_url_strorage'; +import { useQuickTimeRanges } from '../../../../hooks/use_quick_time_ranges'; export interface TimePickerTime { from: string; @@ -27,15 +27,7 @@ interface Props { export function SeriesDatePicker({ seriesId }: Props) { const { onRefreshTimeRange } = useHasData(); - const timePickerQuickRanges = useKibanaUISettings( - UI_SETTINGS.TIMEPICKER_QUICK_RANGES - ); - - const commonlyUsedRanges = timePickerQuickRanges.map(({ from, to, display }) => ({ - start: from, - end: to, - label: display, - })); + const commonlyUsedRanges = useQuickTimeRanges(); const { series, setSeries } = useUrlStorage(seriesId); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/series_date_picker.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/series_date_picker.test.tsx new file mode 100644 index 00000000000000..acc9ba9658a081 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/series_date_picker.test.tsx @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { mockUrlStorage, mockUseHasData, render } from '../rtl_helpers'; +import { fireEvent, waitFor } from '@testing-library/react'; +import { SeriesDatePicker } from './index'; + +describe('SeriesDatePicker', function () { + it('should render properly', function () { + mockUrlStorage({ + data: { + 'uptime-pings-histogram': { + reportType: 'upp', + breakdown: 'monitor.status', + time: { from: 'now-30m', to: 'now' }, + }, + }, + }); + const { getByText } = render(); + + getByText('Last 30 minutes'); + }); + + it('should set defaults', async function () { + const { setSeries: setSeries1 } = mockUrlStorage({ + data: { + 'uptime-pings-histogram': { + reportType: 'upp', + breakdown: 'monitor.status', + }, + }, + } as any); + render(); + expect(setSeries1).toHaveBeenCalledTimes(1); + expect(setSeries1).toHaveBeenCalledWith('uptime-pings-histogram', { + breakdown: 'monitor.status', + reportType: 'upp', + time: { from: 'now-5h', to: 'now' }, + }); + }); + + it('should set series data', async function () { + const { setSeries } = mockUrlStorage({ + data: { + 'uptime-pings-histogram': { + reportType: 'upp', + breakdown: 'monitor.status', + time: { from: 'now-30m', to: 'now' }, + }, + }, + }); + + const { onRefreshTimeRange } = mockUseHasData(); + const { getByTestId } = render(); + + await waitFor(function () { + fireEvent.click(getByTestId('superDatePickerToggleQuickMenuButton')); + }); + + fireEvent.click(getByTestId('superDatePickerCommonlyUsed_Today')); + + expect(onRefreshTimeRange).toHaveBeenCalledTimes(1); + + expect(setSeries).toHaveBeenCalledWith('series-id', { + breakdown: 'monitor.status', + reportType: 'upp', + time: { from: 'now/d', to: 'now/d' }, + }); + expect(setSeries).toHaveBeenCalledTimes(1); + }); +}); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.test.tsx new file mode 100644 index 00000000000000..4c1d2f01e9d682 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.test.tsx @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { fireEvent, screen } from '@testing-library/react'; +import { FilterExpanded } from './filter_expanded'; +import { mockUrlStorage, mockUseValuesList, render } from '../../rtl_helpers'; +import { + USER_AGENT_NAME, + USER_AGENT_VERSION, +} from '../../configurations/data/elasticsearch_fieldnames'; + +describe('FilterExpanded', function () { + it('should render properly', async function () { + mockUrlStorage({ filters: [{ field: USER_AGENT_NAME, values: ['Chrome'] }] }); + + render( + + ); + + screen.getByText('Browser Family'); + }); + it('should call go back on click', async function () { + mockUrlStorage({ filters: [{ field: USER_AGENT_NAME, values: ['Chrome'] }] }); + const goBack = jest.fn(); + + render( + + ); + + fireEvent.click(screen.getByText('Browser Family')); + + expect(goBack).toHaveBeenCalledTimes(1); + expect(goBack).toHaveBeenCalledWith(); + }); + + it('should call useValuesList on load', async function () { + mockUrlStorage({ filters: [{ field: USER_AGENT_NAME, values: ['Chrome'] }] }); + + const { spy } = mockUseValuesList(['Chrome', 'Firefox']); + + const goBack = jest.fn(); + + render( + + ); + + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toBeCalledWith( + expect.objectContaining({ + time: { from: 'now-15m', to: 'now' }, + sourceField: USER_AGENT_NAME, + }) + ); + }); +}); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx index 35b760656474a6..2f97f346b2fda5 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx @@ -76,8 +76,8 @@ export function FilterExpanded({ seriesId, field, label, goBack, nestedField }: negate={true} nestedField={nestedField} seriesId={seriesId} - isOpen={isOpen} - setIsOpen={setIsOpen} + isNestedOpen={isOpen} + setIsNestedOpen={setIsOpen} /> diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.test.tsx new file mode 100644 index 00000000000000..3ef60c65823cf3 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.test.tsx @@ -0,0 +1,231 @@ +import React from 'react'; +import { fireEvent, screen } from '@testing-library/react'; +import { FilterValueButton } from './filter_value_btn'; +import { mockUrlStorage, mockUseSeriesFilter, mockUseValuesList, render } from '../../rtl_helpers'; +import { + USER_AGENT_NAME, + USER_AGENT_VERSION, +} from '../../configurations/data/elasticsearch_fieldnames'; + +describe('FilterValueButton', function () { + it('should render properly', async function () { + render( + + ); + + screen.getByText('Chrome'); + }); + + it('should render display negate state', async function () { + render( + + ); + + screen.getByText('Not Chrome'); + screen.getByTitle('Not Chrome'); + const btn = screen.getByRole('button'); + expect(btn.classList).toContain('euiButtonEmpty--danger'); + }); + + it('should call set filter on click', async function () { + const { setFilter, removeFilter } = mockUseSeriesFilter(); + + render( + + ); + + fireEvent.click(screen.getByText('Not Chrome')); + + expect(removeFilter).toHaveBeenCalledTimes(0); + expect(setFilter).toHaveBeenCalledTimes(1); + + expect(setFilter).toHaveBeenCalledWith({ + field: 'user_agent.name', + negate: true, + value: 'Chrome', + }); + }); + it('should remove filter on click if already selected', async function () { + mockUrlStorage({}); + const { removeFilter } = mockUseSeriesFilter(); + + render( + + ); + + fireEvent.click(screen.getByText('Chrome')); + + expect(removeFilter).toHaveBeenCalledWith({ + field: 'user_agent.name', + negate: false, + value: 'Chrome', + }); + }); + + it('should change filter on negated one', async function () { + const { removeFilter } = mockUseSeriesFilter(); + + render( + + ); + + fireEvent.click(screen.getByText('Not Chrome')); + + expect(removeFilter).toHaveBeenCalledWith({ + field: 'user_agent.name', + negate: true, + value: 'Chrome', + }); + }); + + it('should force open nested', async function () { + mockUseSeriesFilter(); + const { spy } = mockUseValuesList(); + + render( + + ); + + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toBeCalledWith( + expect.objectContaining({ + filters: [ + { + term: { + [USER_AGENT_NAME]: 'Chrome', + }, + }, + ], + sourceField: 'user_agent.version', + }) + ); + }); + it('should set isNestedOpen on click', async function () { + mockUseSeriesFilter(); + const { spy } = mockUseValuesList(); + + render( + + ); + + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toBeCalledWith( + expect.objectContaining({ + filters: [ + { + term: { + [USER_AGENT_NAME]: 'Chrome', + }, + }, + ], + sourceField: USER_AGENT_VERSION, + }) + ); + }); + + it('should set call setIsNestedOpen on click selected', async function () { + mockUseSeriesFilter(); + mockUseValuesList(); + + const setIsNestedOpen = jest.fn(); + + render( + + ); + + fireEvent.click(screen.getByText('Chrome')); + + expect(setIsNestedOpen).toHaveBeenCalledTimes(1); + expect(setIsNestedOpen).toHaveBeenCalledWith({ negate: false, value: '' }); + }); + + it('should set call setIsNestedOpen on click not selected', async function () { + mockUseSeriesFilter(); + mockUseValuesList(); + + const setIsNestedOpen = jest.fn(); + + render( + + ); + + fireEvent.click(screen.getByText('Not Chrome')); + + expect(setIsNestedOpen).toHaveBeenCalledTimes(1); + expect(setIsNestedOpen).toHaveBeenCalledWith({ negate: true, value: 'Chrome' }); + }); +}); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx index e93bc3faeb5ed6..f75b67b96ee54e 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx @@ -16,26 +16,26 @@ import FieldValueSuggestions from '../../../field_value_suggestions'; interface Props { value: string; field: string; - allValues?: string[]; + allSelectedValues?: string[]; negate: boolean; nestedField?: string; seriesId: string; - isOpen: { + isNestedOpen: { value: string; negate: boolean; }; - setIsOpen: (val: { value: string; negate: boolean }) => void; + setIsNestedOpen: (val: { value: string; negate: boolean }) => void; } export function FilterValueButton({ - isOpen, - setIsOpen, + isNestedOpen, + setIsNestedOpen, value, field, - allValues, negate, seriesId, nestedField, + allSelectedValues, }: Props) { const { series } = useUrlStorage(seriesId); @@ -43,7 +43,7 @@ export function FilterValueButton({ const { setFilter, removeFilter } = useSeriesFilters({ seriesId }); - const hasActiveFilters = (allValues ?? []).includes(value); + const hasActiveFilters = (allSelectedValues ?? []).includes(value); const button = ( { setFilter({ field: nestedField!, value: val! }); - setIsOpen({ value: '', negate }); + setIsNestedOpen({ value: '', negate }); }; - return nestedField ? ( + const forceOpenNested = isNestedOpen?.value === value && isNestedOpen.negate === negate; + + return nestedField && forceOpenNested ? ( diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.test.tsx new file mode 100644 index 00000000000000..ced04f0a59c8cb --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.test.tsx @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { fireEvent, screen } from '@testing-library/react'; +import { mockUrlStorage, render } from '../../rtl_helpers'; +import { MetricSelection } from './metric_selection'; + +describe('MetricSelection', function () { + it('should render properly', function () { + render(); + + screen.getByText('Average'); + }); + + it('should display selected value', function () { + mockUrlStorage({ + data: { + 'performance-distribution': { + reportType: 'kpi', + metric: 'median', + time: { from: 'now-15m', to: 'now' }, + }, + }, + }); + + render(); + + screen.getByText('Median'); + }); + + it('should be disabled on disabled state', function () { + render(); + + const btn = screen.getByRole('button'); + + expect(btn.classList).toContain('euiButton-isDisabled'); + }); + + it('should call set series on change', function () { + const { setSeries } = mockUrlStorage({ + data: { + 'performance-distribution': { + reportType: 'kpi', + metric: 'median', + time: { from: 'now-15m', to: 'now' }, + }, + }, + }); + + render(); + + fireEvent.click(screen.getByText('Median')); + + screen.getByText('Chart metric group'); + + fireEvent.click(screen.getByText('95th Percentile')); + + expect(setSeries).toHaveBeenNthCalledWith(1, 'performance-distribution', { + metric: '95th', + reportType: 'kpi', + time: { from: 'now-15m', to: 'now' }, + }); + // FIXME This is a bug in EUI EuiButtonGroup calls on change multiple times + // This should be one https://github.com/elastic/eui/issues/4629 + expect(setSeries).toHaveBeenCalledTimes(3); + }); + + it('should call set series on change for all series', function () { + const { setSeries } = mockUrlStorage({ + data: { + 'page-views': { + reportType: 'kpi', + metric: 'median', + time: { from: 'now-15m', to: 'now' }, + }, + 'performance-distribution': { + reportType: 'kpi', + metric: 'median', + time: { from: 'now-15m', to: 'now' }, + }, + }, + }); + + render(); + + fireEvent.click(screen.getByText('Median')); + + screen.getByText('Chart metric group'); + + fireEvent.click(screen.getByText('95th Percentile')); + + expect(setSeries).toHaveBeenNthCalledWith(1, 'page-views', { + metric: '95th', + reportType: 'kpi', + time: { from: 'now-15m', to: 'now' }, + }); + + expect(setSeries).toHaveBeenNthCalledWith(2, 'performance-distribution', { + metric: '95th', + reportType: 'kpi', + time: { from: 'now-15m', to: 'now' }, + }); + // FIXME This is a bug in EUI EuiButtonGroup calls on change multiple times + // This should be one https://github.com/elastic/eui/issues/4629 + expect(setSeries).toHaveBeenCalledTimes(6); + }); +}); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.test.tsx new file mode 100644 index 00000000000000..5770a7e209f068 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.test.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { screen, waitFor } from '@testing-library/react'; +import { mockIndexPattern, mockUrlStorage, render } from '../rtl_helpers'; +import { SelectedFilters } from './selected_filters'; +import { getDefaultConfigs } from '../configurations/default_configs'; +import { NEW_SERIES_KEY } from '../hooks/use_url_strorage'; +import { USER_AGENT_NAME } from '../configurations/data/elasticsearch_fieldnames'; + +describe('SelectedFilters', function () { + const dataViewSeries = getDefaultConfigs({ + reportType: 'pld', + indexPattern: mockIndexPattern, + seriesId: NEW_SERIES_KEY, + }); + + it('should render properly', async function () { + mockUrlStorage({ filters: [{ field: USER_AGENT_NAME, values: ['Chrome'] }] }); + + render(); + + await waitFor(() => { + screen.getByText('Chrome'); + screen.getByTitle('Filter: Browser family: Chrome. Select for more filter actions.'); + }); + }); +}); diff --git a/x-pack/plugins/observability/public/hooks/use_quick_time_ranges.tsx b/x-pack/plugins/observability/public/hooks/use_quick_time_ranges.tsx new file mode 100644 index 00000000000000..8d37ab32648f20 --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/use_quick_time_ranges.tsx @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useUiSetting } from '../../../../../src/plugins/kibana_react/public/ui_settings'; +import { UI_SETTINGS } from '../../../../../src/plugins/data/common'; +import { TimePickerQuickRange } from '../components/shared/exploratory_view/series_date_picker'; + +export function useQuickTimeRanges() { + const timePickerQuickRanges = useUiSetting( + UI_SETTINGS.TIMEPICKER_QUICK_RANGES + ); + + return timePickerQuickRanges.map(({ from, to, display }) => ({ + start: from, + end: to, + label: display, + })); +} diff --git a/x-pack/plugins/observability/public/hooks/use_values_list.ts b/x-pack/plugins/observability/public/hooks/use_values_list.ts index 75ac16610e0f31..1ce548e2d23da8 100644 --- a/x-pack/plugins/observability/public/hooks/use_values_list.ts +++ b/x-pack/plugins/observability/public/hooks/use_values_list.ts @@ -53,7 +53,9 @@ export const useValuesList = ({ ] : filters, }); - }, [sourceField, query, time, data.autocomplete, indexPattern, filters]); + }, []); + // FIXME + // }, [sourceField, query, time, data.autocomplete, indexPattern]); return { values: values as string[], loading }; }; diff --git a/x-pack/plugins/observability/public/utils/observability_index_patterns.ts b/x-pack/plugins/observability/public/utils/observability_index_patterns.ts index 9bdb2c07807969..b23a2461055442 100644 --- a/x-pack/plugins/observability/public/utils/observability_index_patterns.ts +++ b/x-pack/plugins/observability/public/utils/observability_index_patterns.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DataPublicPluginStart, IIndexPattern } from '../../../../../src/plugins/data/public'; +import { DataPublicPluginStart, IndexPattern } from '../../../../../src/plugins/data/public'; export type DataType = 'synthetics' | 'apm' | 'logs' | 'metrics' | 'rum'; @@ -47,15 +47,16 @@ export class ObservabilityIndexPatterns { fields, title: pattern, id: indexPatternList[app], + timeFieldName: '@timestamp', }); } - async getIndexPattern(app: DataType): Promise { + async getIndexPattern(app: DataType): Promise { if (!this.data) { throw new Error('data is not defined'); } try { - return await this.data?.indexPatterns.get(indexPatternList[app || 'apm']); + return await this.data?.indexPatterns.get(indexPatternList[app]); } catch (e) { return await this.createIndexPattern(app || 'apm'); } diff --git a/x-pack/plugins/observability/tsconfig.json b/x-pack/plugins/observability/tsconfig.json index 7ce621d312a55a..878197788228c3 100644 --- a/x-pack/plugins/observability/tsconfig.json +++ b/x-pack/plugins/observability/tsconfig.json @@ -7,7 +7,14 @@ "declaration": true, "declarationMap": true }, - "include": ["common/**/*", "public/**/*", "server/**/*", "typings/**/*", "../../../typings/**/*"], + "include": [ + "common/**/*", + "public/**/*", + "public/components/shared/exploratory_view/configurations/data/index_pattern.json", + "server/**/*", + "typings/**/*", + "../../../typings/**/*" + ], "references": [ { "path": "../../../src/core/tsconfig.json" }, { "path": "../../../src/plugins/data/tsconfig.json" }, From 68d8243d30ae3380e84e36c331b39c0bd8310fb1 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 29 Mar 2021 17:55:51 +0200 Subject: [PATCH 37/53] added ests --- .../columns/chart_types.test.tsx | 31 ++++++++++ .../series_editor/columns/chart_types.tsx | 2 +- .../columns/filter_expanded.test.tsx | 26 ++++++-- .../series_editor/columns/filter_expanded.tsx | 60 ++++++++++--------- .../public/hooks/use_quick_time_ranges.tsx | 2 +- 5 files changed, 86 insertions(+), 35 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.test.tsx diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.test.tsx new file mode 100644 index 00000000000000..72ca3830076722 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.test.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { screen } from '@testing-library/react'; +import { SeriesChartTypes, XYChartTypes } from './chart_types'; +import { mockUrlStorage, render } from '../../rtl_helpers'; + +describe('SeriesChartTypes', function () { + it('should render properly', async function () { + mockUrlStorage({}); + + render(); + + screen.getByText(/chart type/i); + }); + + describe('XYChartTypes', function () { + it('should render properly', async function () { + mockUrlStorage({}); + + render(); + + screen.getByText(/chart type/i); + }); + }); +}); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx index 85504bf0df5be8..a4c73d39e0fba1 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx @@ -53,7 +53,7 @@ export interface XYChartTypesProps { excludeChartTypes?: string[]; } -function XYChartTypes({ +export function XYChartTypes({ onChange, value, label, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.test.tsx index 4c1d2f01e9d682..edd5546f139409 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.test.tsx @@ -9,10 +9,7 @@ import React from 'react'; import { fireEvent, screen } from '@testing-library/react'; import { FilterExpanded } from './filter_expanded'; import { mockUrlStorage, mockUseValuesList, render } from '../../rtl_helpers'; -import { - USER_AGENT_NAME, - USER_AGENT_VERSION, -} from '../../configurations/data/elasticsearch_fieldnames'; +import { USER_AGENT_NAME } from '../../configurations/data/elasticsearch_fieldnames'; describe('FilterExpanded', function () { it('should render properly', async function () { @@ -72,4 +69,25 @@ describe('FilterExpanded', function () { }) ); }); + it('should filter display values', async function () { + mockUrlStorage({ filters: [{ field: USER_AGENT_NAME, values: ['Chrome'] }] }); + + mockUseValuesList(['Chrome', 'Firefox']); + + render( + + ); + + expect(screen.queryByText('Firefox')).toBeTruthy(); + + fireEvent.input(screen.getByRole('searchbox'), { target: { value: 'ch' } }); + + expect(screen.queryByText('Firefox')).toBeFalsy(); + expect(screen.getByText('Chrome')).toBeTruthy(); + }); }); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx index 2f97f346b2fda5..280912dd0902f7 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx @@ -46,6 +46,10 @@ export function FilterExpanded({ seriesId, field, label, goBack, nestedField }: const currFilter: UrlFilter | undefined = filters.find(({ field: fd }) => field === fd); + const displayValues = (values || []).filter((opt) => + opt.toLowerCase().includes(value.toLowerCase()) + ); + return ( <> goBack()}> @@ -64,35 +68,33 @@ export function FilterExpanded({ seriesId, field, label, goBack, nestedField }:
)} - {(values || []) - .filter((opt) => opt.toLowerCase().includes(value.toLowerCase())) - .map((opt) => ( - - - - - - - - ))} + {displayValues.map((opt) => ( + + + + + + + + ))} ); } diff --git a/x-pack/plugins/observability/public/hooks/use_quick_time_ranges.tsx b/x-pack/plugins/observability/public/hooks/use_quick_time_ranges.tsx index 8d37ab32648f20..82a0fc39b8519c 100644 --- a/x-pack/plugins/observability/public/hooks/use_quick_time_ranges.tsx +++ b/x-pack/plugins/observability/public/hooks/use_quick_time_ranges.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { useUiSetting } from '../../../../../src/plugins/kibana_react/public/ui_settings'; +import { useUiSetting } from '../../../../../src/plugins/kibana_react/public'; import { UI_SETTINGS } from '../../../../../src/plugins/data/common'; import { TimePickerQuickRange } from '../components/shared/exploratory_view/series_date_picker'; From 1f4a516c7a017c016ab1efb8060afd77f78faf88 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 29 Mar 2021 23:30:37 +0200 Subject: [PATCH 38/53] update --- .../configurations/lens_attributes.ts | 3 ++ .../exploratory_view/header/header.test.tsx | 10 +++-- .../hooks/use_init_exploratory_view.ts | 7 +--- .../hooks/use_lens_attributes.ts | 4 +- .../hooks/use_series_filters.ts | 8 +--- .../shared/exploratory_view/rtl_helpers.tsx | 12 +++--- .../columns/data_types_col.test.tsx | 15 ++++--- .../columns/report_breakdowns.test.tsx | 2 +- .../columns/report_definition_col.test.tsx | 2 +- .../columns/report_definition_col.tsx | 4 +- .../columns/report_types_col.test.tsx | 6 +-- .../columns/report_types_col.tsx | 2 +- .../series_editor/columns/chart_types.tsx | 1 + .../columns/filter_value_btn.test.tsx | 7 ++++ .../columns/filter_value_btn.tsx | 20 +++++---- .../series_editor/columns/remove_series.tsx | 10 ++++- .../shared/field_value_suggestions/index.tsx | 4 +- .../public/hooks/use_values_list.ts | 41 ++++++++++--------- 18 files changed, 92 insertions(+), 66 deletions(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts index 596fb12de38572..bbffb9deb05faa 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts @@ -23,7 +23,10 @@ import { } from '../../../../../../../../src/plugins/data/common'; import { FieldLabels } from './constants'; import { DataSeries, UrlFilter } from '../types'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths import { XYCurveType } from '../../../../../../lens/public/xy_visualization/types'; + +// eslint-disable-next-line @kbn/eslint/no-restricted-paths import { DataType } from '../../../../../../lens/public/types'; function getLayerReferenceName(layerId: string) { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.test.tsx index 337a3c7f6b6944..de6912f256be7c 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.test.tsx @@ -23,10 +23,12 @@ describe('ExploratoryViewHeader', function () { it('should be able to click open in lens', function () { mockUrlStorage({ - 'uptime-pings-histogram': { - reportType: 'upp', - breakdown: 'monitor.status', - time: { from: 'now-15m', to: 'now' }, + data: { + 'uptime-pings-histogram': { + reportType: 'upp', + breakdown: 'monitor.status', + time: { from: 'now-15m', to: 'now' }, + }, }, }); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts index 132f7a42c5d529..9f462790e8d37f 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { useMemo } from 'react'; import { useFetcher } from '../../../..'; import { IKbnUrlStateStorage } from '../../../../../../../../src/plugins/kibana_utils/public'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; @@ -31,7 +30,7 @@ export const useInitExploratoryView = (storage: IKbnUrlStateStorage) => { const firstSeries = allSeries[firstSeriesId]; - const { data: indexPattern, loading } = useFetcher(() => { + const { data: indexPattern } = useFetcher(() => { const obsvIndexP = new ObservabilityIndexPatterns(data); let reportType: DataType = 'apm'; if (firstSeries?.rt) { @@ -41,7 +40,5 @@ export const useInitExploratoryView = (storage: IKbnUrlStateStorage) => { return obsvIndexP.getIndexPattern(reportType); }, [firstSeries?.rt, data]); - return useMemo(() => { - return indexPattern; - }, [loading]); + return indexPattern; }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts index f7022a6aa34130..1c735009f66f9d 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts @@ -11,12 +11,12 @@ import { LensAttributes } from '../configurations/lens_attributes'; import { useUrlStorage } from './use_url_strorage'; import { getDefaultConfigs } from '../configurations/default_configs'; -import { IIndexPattern } from '../../../../../../../../src/plugins/data/common'; +import { IndexPattern } from '../../../../../../../../src/plugins/data/common'; import { DataSeries, SeriesUrl, UrlFilter } from '../types'; interface Props { seriesId: string; - indexPattern?: IIndexPattern | null; + indexPattern?: IndexPattern | null; } export const getFiltersFromDefs = ( diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_filters.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_filters.ts index 1016fc2025b85c..35247180c2ee5b 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_filters.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_filters.ts @@ -8,17 +8,13 @@ import { useUrlStorage } from './use_url_strorage'; import { UrlFilter } from '../types'; -interface Props { - seriesId: string; -} - -interface UpdateFilter { +export interface UpdateFilter { field: string; value: string; negate?: boolean; } -export const useSeriesFilters = ({ seriesId }: Props) => { +export const useSeriesFilters = ({ seriesId }: { seriesId: string }) => { const { series, setSeries } = useUrlStorage(seriesId); const filters = series.filters ?? []; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx index c57c8d9608c26d..19db3790d0039f 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx @@ -33,9 +33,12 @@ import * as useUrlHook from './hooks/use_url_strorage'; import * as useSeriesFilterHook from './hooks/use_series_filters'; import * as useHasDataHook from '../../../hooks/use_has_data'; import * as useValuesListHook from '../../../hooks/use_values_list'; -import { getStubIndexPattern } from '../../../../../../../src/plugins/data/public/index_patterns/index_pattern.stub'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getStubIndexPattern } from '../../../../../../../src/plugins/data/public/index_patterns/index_pattern.stub'; import indexPatternData from './configurations/data/index_pattern.json'; + +// eslint-disable-next-line @kbn/eslint/no-restricted-paths import { setIndexPatterns } from '../../../../../../../src/plugins/data/public/services'; import { IndexPatternsContract } from '../../../../../../../src/plugins/data/common/index_patterns/index_patterns'; import { UrlFilter } from './types'; @@ -56,8 +59,7 @@ interface MockKibanaProviderProps> history: History; } -interface MockRouterProps> - extends MockKibanaProviderProps {} +type MockRouterProps> = MockKibanaProviderProps; type Url = | string @@ -219,7 +221,7 @@ const getHistoryFromUrl = (url: Url) => { export const mockFetcher = (data: any) => { return jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ - data: data, + data, status: fetcherHook.FETCH_STATUS.SUCCESS, refetch: jest.fn(), }); @@ -242,7 +244,7 @@ export const mockUseValuesList = (values?: string[]) => { }; export const mockUrlStorage = ({ data, filters }: { data?: AllSeries; filters?: UrlFilter[] }) => { - let mockDataSeries = data || { + const mockDataSeries = data || { 'performance-distribution': { reportType: 'pld', breakdown: 'user_agent.name', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.test.tsx index b44f2d8c593809..1a3aea61c8f889 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { fireEvent, screen } from '@testing-library/react'; +import { act, fireEvent, screen } from '@testing-library/react'; import { mockUrlStorage, render } from '../../rtl_helpers'; import { dataTypes, DataTypesCol } from './data_types_col'; import { NEW_SERIES_KEY } from '../../hooks/use_url_strorage'; @@ -23,15 +23,17 @@ describe('DataTypesCol', function () { it('should set series on change', function () { const { setSeries } = mockUrlStorage({}); - const { getByText } = render(); + render(); - fireEvent.click(getByText(/user experience\(rum\)/i)); + act(() => { + fireEvent.click(screen.getByText(/user experience\(rum\)/i)); + }); expect(setSeries).toHaveBeenCalledTimes(1); expect(setSeries).toHaveBeenCalledWith('newSeriesKey', { dataType: 'rum' }); }); - it('should set series on change', function () { + it('should set series on change on already selected', function () { const { setSeries } = mockUrlStorage({ data: { [NEW_SERIES_KEY]: { @@ -50,7 +52,10 @@ describe('DataTypesCol', function () { }); expect(button.classList).toContain('euiButton--fill'); - fireEvent.click(button); + + act(() => { + fireEvent.click(button); + }); // undefined on click selected expect(setSeries).toHaveBeenCalledWith('newSeriesKey', { dataType: undefined }); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.test.tsx index ee2357e77c3926..dba660fff9c363 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.test.tsx @@ -12,7 +12,7 @@ import { getDefaultConfigs } from '../../configurations/default_configs'; import { mockIndexPattern, mockUrlStorage } from '../../rtl_helpers'; import { NEW_SERIES_KEY } from '../../hooks/use_url_strorage'; import { ReportBreakdowns } from './report_breakdowns'; -import { USER_AGENT_NAME, USER_AGENT_OS } from '../../configurations/data/elasticsearch_fieldnames'; +import { USER_AGENT_OS } from '../../configurations/data/elasticsearch_fieldnames'; describe('Series Builder ReportBreakdowns', function () { const dataViewSeries = getDefaultConfigs({ diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.test.tsx index d09421641555af..2fda5811541660 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.test.tsx @@ -49,7 +49,7 @@ describe('Series Builder ReportDefinitionCol', function () { it('should be able to remove selected definition', function () { render(); - const removeBtn = screen.getByRole('button', { name: /elastic-co/i }); + const removeBtn = screen.getByText(/elastic-co/i); fireEvent.click(removeBtn); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx index 9bde3759a5756c..ce11c869de0ab6 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx @@ -72,8 +72,8 @@ export function ReportDefinitionCol({ dataViewSeries }: { dataViewSeries: DataSe color="hollow" onClick={() => onRemove(field)} iconOnClick={() => onRemove(field)} - iconOnClickAriaLabel={''} - onClickAriaLabel={''} + iconOnClickAriaLabel={'Click to remove'} + onClickAriaLabel={'Click to remove'} > {rtd?.[field]} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.test.tsx index 41f234f76554ed..567e2654130e80 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.test.tsx @@ -13,7 +13,7 @@ import { ReportTypes } from '../series_builder'; describe('ReportTypesCol', function () { it('should render properly', function () { - render(); + render(); screen.getByText('Performance distribution'); screen.getByText('KPI over time'); }); @@ -25,7 +25,7 @@ describe('ReportTypesCol', function () { it('should set series on change', function () { const { setSeries } = mockUrlStorage({}); - render(); + render(); fireEvent.click(screen.getByText(/monitor duration/i)); @@ -50,7 +50,7 @@ describe('ReportTypesCol', function () { }, }); - render(); + render(); const button = screen.getByRole('button', { name: /pings histogram/i, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx index 8bcc2c09a1426f..5c94a5bca60f85 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx @@ -6,10 +6,10 @@ */ import React from 'react'; +import { i18n } from '@kbn/i18n'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { ReportViewTypeId, SeriesUrl } from '../../types'; import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_strorage'; -import { i18n } from '@kbn/i18n'; interface Props { reportTypes: Array<{ id: ReportViewTypeId; label: string }>; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx index a4c73d39e0fba1..a8ff4e0417db91 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx @@ -96,6 +96,7 @@ export function XYChartTypes({ ) : ( id === value)?.label} iconType={vizTypes.find(({ id }) => id === value)?.icon!} onClick={() => { setIsOpen((prevState) => !prevState); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.test.tsx index 3ef60c65823cf3..7f76c9ea999eed 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.test.tsx @@ -1,3 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + import React from 'react'; import { fireEvent, screen } from '@testing-library/react'; import { FilterValueButton } from './filter_value_btn'; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx index f75b67b96ee54e..901f1e7ef9ef31 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { EuiFilterButton, hexToRgb } from '@elastic/eui'; import { useIndexPatternContext } from '../../hooks/use_default_index_pattern'; import { useUrlStorage } from '../../hooks/use_url_strorage'; @@ -74,6 +74,16 @@ export function FilterValueButton({ const forceOpenNested = isNestedOpen?.value === value && isNestedOpen.negate === negate; + const filters = useMemo(() => { + return [ + { + term: { + [field]: value, + }, + }, + ]; + }, [field, value]); + return nestedField && forceOpenNested ? ( { removeSeries(series.id); }; - return ; + return ( + + ); } diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.tsx index abce995e5cbf4e..9ffe2584c8d752 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.tsx @@ -10,14 +10,14 @@ import React, { useState } from 'react'; import { useDebounce } from 'react-use'; import { PopoverAnchorPosition } from '@elastic/eui/src/components/popover/popover'; import { useValuesList } from '../../../hooks/use_values_list'; -import { IIndexPattern } from '../../../../../../../src/plugins/data/common'; +import { IndexPattern } from '../../../../../../../src/plugins/data/common'; import { FieldValueSelection } from './field_value_selection'; import { ESFilter } from '../../../../../../../typings/elasticsearch'; export interface FieldValueSuggestionsProps { value?: string; label: string; - indexPattern: IIndexPattern; + indexPattern: IndexPattern; sourceField: string; onChange: (val?: string) => void; filters: ESFilter[]; diff --git a/x-pack/plugins/observability/public/hooks/use_values_list.ts b/x-pack/plugins/observability/public/hooks/use_values_list.ts index 1ce548e2d23da8..e17f515ed6cb9e 100644 --- a/x-pack/plugins/observability/public/hooks/use_values_list.ts +++ b/x-pack/plugins/observability/public/hooks/use_values_list.ts @@ -5,16 +5,16 @@ * 2.0. */ -import { IIndexPattern } from '../../../../../src/plugins/data/common'; +import { IndexPattern } from '../../../../../src/plugins/data/common'; import { useKibana } from '../../../../../src/plugins/kibana_react/public'; import { DataPublicPluginStart } from '../../../../../src/plugins/data/public'; import { useFetcher } from './use_fetcher'; import { ESFilter } from '../../../../../typings/elasticsearch'; -interface Props { +export interface Props { sourceField: string; query?: string; - indexPattern: IIndexPattern; + indexPattern: IndexPattern; filters?: ESFilter[]; time?: { from: string; to: string }; } @@ -23,13 +23,15 @@ export const useValuesList = ({ sourceField, indexPattern, query = '', - filters = [], + filters, time, }: Props): { values: string[]; loading?: boolean } => { const { services: { data }, } = useKibana<{ data: DataPublicPluginStart }>(); + const { from, to } = time ?? {}; + const { data: values, loading } = useFetcher(() => { if (!sourceField || !indexPattern) { return []; @@ -37,25 +39,24 @@ export const useValuesList = ({ return data.autocomplete.getValueSuggestions({ indexPattern, query: query || '', - useTimeRange: !time, - field: indexPattern.fields.find(({ name }) => name === sourceField)!, - boolFilter: time - ? [ - ...filters, - { - range: { - '@timestamp': { - gte: time.from, - lte: time.to, + useTimeRange: !(from && to), + field: indexPattern.getFieldByName(sourceField)!, + boolFilter: + from && to + ? [ + ...(filters || []), + { + range: { + '@timestamp': { + gte: from, + lte: to, + }, }, }, - }, - ] - : filters, + ] + : filters || [], }); - }, []); - // FIXME - // }, [sourceField, query, time, data.autocomplete, indexPattern]); + }, [query, sourceField, data.autocomplete, indexPattern, from, to, filters]); return { values: values as string[], loading }; }; From 0557ad33c11dacb057db0a60846e0fe31aafddee Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 30 Mar 2021 09:48:37 +0200 Subject: [PATCH 39/53] update --- .../app/RumDashboard/PageViewsTrend/index.tsx | 33 +++----------- .../projections/rum_page_load_transactions.ts | 11 +++++ .../common/charts/ping_histogram.tsx | 44 +++++-------------- 3 files changed, 26 insertions(+), 62 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx index 83ce2bc0320397..85df933bcb9e29 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx @@ -6,30 +6,25 @@ */ import React, { useState } from 'react'; -import { - EuiButton, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiTitle, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { useFetcher } from '../../../../hooks/use_fetcher'; import { I18LABELS } from '../translations'; import { BreakdownFilter } from '../Breakdowns/BreakdownFilter'; import { PageViewsChart } from '../Charts/PageViewsChart'; import { BreakdownItem } from '../../../../../typings/ui_filters'; -import { createExploratoryViewUrl } from '../../../../../../observability/public'; export function PageViewsTrend() { const { urlParams, uiFilters } = useUrlParams(); - const { start, end, rangeFrom, rangeTo, searchTerm, serviceName } = urlParams; + const { start, end, searchTerm } = urlParams; const [breakdown, setBreakdown] = useState(null); const { data, status } = useFetcher( (callApmApi) => { + const { serviceName } = uiFilters; + if (start && end && serviceName) { return callApmApi({ endpoint: 'GET /api/apm/rum-client/page-view-trends', @@ -50,20 +45,7 @@ export function PageViewsTrend() { } return Promise.resolve(undefined); }, - [end, start, uiFilters, breakdown, searchTerm, serviceName] - ); - - const analyzeHref = createExploratoryViewUrl( - { - 'page-views': { - reportType: 'kpi', - reportDefinitions: { - 'service.name': serviceName as string, - }, - time: { from: rangeFrom!, to: rangeTo! }, - }, - }, - '' + [end, start, uiFilters, breakdown, searchTerm] ); return ( @@ -81,11 +63,6 @@ export function PageViewsTrend() { dataTestSubj={'pvBreakdownFilter'} /> - - - Analyze - - diff --git a/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts b/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts index 143d8845912fd5..29fc85128ff3f0 100644 --- a/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts +++ b/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts @@ -31,6 +31,17 @@ export function getRumPageLoadTransactionsProjection({ filter: [ ...rangeQuery(start, end), { term: { [TRANSACTION_TYPE]: TRANSACTION_PAGE_LOAD } }, + ...(checkFetchStartFieldExists + ? [ + { + // Adding this filter to cater for some inconsistent rum data + // not available on aggregated transactions + exists: { + field: 'transaction.marks.navigationTiming.fetchStart', + }, + }, + ] + : []), ...(urlQuery ? [ { diff --git a/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx b/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx index 578870e7fdaa9d..db4e7b968c2dbd 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx +++ b/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx @@ -16,8 +16,7 @@ import { XYChartElementEvent, ElementClickListener, } from '@elastic/charts'; -import rison from 'rison-node'; -import { EuiTitle, EuiSpacer, EuiFlexItem, EuiFlexGroup, EuiButton } from '@elastic/eui'; +import { EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useContext } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -27,7 +26,7 @@ import { getChartDateLabel } from '../../../lib/helper'; import { ChartWrapper } from './chart_wrapper'; import { UptimeThemeContext } from '../../../contexts'; import { HistogramResult } from '../../../../common/runtime_types'; -import { useGetUrlParams, useUrlParams } from '../../../hooks'; +import { useUrlParams } from '../../../hooks'; import { ChartEmptyState } from './chart_empty_state'; import { getDateRangeFromChartElement } from './utils'; import { STATUS_DOWN_LABEL, STATUS_UP_LABEL } from '../translations'; @@ -70,8 +69,6 @@ export const PingHistogramComponent: React.FC = ({ chartTheme, } = useContext(UptimeThemeContext); - const { dateRangeStart, dateRangeEnd } = useGetUrlParams(); - const [, updateUrlParams] = useUrlParams(); let content: JSX.Element | undefined; @@ -182,37 +179,16 @@ export const PingHistogramComponent: React.FC = ({ ); } - const analyzeHref = { - ['uptime-pings-histogram']: { - rt: 'upp', - time: { from: dateRangeStart, to: dateRangeEnd }, - bd: 'monitor.status', - }, - }; - return ( <> - - - -

- -

-
-
- - - Analyze - - -
- + +

+ +

+
{content} From e4ce7bd12df605d25bc3a0fe53db431376604607 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 30 Mar 2021 15:14:16 +0200 Subject: [PATCH 40/53] update labels --- .../shared/exploratory_view/header/header.tsx | 12 +++++-- .../series_builder/series_builder.tsx | 35 ++++++++++++++----- .../series_editor/columns/chart_types.tsx | 4 ++- .../columns/metric_selection.tsx | 17 ++++++--- .../series_editor/columns/series_filter.tsx | 9 +++-- .../series_editor/series_editor.tsx | 29 +++++++++++---- x-pack/plugins/observability/public/plugin.ts | 2 +- 7 files changed, 83 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx index 25011e2713125c..bda3566c766021 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import { i18n } from '@kbn/i18n'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { TypedLensByValueInput } from '../../../../../../lens/public'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; @@ -29,7 +30,12 @@ export function ExploratoryViewHeader({ seriesId, lensAttributes }: Props) { -

{DataViewLabels[series.reportType] ?? 'Exploratory view'}

+

+ {DataViewLabels[series.reportType] ?? + i18n.translate('xpack.observability.expView.heading.label', { + defaultMessage: 'Exploratory view', + })} +

@@ -47,7 +53,9 @@ export function ExploratoryViewHeader({ seriesId, lensAttributes }: Props) { } }} > - Open in Lens + {i18n.translate('xpack.observability.expView.heading.openInLens', { + defaultMessage: 'Open in Lens', + })}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx index 70c0bdf045bbf0..983c18af031d09 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx @@ -7,6 +7,7 @@ import React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; import { EuiButton, EuiBasicTable, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import styled from 'styled-components'; import { AppDataType, ReportViewTypeId, ReportViewTypes, SeriesUrl } from '../types'; @@ -64,19 +65,25 @@ export function SeriesBuilder() { const columns = [ { - name: 'DataType', + name: i18n.translate('xpack.observability.expView.seriesBuilder.dataType', { + defaultMessage: 'Data Type', + }), width: '20%', render: (val: string) => , }, { - name: 'Report', + name: i18n.translate('xpack.observability.expView.seriesBuilder.report', { + defaultMessage: 'Report', + }), width: '20%', render: (val: string) => ( ), }, { - name: 'Definition', + name: i18n.translate('xpack.observability.expView.seriesBuilder.definition', { + defaultMessage: 'Definition', + }), width: '30%', render: (val: string) => reportType && indexPattern ? ( @@ -84,13 +91,17 @@ export function SeriesBuilder() { ) : null, }, { - name: 'Filters', + name: i18n.translate('xpack.observability.expView.seriesBuilder.filters', { + defaultMessage: 'Filters', + }), width: '25%', render: (val: string) => reportType && indexPattern ? : null, }, { - name: 'Breakdowns', + name: i18n.translate('xpack.observability.expView.seriesBuilder.breakdown', { + defaultMessage: 'Breakdowns', + }), width: '25%', field: 'id', render: (val: string) => @@ -138,19 +149,23 @@ export function SeriesBuilder() { - Add + {i18n.translate('xpack.observability.expView.seriesBuilder.add', { + defaultMessage: 'Add', + })} { removeSeries(NEW_SERIES_KEY); setIsFlyoutVisible(false); }} > - Cancel + {i18n.translate('xpack.observability.expView.seriesBuilder.cancel', { + defaultMessage: 'Cancel', + })} @@ -169,7 +184,9 @@ export function SeriesBuilder() { onClick={() => setIsFlyoutVisible((prevState) => !prevState)} disabled={allSeriesIds.length > 0} > - {'Add series'} + {i18n.translate('xpack.observability.expView.seriesBuilder.addSeries', { + defaultMessage: 'Add series', + })} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx index a8ff4e0417db91..c0f1c5ecbd001c 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx @@ -40,7 +40,9 @@ export function SeriesChartTypes({ onChange={onChange} value={seriesType} excludeChartTypes={['bar_percentage_stacked']} - label={'Chart type'} + label={i18n.translate('xpack.observability.expView.chartTypes.label', { + defaultMessage: 'Chart type', + })} /> ); } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.tsx index eaf4a48879781d..e01e371b5eeeb9 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.tsx @@ -6,6 +6,7 @@ */ import React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; import { EuiButton, EuiButtonGroup, EuiPopover } from '@elastic/eui'; import { useUrlStorage } from '../../hooks/use_url_strorage'; import { OperationType } from '../../../../../../../lens/public'; @@ -13,19 +14,27 @@ import { OperationType } from '../../../../../../../lens/public'; const toggleButtons = [ { id: `avg`, - label: 'Average', + label: i18n.translate('xpack.observability.expView.metricsSelect.average', { + defaultMessage: 'Average', + }), }, { id: `median`, - label: 'Median', + label: i18n.translate('xpack.observability.expView.metricsSelect.median', { + defaultMessage: 'Median', + }), }, { id: `95th`, - label: '95th Percentile', + label: i18n.translate('xpack.observability.expView.metricsSelect.9thPercentile', { + defaultMessage: '95th Percentile', + }), }, { id: `99th`, - label: '99th Percentile', + label: i18n.translate('xpack.observability.expView.metricsSelect.99thPercentile', { + defaultMessage: '99th Percentile', + }), }, ]; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx index 6109322052d93b..24b65d2adb38e3 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import React, { useState, Fragment } from 'react'; import { EuiButton, @@ -58,7 +59,9 @@ export function SeriesFilter({ series, isNew, seriesId, defaultFilters = [] }: P isDisabled={disabled} size="s" > - Add filter + {i18n.translate('xpack.observability.expView.seriesEditor.addFilter', { + defaultMessage: 'Add filter', + })} ); @@ -125,7 +128,9 @@ export function SeriesFilter({ series, isNew, seriesId, defaultFilters = [] }: P isDisabled={disabled} size="s" > - Clear filters + {i18n.translate('xpack.observability.expView.seriesEditor.clearFilter', { + defaultMessage: 'Clear filters', + })} )} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx index 7603e944a5bdd4..2d423c9aee3fcd 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import { i18n } from '@kbn/i18n'; import { EuiBasicTable, EuiIcon, EuiSpacer, EuiText } from '@elastic/eui'; import { SeriesFilter } from './columns/series_filter'; import { ActionsCol } from './columns/actions_col'; @@ -23,7 +24,9 @@ export function SeriesEditor() { const columns = [ { - name: 'Name', + name: i18n.translate('xpack.observability.expView.seriesEditor.name', { + defaultMessage: 'Name', + }), field: 'id', width: '15%', render: (val: string) => ( @@ -36,7 +39,9 @@ export function SeriesEditor() { ...(firstSeriesId !== NEW_SERIES_KEY ? [ { - name: 'Filter', + name: i18n.translate('xpack.observability.expView.seriesEditor.filters', { + defaultMessage: 'Filters', + }), field: 'defaultFilters', width: '25%', render: (defaultFilters: string[], series: DataSeries) => ( @@ -44,7 +49,9 @@ export function SeriesEditor() { ), }, { - name: 'Breakdowns', + name: i18n.translate('xpack.observability.expView.seriesEditor.breakdowns', { + defaultMessage: 'Breakdowns', + }), field: 'breakdowns', width: '15%', render: (val: string[], item: DataSeries) => ( @@ -61,7 +68,13 @@ export function SeriesEditor() { ] : []), { - name:
Time
, + name: ( +
+ {i18n.translate('xpack.observability.expView.seriesEditor.time', { + defaultMessage: 'Time', + })} +
+ ), width: '20%', field: 'id', align: 'right' as const, @@ -71,7 +84,9 @@ export function SeriesEditor() { ...(firstSeriesId !== NEW_SERIES_KEY ? [ { - name: 'Actions', + name: i18n.translate('xpack.observability.expView.seriesEditor.actions', { + defaultMessage: 'Actions', + }), align: 'center' as const, width: '5%', field: 'id', @@ -108,7 +123,9 @@ export function SeriesEditor() { rowHeader="firstName" columns={columns} rowProps={() => (firstSeriesId === NEW_SERIES_KEY ? {} : { height: 100 })} - noItemsMessage={'No series found, please add a series.'} + noItemsMessage={i18n.translate('xpack.observability.expView.seriesEditor.notFound', { + defaultMessage: 'No series found, please add a series.', + })} cellProps={{ style: { verticalAlign: 'top', diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index 11b78a30381887..cd3cb66187c6f4 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -56,7 +56,7 @@ export class Plugin plugins: ObservabilityPublicPluginsSetup ) { const category = DEFAULT_APP_CATEGORIES.observability; - const euiIconType = 'logo-observability'; + const euiIconType = 'logoObservability'; const mount = async (params: AppMountParameters) => { // Load application bundle const { renderApp } = await import('./application'); From 1710e5d4f8c23a29251391f0901c817b08887022 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 31 Mar 2021 09:03:46 +0200 Subject: [PATCH 41/53] PR feedback --- .../components/empty_view.tsx | 2 +- .../components/filter_label.tsx | 2 +- .../configurations/cpu_usage_config.ts | 2 +- .../data/elasticsearch_fieldnames.ts | 1 + ...x_pattern.json => test_index_pattern.json} | 0 .../configurations/kpi_trends_config.ts | 40 ++++++++------ .../configurations/lens_attributes.ts | 11 +--- .../configurations/logs_frequency_config.ts | 2 +- .../configurations/memory_usage_config.ts | 2 +- .../configurations/monitor_duration_config.ts | 2 +- .../configurations/monitor_pings_config.ts | 2 +- .../configurations/network_activity_config.ts | 2 +- .../configurations/performance_dist_config.ts | 55 ++++++++++--------- .../configurations/service_latency_config.ts | 2 +- .../service_throughput_config.ts | 2 +- .../exploratory_view.test.tsx | 10 +--- .../exploratory_view/exploratory_view.tsx | 2 +- .../shared/exploratory_view/rtl_helpers.tsx | 5 +- .../columns/data_types_col.test.tsx | 10 +--- .../series_builder/columns/data_types_col.tsx | 10 ++-- .../series_editor/columns/actions_col.tsx | 4 +- .../shared/exploratory_view/types.ts | 2 +- x-pack/plugins/observability/tsconfig.json | 2 +- 23 files changed, 85 insertions(+), 87 deletions(-) rename x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/data/{index_pattern.json => test_index_pattern.json} (100%) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx index 0f7505d9bbef5f..17f1b039667d0b 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx @@ -28,5 +28,5 @@ export function EmptyView() { const Wrapper = styled.div` text-align: center; opacity: 0.4; - height: 550pz; + height: 550px; `; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx index c4d1c6958fd8cb..3d6dc5b3f2bf51 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx @@ -71,7 +71,7 @@ export function FilterLabel({ return indexPattern ? ( { removeFilter(field, value, false); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/cpu_usage_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/cpu_usage_config.ts index 7c210ebc7a0034..5a4fb2aa3a6a59 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/cpu_usage_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/cpu_usage_config.ts @@ -27,7 +27,7 @@ export function getCPUUsageLensConfig({ seriesId }: Props): DataSeries { sourceField: 'system.cpu.user.pct', label: 'CPU Usage %', }, - metricType: true, + hasMetricType: true, defaultFilters: [], breakdowns: ['host.hostname'], filters: [], diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/data/elasticsearch_fieldnames.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/data/elasticsearch_fieldnames.ts index 8a3218d64a01ee..3faf54fff31404 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/data/elasticsearch_fieldnames.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/data/elasticsearch_fieldnames.ts @@ -112,6 +112,7 @@ export const KUBERNETES = 'kubernetes'; export const POD_NAME = 'kubernetes.pod.name'; export const CLIENT_GEO_COUNTRY_ISO_CODE = 'client.geo.country_iso_code'; +export const CLIENT_GEO_COUNTRY_NAME = 'client.geo.country_name'; // RUM Labels export const TRANSACTION_URL = 'url.full'; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/data/index_pattern.json b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/data/test_index_pattern.json similarity index 100% rename from x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/data/index_pattern.json rename to x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/data/test_index_pattern.json diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts index c40a71960a5ac4..a967a8824bca7b 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts @@ -8,6 +8,17 @@ import { ConfigProps, DataSeries } from '../types'; import { FieldLabels } from './constants'; import { buildPhraseFilter } from './utils'; +import { + CLIENT_GEO_COUNTRY_NAME, + PROCESSOR_EVENT, + SERVICE_ENVIRONMENT, + SERVICE_NAME, + TRANSACTION_TYPE, + USER_AGENT_DEVICE, + USER_AGENT_NAME, + USER_AGENT_OS, + USER_AGENT_VERSION, +} from './data/elasticsearch_fieldnames'; export function getKPITrendsLensConfig({ seriesId, indexPattern }: ConfigProps): DataSeries { return { @@ -22,34 +33,29 @@ export function getKPITrendsLensConfig({ seriesId, indexPattern }: ConfigProps): operationType: 'count', label: 'Page views', }, - metricType: false, + hasMetricType: false, defaultFilters: [ - 'user_agent.os.name', - 'client.geo.country_name', - 'user_agent.device.name', + USER_AGENT_OS, + CLIENT_GEO_COUNTRY_NAME, + USER_AGENT_DEVICE, { - field: 'user_agent.name', - nested: 'user_agent.version', + field: USER_AGENT_NAME, + nested: USER_AGENT_VERSION, }, ], - breakdowns: [ - 'user_agent.name', - 'user_agent.os.name', - 'client.geo.country_name', - 'user_agent.device.name', - ], + breakdowns: [USER_AGENT_NAME, USER_AGENT_OS, CLIENT_GEO_COUNTRY_NAME, USER_AGENT_DEVICE], filters: [ - buildPhraseFilter('transaction.type', 'page-load', indexPattern), - buildPhraseFilter('processor.event', 'transaction', indexPattern), + buildPhraseFilter(TRANSACTION_TYPE, 'page-load', indexPattern), + buildPhraseFilter(PROCESSOR_EVENT, 'transaction', indexPattern), ], - labels: { ...FieldLabels, 'service.name': 'Web Application' }, + labels: { ...FieldLabels, SERVICE_NAME: 'Web Application' }, reportDefinitions: [ { - field: 'service.name', + field: SERVICE_NAME, required: true, }, { - field: 'service.environment', + field: SERVICE_ENVIRONMENT, }, { field: 'Business.KPI', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts index bbffb9deb05faa..589a93d1600686 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts @@ -15,6 +15,8 @@ import { SeriesType, TypedLensByValueInput, XYState, + XYCurveType, + DataType, } from '../../../../../../lens/public'; import { buildPhraseFilter, @@ -23,11 +25,6 @@ import { } from '../../../../../../../../src/plugins/data/common'; import { FieldLabels } from './constants'; import { DataSeries, UrlFilter } from '../types'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { XYCurveType } from '../../../../../../lens/public/xy_visualization/types'; - -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { DataType } from '../../../../../../lens/public/types'; function getLayerReferenceName(layerId: string) { return `indexpattern-datasource-layer-${layerId}`; @@ -170,7 +167,7 @@ export class LensAttributes { dataType: 'number', isBucketed: false, label: 'Count of records', - operationType: 'count' as any, + operationType: 'count', scale: 'ratio', sourceField: 'Records', ...this.reportViewConfig.yAxisColumn, @@ -193,13 +190,11 @@ export class LensAttributes { legend: { isVisible: true, position: 'right' }, valueLabels: 'hide', fittingFunction: 'Linear', - // fittingFunction: 'None', curveType: 'CURVE_MONOTONE_X' as XYCurveType, axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true }, gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, preferredSeriesType: 'line', - // preferredSeriesType: 'bar_stacked', layers: [ { accessors: ['y-axis-column'], diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/logs_frequency_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/logs_frequency_config.ts index 7c2db7a80e428a..68e5e697d2f9d1 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/logs_frequency_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/logs_frequency_config.ts @@ -24,7 +24,7 @@ export function getLogsFrequencyLensConfig({ seriesId }: Props): DataSeries { yAxisColumn: { operationType: 'count', }, - metricType: false, + hasMetricType: false, defaultFilters: [], breakdowns: ['agent.hostname'], filters: [], diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/memory_usage_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/memory_usage_config.ts index 247e4909fd6067..579372ed86fa74 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/memory_usage_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/memory_usage_config.ts @@ -27,7 +27,7 @@ export function getMemoryUsageLensConfig({ seriesId }: Props): DataSeries { sourceField: 'system.memory.used.pct', label: 'Memory Usage %', }, - metricType: true, + hasMetricType: true, defaultFilters: [], breakdowns: ['host.hostname'], filters: [], diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_duration_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_duration_config.ts index c07cb5e12b806b..aa9b8b94c6d862 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_duration_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_duration_config.ts @@ -27,7 +27,7 @@ export function getMonitorDurationConfig({ seriesId }: Props): DataSeries { sourceField: 'monitor.duration.us', label: 'Monitor duration (ms)', }, - metricType: true, + hasMetricType: true, defaultFilters: ['monitor.type', 'observer.geo.name', 'tags'], breakdowns: [ 'observer.geo.name', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_pings_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_pings_config.ts index 2df1c901f60d9b..72968626e934bf 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_pings_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_pings_config.ts @@ -25,7 +25,7 @@ export function getMonitorPingsConfig({ seriesId }: Props): DataSeries { operationType: 'count', label: 'Monitor pings', }, - metricType: false, + hasMetricType: false, defaultFilters: ['observer.geo.name'], breakdowns: ['monitor.status', 'observer.geo.name', 'monitor.type'], filters: [], diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/network_activity_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/network_activity_config.ts index 8e317697822a76..63cdd0ec8bd605 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/network_activity_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/network_activity_config.ts @@ -26,7 +26,7 @@ export function getNetworkActivityLensConfig({ seriesId }: Props): DataSeries { operationType: 'avg' as OperationType, sourceField: 'system.memory.used.pct', }, - metricType: true, + hasMetricType: true, defaultFilters: [], breakdowns: ['host.hostname'], filters: [], diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/performance_dist_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/performance_dist_config.ts index 1320d3453d99c8..41617304c9f3df 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/performance_dist_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/performance_dist_config.ts @@ -8,14 +8,23 @@ import { ConfigProps, DataSeries } from '../types'; import { FieldLabels } from './constants'; import { buildPhraseFilter } from './utils'; - -export const TRANSACTION_DURATION = 'transaction.duration.us'; -export const FCP_FIELD = 'transaction.marks.agent.firstContentfulPaint'; -export const LCP_FIELD = 'transaction.marks.agent.largestContentfulPaint'; -export const TBT_FIELD = 'transaction.experience.tbt'; -export const FID_FIELD = 'transaction.experience.fid'; -export const CLS_FIELD = 'transaction.experience.cls'; -export const SERVICE_NAME = 'service.name'; +import { + CLIENT_GEO_COUNTRY_NAME, + CLS_FIELD, + FCP_FIELD, + FID_FIELD, + LCP_FIELD, + PROCESSOR_EVENT, + SERVICE_ENVIRONMENT, + SERVICE_NAME, + TBT_FIELD, + TRANSACTION_DURATION, + TRANSACTION_TYPE, + USER_AGENT_DEVICE, + USER_AGENT_NAME, + USER_AGENT_OS, + USER_AGENT_VERSION, +} from './data/elasticsearch_fieldnames'; export function getPerformanceDistLensConfig({ seriesId, indexPattern }: ConfigProps): DataSeries { return { @@ -30,29 +39,24 @@ export function getPerformanceDistLensConfig({ seriesId, indexPattern }: ConfigP operationType: 'count', label: 'Pages loaded', }, - metricType: false, + hasMetricType: false, defaultFilters: [ - 'user_agent.os.name', - 'client.geo.country_name', - 'user_agent.device.name', + USER_AGENT_OS, + CLIENT_GEO_COUNTRY_NAME, + USER_AGENT_DEVICE, { - field: 'user_agent.name', - nested: 'user_agent.version', + field: USER_AGENT_NAME, + nested: USER_AGENT_VERSION, }, ], - breakdowns: [ - 'user_agent.name', - 'user_agent.os.name', - 'client.geo.country_name', - 'user_agent.device.name', - ], + breakdowns: [USER_AGENT_NAME, USER_AGENT_OS, CLIENT_GEO_COUNTRY_NAME, USER_AGENT_DEVICE], reportDefinitions: [ { - field: 'service.name', + field: SERVICE_NAME, required: true, }, { - field: 'service.environment', + field: SERVICE_ENVIRONMENT, }, { field: 'performance.metric', @@ -62,6 +66,7 @@ export function getPerformanceDistLensConfig({ seriesId, indexPattern }: ConfigP { label: 'Page load time', field: TRANSACTION_DURATION }, { label: 'First contentful paint', field: FCP_FIELD }, { label: 'Total blocking time', field: TBT_FIELD }, + // FIXME, review if we need these descriptions { label: 'Largest contentful paint', field: LCP_FIELD, description: 'Core web vital' }, { label: 'First input delay', field: FID_FIELD, description: 'Core web vital' }, { label: 'Cumulative layout shift', field: CLS_FIELD, description: 'Core web vital' }, @@ -69,12 +74,12 @@ export function getPerformanceDistLensConfig({ seriesId, indexPattern }: ConfigP }, ], filters: [ - buildPhraseFilter('transaction.type', 'page-load', indexPattern), - buildPhraseFilter('processor.event', 'transaction', indexPattern), + buildPhraseFilter(TRANSACTION_TYPE, 'page-load', indexPattern), + buildPhraseFilter(PROCESSOR_EVENT, 'transaction', indexPattern), ], labels: { ...FieldLabels, - 'service.name': 'Web Application', + [SERVICE_NAME]: 'Web Application', [TRANSACTION_DURATION]: 'Page load time', }, }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts index 3883e82f93c419..a31679c61a4aba 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts @@ -24,7 +24,7 @@ export function getServiceLatencyLensConfig({ seriesId, indexPattern }: ConfigPr sourceField: 'transaction.duration.us', label: 'Latency', }, - metricType: true, + hasMetricType: true, defaultFilters: [ 'user_agent.name', 'user_agent.os.name', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts index f0b0626a27c44d..32cae2167ddf02 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts @@ -27,7 +27,7 @@ export function getServiceThroughputLensConfig({ sourceField: 'transaction.duration.us', label: 'Throughput', }, - metricType: true, + hasMetricType: true, defaultFilters: [ 'user_agent.name', 'user_agent.os.name', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx index fb2099b9538116..7e99874f557b30 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { act, within } from '@testing-library/react'; +import { within } from '@testing-library/react'; import { fireEvent, screen, waitFor } from '@testing-library/dom'; import { render, mockUrlStorage, mockCore } from './rtl_helpers'; import { ExploratoryView } from './exploratory_view'; @@ -52,9 +52,7 @@ describe('ExploratoryView', () => { it('can add, cancel new series', async () => { render(); - await act(async () => { - await fireEvent.click(screen.getByText(/add series/i)); - }); + await fireEvent.click(screen.getByText(/add series/i)); await waitFor(() => { screen.getByText(/open in lens/i); @@ -64,9 +62,7 @@ describe('ExploratoryView', () => { within(button).getByText(/add/i); }); - await act(async () => { - await fireEvent.click(screen.getByText(/cancel/i)); - }); + await fireEvent.click(screen.getByText(/cancel/i)); await waitFor(() => { screen.getByText(/add series/i); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx index c94cdaa6148925..1d4567a4371a6e 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx @@ -54,7 +54,7 @@ export function ExploratoryView() { {lensAttributes && seriesId && series?.reportType && series?.time ? ( ({ ); } -configure({ testIdAttribute: 'data-test-subj' }); /* Custom react testing library render */ export function render( diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.test.tsx index 1a3aea61c8f889..d33d8515d3bee0 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { act, fireEvent, screen } from '@testing-library/react'; +import { fireEvent, screen } from '@testing-library/react'; import { mockUrlStorage, render } from '../../rtl_helpers'; import { dataTypes, DataTypesCol } from './data_types_col'; import { NEW_SERIES_KEY } from '../../hooks/use_url_strorage'; @@ -25,9 +25,7 @@ describe('DataTypesCol', function () { render(); - act(() => { - fireEvent.click(screen.getByText(/user experience\(rum\)/i)); - }); + fireEvent.click(screen.getByText(/user experience\(rum\)/i)); expect(setSeries).toHaveBeenCalledTimes(1); expect(setSeries).toHaveBeenCalledWith('newSeriesKey', { dataType: 'rum' }); @@ -53,9 +51,7 @@ describe('DataTypesCol', function () { expect(button.classList).toContain('euiButton--fill'); - act(() => { - fireEvent.click(button); - }); + fireEvent.click(button); // undefined on click selected expect(setSeries).toHaveBeenCalledWith('newSeriesKey', { dataType: undefined }); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx index 05ae20b07288a5..7ea44e66a721af 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx @@ -35,16 +35,16 @@ export function DataTypesCol() { return ( - {dataTypes.map(({ id: dt, label }) => ( - + {dataTypes.map(({ id: dataTypeId, label }) => ( + { - onDataTypeChange(dt === selectedDataType ? undefined : dt); + onDataTypeChange(dataTypeId === selectedDataType ? undefined : dataTypeId); }} > {label} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/actions_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/actions_col.tsx index a9f36020e211ba..c6209381a4da13 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/actions_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/actions_col.tsx @@ -21,9 +21,9 @@ export function ActionsCol({ series }: Props) { - {series.metricType && ( + {series.hasMetricType && ( - + )} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts index 8331962be0b236..444e0ddaecb4a1 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts @@ -57,7 +57,7 @@ export interface DataSeries { filters?: PersistableFilter[]; reportDefinitions: ReportDefinition[]; labels: Record; - metricType: boolean; + hasMetricType: boolean; palette?: PaletteOutput; } diff --git a/x-pack/plugins/observability/tsconfig.json b/x-pack/plugins/observability/tsconfig.json index 878197788228c3..f55ae640a80263 100644 --- a/x-pack/plugins/observability/tsconfig.json +++ b/x-pack/plugins/observability/tsconfig.json @@ -10,7 +10,7 @@ "include": [ "common/**/*", "public/**/*", - "public/components/shared/exploratory_view/configurations/data/index_pattern.json", + "public/**/*.json", "server/**/*", "typings/**/*", "../../../typings/**/*" From f162f9627c35943edb84a11b8bc5787df52ce469 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 31 Mar 2021 09:28:17 +0200 Subject: [PATCH 42/53] update --- .../series_editor/columns/breakdowns.tsx | 7 ++----- .../series_editor/columns/filter_value_btn.tsx | 10 +++++++--- .../series_editor/columns/remove_series.tsx | 5 ++++- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx index 4d10b464249a3b..0d34d7245725ad 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect } from 'react'; +import React from 'react'; import { EuiSuperSelect } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FieldLabels } from '../../configurations/constants'; @@ -17,8 +17,6 @@ interface Props { } export function Breakdowns({ seriesId, breakdowns = [] }: Props) { - const items = breakdowns.map((breakdown) => ({ id: breakdown, label: FieldLabels[breakdown] })); - const { setSeries, series } = useUrlStorage(seriesId); const selectedBreakdown = series.breakdown; @@ -38,8 +36,7 @@ export function Breakdowns({ seriesId, breakdowns = [] }: Props) { } }; - useEffect(() => {}, [selectedBreakdown]); - + const items = breakdowns.map((breakdown) => ({ id: breakdown, label: FieldLabels[breakdown] })); items.push({ id: NO_BREAKDOWN, label: i18n.translate('xpack.observability.exp.breakDownFilter.noBreakdown', { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx index 901f1e7ef9ef31..42cdfd595e66ba 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { i18n } from '@kbn/i18n'; import React, { useMemo } from 'react'; import { EuiFilterButton, hexToRgb } from '@elastic/eui'; import { useIndexPatternContext } from '../../hooks/use_default_index_pattern'; @@ -61,9 +61,13 @@ export function FilterValueButton({ setIsNestedOpen({ value: '', negate }); } }} - className="" > - {negate ? `Not ${value}` : value} + {negate + ? i18n.translate('xpack.observability.expView.filterValueButton.negate', { + defaultMessage: 'Not {value}', + values: { value }, + }) + : value} ); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/remove_series.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/remove_series.tsx index a6a47a5ad0c2d7..67aebed9433269 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/remove_series.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/remove_series.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import React from 'react'; import { EuiButtonIcon } from '@elastic/eui'; import { DataSeries } from '../../types'; @@ -22,7 +23,9 @@ export function RemoveSeries({ series }: Props) { }; return ( Date: Wed, 31 Mar 2021 09:41:22 +0200 Subject: [PATCH 43/53] add loading state --- .../series_editor/columns/chart_types.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx index c0f1c5ecbd001c..017655053eef2c 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx @@ -7,7 +7,13 @@ import React, { useState } from 'react'; -import { EuiButton, EuiButtonGroup, EuiButtonIcon, EuiPopover } from '@elastic/eui'; +import { + EuiButton, + EuiButtonGroup, + EuiButtonIcon, + EuiLoadingSpinner, + EuiPopover, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; import { useKibana } from '../../../../../../../../../src/plugins/kibana_react/public'; @@ -68,7 +74,7 @@ export function XYChartTypes({ services: { lens }, } = useKibana(); - const { data = [] } = useFetcher(() => lens.getXyVisTypes(), [lens]); + const { data = [], loading } = useFetcher(() => lens.getXyVisTypes(), [lens]); let vizTypes = data ?? []; @@ -80,7 +86,9 @@ export function XYChartTypes({ vizTypes = vizTypes.filter(({ id }) => includeChartTypes?.includes(id)); } - return ( + return loading ? ( + + ) : ( Date: Wed, 31 Mar 2021 09:54:54 +0200 Subject: [PATCH 44/53] fix types --- .../shared/exploratory_view/configurations/constants.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts index 94ace1f2b9d131..aa3ac2fa64317b 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts @@ -6,7 +6,13 @@ */ import { AppDataType, ReportViewTypeId } from '../types'; -import { CLS_FIELD, FCP_FIELD, FID_FIELD, LCP_FIELD, TBT_FIELD } from './performance_dist_config'; +import { + CLS_FIELD, + FCP_FIELD, + FID_FIELD, + LCP_FIELD, + TBT_FIELD, +} from './data/elasticsearch_fieldnames'; export const FieldLabels: Record = { 'user_agent.name': 'Browser family', From e004a7a27147db25a24e7f55c5557240064fc324 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 31 Mar 2021 09:56:07 +0200 Subject: [PATCH 45/53] fix types --- .../exploratory_view/configurations/lens_attributes.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts index 37b3178affa2db..dcfaed938cc0f7 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts @@ -9,7 +9,7 @@ import { LensAttributes } from './lens_attributes'; import { mockIndexPattern } from '../rtl_helpers'; import { getDefaultConfigs } from './default_configs'; import { sampleAttribute } from './data/sample_attribute'; -import { LCP_FIELD, SERVICE_NAME } from './performance_dist_config'; +import { LCP_FIELD, SERVICE_NAME } from './data/elasticsearch_fieldnames'; import { USER_AGENT_NAME } from './data/elasticsearch_fieldnames'; describe('Lens Attribute', () => { From 01a155927dd787a1d4d637401084af8101598e02 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 31 Mar 2021 11:25:53 +0200 Subject: [PATCH 46/53] update test --- x-pack/plugins/lens/public/mocks.tsx | 2 +- .../shared/exploratory_view/rtl_helpers.tsx | 14 ++++-- .../series_editor/columns/breakdowns.test.tsx | 49 +++++++++++++++++++ .../columns/chart_types.test.tsx | 32 ++++++++++-- 4 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.test.tsx diff --git a/x-pack/plugins/lens/public/mocks.tsx b/x-pack/plugins/lens/public/mocks.tsx index 743846d81213c0..c1f885d167659d 100644 --- a/x-pack/plugins/lens/public/mocks.tsx +++ b/x-pack/plugins/lens/public/mocks.tsx @@ -18,7 +18,7 @@ const createStartContract = (): Start => { }), canUseEditor: jest.fn(() => true), navigateToPrefilledEditor: jest.fn(), - getXyVisTypes: jest.fn().mockReturnValue(new Promise(() => visualizationTypes)), + getXyVisTypes: jest.fn().mockReturnValue(new Promise((resolve) => resolve(visualizationTypes))), }; return startContract; }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx index 5a252152736741..112bfcc3ccb580 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx @@ -242,11 +242,19 @@ export const mockUseValuesList = (values?: string[]) => { return { spy, onRefreshTimeRange }; }; -export const mockUrlStorage = ({ data, filters }: { data?: AllSeries; filters?: UrlFilter[] }) => { +export const mockUrlStorage = ({ + data, + filters, + breakdown, +}: { + data?: AllSeries; + filters?: UrlFilter[]; + breakdown?: string; +}) => { const mockDataSeries = data || { 'performance-distribution': { reportType: 'pld', - breakdown: 'user_agent.name', + breakdown: breakdown || 'user_agent.name', time: { from: 'now-15m', to: 'now' }, ...(filters ? { filters } : {}), }, @@ -266,7 +274,7 @@ export const mockUrlStorage = ({ data, filters }: { data?: AllSeries; filters?: setSeries, series, firstSeries: mockDataSeries[firstSeriesId], - allSeries: data, + allSeries: mockDataSeries, } as any); return { spy, removeSeries, setSeries }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.test.tsx new file mode 100644 index 00000000000000..654a93a08a7c8d --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.test.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { fireEvent, screen } from '@testing-library/react'; +import { Breakdowns } from './breakdowns'; +import { mockIndexPattern, mockUrlStorage, render } from '../../rtl_helpers'; +import { NEW_SERIES_KEY } from '../../hooks/use_url_strorage'; +import { getDefaultConfigs } from '../../configurations/default_configs'; +import { USER_AGENT_OS } from '../../configurations/data/elasticsearch_fieldnames'; + +describe('Breakdowns', function () { + const dataViewSeries = getDefaultConfigs({ + reportType: 'pld', + indexPattern: mockIndexPattern, + seriesId: NEW_SERIES_KEY, + }); + + it('should render properly', async function () { + mockUrlStorage({}); + + render(); + + screen.getAllByText('Browser family'); + }); + + it('should call set series on change', function () { + const { setSeries } = mockUrlStorage({ breakdown: USER_AGENT_OS }); + + render(); + + screen.getAllByText('Operating system'); + + fireEvent.click(screen.getByTestId('seriesBreakdown')); + + fireEvent.click(screen.getByText('Browser family')); + + expect(setSeries).toHaveBeenCalledWith('series-id', { + breakdown: 'user_agent.name', + reportType: 'pld', + time: { from: 'now-15m', to: 'now' }, + }); + expect(setSeries).toHaveBeenCalledTimes(1); + }); +}); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.test.tsx index 72ca3830076722..ac8ae17e827b2a 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { screen } from '@testing-library/react'; +import { fireEvent, screen, waitFor } from '@testing-library/react'; import { SeriesChartTypes, XYChartTypes } from './chart_types'; import { mockUrlStorage, render } from '../../rtl_helpers'; @@ -16,7 +16,31 @@ describe('SeriesChartTypes', function () { render(); - screen.getByText(/chart type/i); + await waitFor(() => { + screen.getByText(/chart type/i); + }); + }); + + it('should call set series on change', async function () { + const { setSeries } = mockUrlStorage({}); + + render(); + + await waitFor(() => { + screen.getByText(/chart type/i); + }); + + fireEvent.click(screen.getByText(/chart type/i)); + fireEvent.click(screen.getByTestId('lnsXY_seriesType-bar_stacked')); + + expect(setSeries).toHaveBeenNthCalledWith(1, 'performance-distribution', { + breakdown: 'user_agent.name', + reportType: 'pld', + seriesType: 'bar_stacked', + time: { from: 'now-15m', to: 'now' }, + }); + expect(setSeries).toHaveBeenCalledTimes(3); + screen.debug(); }); describe('XYChartTypes', function () { @@ -25,7 +49,9 @@ describe('SeriesChartTypes', function () { render(); - screen.getByText(/chart type/i); + await waitFor(() => { + screen.getByText(/chart type/i); + }); }); }); }); From 2b5229b86a1023cae538dfddfcc6d6f9832ccd9a Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 31 Mar 2021 14:41:07 +0200 Subject: [PATCH 47/53] update test --- .../components/filter_label.test.tsx | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.test.tsx index 708793bbba0006..9de90f958c7c54 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.test.tsx @@ -16,6 +16,7 @@ describe('FilterLabel', function () { jest.spyOn(useSeriesHook, 'useSeriesFilters').mockReturnValue({ invertFilter, } as any); + it('should render properly', async function () { render( + ); + + await waitFor(() => { + fireEvent.click(screen.getByLabelText('Filter actions')); + }); fireEvent.click(screen.getByTestId('negateFilter')); expect(invertFilter).toHaveBeenCalledTimes(1); From f894f18d321f0ba0ee0d55bd7c7395c667f6dd5d Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 31 Mar 2021 14:52:56 +0200 Subject: [PATCH 48/53] update constants --- .../configurations/url_constants.ts | 15 ++++++++++++++ .../exploratory_view/configurations/utils.ts | 20 +++++++------------ .../hooks/use_url_strorage.tsx | 19 +++++++----------- 3 files changed, 29 insertions(+), 25 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/url_constants.ts diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/url_constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/url_constants.ts new file mode 100644 index 00000000000000..faae7736f3cf1a --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/url_constants.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const URL_KEYS = { + METRIC_TYPE: 'mt', + REPORT_TYPE: 'rt', + SERIES_TYPE: 'st', + BREAK_DOWN: 'bd', + FILTERS: 'ft', + REPORT_DEFINITIONS: 'rdf', +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts index ad563db61f82a2..38b8ce81b2acd6 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts @@ -9,13 +9,7 @@ import type { AllSeries, AllShortSeries } from '../hooks/use_url_strorage'; import type { SeriesUrl } from '../types'; import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns'; import { esFilters } from '../../../../../../../../src/plugins/data/public'; - -const METRIC_TYPE = 'mt'; -const REPORT_TYPE = 'rt'; -const SERIES_TYPE = 'st'; -const BREAK_DOWN = 'bd'; -const FILTERS = 'ft'; -const REPORT_DEFINITIONS = 'rdf'; +import { URL_KEYS } from './url_constants'; export function convertToShortUrl(series: SeriesUrl) { const { @@ -29,12 +23,12 @@ export function convertToShortUrl(series: SeriesUrl) { } = series; return { - [METRIC_TYPE]: metric, - [REPORT_TYPE]: reportType, - [SERIES_TYPE]: seriesType, - [BREAK_DOWN]: breakdown, - [FILTERS]: filters, - [REPORT_DEFINITIONS]: reportDefinitions, + [URL_KEYS.METRIC_TYPE]: metric, + [URL_KEYS.REPORT_TYPE]: reportType, + [URL_KEYS.SERIES_TYPE]: seriesType, + [URL_KEYS.BREAK_DOWN]: breakdown, + [URL_KEYS.FILTERS]: filters, + [URL_KEYS.REPORT_DEFINITIONS]: reportDefinitions, ...restSeries, }; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx index c6ef99b38ea248..d38429703b7099 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx @@ -10,18 +10,13 @@ import { IKbnUrlStateStorage } from '../../../../../../../../src/plugins/kibana_ import type { AppDataType, ReportViewTypeId, SeriesUrl, UrlFilter } from '../types'; import { convertToShortUrl } from '../configurations/utils'; import { OperationType, SeriesType } from '../../../../../../lens/public'; +import { URL_KEYS } from '../configurations/url_constants'; export const UrlStorageContext = createContext(null); interface ProviderProps { storage: IKbnUrlStateStorage; } -const METRIC_TYPE = 'mt'; -const REPORT_TYPE = 'rt'; -const SERIES_TYPE = 'st'; -const BREAK_DOWN = 'bd'; -const FILTERS = 'ft'; -const REPORT_DEFINITIONS = 'rdf'; export function UrlStorageContextProvider({ children, @@ -45,12 +40,12 @@ function convertFromShortUrl(newValue: ShortUrlSeries): SeriesUrl { } interface ShortUrlSeries { - [METRIC_TYPE]?: OperationType; - [REPORT_TYPE]?: ReportViewTypeId; - [SERIES_TYPE]?: SeriesType; - [BREAK_DOWN]?: string; - [FILTERS]?: UrlFilter[]; - [REPORT_DEFINITIONS]?: Record; + [URL_KEYS.METRIC_TYPE]?: OperationType; + [URL_KEYS.REPORT_TYPE]?: ReportViewTypeId; + [URL_KEYS.SERIES_TYPE]?: SeriesType; + [URL_KEYS.BREAK_DOWN]?: string; + [URL_KEYS.FILTERS]?: UrlFilter[]; + [URL_KEYS.REPORT_DEFINITIONS]?: Record; time?: { to: string; from: string; From 70504600043de45e55bb0022eb6c9e8b4b3c5ced Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 31 Mar 2021 17:54:36 +0200 Subject: [PATCH 49/53] fix type --- .../configurations/url_constants.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/url_constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/url_constants.ts index faae7736f3cf1a..5b99c19dbabb7d 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/url_constants.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/url_constants.ts @@ -5,11 +5,11 @@ * 2.0. */ -export const URL_KEYS = { - METRIC_TYPE: 'mt', - REPORT_TYPE: 'rt', - SERIES_TYPE: 'st', - BREAK_DOWN: 'bd', - FILTERS: 'ft', - REPORT_DEFINITIONS: 'rdf', -}; +export enum URL_KEYS { + METRIC_TYPE = 'mt', + REPORT_TYPE = 'rt', + SERIES_TYPE = 'st', + BREAK_DOWN = 'bd', + FILTERS = 'ft', + REPORT_DEFINITIONS = 'rdf', +} From 9090e641fd4d8dbe0a58ff3904a60b392a4366f7 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Thu, 1 Apr 2021 18:08:16 +0200 Subject: [PATCH 50/53] update mock --- x-pack/plugins/lens/public/mocks.tsx | 2 +- .../exploratory_view/series_editor/columns/chart_types.test.tsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/plugins/lens/public/mocks.tsx b/x-pack/plugins/lens/public/mocks.tsx index c1f885d167659d..576987b40a7be1 100644 --- a/x-pack/plugins/lens/public/mocks.tsx +++ b/x-pack/plugins/lens/public/mocks.tsx @@ -18,7 +18,7 @@ const createStartContract = (): Start => { }), canUseEditor: jest.fn(() => true), navigateToPrefilledEditor: jest.fn(), - getXyVisTypes: jest.fn().mockReturnValue(new Promise((resolve) => resolve(visualizationTypes))), + getXyVisTypes: jest.fn().mockReturnValue(visualizationTypes), }; return startContract; }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.test.tsx index ac8ae17e827b2a..0c481a26fb2828 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.test.tsx @@ -40,7 +40,6 @@ describe('SeriesChartTypes', function () { time: { from: 'now-15m', to: 'now' }, }); expect(setSeries).toHaveBeenCalledTimes(3); - screen.debug(); }); describe('XYChartTypes', function () { From 25c45e060c9d0b3ee9c6b32fedb4925aca8f1806 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Thu, 1 Apr 2021 19:31:36 +0200 Subject: [PATCH 51/53] update test file --- tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tsconfig.json b/tsconfig.json index 03597114333cac..30944ac71fcc8a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -83,6 +83,7 @@ "x-pack/plugins/uptime/server/lib/requests/helper.ts", "x-pack/plugins/uptime/public/lib/helper/rtl_helpers.tsx", "x-pack/plugins/uptime/public/lib/helper/enzyme_helpers.tsx", + "x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx", "x-pack/plugins/apm/server/utils/test_helpers.tsx", "x-pack/plugins/apm/public/utils/testHelpers.tsx", From c18c82954c450db22920aa0cbc59355d03ae902d Mon Sep 17 00:00:00 2001 From: Shahzad Date: Thu, 1 Apr 2021 23:33:59 +0200 Subject: [PATCH 52/53] debug tests --- x-pack/plugins/lens/public/mocks.tsx | 2 +- .../shared/exploratory_view/components/filter_label.test.tsx | 4 ++-- .../series_editor/columns/chart_types.test.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/lens/public/mocks.tsx b/x-pack/plugins/lens/public/mocks.tsx index 576987b40a7be1..743846d81213c0 100644 --- a/x-pack/plugins/lens/public/mocks.tsx +++ b/x-pack/plugins/lens/public/mocks.tsx @@ -18,7 +18,7 @@ const createStartContract = (): Start => { }), canUseEditor: jest.fn(() => true), navigateToPrefilledEditor: jest.fn(), - getXyVisTypes: jest.fn().mockReturnValue(visualizationTypes), + getXyVisTypes: jest.fn().mockReturnValue(new Promise(() => visualizationTypes)), }; return startContract; }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.test.tsx index 9de90f958c7c54..37597e0ce513f2 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.test.tsx @@ -39,7 +39,7 @@ describe('FilterLabel', function () { }); }); - it('should delete filter', async function () { + it.skip('should delete filter', async function () { const removeFilter = jest.fn(); render( Date: Mon, 5 Apr 2021 07:37:30 +0200 Subject: [PATCH 53/53] update plugin setup --- x-pack/plugins/observability/kibana.json | 4 +- .../exploratory_view/exploratory_view.tsx | 50 ++++++++++++------- x-pack/plugins/uptime/public/apps/plugin.ts | 27 +++++----- 3 files changed, 48 insertions(+), 33 deletions(-) diff --git a/x-pack/plugins/observability/kibana.json b/x-pack/plugins/observability/kibana.json index 7d7297e6a51747..5c47d0376581a0 100644 --- a/x-pack/plugins/observability/kibana.json +++ b/x-pack/plugins/observability/kibana.json @@ -3,8 +3,8 @@ "version": "8.0.0", "kibanaVersion": "kibana", "configPath": ["xpack", "observability"], - "optionalPlugins": ["licensing", "home", "usageCollection"], - "requiredPlugins": ["data", "lens"], + "optionalPlugins": ["licensing", "home", "usageCollection","lens"], + "requiredPlugins": ["data"], "ui": true, "server": true, "requiredBundles": ["data", "kibanaReact", "kibanaUtils"] diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx index 1d4567a4371a6e..b3ad107bbe0e2f 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx @@ -4,10 +4,10 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { i18n } from '@kbn/i18n'; import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; -import { EuiLoadingSpinner, EuiPanel } from '@elastic/eui'; +import { EuiLoadingSpinner, EuiPanel, EuiTitle } from '@elastic/eui'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { ObservabilityPublicPluginsStart } from '../../../plugin'; import { ExploratoryViewHeader } from './header/header'; @@ -29,7 +29,7 @@ export function ExploratoryView() { const { indexPattern } = useIndexPatternContext(); - const LensComponent = lens.EmbeddableComponent; + const LensComponent = lens?.EmbeddableComponent; const { firstSeriesId: seriesId, firstSeries: series } = useUrlStorage(); @@ -45,24 +45,36 @@ export function ExploratoryView() { return ( - - {!indexPattern && ( - - - - )} - - {lensAttributes && seriesId && series?.reportType && series?.time ? ( - + {lens ? ( + <> + + {!indexPattern && ( + + + + )} + {lensAttributes && seriesId && series?.reportType && series?.time ? ( + + ) : ( + + )} + + ) : ( - + +

+ {i18n.translate('xpack.observability.overview.exploratoryView.lensDisabled', { + defaultMessage: + 'Lens app is not available, please enable Lens to use exploratory view.', + })} +

+
)} -
); } diff --git a/x-pack/plugins/uptime/public/apps/plugin.ts b/x-pack/plugins/uptime/public/apps/plugin.ts index e3457884594a98..7ea6b72547386b 100644 --- a/x-pack/plugins/uptime/public/apps/plugin.ts +++ b/x-pack/plugins/uptime/public/apps/plugin.ts @@ -68,18 +68,21 @@ export class UptimePlugin return UptimeDataHelper(coreStart); }; - plugins.observability.dashboard.register({ - appName: 'uptime', - hasData: async () => { - const dataHelper = await getUptimeDataHelper(); - const status = await dataHelper.indexStatus(); - return status.docCount > 0; - }, - fetchData: async (params: FetchDataParams) => { - const dataHelper = await getUptimeDataHelper(); - return await dataHelper.overviewData(params); - }, - }); + + if (plugins.observability) { + plugins.observability.dashboard.register({ + appName: 'uptime', + hasData: async () => { + const dataHelper = await getUptimeDataHelper(); + const status = await dataHelper.indexStatus(); + return status.docCount > 0; + }, + fetchData: async (params: FetchDataParams) => { + const dataHelper = await getUptimeDataHelper(); + return await dataHelper.overviewData(params); + }, + }); + } core.application.register({ id: PLUGIN.ID,
@@ -63,6 +78,16 @@ export function PageViewsTrend() { dataTestSubj={'pvBreakdownFilter'} /> + + + Analyze + + diff --git a/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts b/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts index 9c6ea6bc83511d..3ba8de1f57fb1d 100644 --- a/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts +++ b/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts @@ -31,17 +31,6 @@ export function getRumPageLoadTransactionsProjection({ filter: [ ...rangeQuery(start, end), { term: { [TRANSACTION_TYPE]: TRANSACTION_PAGE_LOAD } }, - ...(checkFetchStartFieldExists - ? [ - { - // Adding this filter to cater for some inconsistent rum data - // not available on aggregated transactions - exists: { - field: 'transaction.marks.navigationTiming.fetchStart', - }, - }, - ] - : []), ...(urlQuery ? [ { diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index c5e7b0af9654f5..f439ccb7821c83 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -24,6 +24,7 @@ import { HorizontalAlignment, ElementClickListener, BrushEndListener, + CurveType, } from '@elastic/charts'; import { I18nProvider } from '@kbn/i18n/react'; import { @@ -773,7 +774,12 @@ export function XYChart({ switch (seriesType) { case 'line': return ( - + ); case 'bar': case 'bar_stacked': diff --git a/x-pack/plugins/observability/public/assets/kibana_dashboard_dark.svg b/x-pack/plugins/observability/public/assets/kibana_dashboard_dark.svg new file mode 100644 index 00000000000000..834dd98d60e4c6 --- /dev/null +++ b/x-pack/plugins/observability/public/assets/kibana_dashboard_dark.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/x-pack/plugins/observability/public/assets/kibana_dashboard_light.svg b/x-pack/plugins/observability/public/assets/kibana_dashboard_light.svg new file mode 100644 index 00000000000000..958d25362c4393 --- /dev/null +++ b/x-pack/plugins/observability/public/assets/kibana_dashboard_light.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/x-pack/plugins/observability/public/components/app/header/index.tsx b/x-pack/plugins/observability/public/components/app/header/index.tsx index a41e3364d22b6e..4bb106ddbd34e9 100644 --- a/x-pack/plugins/observability/public/components/app/header/index.tsx +++ b/x-pack/plugins/observability/public/components/app/header/index.tsx @@ -19,6 +19,7 @@ import React, { ReactNode } from 'react'; import styled from 'styled-components'; import { usePluginContext } from '../../../hooks/use_plugin_context'; import HeaderMenuPortal from '../../shared/header_menu_portal'; +import { Tabs } from './tabs'; const Container = styled.div<{ color: string }>` background: ${(props) => props.color}; @@ -59,13 +60,13 @@ export function Header({ color, datePicker = null, restrictWidth }: Props) { - + - +

{i18n.translate('xpack.observability.home.title', { @@ -74,6 +75,9 @@ export function Header({ color, datePicker = null, restrictWidth }: Props) {

+ + +
{datePicker} diff --git a/x-pack/plugins/observability/public/components/app/header/tabs.tsx b/x-pack/plugins/observability/public/components/app/header/tabs.tsx new file mode 100644 index 00000000000000..c56cfa1781086f --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/header/tabs.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiTabs, EuiTab } from '@elastic/eui'; +import { useRouteMatch } from 'react-router'; + +const tabs = [ + { + id: 'overview', + name: 'Overview', + href: '/app/observability/overview', + }, + { + id: 'exploratoryView', + name: 'Exploratory View', + href: '/app/observability/exploratory-view', + }, +]; + +export function Tabs() { + const overviewTab = useRouteMatch('/overview'); + + const renderTabs = () => { + return tabs.map((tab, index) => ( + + {tab.name} + + )); + }; + + return {renderTabs()}; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx new file mode 100644 index 00000000000000..3600b6d05f6dd2 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiImage } from '@elastic/eui'; +import { usePluginContext } from '../../../../hooks/use_plugin_context'; +import styled from 'styled-components'; + +export const EmptyView = () => { + const { core } = usePluginContext(); + + return ( + + + + ); +}; + +const Wrapper = styled.div` + text-align: center; + opacity: 0.4; +`; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx new file mode 100644 index 00000000000000..4c732e7278680b --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { esFilters, Filter } from '../../../../../../../../src/plugins/data/public'; +import { useIndexPatternContext } from '../../../../hooks/use_default_index_pattern'; +import { injectI18n } from '@kbn/i18n/react'; +import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; +import { FieldLabels } from '../configurations/constants'; +import { useSeriesFilters } from '../hooks/use_series_filters'; + +interface Props { + field: string; + value: string; + seriesId: string; + negate: boolean; + removeFilter: (field: string, value: string, notVal: boolean) => void; +} +export const FilterLabel = ({ seriesId, field, value, negate, removeFilter }: Props) => { + const FilterItem = injectI18n(esFilters.FilterItem); + + const { indexPattern } = useIndexPatternContext(); + + const indexField = indexPattern.fields.find((fd) => fd.name === field)!; + + const filter = esFilters.buildPhraseFilter(indexField, value, indexPattern); + + filter.meta.value = value; + filter.meta.key = FieldLabels[field]; + filter.meta.alias = null; + filter.meta.negate = negate; + filter.meta.disabled = false; + filter.meta.type = 'phrase'; + + const { invertFilter } = useSeriesFilters({ seriesId }); + + const { + services: { uiSettings }, + } = useKibana(); + + return ( + { + removeFilter(field, value, false); + }} + onUpdate={(filterN: Filter) => { + if (filterN.meta.negate !== negate) { + invertFilter(field, value, negate); + } + }} + uiSettings={uiSettings!} + hiddenPanelOptions={['pinFilter', 'editFilter', 'disableFilter']} + /> + ); +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts index 85dd3cc6019ba1..d53924ad0e1d61 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts @@ -5,7 +5,13 @@ * 2.0. */ -import { DataViewType } from '../types'; +import { ReportViewType } from '../types'; + +export const METRIC_TYPE = 'mt'; +export const REPORT_TYPE = 'rt'; +export const SERIES_TYPE = 'st'; +export const BREAK_DOWN = 'bd'; +export const FILTERS = 'ft'; export const FieldLabels: Record = { 'user_agent.name': 'Browser family', @@ -14,9 +20,11 @@ export const FieldLabels: Record = { 'user_agent.device.name': 'Device', 'observer.geo.name': 'Observer location', 'service.name': 'Service', + 'monitor.id': 'Monitor Id', + 'monitor.status': 'Monitor Status', }; -export const DataViewLabels: Record = { +export const DataViewLabels: Record = { 'page-load-dist': 'Page load distribution', 'page-views': 'Page views', 'uptime-duration': 'Uptime monitor duration', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts index b1b3cce83593c1..f0543ede18ae61 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts @@ -5,23 +5,35 @@ * 2.0. */ -import { DataViewType } from '../types'; +import { ReportViewTypes } from '../types'; import { getPageLoadDistLensConfig } from './page_load_dist_config'; import { getPageViewLensConfig } from './page_view_config'; import { getMonitorDurationConfig } from './monitor_duration_config'; import { getServiceLatencyLensConfig } from './service_latency_config'; +import { getMonitorPingsConfig } from './monitor_pings_config'; +import { getServiceThroughputLensConfig } from './service_throughput_config'; -export const getDefaultConfigs = ({ dataViewType }: { dataViewType: DataViewType }) => { - switch (dataViewType) { +interface Props { + reportType: keyof typeof ReportViewTypes; + seriesId: string; + serviceName: string; +} + +export const getDefaultConfigs = ({ reportType, seriesId, serviceName }: Props) => { + switch (ReportViewTypes[reportType]) { case 'page-load-dist': - return getPageLoadDistLensConfig({}); + return getPageLoadDistLensConfig({ seriesId, serviceName }); case 'page-views': - return getPageViewLensConfig(); + return getPageViewLensConfig({ seriesId, serviceName }); case 'uptime-duration': return getMonitorDurationConfig(); + case 'uptime-pings': + return getMonitorPingsConfig({ seriesId }); case 'service-latency': - return getServiceLatencyLensConfig(); + return getServiceLatencyLensConfig({ seriesId, serviceName }); + case 'service-throughput': + return getServiceThroughputLensConfig({ seriesId, serviceName }); default: - return getPageViewLensConfig(); + return getPageViewLensConfig({ seriesId, serviceName }); } }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts index 3e649fcbcb76ff..76aad6f85d8b79 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts @@ -20,19 +20,24 @@ export class LensAttributes { visualization: XYState; filters: UrlFilter[]; seriesType: string; - dataViewConfig: DataSeries; + reportViewConfig: DataSeries; constructor( indexPattern: IIndexPattern, - dataViewConfig: DataSeries, + reportViewConfig: DataSeries, seriesType: string, - filters: UrlFilter[] + filters: UrlFilter[], + metricType: string ) { this.indexPattern = indexPattern; this.layers = {}; this.filters = filters ?? []; - this.seriesType = seriesType ?? dataViewConfig.defaultSeriesType; - this.dataViewConfig = dataViewConfig; + + if (typeof reportViewConfig.yAxisColumn.operationType !== undefined && metricType) { + reportViewConfig.yAxisColumn.operationType = metricType; + } + this.seriesType = seriesType ?? reportViewConfig.defaultSeriesType; + this.reportViewConfig = reportViewConfig; this.addLayer(); this.visualization = this.getXyState(); } @@ -96,7 +101,7 @@ export class LensAttributes { } getXAxis() { - const { xAxisColumn } = this.dataViewConfig; + const { xAxisColumn } = this.reportViewConfig; if (xAxisColumn.sourceField) { const fieldMeta = this.indexPattern.fields.find( @@ -132,7 +137,7 @@ export class LensAttributes { operationType: 'count', scale: 'ratio', sourceField: 'Records', - ...this.dataViewConfig.yAxisColumn, + ...this.reportViewConfig.yAxisColumn, }; } @@ -171,8 +176,8 @@ export class LensAttributes { } parseFilters() { - const defaultFilters = this.dataViewConfig.filters ?? []; - const parsedFilters = this.dataViewConfig.filters ? [...defaultFilters] : []; + const defaultFilters = this.reportViewConfig.filters ?? []; + const parsedFilters = this.reportViewConfig.filters ? [...defaultFilters] : []; this.filters.forEach(({ field, values = [], notValues = [] }) => { values?.forEach((value) => { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_pings_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_pings_config.ts new file mode 100644 index 00000000000000..6cd38d3b99ae6e --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_pings_config.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataSeries } from '../types'; + +interface Props { + seriesId: string; + monitorId: string; +} + +export function getMonitorPingsConfig({ seriesId, monitorId }: Props): DataSeries { + return { + id: seriesId, + reportType: 'uptime-pings', + defaultSeriesType: 'line', + indexPattern: 'df32db00-819e-11eb-87f5-d7da22b1dde3', + seriesTypes: ['bar', 'bar_stacked'], + xAxisColumn: { + sourceField: '@timestamp', + }, + yAxisColumn: { + operationType: 'count', + label: 'Monitor pings', + }, + defaultFilters: ['observer.geo.name'], + breakdowns: ['monitor.status', 'observer.geo.name'], + filters: monitorId + ? [ + { + query: { match_phrase: { 'monitor.id': 'android-homepage' } }, + }, + ] + : [], + }; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts index 2a7dcbc83bd1e9..546782823f938d 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts @@ -9,13 +9,13 @@ import { DataSeries } from '../types'; interface Props { seriesId: string; - serviceName?: string; + serviceName: string; } export function getPageLoadDistLensConfig({ seriesId, serviceName }: Props): DataSeries { return { id: seriesId ?? 'unique-key', - dataViewType: 'page-load-dist', + reportType: 'page-load-dist', defaultSeriesType: 'line', indexPattern: 'apm_static_index_pattern_id', seriesTypes: ['line', 'bar'], @@ -39,7 +39,8 @@ export function getPageLoadDistLensConfig({ seriesId, serviceName }: Props): Dat ], filters: [ { query: { match_phrase: { 'transaction.type': 'page-load' } } }, - ...(serviceName ? [{ query: { match_phrase: { 'service.type': serviceName } } }] : []), + { query: { match_phrase: { 'processor.event': 'transaction' } } }, + ...(serviceName ? [{ query: { match_phrase: { 'service.name': serviceName } } }] : []), ], }; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_view_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_view_config.ts index e8028fc2406ef1..9991d2aa226d0f 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_view_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_view_config.ts @@ -7,12 +7,16 @@ import { DataSeries } from '../types'; -export function getPageViewLensConfig(): DataSeries { +interface Props { + seriesId: string; + serviceName: string; +} + +export function getPageViewLensConfig({ seriesId, serviceName }: Props): DataSeries { return { - name: 'elastic.co', - id: 'elastic-co', - defaultSeriesType: 'bar', - dataViewType: 'page-views', + id: seriesId, + defaultSeriesType: 'bar_stacked', + reportType: 'page-views', indexPattern: 'apm_static_index_pattern_id', seriesTypes: ['bar', 'bar_stacked'], xAxisColumn: { @@ -33,8 +37,11 @@ export function getPageViewLensConfig(): DataSeries { 'client.geo.country_name', 'user_agent.device.name', ], - filters: { - query: { match_phrase: { 'transaction.type': 'page-load' } }, - }, + filters: [ + { + query: { match_phrase: { 'transaction.type': 'page-load' } }, + }, + ...(serviceName ? [{ query: { match_phrase: { 'service.name': serviceName } } }] : []), + ], }; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts index 9c15dda1c41f0f..aa9ad3c7671ad7 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts @@ -7,11 +7,15 @@ import { DataSeries } from '../types'; -export function getServiceLatencyLensConfig(): DataSeries { +interface Props { + seriesId: string; + serviceName: string; +} + +export function getServiceLatencyLensConfig({ seriesId, serviceName }: Props): DataSeries { return { - name: 'elastic.co', - id: 'elastic-co', - dataViewType: 'service-latency', + id: seriesId, + reportType: 'service-latency', defaultSeriesType: 'line', indexPattern: 'apm_static_index_pattern_id', seriesTypes: ['line', 'bar'], @@ -35,8 +39,9 @@ export function getServiceLatencyLensConfig(): DataSeries { 'client.geo.country_name', 'user_agent.device.name', ], - filters: { - query: { match_phrase: { 'transaction.type': 'request' } }, - }, + filters: [ + { query: { match_phrase: { 'transaction.type': 'request' } } }, + ...(serviceName ? [{ query: { match_phrase: { 'service.name': serviceName } } }] : []), + ], }; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts new file mode 100644 index 00000000000000..18fc7c47bce8a4 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataSeries } from '../types'; + +interface Props { + seriesId: string; + serviceName: string; +} + +export function getServiceThroughputLensConfig({ seriesId, serviceName }: Props): DataSeries { + return { + id: seriesId, + reportType: 'service-latency', + defaultSeriesType: 'line', + indexPattern: 'apm_static_index_pattern_id', + seriesTypes: ['line', 'bar'], + xAxisColumn: { + sourceField: '@timestamp', + }, + yAxisColumn: { + operationType: 'avg', + sourceField: 'transaction.duration.us', + label: 'Throughput', + }, + defaultFilters: [ + 'user_agent.name', + 'user_agent.os.name', + 'client.geo.country_name', + 'user_agent.device.name', + ], + breakdowns: [ + 'user_agent.name', + 'user_agent.os.name', + 'client.geo.country_name', + 'user_agent.device.name', + ], + filters: [ + { query: { match_phrase: { 'transaction.type': 'request' } } }, + ...(serviceName ? [{ query: { match_phrase: { 'service.name': serviceName } } }] : []), + ], + }; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx index e70a9838b72fd0..7bbe0e64e1b3f2 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx @@ -12,58 +12,53 @@ import { EuiPageBody, EuiPageContent, EuiPageContentBody, - EuiPageHeader, - EuiPageHeaderSection, - EuiTitle, } from '@elastic/eui'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { ObservabilityClientPluginsStart } from '../../../plugin'; import { IndexPattern } from '../../../../../../../src/plugins/data/common'; -import { ExploratoryViewHeader } from './header'; +import { ExploratoryViewHeader } from './header/header'; import { SeriesEditor } from './series_editor/series_editor'; import { useUrlStorage } from './hooks/use_url_strorage'; import { useLensAttributes } from './hooks/use_lens_attributes'; import styled from 'styled-components'; +import { EmptyView } from './components/empty_view'; export interface Props { - seriesId: string; - defaultIndexPattern?: IndexPattern | null; + indexPattern?: IndexPattern | null; } -export const ExploratoryView = ({ defaultIndexPattern }: Props) => { +export const ExploratoryView = ({ indexPattern }: Props) => { const { services: { lens }, } = useKibana(); const LensComponent = lens.EmbeddableComponent; - const { firstSeries: seriesId } = useUrlStorage(); - - const { series } = useUrlStorage(seriesId); + const { firstSeriesId: seriesId, firstSeries: series } = useUrlStorage(); const lensAttributes = useLensAttributes({ seriesId, + indexPattern, }); return ( - + - - {defaultIndexPattern ? ( + + {indexPattern ? ( <> - - + + {lensAttributes && seriesId ? ( + + ) : ( + + )} ) : ( diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header.tsx deleted file mode 100644 index eef54c9654cfe5..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/header.tsx +++ /dev/null @@ -1,64 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { useParams } from 'react-router-dom'; -import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; -import { TypedLensByValueInput } from '../../../../../lens/public'; -import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; -import { ObservabilityClientPluginsStart } from '../../../plugin'; -import { ChartTemplates } from './chart_templates/chart_templates'; -import { ChartTypes } from './header/chart_types'; -import { DataViewType } from './types'; -import { DataViewLabels } from './configurations/constants'; - -interface Props { - lensAttributes: TypedLensByValueInput['attributes']; -} - -export function ExploratoryViewHeader({ lensAttributes }: Props) { - const { - services: { lens }, - } = useKibana(); - - const { dataViewType } = useParams<{ dataViewType: DataViewType }>(); - - return ( - - - -

{DataViewLabels[dataViewType]}

-
-
- - - - - { - lens.navigateToPrefilledEditor({ - id: '', - timeRange: { - from: '2021-01-18T12:19:28.685Z', - to: '2021-01-18T12:26:20.767Z', - }, - attributes: lensAttributes, - }); - }} - > - Open in Lens - - - - - -
- ); -} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/chart_types.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/chart_types.tsx index 9656dba2c67d42..d9d4dc163ccac3 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/chart_types.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/chart_types.tsx @@ -22,6 +22,7 @@ import { LensIconChartLine } from '../assets/chart_line'; import { LensIconChartAreaStacked } from '../assets/chart_area_stacked'; import styled from 'styled-components'; import { useUrlStorage } from '../hooks/use_url_strorage'; +import { SERIES_TYPE } from '../configurations/constants'; const ButtonGroup = styled(EuiButtonGroup)` &&& { @@ -31,8 +32,8 @@ const ButtonGroup = styled(EuiButtonGroup)` } `; -export const ChartTypes = () => { - const { series, setSeries, allSeries } = useUrlStorage(); +export const ChartTypes = ({ seriesId }: { seriesId: string }) => { + const { series, setSeries, allSeries } = useUrlStorage(seriesId); return ( { iconType: t.icon || 'empty', 'data-test-subj': `lnsXY_seriesType-${t.id}`, }))} - idSelected={series?.seriesType ?? 'line'} + idSelected={series?.[SERIES_TYPE] ?? 'line'} onChange={(seriesType: string) => { Object.keys(allSeries).forEach((seriesKey) => { const series = allSeries[seriesKey]; - setSeries(seriesKey, { ...series, seriesType }); + setSeries(seriesKey, { ...series, [SERIES_TYPE]: seriesType }); }); }} /> diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx new file mode 100644 index 00000000000000..2ca8471157bab9 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { useParams } from 'react-router-dom'; +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { TypedLensByValueInput } from '../../../../../../lens/public'; +import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; +import { ObservabilityClientPluginsStart } from '../../../../plugin'; +import { ChartTemplates } from '../chart_templates/chart_templates'; +import { ChartTypes } from './chart_types'; +import { ReportViewType } from '../types'; +import { DataViewLabels } from '../configurations/constants'; +import { useUrlStorage } from '../hooks/use_url_strorage'; +import { MetricSelection } from './metric_selection'; + +interface Props { + seriesId: string; + lensAttributes: TypedLensByValueInput['attributes'] | null; +} + +export function ExploratoryViewHeader({ seriesId, lensAttributes }: Props) { + const { + services: { lens }, + } = useKibana(); + + const { dataViewType } = useParams<{ dataViewType: ReportViewType }>(); + + const { series } = useUrlStorage(seriesId); + + return ( + + + +

{DataViewLabels[dataViewType]}

+
+
+ + + + + + + + { + if (lensAttributes) { + lens.navigateToPrefilledEditor({ + id: '', + timeRange: series.time, + attributes: lensAttributes, + }); + } + }} + > + Open in Lens + + + + + +
+ ); +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/metric_selection.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/metric_selection.tsx new file mode 100644 index 00000000000000..92d88e5c496f73 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/metric_selection.tsx @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; +import { EuiButtonGroup } from '@elastic/eui'; +import { useUrlStorage } from '../hooks/use_url_strorage'; +import { METRIC_TYPE } from '../configurations/constants'; + +export const MetricSelection = ({ seriesId }: { seriesId: string }) => { + const toggleButtons = [ + { + id: `avg`, + label: 'Avg', + }, + { + id: `median`, + label: 'Median', + }, + { + id: `95th`, + label: '95th', + }, + { + id: `99th`, + label: '99th', + }, + ]; + + const { series, setSeries, allSeries } = useUrlStorage(seriesId); + + const [toggleIdSelected, setToggleIdSelected] = useState(series?.[METRIC_TYPE] ?? 'avg'); + + const onChange = (optionId: string) => { + setToggleIdSelected(optionId); + + Object.keys(allSeries).forEach((seriesKey) => { + const seriesN = allSeries[seriesKey]; + + setSeries(seriesKey, { ...seriesN, [METRIC_TYPE]: optionId }); + }); + }; + + return ( + onChange(id)} + /> + ); +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts new file mode 100644 index 00000000000000..44f433468e3077 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useFetcher } from '../../../..'; +import { IKbnUrlStateStorage } from '../../../../../../../../src/plugins/kibana_utils/public'; +import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; +import { ObservabilityClientPluginsStart } from '../../../../plugin'; +import { AllSeries } from './use_url_strorage'; +import { REPORT_TYPE } from '../configurations/constants'; +import { useMemo } from 'react'; + +const APM_STATIC_INDEX_PATTERN_ID = 'apm_static_index_pattern_id'; + +export const useInitExploratoryView = (storage: IKbnUrlStateStorage) => { + const { + services: { data }, + } = useKibana(); + + const allSeriesKey = 'sr'; + + const allSeries = storage.get(allSeriesKey) ?? {}; + + const allSeriesIds = Object.keys(allSeries); + + const firstSeriesId = allSeriesIds?.[0]; + + const firstSeries = allSeries[firstSeriesId]; + + const { data: indexPattern, status } = useFetcher(() => { + if (firstSeries?.[REPORT_TYPE] === 'upp') { + return data.indexPatterns.get('df32db00-819e-11eb-87f5-d7da22b1dde3'); + } + return data.indexPatterns.get(APM_STATIC_INDEX_PATTERN_ID); + }, [firstSeries?.[REPORT_TYPE]]); + + return useMemo(() => { + return indexPattern; + }, [status]); +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts index e781cc2d03cdfa..c20743c10bc520 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts @@ -6,47 +6,60 @@ */ import { TypedLensByValueInput } from '../../../../../../lens/public'; -import { useIndexPatternContext } from '../../../../hooks/use_default_index_pattern'; import { LensAttributes } from '../configurations/lens_attributes'; import { useUrlStorage } from './use_url_strorage'; -import { DataViewType, SeriesUrl } from '../types'; import { useMemo } from 'react'; -import { useParams } from 'react-router-dom'; import { getDefaultConfigs } from '../configurations/default_configs'; +import { + BREAK_DOWN, + FILTERS, + METRIC_TYPE, + REPORT_TYPE, + SERIES_TYPE, +} from '../configurations/constants'; +import { IIndexPattern } from '../../../../../../../../src/plugins/data/common'; interface Props { seriesId: string; + indexPattern: IIndexPattern; } export const useLensAttributes = ({ seriesId, + indexPattern, }: Props): TypedLensByValueInput['attributes'] | null => { - const { dataViewType } = useParams<{ dataViewType: DataViewType }>(); - - const dataViewConfig = getDefaultConfigs({ dataViewType }); - - const { indexPattern: defaultIndexPattern } = useIndexPatternContext(dataViewConfig.indexPattern); - const { series } = useUrlStorage(seriesId); - const { filters = [] } = series ?? {}; + const dataViewConfig = getDefaultConfigs({ + reportType: series[REPORT_TYPE], + serviceName: series.serviceName, + seriesId, + }); + + const { + [FILTERS]: filters = [], + [BREAK_DOWN]: breakdown, + [SERIES_TYPE]: seriesType, + [METRIC_TYPE]: metricType, + } = series ?? {}; return useMemo(() => { - if (!defaultIndexPattern) { + if (!indexPattern) { return null; } const lensAttributes = new LensAttributes( - defaultIndexPattern, + indexPattern, dataViewConfig, - series?.seriesType!, - filters + seriesType!, + filters, + metricType ); - if (series?.breakdown) { - lensAttributes.addBreakdown(series.breakdown); + if (breakdown) { + lensAttributes.addBreakdown(breakdown); } return lensAttributes.getJSON(); - }, [defaultIndexPattern, series?.breakdown, series?.seriesType, filters]); + }, [indexPattern, breakdown, seriesType, filters, metricType]); }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_filters.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_filters.ts new file mode 100644 index 00000000000000..0a149467f08d52 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_filters.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useUrlStorage } from './use_url_strorage'; +import { FILTERS } from '../configurations/constants'; +import { UrlFilter } from '../types'; + +interface Props { + seriesId: string; +} +export const useSeriesFilters = ({ seriesId }: Props) => { + const { series, setSeries } = useUrlStorage(seriesId); + + const filters = series[FILTERS] ?? []; + + const invertFilter = (field: string, value: string, negate: boolean) => { + let currFilter: UrlFilter | undefined = filters.find(({ field: fd }) => field === fd)!; + + const currNotValues = currFilter.notValues ?? []; + const currValues = currFilter.values ?? []; + + const notValues = currNotValues.filter((val) => val !== value); + const values = currValues.filter((val) => val !== value); + + if (negate) { + values.push(value); + } else { + notValues.push(value); + } + + currFilter.notValues = notValues.length > 0 ? notValues : undefined; + currFilter.values = values.length > 0 ? values : undefined; + + if (notValues.length > 0 || values.length > 0) { + setSeries(seriesId, { ...series, [FILTERS]: [currFilter] }); + } else { + setSeries(seriesId, { ...series, [FILTERS]: undefined }); + } + }; + + return { invertFilter }; +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx index 9b4ad7c4e1a708..0d299f11eac015 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx @@ -19,7 +19,7 @@ export const UrlStorageContextProvider: React.FC = ({ children, s return {children}; }; -type AllSeries = Record; +export type AllSeries = Record; export const useUrlStorage = (seriesId?: string) => { const allSeriesKey = 'sr'; @@ -43,7 +43,16 @@ export const useUrlStorage = (seriesId?: string) => { const allSeriesIds = Object.keys(allSeries); - const firstSeries = allSeriesIds?.[0]; - - return { storage, setSeries, removeSeries, series, firstSeries, allSeries, allSeriesIds }; + const firstSeriesId = allSeriesIds?.[0]; + + return { + storage, + setSeries, + removeSeries, + series, + firstSeriesId, + allSeries, + allSeriesIds, + firstSeries: allSeries?.[firstSeriesId], + }; }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx index 7a8b65c72f45da..a6edc6e1fc1c61 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx @@ -5,43 +5,36 @@ * 2.0. */ -import React from 'react'; +import React, { useContext } from 'react'; import { ExploratoryView } from './exploratory_view'; -import { useFetcher } from '../../..'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { ObservabilityClientPluginsStart } from '../../../plugin'; import { useBreadcrumbs } from '../../../hooks/use_breadcrumbs'; import { i18n } from '@kbn/i18n'; import { IndexPatternContextProvider } from '../../../hooks/use_default_index_pattern'; import { useHistory } from 'react-router-dom'; +import { ThemeContext } from 'styled-components'; import { createKbnUrlStateStorage, withNotifyOnErrors, } from '../../../../../../../src/plugins/kibana_utils/public/'; import { UrlStorageContextProvider } from './hooks/use_url_strorage'; -import { DataViewType } from './types'; -import { getDefaultConfigs } from './configurations/default_configs'; +import { useInitExploratoryView } from './hooks/use_init_exploratory_view'; +import { WithHeaderLayout } from '../../app/layout/with_header'; -export interface Props { - dataViewType: DataViewType; -} - -export const ExploratoryViewPage = ({ dataViewType }: Props) => { +export const ExploratoryViewPage = () => { useBreadcrumbs([ { text: i18n.translate('xpack.observability.overview.exploratoryView', { defaultMessage: 'Exploratory view', }), }, - { - text: i18n.translate('xpack.observability.overview.exploratoryView.dataViewType', { - defaultMessage: dataViewType, - }), - }, ]); + const theme = useContext(ThemeContext); + const { - services: { data, uiSettings, notifications }, + services: { uiSettings, notifications }, } = useKibana(); const history = useHistory(); @@ -52,18 +45,18 @@ export const ExploratoryViewPage = ({ dataViewType }: Props) => { ...withNotifyOnErrors(notifications!.toasts), }); - const dataViewConfig = getDefaultConfigs({ dataViewType }); - - const { data: defaultIndexPattern } = useFetcher( - () => data.indexPatterns.get(dataViewConfig.indexPattern), - [dataViewConfig.indexPattern] - ); + const indexPattern = useInitExploratoryView(kbnUrlStateStorage); return ( - - - - - + + + + + + + ); }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx index e112905a7f4f9a..e0ec8126453b16 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx @@ -8,6 +8,7 @@ import React, { Dispatch, SetStateAction } from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { AppDataType } from '../../types'; +import { useIndexPatternContext } from '../../../../../hooks/use_default_index_pattern'; interface Props { selectedDataType: AppDataType | null; @@ -18,10 +19,19 @@ const dataTypes: { id: AppDataType; label: string }[] = [ { id: 'synthetics', label: 'Synthetic Monitoring' }, { id: 'rum', label: 'User Experience(RUM)' }, { id: 'logs', label: 'Logs' }, - { id: 'metrics', label: 'Logs' }, + { id: 'metrics', label: 'Metrics' }, { id: 'apm', label: 'APM' }, ]; export const DataTypesCol = ({ selectedDataType, onChange }: Props) => { + const { loadIndexPattern } = useIndexPatternContext(); + + const onDataTypeChange = (dataType: AppDataType | null) => { + onChange(dataType); + if (dataType) { + loadIndexPattern(dataType); + } + }; + return ( {dataTypes.map(({ id: dt, label }) => ( @@ -32,7 +42,7 @@ export const DataTypesCol = ({ selectedDataType, onChange }: Props) => { iconType="arrowRight" color={selectedDataType === dt ? 'primary' : 'text'} fill={selectedDataType === dt} - onClick={() => onChange(dt === selectedDataType ? null : dt)} + onClick={() => onDataTypeChange(dt === selectedDataType ? null : dt)} > {label} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx index eb265df8b1f0ce..07e1d68e8acf57 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx @@ -9,34 +9,71 @@ import React, { Dispatch, SetStateAction } from 'react'; import { FieldValueSelection } from '../../../field_value_selection'; import { useIndexPatternContext } from '../../../../../hooks/use_default_index_pattern'; import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { AppDataType, ReportViewTypeId } from '../../types'; + +const ReportDefinitionMap: Record< + ReportViewTypeId, + Array<{ field: string; required?: boolean }> +> = { + pld: [ + { + field: 'service.name', + required: true, + }, + ], + upp: [ + { + field: 'monitor.id', + }, + ], + kpi: [], + pgv: [], + svl: [ + { + field: 'service.name', + required: true, + }, + ], + tpt: [ + { + field: 'service.name', + required: true, + }, + ], + upd: [], +}; interface Props { - selectedServiceName: string | null; - reportType: string; + dataType: AppDataType; + reportType: ReportViewTypeId; + selectedReportDefinitions: Record; onChange: Dispatch>; } -export const ReportDefinitionCol = ({ selectedServiceName, onChange }: Props) => { +export const ReportDefinitionCol = ({ reportType, selectedReportDefinitions, onChange }: Props) => { const { indexPattern } = useIndexPatternContext(); return (
- {selectedServiceName && ( - - + {selectedReportDefinitions && ( + + - Web App: {selectedServiceName} + Web App: {selectedReportDefinitions} )} - onChange(val)} - /> + {ReportDefinitionMap[reportType].map(({ field }) => ( + onChange(val)} + /> + ))}
); }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx index 05441cc93927e7..4c98c0de035de5 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx @@ -7,17 +7,18 @@ import React, { Dispatch, SetStateAction } from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { ReportViewTypeId } from '../../types'; interface Props { - selectedReportType: string | null; - reportTypes: string[]; - onChange: Dispatch>; + selectedReportType: ReportViewTypeId | null; + reportTypes: { id: ReportViewTypeId; label: string }[]; + onChange: Dispatch>; } export const ReportTypesCol = ({ selectedReportType, reportTypes, onChange }: Props) => { return reportTypes?.length > 0 ? ( - {reportTypes.map((reportType) => ( + {reportTypes.map(({ id: reportType, label }) => ( onChange(reportType === selectedReportType ? null : reportType)} > - {reportType} + {label} ))} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx index 57a9ce714a2fbb..1489f59d79cf03 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx @@ -9,18 +9,29 @@ import React, { useState } from 'react'; import { EuiButton, EuiBasicTable, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import styled from 'styled-components'; -import { AppDataType, SeriesUrl } from '../types'; +import { AppDataType, ReportViewTypeId, SeriesUrl } from '../types'; import { DataTypesCol } from './columns/data_types_col'; import { ReportTypesCol } from './columns/report_types_col'; import { ReportDefinitionCol } from './columns/report_definition_col'; import { ReportFilters } from './columns/report_filters'; import { ReportBreakdowns } from './columns/report_breakdowns'; import { useUrlStorage } from '../hooks/use_url_strorage'; +import { REPORT_TYPE } from '../configurations/constants'; -export const ReportTypes = { - synthetics: ['Monitor duration', 'Pings histogram'], - rum: ['Performance distribution', 'Page views', 'KPI over time'], - apm: ['Latency', 'Throughput'], +export const ReportTypes: Record> = { + synthetics: [ + { id: 'upd', label: 'Monitor duration' }, + { id: 'upp', label: 'Pings histogram' }, + ], + rum: [ + { id: 'pld', label: 'Performance distribution' }, + { id: 'pgv', label: 'Page views' }, + { id: 'kpi', label: 'KPI over time' }, + ], + apm: [ + { id: 'svl', label: 'Latency' }, + { id: 'tpt', label: 'Throughput' }, + ], logs: [], metrics: [], }; @@ -29,7 +40,7 @@ export const SeriesBuilder = () => { const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); const [dataType, setDataType] = useState(null); - const [reportType, setReportType] = useState(null); + const [reportType, setReportType] = useState(null); const [serviceName, setServiceName] = useState(null); const columns = [ @@ -58,8 +69,9 @@ export const SeriesBuilder = () => { render: (val: string) => reportType ? ( ) : null, @@ -81,16 +93,22 @@ export const SeriesBuilder = () => { const { setSeries, allSeriesIds } = useUrlStorage(); const addSeries = () => { - const newSeriesId = `${serviceName!}-pd`; + if (reportType) { + const newSeriesId = `${serviceName!}-${reportType}`; - const newSeries = { reportType: 'pd', serviceName } as SeriesUrl; - setSeries(newSeriesId, newSeries); + const newSeries = { + [REPORT_TYPE]: reportType, + serviceName, + time: { from: 'now-30m', to: 'now' }, + } as SeriesUrl; + setSeries(newSeriesId, newSeries); - // reset state - setDataType(null); - setReportType(null); - setServiceName(null); - setIsFlyoutVisible(false); + // reset state + setDataType(null); + setReportType(null); + setServiceName(null); + setIsFlyoutVisible(false); + } }; const items = [{ dataTypes: ['APM'] }]; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx index 0b8f6f2fef1282..9678adf015770c 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx @@ -7,9 +7,8 @@ import React, { useEffect, useState } from 'react'; import { EuiButton, EuiSpacer } from '@elastic/eui'; -import { FieldLabels } from '../../configurations/constants'; +import { BREAK_DOWN, FieldLabels } from '../../configurations/constants'; import { useUrlStorage } from '../../hooks/use_url_strorage'; -import { SeriesUrl } from '../../types'; interface Props { seriesId: string; @@ -28,7 +27,7 @@ export const Breakdowns = ({ seriesId, breakdowns = [] }: Props) => { const { setSeries, series } = useUrlStorage(seriesId); useEffect(() => { - setSeries(seriesId, { ...series, breakdown: selectedBreakdown }); + setSeries(seriesId, { ...series, [BREAK_DOWN]: selectedBreakdown }); }, [selectedBreakdown]); return ( diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx index 336e771f09fe2d..d6f0ab672c145e 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx @@ -20,6 +20,7 @@ import { useKibana } from '../../../../../../../../../src/plugins/kibana_react/p import { useIndexPatternContext } from '../../../../../hooks/use_default_index_pattern'; import { useUrlStorage } from '../../hooks/use_url_strorage'; import { UrlFilter } from '../../types'; +import { FILTERS } from '../../configurations/constants'; interface Props { seriesId: string; @@ -39,7 +40,7 @@ export const FilterExpanded = ({ seriesId, field, label, goBack }: Props) => { const { series, setSeries } = useUrlStorage(seriesId); - const { data: values, status } = useFetcher(() => { + const { data: values, status } = useFetcher>(() => { return data.autocomplete.getValueSuggestions({ indexPattern, query: '', @@ -48,19 +49,26 @@ export const FilterExpanded = ({ seriesId, field, label, goBack }: Props) => { }); }, [field]); - const filters = series?.filters ?? []; + const filters = series?.[FILTERS] ?? []; let currFilter: UrlFilter | undefined = filters.find(({ field: fd }) => field === fd); const onChange = (id: string, not?: boolean) => { - if (!currFilter && filters.length === 0) { + if (!currFilter) { currFilter = { field }; if (not) { currFilter.notValues = [id]; } else { currFilter.values = [id]; } - setSeries(seriesId, { ...series, filters: [currFilter] }); + if (filters.length === 0) { + setSeries(seriesId, { ...series, [FILTERS]: [currFilter] }); + } else { + setSeries(seriesId, { + ...series, + [FILTERS]: [currFilter, ...filters.filter((ft) => ft.field != field)], + }); + } return; } @@ -81,9 +89,9 @@ export const FilterExpanded = ({ seriesId, field, label, goBack }: Props) => { currFilter.values = values.length > 0 ? values : undefined; if (notValues.length > 0 || values.length > 0) { - setSeries(seriesId, { ...series, filters: [currFilter] }); + setSeries(seriesId, { ...series, [FILTERS]: [currFilter] }); } else { - setSeries(seriesId, { ...series, filters: undefined }); + setSeries(seriesId, { ...series, [FILTERS]: undefined }); } } }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx index 0ea8ab4686e4d4..5888a503c01f81 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx @@ -18,6 +18,10 @@ import { FilterExpanded } from './filter_expanded'; import { DataSeries } from '../../types'; import { FieldLabels } from '../../configurations/constants'; import { SelectedFilters } from '../selected_filters'; +import { DefaultFilters } from '../default_filters'; +import { usePluginContext } from '../../../../../hooks/use_plugin_context'; +import { StatefulSearchBarProps } from '../../../../../../../../../src/plugins/data/public'; +import { useIndexPatternContext } from '../../../../../hooks/use_default_index_pattern'; interface Props { seriesId: string; @@ -38,6 +42,7 @@ export const SeriesFilter = ({ seriesId, defaultFilters = [] }: Props) => { const button = ( { setIsPopoverVisible(true); @@ -47,6 +52,14 @@ export const SeriesFilter = ({ seriesId, defaultFilters = [] }: Props) => { ); + const { + plugins: { data }, + } = usePluginContext(); + + const SearchBar: React.ComponentType = data.ui.SearchBar; + + const { indexPattern } = useIndexPatternContext(); + const mainPanel = ( <> @@ -65,11 +78,20 @@ export const SeriesFilter = ({ seriesId, defaultFilters = [] }: Props) => { ))} + ); const childPanel = selectedField ? ( { @@ -85,19 +107,26 @@ export const SeriesFilter = ({ seriesId, defaultFilters = [] }: Props) => { return ( - - - {!selectedField ? mainPanel : childPanel} - - + + + + + {!selectedField ? mainPanel : childPanel} + + + + + + + ); }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/default_filters.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/default_filters.tsx new file mode 100644 index 00000000000000..adc148bfbcabb5 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/default_filters.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; +import { EuiButtonEmpty, EuiCode, EuiPopover, EuiText } from '@elastic/eui'; + +interface Props { + seriesId: string; +} +export const DefaultFilters = ({ seriesId }: Props) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const onButtonClick = () => setIsPopoverOpen((isPopoverOpen) => !isPopoverOpen); + const closePopover = () => setIsPopoverOpen(false); + + const button = ( + + Default filters + + ); + + const jsCodeCode = `query: { match_phrase: { 'transaction.type': 'page-load' } }`; + + return ( + + + + {jsCodeCode.trim()} + + + + ); +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx index 83f54a39131947..b719761b677594 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx @@ -7,8 +7,9 @@ import React, { Fragment } from 'react'; import { useUrlStorage } from '../hooks/use_url_strorage'; -import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { FieldLabels } from '../configurations/constants'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { FILTERS } from '../configurations/constants'; +import { FilterLabel } from '../components/filter_label'; interface Props { seriesId: string; @@ -16,9 +17,7 @@ interface Props { export const SelectedFilters = ({ seriesId }: Props) => { const { series, setSeries } = useUrlStorage(seriesId); - const filters = series?.filters ?? []; - - const style = { maxWidth: 250 }; + const filters = series?.[FILTERS] ?? []; const removeFilter = (field: string, value: string, notVal: boolean) => { const filtersN = filters.map((filter) => { @@ -34,7 +33,7 @@ export const SelectedFilters = ({ seriesId }: Props) => { return filter; }); - setSeries(seriesId, { ...series, filters: filtersN }); + setSeries(seriesId, { ...series, [FILTERS]: filtersN }); }; return ( @@ -42,31 +41,25 @@ export const SelectedFilters = ({ seriesId }: Props) => { {filters.map(({ field, values, notValues }) => ( {(values ?? []).map((val) => ( - - removeFilter(field, val, false)} - > - {FieldLabels[field]}: {val} - + + removeFilter(field, val, false)} + negate={false} + /> ))} {(notValues ?? []).map((val) => ( - - removeFilter(field, val, true)} - > - Not {FieldLabels[field]}: {val} - + + removeFilter(field, val, true)} + /> ))} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx index de34f3f86ffdd2..641431009f1bdf 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx @@ -6,16 +6,16 @@ */ import React from 'react'; -import { useParams } from 'react-router-dom'; import { EuiBasicTable, EuiIcon, EuiSpacer, EuiText } from '@elastic/eui'; import { SeriesFilter } from './columns/series_filter'; import { ActionsCol } from './columns/actions_col'; import { Breakdowns } from './columns/breakdowns'; -import { DataSeries, DataViewType } from '../types'; +import { DataSeries } from '../types'; import { SeriesBuilder } from '../series_builder/series_builder'; import { useUrlStorage } from '../hooks/use_url_strorage'; -import { getPageLoadDistLensConfig } from '../configurations/page_load_dist_config'; import { RemoveSeries } from './columns/remove_series'; +import { getDefaultConfigs } from '../configurations/default_configs'; +import { REPORT_TYPE } from '../configurations/constants'; export const SeriesEditor = () => { const columns = [ @@ -59,19 +59,16 @@ export const SeriesEditor = () => { }, ]; - const { dataViewType } = useParams<{ dataViewType: DataViewType }>(); - const { allSeries } = useUrlStorage(); - console.log(allSeries); - const allSeriesKeys = Object.keys(allSeries); const items: DataSeries[] = allSeriesKeys.map((seriesKey) => { const series = allSeries[seriesKey]; - return getPageLoadDistLensConfig({ - seriesId: seriesKey, + return getDefaultConfigs({ + reportType: series[REPORT_TYPE], serviceName: series.serviceName, + seriesId: seriesKey, }); }); @@ -83,6 +80,7 @@ export const SeriesEditor = () => { rowHeader="firstName" columns={columns} rowProps={() => ({ height: 100 })} + noItemsMessage={'No series found, please add a series.'} /> diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts index 2d9464dc8600b3..25ab0a07382a47 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts @@ -11,8 +11,27 @@ import { DateHistogramIndexPatternColumn, LastValueIndexPatternColumn, } from '../../../../../lens/public'; +import { + BREAK_DOWN, + FILTERS, + METRIC_TYPE, + REPORT_TYPE, + SERIES_TYPE, +} from './configurations/constants'; + +export const ReportViewTypes = { + pld: 'page-load-dist', + pgv: 'page-views', + upd: 'uptime-duration', + upp: 'uptime-pings', + svl: 'service-latency', + kpi: 'service-latency', + tpt: 'service-throughput', +}; -export type DataViewType = +export type ReportViewTypeId = keyof typeof ReportViewTypes; + +export type ReportViewType = | 'page-load-dist' | 'page-views' | 'uptime-duration' @@ -20,7 +39,7 @@ export type DataViewType = | 'service-latency'; export interface DataSeries { - dataViewType: DataViewType; + reportType: ReportViewType; indexPattern: string; id: string; xAxisColumn: Partial | Partial; @@ -32,18 +51,20 @@ export interface DataSeries { defaultSeriesType: string; defaultFilters: string[]; seriesTypes?: string[]; - filters?: Record; + filters?: Array>; } export interface SeriesUrl { - id: string; time: { to: string; from: string; }; - breakdown?: string; - filters?: UrlFilter[]; - seriesType: string; + [BREAK_DOWN]?: string; + [FILTERS]?: UrlFilter[]; + [SERIES_TYPE]?: string; + [REPORT_TYPE]: ReportViewTypeId; + serviceName: string; + [METRIC_TYPE]?: string; } export interface UrlFilter { diff --git a/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx b/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx index bab924e8750952..3f5269dee8b00f 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx +++ b/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx @@ -66,7 +66,7 @@ export const FieldValueSelection = ({ const button = ( - Web Application + {FieldLabels[sourceField]} ); diff --git a/x-pack/plugins/observability/public/hooks/use_default_index_pattern.tsx b/x-pack/plugins/observability/public/hooks/use_default_index_pattern.tsx index 45c886535290be..15c2ac5b5a114e 100644 --- a/x-pack/plugins/observability/public/hooks/use_default_index_pattern.tsx +++ b/x-pack/plugins/observability/public/hooks/use_default_index_pattern.tsx @@ -5,11 +5,15 @@ * 2.0. */ -import React, { createContext, useContext, Context } from 'react'; +import React, { createContext, useContext, Context, useState, useMemo, useEffect } from 'react'; import { IIndexPattern } from '../../../../../src/plugins/data/common'; +import { AppDataType } from '../components/shared/exploratory_view/types'; +import { useKibana } from '../../../../../src/plugins/kibana_react/public'; +import { ObservabilityClientPluginsStart } from '../plugin'; export interface IIndexPatternContext { indexPattern: IIndexPattern; + loadIndexPattern: (dataType: AppDataType) => void; } export const IndexPatternContext = createContext>({}); @@ -20,18 +24,35 @@ interface ProviderProps { export const IndexPatternContextProvider: React.FC = ({ children, - indexPattern, + indexPattern: initialIndexPattern, }) => { - return ( - - {children} - - ); + const [indexPattern, setIndexPattern] = useState(initialIndexPattern); + + useEffect(() => { + setIndexPattern(initialIndexPattern); + }, [initialIndexPattern]); + + const { + services: { data }, + } = useKibana(); + + const loadIndexPattern = async (dataType: AppDataType) => { + if (dataType === 'synthetics') { + const newIndexPattern = await data.indexPatterns.get('df32db00-819e-11eb-87f5-d7da22b1dde3'); + setIndexPattern(newIndexPattern); + } + }; + + const value = useMemo(() => { + return { + indexPattern, + loadIndexPattern, + }; + }, [indexPattern]); + + return {children}; }; -export const useIndexPatternContext = () => - useContext((IndexPatternContext as unknown) as Context); +export const useIndexPatternContext = () => { + return useContext((IndexPatternContext as unknown) as Context); +}; diff --git a/x-pack/plugins/observability/public/routes/index.tsx b/x-pack/plugins/observability/public/routes/index.tsx index 04ae8df15fe9dd..56f30c53bfb2d9 100644 --- a/x-pack/plugins/observability/public/routes/index.tsx +++ b/x-pack/plugins/observability/public/routes/index.tsx @@ -8,13 +8,11 @@ import React from 'react'; import * as t from 'io-ts'; import { i18n } from '@kbn/i18n'; -import { useParams } from 'react-router-dom'; import { HomePage } from '../pages/home'; import { LandingPage } from '../pages/landing'; import { OverviewPage } from '../pages/overview'; import { jsonRt } from './json_rt'; import { ExploratoryViewPage } from '../components/shared/exploratory_view'; -import { DataViewType } from '../components/shared/exploratory_view/types'; export type RouteParams = DecodeParams; @@ -75,10 +73,9 @@ export const routes = { }, ], }, - '/exploratory-view/:dataViewType': { + '/exploratory-view': { handler: () => { - const { dataViewType } = useParams<{ dataViewType: DataViewType }>(); - return ; + return ; }, params: { query: t.partial({ diff --git a/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx b/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx index db4e7b968c2dbd..04e3c851e8b3e2 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx +++ b/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx @@ -16,7 +16,7 @@ import { XYChartElementEvent, ElementClickListener, } from '@elastic/charts'; -import { EuiTitle, EuiSpacer } from '@elastic/eui'; +import { EuiTitle, EuiSpacer, EuiFlexItem, EuiFlexGroup, EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useContext } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -26,10 +26,11 @@ import { getChartDateLabel } from '../../../lib/helper'; import { ChartWrapper } from './chart_wrapper'; import { UptimeThemeContext } from '../../../contexts'; import { HistogramResult } from '../../../../common/runtime_types'; -import { useUrlParams } from '../../../hooks'; +import { useGetUrlParams, useUrlParams } from '../../../hooks'; import { ChartEmptyState } from './chart_empty_state'; import { getDateRangeFromChartElement } from './utils'; import { STATUS_DOWN_LABEL, STATUS_UP_LABEL } from '../translations'; +import rison from 'rison-node'; export interface PingHistogramComponentProps { /** @@ -69,6 +70,8 @@ export const PingHistogramComponent: React.FC = ({ chartTheme, } = useContext(UptimeThemeContext); + const { dateRangeStart, dateRangeEnd } = useGetUrlParams(); + const [, updateUrlParams] = useUrlParams(); let content: JSX.Element | undefined; @@ -179,16 +182,37 @@ export const PingHistogramComponent: React.FC = ({ ); } + const analyzeHref = { + ['uptime-pings-pgv']: { + rt: 'upp', + time: { from: dateRangeStart, to: dateRangeEnd }, + bd: 'monitor.status', + }, + }; + return ( <> - -

- -

-
+ + + +

+ +

+
+
+ + + Analyze + + +
+ {content} From 9166efc656a2d215590bfee995990164e77328b3 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 15 Mar 2021 09:49:45 +0100 Subject: [PATCH 05/53] add def col --- .../series_builder/columns/report_definition_col.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx index 07e1d68e8acf57..909fd40c9a26b2 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx @@ -27,7 +27,12 @@ const ReportDefinitionMap: Record< }, ], kpi: [], - pgv: [], + pgv: [ + { + field: 'service.name', + required: true, + }, + ], svl: [ { field: 'service.name', From 04316ab24401885adc7f009868c3868decfc2e9d Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 15 Mar 2021 14:30:24 +0100 Subject: [PATCH 06/53] update --- .../configurations/constants.ts | 12 +++- .../exploratory_view/exploratory_view.tsx | 56 +++++++---------- .../hooks/use_init_exploratory_view.ts | 13 ++-- .../hooks/use_lens_attributes.ts | 2 +- .../hooks/use_default_index_pattern.tsx | 8 +-- .../utils/observability_Index_patterns.ts | 63 +++++++++++++++++++ 6 files changed, 107 insertions(+), 47 deletions(-) create mode 100644 x-pack/plugins/observability/public/utils/observability_Index_patterns.ts diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts index d53924ad0e1d61..2f86232457e6b9 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ReportViewType } from '../types'; +import { AppDataType, ReportViewType, ReportViewTypeId } from '../types'; export const METRIC_TYPE = 'mt'; export const REPORT_TYPE = 'rt'; @@ -31,3 +31,13 @@ export const DataViewLabels: Record = { 'uptime-pings': 'Uptime pings', 'service-latency': 'APM Service latency', }; + +export const ReportToDataTypeMap: Record = { + upd: 'synthetics', + upp: 'synthetics', + tpt: 'apm', + svl: 'apm', + pgv: 'rum', + kpi: 'rum', + pld: 'rum', +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx index 7bbe0e64e1b3f2..c2128619a0e9a3 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx @@ -6,13 +6,7 @@ */ import React from 'react'; -import { - EuiLoadingSpinner, - EuiPage, - EuiPageBody, - EuiPageContent, - EuiPageContentBody, -} from '@elastic/eui'; +import { EuiLoadingSpinner, EuiPageContentBody, EuiPanel } from '@elastic/eui'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { ObservabilityClientPluginsStart } from '../../../plugin'; import { IndexPattern } from '../../../../../../../src/plugins/data/common'; @@ -42,34 +36,30 @@ export const ExploratoryView = ({ indexPattern }: Props) => { }); return ( - - - - - {indexPattern ? ( - <> - - {lensAttributes && seriesId ? ( - - ) : ( - - )} - - + + + {indexPattern ? ( + <> + + {lensAttributes && seriesId ? ( + ) : ( - - - + )} - - - - + + + ) : ( + + + + )} +
+ ); }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts index 44f433468e3077..f87cba47d5895e 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts @@ -5,15 +5,14 @@ * 2.0. */ +import { useMemo } from 'react'; import { useFetcher } from '../../../..'; import { IKbnUrlStateStorage } from '../../../../../../../../src/plugins/kibana_utils/public'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { ObservabilityClientPluginsStart } from '../../../../plugin'; import { AllSeries } from './use_url_strorage'; -import { REPORT_TYPE } from '../configurations/constants'; -import { useMemo } from 'react'; - -const APM_STATIC_INDEX_PATTERN_ID = 'apm_static_index_pattern_id'; +import { REPORT_TYPE, ReportToDataTypeMap } from '../configurations/constants'; +import { ObservabilityIndexPatterns } from '../../../../utils/observability_Index_patterns'; export const useInitExploratoryView = (storage: IKbnUrlStateStorage) => { const { @@ -31,10 +30,8 @@ export const useInitExploratoryView = (storage: IKbnUrlStateStorage) => { const firstSeries = allSeries[firstSeriesId]; const { data: indexPattern, status } = useFetcher(() => { - if (firstSeries?.[REPORT_TYPE] === 'upp') { - return data.indexPatterns.get('df32db00-819e-11eb-87f5-d7da22b1dde3'); - } - return data.indexPatterns.get(APM_STATIC_INDEX_PATTERN_ID); + const obsvIndexP = new ObservabilityIndexPatterns(data); + return obsvIndexP.getIndexPattern(ReportToDataTypeMap[firstSeries?.[REPORT_TYPE]]); }, [firstSeries?.[REPORT_TYPE]]); return useMemo(() => { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts index c20743c10bc520..efd6ebb4c97e7c 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts @@ -21,7 +21,7 @@ import { IIndexPattern } from '../../../../../../../../src/plugins/data/common'; interface Props { seriesId: string; - indexPattern: IIndexPattern; + indexPattern?: IIndexPattern | null; } export const useLensAttributes = ({ diff --git a/x-pack/plugins/observability/public/hooks/use_default_index_pattern.tsx b/x-pack/plugins/observability/public/hooks/use_default_index_pattern.tsx index 15c2ac5b5a114e..0735b964908d82 100644 --- a/x-pack/plugins/observability/public/hooks/use_default_index_pattern.tsx +++ b/x-pack/plugins/observability/public/hooks/use_default_index_pattern.tsx @@ -10,6 +10,7 @@ import { IIndexPattern } from '../../../../../src/plugins/data/common'; import { AppDataType } from '../components/shared/exploratory_view/types'; import { useKibana } from '../../../../../src/plugins/kibana_react/public'; import { ObservabilityClientPluginsStart } from '../plugin'; +import { ObservabilityIndexPatterns } from '../utils/observability_Index_patterns'; export interface IIndexPatternContext { indexPattern: IIndexPattern; @@ -37,10 +38,9 @@ export const IndexPatternContextProvider: React.FC = ({ } = useKibana(); const loadIndexPattern = async (dataType: AppDataType) => { - if (dataType === 'synthetics') { - const newIndexPattern = await data.indexPatterns.get('df32db00-819e-11eb-87f5-d7da22b1dde3'); - setIndexPattern(newIndexPattern); - } + const obsvIndexP = new ObservabilityIndexPatterns(data); + const indPattern = await obsvIndexP.getIndexPattern(dataType); + setIndexPattern(indPattern!); }; const value = useMemo(() => { diff --git a/x-pack/plugins/observability/public/utils/observability_Index_patterns.ts b/x-pack/plugins/observability/public/utils/observability_Index_patterns.ts new file mode 100644 index 00000000000000..42694940c9ce47 --- /dev/null +++ b/x-pack/plugins/observability/public/utils/observability_Index_patterns.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataPublicPluginStart, IIndexPattern } from '../../../../../src/plugins/data/public'; + +type DataType = 'synthetics' | 'apm' | 'logs' | 'metrics' | 'rum'; + +const indexPatternList: Record = { + synthetics: 'synthetics_static_index_pattern_id', + apm: 'apm_static_index_pattern_id', + rum: 'apm_static_index_pattern_id', + logs: 'logs_static_index_pattern_id', + metrics: 'metrics_static_index_pattern_id', +}; + +const appToPatternMap: Record = { + synthetics: 'heartbeat-*', + apm: 'apm-*', + rum: 'apm-*', + logs: 'logs-*', + metrics: 'metrics-*', +}; + +export class ObservabilityIndexPatterns { + data?: DataPublicPluginStart; + + constructor(data: DataPublicPluginStart) { + this.data = data; + } + + async createIndexPattern(app: DataType) { + if (!this.data) { + throw 'data is not defined'; + } + + const pattern = appToPatternMap[app]; + + const fields = await this.data.indexPatterns.getFieldsForWildcard({ + pattern, + }); + + return await this.data.indexPatterns.createAndSave({ + fields, + title: pattern, + id: indexPatternList[app], + }); + } + + async getIndexPattern(app: DataType): Promise { + if (!this.data) { + throw 'data is not defined'; + } + try { + return await this.data?.indexPatterns.get(indexPatternList[app]); + } catch (e) { + return await this.createIndexPattern(app); + } + } +} From e715acef79548556c4895b8c94dd30a2b85a0802 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 15 Mar 2021 16:25:31 +0100 Subject: [PATCH 07/53] wip --- .../exploratory_view/configurations/constants.ts | 16 +++++++++------- .../configurations/lens_attributes.ts | 2 +- .../shared/exploratory_view/exploratory_view.tsx | 12 +++++------- .../shared/exploratory_view/header/header.tsx | 8 ++------ .../hooks/use_init_exploratory_view.ts | 2 +- .../components/shared/exploratory_view/index.tsx | 2 +- 6 files changed, 19 insertions(+), 23 deletions(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts index 2f86232457e6b9..0f6ecfea243121 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { AppDataType, ReportViewType, ReportViewTypeId } from '../types'; +import { AppDataType, ReportViewTypeId } from '../types'; export const METRIC_TYPE = 'mt'; export const REPORT_TYPE = 'rt'; @@ -24,12 +24,14 @@ export const FieldLabels: Record = { 'monitor.status': 'Monitor Status', }; -export const DataViewLabels: Record = { - 'page-load-dist': 'Page load distribution', - 'page-views': 'Page views', - 'uptime-duration': 'Uptime monitor duration', - 'uptime-pings': 'Uptime pings', - 'service-latency': 'APM Service latency', +export const DataViewLabels: Record = { + pld: 'Page load distribution', + pgv: 'Page views', + upd: 'Uptime monitor duration', + upp: 'Uptime pings', + svl: 'APM Service latency', + kpi: 'Business KPI', + tpt: 'APM Service throughput', }; export const ReportToDataTypeMap: Record = { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts index 76aad6f85d8b79..a22eb88b0ab4cf 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts @@ -27,7 +27,7 @@ export class LensAttributes { reportViewConfig: DataSeries, seriesType: string, filters: UrlFilter[], - metricType: string + metricType?: string ) { this.indexPattern = indexPattern; this.layers = {}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx index c2128619a0e9a3..8b37bc5bf566d3 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx @@ -9,23 +9,21 @@ import React from 'react'; import { EuiLoadingSpinner, EuiPageContentBody, EuiPanel } from '@elastic/eui'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { ObservabilityClientPluginsStart } from '../../../plugin'; -import { IndexPattern } from '../../../../../../../src/plugins/data/common'; import { ExploratoryViewHeader } from './header/header'; import { SeriesEditor } from './series_editor/series_editor'; import { useUrlStorage } from './hooks/use_url_strorage'; import { useLensAttributes } from './hooks/use_lens_attributes'; import styled from 'styled-components'; import { EmptyView } from './components/empty_view'; +import { useIndexPatternContext } from '../../../hooks/use_default_index_pattern'; -export interface Props { - indexPattern?: IndexPattern | null; -} - -export const ExploratoryView = ({ indexPattern }: Props) => { +export const ExploratoryView = () => { const { services: { lens }, } = useKibana(); + const { indexPattern } = useIndexPatternContext(); + const LensComponent = lens.EmbeddableComponent; const { firstSeriesId: seriesId, firstSeries: series } = useUrlStorage(); @@ -44,7 +42,7 @@ export const ExploratoryView = ({ indexPattern }: Props) => { {lensAttributes && seriesId ? ( diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx index 2ca8471157bab9..2569791c9c9358 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx @@ -6,15 +6,13 @@ */ import React from 'react'; -import { useParams } from 'react-router-dom'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { TypedLensByValueInput } from '../../../../../../lens/public'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { ObservabilityClientPluginsStart } from '../../../../plugin'; import { ChartTemplates } from '../chart_templates/chart_templates'; import { ChartTypes } from './chart_types'; -import { ReportViewType } from '../types'; -import { DataViewLabels } from '../configurations/constants'; +import { DataViewLabels, REPORT_TYPE } from '../configurations/constants'; import { useUrlStorage } from '../hooks/use_url_strorage'; import { MetricSelection } from './metric_selection'; @@ -28,15 +26,13 @@ export function ExploratoryViewHeader({ seriesId, lensAttributes }: Props) { services: { lens }, } = useKibana(); - const { dataViewType } = useParams<{ dataViewType: ReportViewType }>(); - const { series } = useUrlStorage(seriesId); return ( -

{DataViewLabels[dataViewType]}

+

{DataViewLabels[series[REPORT_TYPE]]}

diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts index f87cba47d5895e..08957c236bd444 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts @@ -31,7 +31,7 @@ export const useInitExploratoryView = (storage: IKbnUrlStateStorage) => { const { data: indexPattern, status } = useFetcher(() => { const obsvIndexP = new ObservabilityIndexPatterns(data); - return obsvIndexP.getIndexPattern(ReportToDataTypeMap[firstSeries?.[REPORT_TYPE]]); + return obsvIndexP.getIndexPattern(ReportToDataTypeMap[firstSeries?.[REPORT_TYPE]] ?? 'apm'); }, [firstSeries?.[REPORT_TYPE]]); return useMemo(() => { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx index a6edc6e1fc1c61..4135502eb43b21 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx @@ -54,7 +54,7 @@ export const ExploratoryViewPage = () => { > - + From 3d824bee1f8afbfc231fe6281ef40ee9626bf15c Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 16 Mar 2021 01:00:48 +0100 Subject: [PATCH 08/53] wip --- .../embeddable/embeddable.tsx | 24 +++++++++++++++++++ .../exploratory_view/exploratory_view.tsx | 8 ++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx index f397dffc03262a..79b6fbd2ecb23b 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx @@ -45,6 +45,9 @@ import { isLensBrushEvent, isLensFilterEvent, isLensTableRowContextMenuClickEvent, + LensBrushEvent, + LensFilterEvent, + LensTableRowContextMenuEvent, } from '../../types'; import { IndexPatternsContract } from '../../../../../../src/plugins/data/public'; @@ -63,6 +66,10 @@ interface LensBaseEmbeddableInput extends EmbeddableInput { renderMode?: RenderMode; style?: React.CSSProperties; className?: string; + onBrushEnd?: (data: LensBrushEvent['data']) => void; + onLoad?: (isLoading: boolean) => void; + onFilter?: (data: LensFilterEvent['data']) => void; + onTableRowClick?: (data: LensTableRowContextMenuEvent['data']) => void; } export type LensByValueInput = { @@ -268,6 +275,10 @@ export class Embeddable inspectorAdapters?: Partial | undefined ) => { this.activeData = inspectorAdapters; + if (this.input.onLoad) { + // once onData$ is get's called from expression renderer, loading becomes false + this.input.onLoad(false); + } }; /** @@ -280,6 +291,9 @@ export class Embeddable if (!this.savedVis || !this.isInitialized) { return; } + if (this.input.onLoad) { + this.input.onLoad(true); + } const input = this.getInput(); render( { +export function ExploratoryView() { const { services: { lens }, } = useKibana(); @@ -45,6 +45,8 @@ export const ExploratoryView = () => { style={{ height: 550 }} timeRange={series?.time} attributes={lensAttributes} + onBrushEnd={(data) => {}} + onLoad={(val) => {}} /> ) : ( @@ -59,7 +61,7 @@ export const ExploratoryView = () => {
); -}; +} const SpinnerWrap = styled.div` height: 100vh; From 306fa3f0cbe8e918d3a9d6d95b8a1cedfe8510cc Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 17 Mar 2021 17:45:24 +0100 Subject: [PATCH 09/53] wip --- .../public/components/app/header/index.tsx | 4 - .../public/components/app/header/tabs.tsx | 41 ------- .../chart_templates/chart_templates.tsx | 87 -------------- .../exploratory_view/chart_templates/tabs.tsx | 43 ------- .../components/filter_label.tsx | 5 +- .../configurations/constants.ts | 10 +- .../configurations/default_configs.ts | 15 ++- ...ge_view_config.ts => kpi_trends_config.ts} | 19 ++- .../configurations/monitor_duration_config.ts | 21 +++- .../configurations/monitor_pings_config.ts | 19 +-- .../configurations/page_load_dist_config.ts | 17 ++- .../configurations/service_latency_config.ts | 18 ++- .../service_throughput_config.ts | 18 ++- .../exploratory_view/exploratory_view.tsx | 48 ++++---- .../shared/exploratory_view/header/header.tsx | 14 +-- .../header/metric_selection.tsx | 56 --------- .../hooks/use_url_strorage.tsx | 12 +- .../series_builder/columns/data_types_col.tsx | 22 ++-- .../columns/report_definition_col.tsx | 111 +++++++----------- .../columns/report_types_col.tsx | 19 ++- .../series_builder/series_builder.tsx | 77 ++++++------ .../series_editor/columns/actions_col.tsx | 23 ++-- .../series_editor/columns/breakdowns.tsx | 49 ++++---- .../columns}/chart_types.tsx | 100 ++++++++++------ .../series_editor/columns/date_picker_col.tsx | 20 ++++ .../series_editor/columns/filter_expanded.tsx | 4 +- .../columns/metric_selection.tsx | 77 ++++++++++++ .../series_editor/columns/remove_series.tsx | 6 +- .../series_editor/columns/series_filter.tsx | 32 +---- .../series_editor/default_filters.tsx | 43 ------- .../series_editor/selected_filters.tsx | 68 ++++++----- .../series_editor/series_editor.tsx | 29 +++-- .../shared/exploratory_view/types.ts | 24 +++- .../shared/field_value_selection.tsx | 103 ++++++++-------- .../public/hooks/use_values_list.ts | 7 +- 35 files changed, 589 insertions(+), 672 deletions(-) delete mode 100644 x-pack/plugins/observability/public/components/app/header/tabs.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/chart_templates/chart_templates.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/chart_templates/tabs.tsx rename x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/{page_view_config.ts => kpi_trends_config.ts} (72%) delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/header/metric_selection.tsx rename x-pack/plugins/observability/public/components/shared/exploratory_view/{header => series_editor/columns}/chart_types.tsx (64%) create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/date_picker_col.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/default_filters.tsx diff --git a/x-pack/plugins/observability/public/components/app/header/index.tsx b/x-pack/plugins/observability/public/components/app/header/index.tsx index 4bb106ddbd34e9..8b86e0b25379b8 100644 --- a/x-pack/plugins/observability/public/components/app/header/index.tsx +++ b/x-pack/plugins/observability/public/components/app/header/index.tsx @@ -19,7 +19,6 @@ import React, { ReactNode } from 'react'; import styled from 'styled-components'; import { usePluginContext } from '../../../hooks/use_plugin_context'; import HeaderMenuPortal from '../../shared/header_menu_portal'; -import { Tabs } from './tabs'; const Container = styled.div<{ color: string }>` background: ${(props) => props.color}; @@ -75,9 +74,6 @@ export function Header({ color, datePicker = null, restrictWidth }: Props) { - - -
{datePicker} diff --git a/x-pack/plugins/observability/public/components/app/header/tabs.tsx b/x-pack/plugins/observability/public/components/app/header/tabs.tsx deleted file mode 100644 index c56cfa1781086f..00000000000000 --- a/x-pack/plugins/observability/public/components/app/header/tabs.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiTabs, EuiTab } from '@elastic/eui'; -import { useRouteMatch } from 'react-router'; - -const tabs = [ - { - id: 'overview', - name: 'Overview', - href: '/app/observability/overview', - }, - { - id: 'exploratoryView', - name: 'Exploratory View', - href: '/app/observability/exploratory-view', - }, -]; - -export function Tabs() { - const overviewTab = useRouteMatch('/overview'); - - const renderTabs = () => { - return tabs.map((tab, index) => ( - - {tab.name} - - )); - }; - - return {renderTabs()}; -} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/chart_templates/chart_templates.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/chart_templates/chart_templates.tsx deleted file mode 100644 index 2bad254c33d7b4..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/chart_templates/chart_templates.tsx +++ /dev/null @@ -1,87 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useState } from 'react'; - -import { - EuiFlyout, - EuiFlyoutBody, - EuiFlyoutHeader, - EuiButton, - EuiTitle, - EuiCard, - EuiIcon, - EuiFlexItem, - EuiFlexGroup, - EuiSpacer, -} from '@elastic/eui'; -import { SelectionTabs } from './tabs'; - -export function ChartTemplates() { - const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); - - let flyout; - - const style = { minWidth: 200 }; - - if (isFlyoutVisible) { - flyout = ( - setIsFlyoutVisible(false)} aria-labelledby="flyoutTitle"> - - -

Choose chart

-
-
- - - - - - } - title={`Page load distribution`} - description="Example of a card's description. Stick to one or two sentences." - href="/app/observability/exploratory-view/page-load-dist" - /> - - - } - title={`Page views`} - description="Example of a card's description. Stick to one or two sentences." - href="/app/observability/exploratory-view/page-views" - /> - - - } - title={`Monitor duration`} - description="Uptime monitor duration, slice and dice by location etc" - href="/app/observability/exploratory-view/uptime-duration" - /> - - - } - title={`APM Service latency`} - description="Uptime monitor duration, slice and dice by location etc" - href="/app/observability/exploratory-view/service-latency" - /> - - - -
- ); - } - - return ( -
- setIsFlyoutVisible(true)}>Chart templates - {flyout} -
- ); -} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/chart_templates/tabs.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/chart_templates/tabs.tsx deleted file mode 100644 index 4643bc4ee7f99c..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/chart_templates/tabs.tsx +++ /dev/null @@ -1,43 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useState } from 'react'; - -import { EuiTabs, EuiTab } from '@elastic/eui'; - -const tabs = [ - { - id: 'savedCharts', - name: 'Saved charts', - }, - { - id: 'predefinedCharts', - name: 'Predefined charts', - }, -]; - -export function SelectionTabs() { - const [selectedTabId, setSelectedTabId] = useState('predefinedCharts'); - - const onSelectedTabChanged = (id: string) => { - setSelectedTabId(id); - }; - - const renderTabs = () => { - return tabs.map((tab, index) => ( - onSelectedTabChanged(tab.id)} - isSelected={tab.id === selectedTabId} - key={index} - > - {tab.name} - - )); - }; - - return {renderTabs()}; -} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx index 4c732e7278680b..71cfd3a48b1d25 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx @@ -15,12 +15,13 @@ import { useSeriesFilters } from '../hooks/use_series_filters'; interface Props { field: string; + label: string; value: string; seriesId: string; negate: boolean; removeFilter: (field: string, value: string, notVal: boolean) => void; } -export const FilterLabel = ({ seriesId, field, value, negate, removeFilter }: Props) => { +export const FilterLabel = ({ label, seriesId, field, value, negate, removeFilter }: Props) => { const FilterItem = injectI18n(esFilters.FilterItem); const { indexPattern } = useIndexPatternContext(); @@ -30,7 +31,7 @@ export const FilterLabel = ({ seriesId, field, value, negate, removeFilter }: Pr const filter = esFilters.buildPhraseFilter(indexField, value, indexPattern); filter.meta.value = value; - filter.meta.key = FieldLabels[field]; + filter.meta.key = label; filter.meta.alias = null; filter.meta.negate = negate; filter.meta.disabled = false; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts index 0f6ecfea243121..38de9699d4b9ad 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts @@ -19,18 +19,19 @@ export const FieldLabels: Record = { 'client.geo.country_name': 'Location', 'user_agent.device.name': 'Device', 'observer.geo.name': 'Observer location', - 'service.name': 'Service', + 'service.name': 'Service Name', + 'service.environment': 'Environment', + 'monitor.id': 'Monitor Id', 'monitor.status': 'Monitor Status', }; export const DataViewLabels: Record = { - pld: 'Page load distribution', - pgv: 'Page views', + pld: 'Performance Distribution', upd: 'Uptime monitor duration', upp: 'Uptime pings', svl: 'APM Service latency', - kpi: 'Business KPI', + kpi: 'Business KPI over time', tpt: 'APM Service throughput', }; @@ -39,7 +40,6 @@ export const ReportToDataTypeMap: Record = { upp: 'synthetics', tpt: 'apm', svl: 'apm', - pgv: 'rum', kpi: 'rum', pld: 'rum', }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts index f0543ede18ae61..651750cb8af4fc 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts @@ -7,33 +7,32 @@ import { ReportViewTypes } from '../types'; import { getPageLoadDistLensConfig } from './page_load_dist_config'; -import { getPageViewLensConfig } from './page_view_config'; import { getMonitorDurationConfig } from './monitor_duration_config'; import { getServiceLatencyLensConfig } from './service_latency_config'; import { getMonitorPingsConfig } from './monitor_pings_config'; import { getServiceThroughputLensConfig } from './service_throughput_config'; +import { getKPITrendsLensConfig } from './kpi_trends_config'; interface Props { reportType: keyof typeof ReportViewTypes; seriesId: string; - serviceName: string; } -export const getDefaultConfigs = ({ reportType, seriesId, serviceName }: Props) => { +export const getDefaultConfigs = ({ reportType, seriesId }: Props) => { switch (ReportViewTypes[reportType]) { case 'page-load-dist': - return getPageLoadDistLensConfig({ seriesId, serviceName }); + return getPageLoadDistLensConfig({ seriesId }); case 'page-views': - return getPageViewLensConfig({ seriesId, serviceName }); + return getKPITrendsLensConfig({ seriesId }); case 'uptime-duration': return getMonitorDurationConfig(); case 'uptime-pings': return getMonitorPingsConfig({ seriesId }); case 'service-latency': - return getServiceLatencyLensConfig({ seriesId, serviceName }); + return getServiceLatencyLensConfig({ seriesId }); case 'service-throughput': - return getServiceThroughputLensConfig({ seriesId, serviceName }); + return getServiceThroughputLensConfig({ seriesId }); default: - return getPageViewLensConfig({ seriesId, serviceName }); + return getKPITrendsLensConfig({ seriesId }); } }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_view_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts similarity index 72% rename from x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_view_config.ts rename to x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts index 9991d2aa226d0f..59f2fc0e82f4f4 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_view_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts @@ -6,17 +6,17 @@ */ import { DataSeries } from '../types'; +import { FieldLabels } from './constants'; interface Props { seriesId: string; - serviceName: string; } -export function getPageViewLensConfig({ seriesId, serviceName }: Props): DataSeries { +export function getKPITrendsLensConfig({ seriesId }: Props): DataSeries { return { id: seriesId, defaultSeriesType: 'bar_stacked', - reportType: 'page-views', + reportType: 'kpi-trends', indexPattern: 'apm_static_index_pattern_id', seriesTypes: ['bar', 'bar_stacked'], xAxisColumn: { @@ -24,7 +24,9 @@ export function getPageViewLensConfig({ seriesId, serviceName }: Props): DataSer }, yAxisColumn: { operationType: 'count', + label: 'Page views', }, + metricType: false, defaultFilters: [ 'user_agent.name', 'user_agent.os.name', @@ -41,7 +43,16 @@ export function getPageViewLensConfig({ seriesId, serviceName }: Props): DataSer { query: { match_phrase: { 'transaction.type': 'page-load' } }, }, - ...(serviceName ? [{ query: { match_phrase: { 'service.name': serviceName } } }] : []), + ], + labels: { ...FieldLabels }, + reportDefinitions: [ + { + field: 'service.name', + required: true, + }, + { + field: 'service.environment', + }, ], }; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_duration_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_duration_config.ts index 82a3ff98c62c4b..e61b7b40a3f983 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_duration_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_duration_config.ts @@ -6,15 +6,15 @@ */ import { DataSeries } from '../types'; +import { FieldLabels } from './constants'; export function getMonitorDurationConfig(): DataSeries { return { - name: 'Android homepage', id: 'elastic-co', - dataViewType: 'uptime-duration', + reportType: 'uptime-duration', defaultSeriesType: 'line', indexPattern: 'df32db00-819e-11eb-87f5-d7da22b1dde3', - seriesTypes: ['bar', 'bar_stacked'], + seriesTypes: ['line', 'bar_stacked'], xAxisColumn: { sourceField: '@timestamp', }, @@ -23,10 +23,19 @@ export function getMonitorDurationConfig(): DataSeries { sourceField: 'monitor.duration.us', label: 'Monitor duration', }, + metricType: true, defaultFilters: ['observer.geo.name'], breakdowns: ['observer.geo.name'], - filters: { - query: { match_phrase: { 'monitor.id': 'android-homepage' } }, - }, + filters: [ + { + query: { match_phrase: { 'monitor.id': 'android-homepage' } }, + }, + ], + reportDefinitions: [ + { + field: 'monitor.id', + }, + ], + labels: { ...FieldLabels }, }; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_pings_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_pings_config.ts index 6cd38d3b99ae6e..2c5e74191f1b91 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_pings_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_pings_config.ts @@ -6,13 +6,13 @@ */ import { DataSeries } from '../types'; +import { FieldLabels } from './constants'; interface Props { seriesId: string; - monitorId: string; } -export function getMonitorPingsConfig({ seriesId, monitorId }: Props): DataSeries { +export function getMonitorPingsConfig({ seriesId }: Props): DataSeries { return { id: seriesId, reportType: 'uptime-pings', @@ -26,14 +26,15 @@ export function getMonitorPingsConfig({ seriesId, monitorId }: Props): DataSerie operationType: 'count', label: 'Monitor pings', }, + metricType: false, defaultFilters: ['observer.geo.name'], breakdowns: ['monitor.status', 'observer.geo.name'], - filters: monitorId - ? [ - { - query: { match_phrase: { 'monitor.id': 'android-homepage' } }, - }, - ] - : [], + filters: [], + reportDefinitions: [ + { + field: 'monitor.id', + }, + ], + labels: { ...FieldLabels }, }; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts index 546782823f938d..d3c4bf3e39d4f7 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts @@ -6,13 +6,13 @@ */ import { DataSeries } from '../types'; +import { FieldLabels } from './constants'; interface Props { seriesId: string; - serviceName: string; } -export function getPageLoadDistLensConfig({ seriesId, serviceName }: Props): DataSeries { +export function getPageLoadDistLensConfig({ seriesId }: Props): DataSeries { return { id: seriesId ?? 'unique-key', reportType: 'page-load-dist', @@ -24,7 +24,9 @@ export function getPageLoadDistLensConfig({ seriesId, serviceName }: Props): Dat }, yAxisColumn: { operationType: 'count', + label: 'Pages loaded', }, + metricType: false, defaultFilters: [ 'user_agent.name', 'user_agent.os.name', @@ -37,10 +39,19 @@ export function getPageLoadDistLensConfig({ seriesId, serviceName }: Props): Dat 'client.geo.country_name', 'user_agent.device.name', ], + reportDefinitions: [ + { + field: 'service.name', + required: true, + }, + { + field: 'service.environment', + }, + ], filters: [ { query: { match_phrase: { 'transaction.type': 'page-load' } } }, { query: { match_phrase: { 'processor.event': 'transaction' } } }, - ...(serviceName ? [{ query: { match_phrase: { 'service.name': serviceName } } }] : []), ], + labels: { ...FieldLabels, 'service.name': 'Web Application' }, }; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts index aa9ad3c7671ad7..1b05757b3e04f7 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts @@ -6,13 +6,13 @@ */ import { DataSeries } from '../types'; +import { FieldLabels } from './constants'; interface Props { seriesId: string; - serviceName: string; } -export function getServiceLatencyLensConfig({ seriesId, serviceName }: Props): DataSeries { +export function getServiceLatencyLensConfig({ seriesId }: Props): DataSeries { return { id: seriesId, reportType: 'service-latency', @@ -27,6 +27,7 @@ export function getServiceLatencyLensConfig({ seriesId, serviceName }: Props): D sourceField: 'transaction.duration.us', label: 'Latency', }, + metricType: true, defaultFilters: [ 'user_agent.name', 'user_agent.os.name', @@ -39,9 +40,16 @@ export function getServiceLatencyLensConfig({ seriesId, serviceName }: Props): D 'client.geo.country_name', 'user_agent.device.name', ], - filters: [ - { query: { match_phrase: { 'transaction.type': 'request' } } }, - ...(serviceName ? [{ query: { match_phrase: { 'service.name': serviceName } } }] : []), + filters: [{ query: { match_phrase: { 'transaction.type': 'request' } } }], + labels: { ...FieldLabels }, + reportDefinitions: [ + { + field: 'service.name', + required: true, + }, + { + field: 'service.environment', + }, ], }; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts index 18fc7c47bce8a4..5f62757a1ec027 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts @@ -6,13 +6,13 @@ */ import { DataSeries } from '../types'; +import { FieldLabels } from './constants'; interface Props { seriesId: string; - serviceName: string; } -export function getServiceThroughputLensConfig({ seriesId, serviceName }: Props): DataSeries { +export function getServiceThroughputLensConfig({ seriesId }: Props): DataSeries { return { id: seriesId, reportType: 'service-latency', @@ -27,6 +27,7 @@ export function getServiceThroughputLensConfig({ seriesId, serviceName }: Props) sourceField: 'transaction.duration.us', label: 'Throughput', }, + metricType: true, defaultFilters: [ 'user_agent.name', 'user_agent.os.name', @@ -39,9 +40,16 @@ export function getServiceThroughputLensConfig({ seriesId, serviceName }: Props) 'client.geo.country_name', 'user_agent.device.name', ], - filters: [ - { query: { match_phrase: { 'transaction.type': 'request' } } }, - ...(serviceName ? [{ query: { match_phrase: { 'service.name': serviceName } } }] : []), + filters: [{ query: { match_phrase: { 'transaction.type': 'request' } } }], + labels: { ...FieldLabels }, + reportDefinitions: [ + { + field: 'service.name', + required: true, + }, + { + field: 'service.environment', + }, ], }; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx index ff3eaeb3b706a9..33c7a6508570f6 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx @@ -7,7 +7,7 @@ import React from 'react'; import styled from 'styled-components'; -import { EuiLoadingSpinner, EuiPageContentBody, EuiPanel } from '@elastic/eui'; +import { EuiLoadingSpinner, EuiPanel } from '@elastic/eui'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { ObservabilityClientPluginsStart } from '../../../plugin'; import { ExploratoryViewHeader } from './header/header'; @@ -35,30 +35,28 @@ export function ExploratoryView() { return ( - - {indexPattern ? ( - <> - - {lensAttributes && seriesId ? ( - {}} - onLoad={(val) => {}} - /> - ) : ( - - )} - - - ) : ( - - - - )} - + {indexPattern ? ( + <> + + {lensAttributes && seriesId ? ( + {}} + onLoad={(val) => {}} + /> + ) : ( + + )} + + + ) : ( + + + + )} ); } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx index 2569791c9c9358..f75fdfe5c9f2c4 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx @@ -10,11 +10,8 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { TypedLensByValueInput } from '../../../../../../lens/public'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { ObservabilityClientPluginsStart } from '../../../../plugin'; -import { ChartTemplates } from '../chart_templates/chart_templates'; -import { ChartTypes } from './chart_types'; import { DataViewLabels, REPORT_TYPE } from '../configurations/constants'; import { useUrlStorage } from '../hooks/use_url_strorage'; -import { MetricSelection } from './metric_selection'; interface Props { seriesId: string; @@ -32,15 +29,9 @@ export function ExploratoryViewHeader({ seriesId, lensAttributes }: Props) { -

{DataViewLabels[series[REPORT_TYPE]]}

+

{DataViewLabels[series[REPORT_TYPE]] ?? 'Exploratory view'}

- - - - - - - - -
); } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/metric_selection.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/metric_selection.tsx deleted file mode 100644 index 92d88e5c496f73..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/metric_selection.tsx +++ /dev/null @@ -1,56 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useState } from 'react'; -import { EuiButtonGroup } from '@elastic/eui'; -import { useUrlStorage } from '../hooks/use_url_strorage'; -import { METRIC_TYPE } from '../configurations/constants'; - -export const MetricSelection = ({ seriesId }: { seriesId: string }) => { - const toggleButtons = [ - { - id: `avg`, - label: 'Avg', - }, - { - id: `median`, - label: 'Median', - }, - { - id: `95th`, - label: '95th', - }, - { - id: `99th`, - label: '99th', - }, - ]; - - const { series, setSeries, allSeries } = useUrlStorage(seriesId); - - const [toggleIdSelected, setToggleIdSelected] = useState(series?.[METRIC_TYPE] ?? 'avg'); - - const onChange = (optionId: string) => { - setToggleIdSelected(optionId); - - Object.keys(allSeries).forEach((seriesKey) => { - const seriesN = allSeries[seriesKey]; - - setSeries(seriesKey, { ...seriesN, [METRIC_TYPE]: optionId }); - }); - }; - - return ( - onChange(id)} - /> - ); -}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx index 0d299f11eac015..05ba7914588277 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx @@ -7,7 +7,7 @@ import React, { createContext, useContext, Context } from 'react'; import { IKbnUrlStateStorage } from '../../../../../../../../src/plugins/kibana_utils/public'; -import { SeriesUrl } from '../types'; +import { NewSeriesUrl, SeriesUrl } from '../types'; export const UrlStorageContext = createContext(null); @@ -21,6 +21,8 @@ export const UrlStorageContextProvider: React.FC = ({ children, s export type AllSeries = Record; +export const NEW_SERIES_KEY = 'newSeries'; + export const useUrlStorage = (seriesId?: string) => { const allSeriesKey = 'sr'; const storage = useContext((UrlStorageContext as unknown) as Context); @@ -45,6 +47,12 @@ export const useUrlStorage = (seriesId?: string) => { const firstSeriesId = allSeriesIds?.[0]; + const newSeries = storage.get('newSeries') ?? {}; + + const setNewSeries = (newValue: NewSeriesUrl) => { + storage.set(NEW_SERIES_KEY, newValue); + }; + return { storage, setSeries, @@ -53,6 +61,8 @@ export const useUrlStorage = (seriesId?: string) => { firstSeriesId, allSeries, allSeriesIds, + newSeries, + setNewSeries, firstSeries: allSeries?.[firstSeriesId], }; }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx index e0ec8126453b16..e241a97d309b20 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx @@ -5,15 +5,11 @@ * 2.0. */ -import React, { Dispatch, SetStateAction } from 'react'; +import React from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { AppDataType } from '../../types'; import { useIndexPatternContext } from '../../../../../hooks/use_default_index_pattern'; - -interface Props { - selectedDataType: AppDataType | null; - onChange: Dispatch>; -} +import { useUrlStorage } from '../../hooks/use_url_strorage'; const dataTypes: { id: AppDataType; label: string }[] = [ { id: 'synthetics', label: 'Synthetic Monitoring' }, @@ -22,16 +18,20 @@ const dataTypes: { id: AppDataType; label: string }[] = [ { id: 'metrics', label: 'Metrics' }, { id: 'apm', label: 'APM' }, ]; -export const DataTypesCol = ({ selectedDataType, onChange }: Props) => { +export const DataTypesCol = () => { + const { newSeries, setNewSeries } = useUrlStorage(); + const { loadIndexPattern } = useIndexPatternContext(); - const onDataTypeChange = (dataType: AppDataType | null) => { - onChange(dataType); + const onDataTypeChange = (dataType?: AppDataType) => { if (dataType) { loadIndexPattern(dataType); } + setNewSeries({ dataType }); }; + const selectedDataType = newSeries.dataType; + return ( {dataTypes.map(({ id: dt, label }) => ( @@ -42,7 +42,9 @@ export const DataTypesCol = ({ selectedDataType, onChange }: Props) => { iconType="arrowRight" color={selectedDataType === dt ? 'primary' : 'text'} fill={selectedDataType === dt} - onClick={() => onDataTypeChange(dt === selectedDataType ? null : dt)} + onClick={() => { + onDataTypeChange(dt === selectedDataType ? undefined : dt); + }} > {label} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx index 909fd40c9a26b2..040104fd952f6c 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx @@ -5,80 +5,57 @@ * 2.0. */ -import React, { Dispatch, SetStateAction } from 'react'; +import React from 'react'; +import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FieldValueSelection } from '../../../field_value_selection'; import { useIndexPatternContext } from '../../../../../hooks/use_default_index_pattern'; -import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { AppDataType, ReportViewTypeId } from '../../types'; +import { getDefaultConfigs } from '../../configurations/default_configs'; +import { useUrlStorage } from '../../hooks/use_url_strorage'; -const ReportDefinitionMap: Record< - ReportViewTypeId, - Array<{ field: string; required?: boolean }> -> = { - pld: [ - { - field: 'service.name', - required: true, - }, - ], - upp: [ - { - field: 'monitor.id', - }, - ], - kpi: [], - pgv: [ - { - field: 'service.name', - required: true, - }, - ], - svl: [ - { - field: 'service.name', - required: true, - }, - ], - tpt: [ - { - field: 'service.name', - required: true, - }, - ], - upd: [], -}; +export const ReportDefinitionCol = () => { + const { indexPattern } = useIndexPatternContext(); -interface Props { - dataType: AppDataType; - reportType: ReportViewTypeId; - selectedReportDefinitions: Record; - onChange: Dispatch>; -} + const { newSeries, setNewSeries } = useUrlStorage(); -export const ReportDefinitionCol = ({ reportType, selectedReportDefinitions, onChange }: Props) => { - const { indexPattern } = useIndexPatternContext(); + const { reportType, reportDefinitions: rtd = {} } = newSeries; + + const { reportDefinitions, labels, filters } = getDefaultConfigs({ + reportType: reportType!, + seriesId: 'newSeries', + }); + + const onChange = (field: string, value: string) => { + setNewSeries({ + ...newSeries, + reportDefinitions: { ...rtd, [field]: value }, + }); + }; return ( -
- {selectedReportDefinitions && ( - - - - Web App: {selectedReportDefinitions} - - - - )} - - {ReportDefinitionMap[reportType].map(({ field }) => ( - onChange(val)} - /> + + {reportDefinitions.map(({ field }) => ( + + + + onChange(field, val)} + filters={(filters ?? []).map(({ query }) => query)} + /> + + {rtd?.[field] && ( + + + {rtd?.[field]} + + + )} + + ))} -
+
); }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx index 4c98c0de035de5..d86dfe00f3e28e 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx @@ -5,17 +5,21 @@ * 2.0. */ -import React, { Dispatch, SetStateAction } from 'react'; +import React from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { ReportViewTypeId } from '../../types'; +import { useUrlStorage } from '../../hooks/use_url_strorage'; interface Props { - selectedReportType: ReportViewTypeId | null; reportTypes: { id: ReportViewTypeId; label: string }[]; - onChange: Dispatch>; } -export const ReportTypesCol = ({ selectedReportType, reportTypes, onChange }: Props) => { +export const ReportTypesCol = ({ reportTypes }: Props) => { + const { + newSeries: { reportType: selectedReportType, ...restSeries }, + setNewSeries, + } = useUrlStorage(); + return reportTypes?.length > 0 ? ( {reportTypes.map(({ id: reportType, label }) => ( @@ -26,7 +30,12 @@ export const ReportTypesCol = ({ selectedReportType, reportTypes, onChange }: Pr iconType="arrowRight" color={selectedReportType === reportType ? 'primary' : 'text'} fill={selectedReportType === reportType} - onClick={() => onChange(reportType === selectedReportType ? null : reportType)} + onClick={() => + setNewSeries({ + ...restSeries, + reportType: reportType === selectedReportType ? undefined : reportType, + }) + } > {label} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx index 1489f59d79cf03..58f83092996689 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx @@ -16,7 +16,7 @@ import { ReportDefinitionCol } from './columns/report_definition_col'; import { ReportFilters } from './columns/report_filters'; import { ReportBreakdowns } from './columns/report_breakdowns'; import { useUrlStorage } from '../hooks/use_url_strorage'; -import { REPORT_TYPE } from '../configurations/constants'; +import { FILTERS, REPORT_TYPE } from '../configurations/constants'; export const ReportTypes: Record> = { synthetics: [ @@ -25,7 +25,6 @@ export const ReportTypes: Record { const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); - const [dataType, setDataType] = useState(null); - const [reportType, setReportType] = useState(null); - const [serviceName, setServiceName] = useState(null); + const { newSeries, setNewSeries, setSeries, allSeriesIds } = useUrlStorage(); + + const { dataType, reportType } = newSeries; const columns = [ { name: 'DataType', - field: 'dataType', width: '20%', - render: (val: string) => , + render: (val: string) => , }, { name: 'Report', - field: 'defaultFilters', - width: '30%', + width: '20%', render: (val: string) => ( - + ), }, { name: 'Definition', - field: 'defaultFilters', width: '30%', - render: (val: string) => - reportType ? ( - - ) : null, + render: (val: string) => (reportType ? : null), }, { name: 'Filters', - field: 'filters', width: '25%', - render: (val: string[]) => (reportType ? : null), + render: (val: string) => (reportType ? : null), }, { name: 'Breakdowns', width: '25%', field: 'id', - render: (val: string[]) => (reportType ? : null), + render: (val: string) => (reportType ? : null), }, ]; - const { setSeries, allSeriesIds } = useUrlStorage(); - const addSeries = () => { + const { reportDefinitions = {} } = newSeries; + + const getFiltersFromDefs = () => { + return Object.entries(reportDefinitions).map(([field, value]) => ({ + field, + values: [value], + })); + }; + if (reportType) { - const newSeriesId = `${serviceName!}-${reportType}`; + const newSeriesId = `${ + reportDefinitions?.['service.name'] || reportDefinitions?.['monitor.id'] + }`; - const newSeries = { + const newSeriesN = { [REPORT_TYPE]: reportType, - serviceName, time: { from: 'now-30m', to: 'now' }, + [FILTERS]: getFiltersFromDefs(), } as SeriesUrl; - setSeries(newSeriesId, newSeries); + setSeries(newSeriesId, newSeriesN); // reset state - setDataType(null); - setReportType(null); - setServiceName(null); + setNewSeries({}); setIsFlyoutVisible(false); } }; - const items = [{ dataTypes: ['APM'] }]; + const items = [{ id: 'newSeries' }]; let flyout; @@ -126,12 +116,19 @@ export const SeriesBuilder = () => { - + Add - + { + setNewSeries({}); + setIsFlyoutVisible(false); + }} + > Cancel @@ -144,7 +141,7 @@ export const SeriesBuilder = () => {
setIsFlyoutVisible((prevState) => !prevState)} disabled={allSeriesIds.length > 0} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/actions_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/actions_col.tsx index 902529f827e132..6658bbb716b79c 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/actions_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/actions_col.tsx @@ -6,15 +6,24 @@ */ import React from 'react'; -import { SeriesDatePicker } from '../../series_date_picker'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { DataSeries } from '../../types'; +import { ChartTypes } from './chart_types'; +import { MetricSelection } from './metric_selection'; interface Props { - series: any; + series: DataSeries; } -export const ActionsCol = ({ series }: Props) => { + +export function ActionsCol({ series }: Props) { return ( -
- -
+ + + + + + + + ); -}; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx index 9678adf015770c..7592c01bed55d9 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx @@ -6,21 +6,22 @@ */ import React, { useEffect, useState } from 'react'; -import { EuiButton, EuiSpacer } from '@elastic/eui'; +import { EuiSuperSelect } from '@elastic/eui'; import { BREAK_DOWN, FieldLabels } from '../../configurations/constants'; import { useUrlStorage } from '../../hooks/use_url_strorage'; +import { i18n } from '@kbn/i18n'; interface Props { seriesId: string; breakdowns: string[]; } -export const Breakdowns = ({ seriesId, breakdowns = [] }: Props) => { - const options = breakdowns.map((breakdown) => ({ id: breakdown, label: FieldLabels[breakdown] })); +export function Breakdowns({ seriesId, breakdowns = [] }: Props) { + const items = breakdowns.map((breakdown) => ({ id: breakdown, label: FieldLabels[breakdown] })); const [selectedBreakdown, setSelectedBreakdown] = useState(); - const onClick = (optionId: string) => { + const onOptionChange = (optionId: string) => { setSelectedBreakdown((prevState) => (prevState === optionId ? undefined : optionId)); }; @@ -30,23 +31,31 @@ export const Breakdowns = ({ seriesId, breakdowns = [] }: Props) => { setSeries(seriesId, { ...series, [BREAK_DOWN]: selectedBreakdown }); }, [selectedBreakdown]); + const NO_BREAKDOWN = 'no_breakdown'; + + items.push({ + id: NO_BREAKDOWN, + label: i18n.translate('xpack.observability.exp.breakDownFilter.noBreakdown', { + defaultMessage: 'No breakdown', + }), + }); + + const options = items.map(({ id, label }) => ({ + inputDisplay: id === NO_BREAKDOWN ? label : {label}, + value: id, + dropdownDisplay: label, + })); + return (
- {options.map(({ id, label }) => ( -
- onClick(id)} - > - {label} - - -
- ))} + onOptionChange(value)} + data-test-subj={'seriesBreakdown'} + />
); -}; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/chart_types.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx similarity index 64% rename from x-pack/plugins/observability/public/components/shared/exploratory_view/header/chart_types.tsx rename to x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx index d9d4dc163ccac3..da5e8922739c91 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/chart_types.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx @@ -5,24 +5,24 @@ * 2.0. */ -import React from 'react'; -import { EuiButtonGroup } from '@elastic/eui'; -import { VisualizationType } from '../../../../../../lens/public/types'; +import React, { useState } from 'react'; +import { EuiButton, EuiButtonGroup, EuiPopover } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { LensIconChartBar } from '../assets/chart_bar'; -import { LensIconChartBarHorizontal } from '../assets/chart_bar_horizontal'; -import { LensIconChartBarStacked } from '../assets/chart_bar_stacked'; // import { LensIconChartBarPercentage } from '../assets/chart_bar_percentage'; -import { LensIconChartBarHorizontalStacked } from '../assets/chart_bar_horizontal_stacked'; // import { LensIconChartBarHorizontalPercentage } from '../assets/chart_bar_horizontal_percentage'; -import { LensIconChartArea } from '../assets/chart_area'; // import { LensIconChartAreaPercentage } from '../assets/chart_area_percentage'; -import { LensIconChartLine } from '../assets/chart_line'; -import { LensIconChartAreaStacked } from '../assets/chart_area_stacked'; import styled from 'styled-components'; -import { useUrlStorage } from '../hooks/use_url_strorage'; -import { SERIES_TYPE } from '../configurations/constants'; +import { useUrlStorage } from '../../hooks/use_url_strorage'; +import { LensIconChartBar } from '../../assets/chart_bar'; +import { VisualizationType } from '../../../../../../../lens/public/types'; +import { SERIES_TYPE } from '../../configurations/constants'; +import { LensIconChartBarHorizontal } from '../../assets/chart_bar_horizontal'; +import { LensIconChartBarStacked } from '../../assets/chart_bar_stacked'; +import { LensIconChartBarHorizontalStacked } from '../../assets/chart_bar_horizontal_stacked'; +import { LensIconChartArea } from '../../assets/chart_area'; +import { LensIconChartAreaStacked } from '../../assets/chart_area_stacked'; +import { LensIconChartLine } from '../../assets/chart_line'; const ButtonGroup = styled(EuiButtonGroup)` &&& { @@ -32,35 +32,63 @@ const ButtonGroup = styled(EuiButtonGroup)` } `; -export const ChartTypes = ({ seriesId }: { seriesId: string }) => { +export function ChartTypes({ + seriesId, + defaultChartType, +}: { + seriesId: string; + defaultChartType: string; +}) { const { series, setSeries, allSeries } = useUrlStorage(seriesId); + const [isOpen, setIsOpen] = useState(false); + + const seriesType = series?.[SERIES_TYPE] ?? defaultChartType; + return ( - ({ - id: t.id, - label: t.label, - iconType: t.icon || 'empty', - 'data-test-subj': `lnsXY_seriesType-${t.id}`, - }))} - idSelected={series?.[SERIES_TYPE] ?? 'line'} - onChange={(seriesType: string) => { - Object.keys(allSeries).forEach((seriesKey) => { - const series = allSeries[seriesKey]; + id == seriesType)?.icon} + onClick={() => { + setIsOpen((prevState) => !prevState); + }} + > + Chart type +
+ } + closePopover={() => setIsOpen(false)} + > + ({ + id: t.id, + label: t.label, + iconType: t.icon || 'empty', + 'data-test-subj': `lnsXY_seriesType-${t.id}`, + }))} + idSelected={series?.[SERIES_TYPE] ?? 'line'} + onChange={(seriesType: string) => { + Object.keys(allSeries).forEach((seriesKey) => { + const series = allSeries[seriesKey]; - setSeries(seriesKey, { ...series, [SERIES_TYPE]: seriesType }); - }); - }} - /> + setSeries(seriesKey, { ...series, [SERIES_TYPE]: seriesType }); + }); + }} + /> + ); -}; +} const groupLabelForBar = i18n.translate('xpack.lens.xyVisualization.barGroupLabel', { defaultMessage: 'Bar', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/date_picker_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/date_picker_col.tsx new file mode 100644 index 00000000000000..8051635ef1a24e --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/date_picker_col.tsx @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { SeriesDatePicker } from '../../series_date_picker'; + +interface Props { + series: any; +} +export function DatePickerCol({ series }: Props) { + return ( +
+ +
+ ); +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx index d6f0ab672c145e..cea6fdd6e0d96d 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx @@ -29,7 +29,7 @@ interface Props { goBack: () => void; } -export const FilterExpanded = ({ seriesId, field, label, goBack }: Props) => { +export function FilterExpanded({ seriesId, field, label, goBack }: Props) { const { indexPattern } = useIndexPatternContext(); const [value, setValue] = useState(''); @@ -135,4 +135,4 @@ export const FilterExpanded = ({ seriesId, field, label, goBack }: Props) => { ))} ); -}; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.tsx new file mode 100644 index 00000000000000..7d5312d8933d5e --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.tsx @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; +import { EuiButton, EuiButtonGroup, EuiPopover } from '@elastic/eui'; +import { useUrlStorage } from '../../hooks/use_url_strorage'; +import { METRIC_TYPE } from '../../configurations/constants'; + +const toggleButtons = [ + { + id: `avg`, + label: 'Average', + }, + { + id: `median`, + label: 'Median', + }, + { + id: `95th`, + label: '95th Percentile', + }, + { + id: `99th`, + label: '99th Percentile', + }, +]; + +export function MetricSelection({ + seriesId, + isDisabled, +}: { + seriesId: string; + isDisabled: boolean; +}) { + const { series, setSeries, allSeries } = useUrlStorage(seriesId); + + const [isOpen, setIsOpen] = useState(false); + + const [toggleIdSelected, setToggleIdSelected] = useState(series?.[METRIC_TYPE] ?? 'avg'); + + const onChange = (optionId: string) => { + setToggleIdSelected(optionId); + + Object.keys(allSeries).forEach((seriesKey) => { + const seriesN = allSeries[seriesKey]; + + setSeries(seriesKey, { ...seriesN, [METRIC_TYPE]: optionId }); + }); + }; + const button = ( + setIsOpen((prevState) => !prevState)} + size="s" + color="text" + isDisabled={isDisabled} + > + {toggleButtons.find(({ id }) => id === toggleIdSelected)!.label} + + ); + + return ( + setIsOpen(false)}> + onChange(id)} + /> + + ); +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/remove_series.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/remove_series.tsx index c55c0388357bbb..7b80e418daeea1 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/remove_series.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/remove_series.tsx @@ -14,11 +14,11 @@ interface Props { series: DataSeries; } -export const RemoveSeries = ({ series }: Props) => { +export function RemoveSeries({ series }: Props) { const { removeSeries } = useUrlStorage(); const onClick = () => { removeSeries(series.id); }; - return ; -}; + return ; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx index 5888a503c01f81..4cee3c556da9e0 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx @@ -18,14 +18,11 @@ import { FilterExpanded } from './filter_expanded'; import { DataSeries } from '../../types'; import { FieldLabels } from '../../configurations/constants'; import { SelectedFilters } from '../selected_filters'; -import { DefaultFilters } from '../default_filters'; -import { usePluginContext } from '../../../../../hooks/use_plugin_context'; -import { StatefulSearchBarProps } from '../../../../../../../../../src/plugins/data/public'; -import { useIndexPatternContext } from '../../../../../hooks/use_default_index_pattern'; interface Props { seriesId: string; defaultFilters: DataSeries['defaultFilters']; + series: DataSeries; } export interface Field { @@ -33,7 +30,7 @@ export interface Field { field: string; } -export const SeriesFilter = ({ seriesId, defaultFilters = [] }: Props) => { +export function SeriesFilter({ series, seriesId, defaultFilters = [] }: Props) { const [isPopoverVisible, setIsPopoverVisible] = useState(false); const [selectedField, setSelectedField] = useState(null); @@ -52,14 +49,6 @@ export const SeriesFilter = ({ seriesId, defaultFilters = [] }: Props) => { ); - const { - plugins: { data }, - } = usePluginContext(); - - const SearchBar: React.ComponentType = data.ui.SearchBar; - - const { indexPattern } = useIndexPatternContext(); - const mainPanel = ( <> @@ -78,14 +67,6 @@ export const SeriesFilter = ({ seriesId, defaultFilters = [] }: Props) => { ))} - ); @@ -107,9 +88,7 @@ export const SeriesFilter = ({ seriesId, defaultFilters = [] }: Props) => { return ( - - - + @@ -122,11 +101,8 @@ export const SeriesFilter = ({ seriesId, defaultFilters = [] }: Props) => { {!selectedField ? mainPanel : childPanel} - - - ); -}; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/default_filters.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/default_filters.tsx deleted file mode 100644 index adc148bfbcabb5..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/default_filters.tsx +++ /dev/null @@ -1,43 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useState } from 'react'; -import { EuiButtonEmpty, EuiCode, EuiPopover, EuiText } from '@elastic/eui'; - -interface Props { - seriesId: string; -} -export const DefaultFilters = ({ seriesId }: Props) => { - const [isPopoverOpen, setIsPopoverOpen] = useState(false); - - const onButtonClick = () => setIsPopoverOpen((isPopoverOpen) => !isPopoverOpen); - const closePopover = () => setIsPopoverOpen(false); - - const button = ( - - Default filters - - ); - - const jsCodeCode = `query: { match_phrase: { 'transaction.type': 'page-load' } }`; - - return ( - - - - {jsCodeCode.trim()} - - - - ); -}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx index b719761b677594..d81876accb2090 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx @@ -10,11 +10,13 @@ import { useUrlStorage } from '../hooks/use_url_strorage'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FILTERS } from '../configurations/constants'; import { FilterLabel } from '../components/filter_label'; +import { DataSeries } from '../types'; interface Props { seriesId: string; + series: DataSeries; } -export const SelectedFilters = ({ seriesId }: Props) => { +export const SelectedFilters = ({ seriesId, series: { labels } }: Props) => { const { series, setSeries } = useUrlStorage(seriesId); const filters = series?.[FILTERS] ?? []; @@ -36,34 +38,38 @@ export const SelectedFilters = ({ seriesId }: Props) => { setSeries(seriesId, { ...series, [FILTERS]: filtersN }); }; - return ( - - {filters.map(({ field, values, notValues }) => ( - - {(values ?? []).map((val) => ( - - removeFilter(field, val, false)} - negate={false} - /> - - ))} - {(notValues ?? []).map((val) => ( - - removeFilter(field, val, true)} - /> - - ))} - - ))} - - ); + return filters.length > 0 ? ( + + + {filters.map(({ field, values, notValues }) => ( + + {(values ?? []).map((val) => ( + + removeFilter(field, val, false)} + negate={false} + /> + + ))} + {(notValues ?? []).map((val) => ( + + removeFilter(field, val, true)} + /> + + ))} + + ))} + + + ) : null; }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx index 641431009f1bdf..c174ac5fb40e6a 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx @@ -13,9 +13,10 @@ import { Breakdowns } from './columns/breakdowns'; import { DataSeries } from '../types'; import { SeriesBuilder } from '../series_builder/series_builder'; import { useUrlStorage } from '../hooks/use_url_strorage'; -import { RemoveSeries } from './columns/remove_series'; import { getDefaultConfigs } from '../configurations/default_configs'; import { REPORT_TYPE } from '../configurations/constants'; +import { DatePickerCol } from './columns/date_picker_col'; +import { RemoveSeries } from './columns/remove_series'; export const SeriesEditor = () => { const columns = [ @@ -32,27 +33,35 @@ export const SeriesEditor = () => { { name: 'Filter', field: 'defaultFilters', - width: '30%', + width: '20%', render: (defaultFilters: string[], series: DataSeries) => ( - + ), }, { name: 'Breakdowns', field: 'breakdowns', - width: '25%', + width: '15%', render: (val: string[], item: DataSeries) => ( ), }, { - name: 'Time', - width: '25%', + name: '', + align: 'center' as const, + width: '15%', field: 'id', render: (val: string, item: DataSeries) => , }, { - name: 'Remove', + name: 'Time', + width: '20%', + field: 'id', + render: (val: string, item: DataSeries) => , + }, + { + name: 'Actions', + align: 'center' as const, width: '5%', field: 'id', render: (val: string, item: DataSeries) => , @@ -67,7 +76,6 @@ export const SeriesEditor = () => { const series = allSeries[seriesKey]; return getDefaultConfigs({ reportType: series[REPORT_TYPE], - serviceName: series.serviceName, seriesId: seriesKey, }); }); @@ -81,6 +89,11 @@ export const SeriesEditor = () => { columns={columns} rowProps={() => ({ height: 100 })} noItemsMessage={'No series found, please add a series.'} + cellProps={{ + style: { + verticalAlign: 'top', + }, + }} /> diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts index 25ab0a07382a47..d411c18ae53c79 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts @@ -18,14 +18,14 @@ import { REPORT_TYPE, SERIES_TYPE, } from './configurations/constants'; +import { ESFilter } from '../../../../../../../typings/elasticsearch'; export const ReportViewTypes = { pld: 'page-load-dist', - pgv: 'page-views', + kpi: 'kpi-trends', upd: 'uptime-duration', upp: 'uptime-pings', svl: 'service-latency', - kpi: 'service-latency', tpt: 'service-throughput', }; @@ -33,7 +33,7 @@ export type ReportViewTypeId = keyof typeof ReportViewTypes; export type ReportViewType = | 'page-load-dist' - | 'page-views' + | 'kpi-trends' | 'uptime-duration' | 'uptime-pings' | 'service-latency'; @@ -50,8 +50,15 @@ export interface DataSeries { breakdowns: string[]; defaultSeriesType: string; defaultFilters: string[]; - seriesTypes?: string[]; - filters?: Array>; + seriesTypes: string[]; + filters?: ESFilter[]; + reportDefinitions: { + field: string; + required?: boolean; + custom?: boolean; + }[]; + labels: Record; + metricType: boolean; } export interface SeriesUrl { @@ -63,10 +70,15 @@ export interface SeriesUrl { [FILTERS]?: UrlFilter[]; [SERIES_TYPE]?: string; [REPORT_TYPE]: ReportViewTypeId; - serviceName: string; [METRIC_TYPE]?: string; } +export interface NewSeriesUrl { + dataType?: AppDataType; + reportType?: ReportViewTypeId; + reportDefinitions?: Record; +} + export interface UrlFilter { field: string; values?: string[]; diff --git a/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx b/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx index 3f5269dee8b00f..faa4b2e16120a4 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx +++ b/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FormEvent, Fragment, useEffect, useState } from 'react'; +import React, { FormEvent, useEffect, useState } from 'react'; import { EuiButton, EuiPopover, @@ -15,7 +15,7 @@ import { } from '@elastic/eui'; import { useValuesList } from '../../hooks/use_values_list'; import { IIndexPattern } from '../../../../../../src/plugins/data/common'; -import { FieldLabels } from './exploratory_view/configurations/constants'; +import { ESFilter } from '../../../../../../typings/elasticsearch'; interface Option { id: string; @@ -25,19 +25,23 @@ interface Option { interface Props { value?: string; + label: string; indexPattern: IIndexPattern; sourceField: string; onChange: (val: string) => void; + filters: ESFilter[]; } export const FieldValueSelection = ({ sourceField, indexPattern, value, + label, + filters, onChange: onSelectionChange, }: Props) => { const [query, setQuery] = useState(''); - const { values, loading } = useValuesList({ indexPattern, query, sourceField }); + const { values, loading } = useValuesList({ indexPattern, query, sourceField, filters }); const [options, setOptions] = useState([]); const [isPopoverOpen, setIsPopoverOpen] = useState(false); @@ -65,54 +69,59 @@ export const FieldValueSelection = ({ }; const button = ( - - {FieldLabels[sourceField]} + + {label} ); return ( - - + - - {(list, search) => ( -
- {search} - {list} - - opt?.checked === 'on')} - onClick={() => { - const selected = options.find((opt) => opt?.checked === 'on')!; - onSelectionChange(selected.label); - setIsPopoverOpen(false); - }} - > - Apply - - -
- )} -
-
-
+ {(list, search) => ( +
+ {search} + {list} + + opt?.checked === 'on')} + onClick={() => { + const selected = options.find((opt) => opt?.checked === 'on')!; + onSelectionChange(selected.label); + setIsPopoverOpen(false); + }} + > + Apply + + +
+ )} + + ); }; diff --git a/x-pack/plugins/observability/public/hooks/use_values_list.ts b/x-pack/plugins/observability/public/hooks/use_values_list.ts index f795f121250f29..af3ff883aa3246 100644 --- a/x-pack/plugins/observability/public/hooks/use_values_list.ts +++ b/x-pack/plugins/observability/public/hooks/use_values_list.ts @@ -9,14 +9,16 @@ import { IIndexPattern } from '../../../../../src/plugins/data/common'; import { useKibana } from '../../../../../src/plugins/kibana_react/public'; import { ObservabilityClientPluginsStart } from '../plugin'; import { useFetcher } from './use_fetcher'; +import { ESFilter } from '../../../../../typings/elasticsearch'; interface Props { sourceField: string; query?: string; indexPattern: IIndexPattern; + filters: ESFilter[]; } -export const useValuesList = ({ sourceField, indexPattern, query }: Props) => { +export const useValuesList = ({ sourceField, indexPattern, query, filters }: Props) => { const { services: { data }, } = useKibana(); @@ -25,8 +27,9 @@ export const useValuesList = ({ sourceField, indexPattern, query }: Props) => { return data.autocomplete.getValueSuggestions({ indexPattern, query: query || '', - useTimeRange: false, + // useTimeRange: false, field: indexPattern.fields.find(({ name }) => name === sourceField)!, + boolFilter: filters, }); }, [sourceField, query]); From 700cdd730ceb8cf6b34c0c4e17a7b495e4510269 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Thu, 18 Mar 2021 15:38:59 +0100 Subject: [PATCH 10/53] fix more stuff --- .../components/empty_view.tsx | 1 + .../configurations/constants.ts | 21 +++- .../configurations/cpu_usage_config.ts | 41 +++++++ .../configurations/default_configs.ts | 16 ++- .../configurations/kpi_trends_config.ts | 3 +- .../configurations/lens_attributes.ts | 6 +- .../configurations/logs_frequency_config.ts | 39 +++++++ .../configurations/memory_usage_config.ts | 41 +++++++ .../configurations/monitor_duration_config.ts | 26 +++-- .../configurations/monitor_pings_config.ts | 5 +- .../configurations/network_activity_config.ts | 40 +++++++ .../configurations/page_load_dist_config.ts | 1 - .../configurations/service_latency_config.ts | 1 - .../service_throughput_config.ts | 1 - .../exploratory_view/exploratory_view.tsx | 47 ++++---- .../shared/exploratory_view/header/header.tsx | 2 +- .../hooks/use_init_exploratory_view.ts | 12 +- .../hooks/use_lens_attributes.ts | 39 +++---- .../hooks/use_url_strorage.tsx | 81 +++++++++++--- .../series_builder/columns/data_types_col.tsx | 9 +- .../columns/report_breakdowns.tsx | 15 ++- .../columns/report_definition_col.tsx | 66 ++++++----- .../series_builder/columns/report_filters.tsx | 33 +++++- .../columns/report_types_col.tsx | 27 +++-- .../series_builder/series_builder.tsx | 77 +++++++------ .../series_date_picker/index.tsx | 2 +- .../series_editor/columns/actions_col.tsx | 8 +- .../series_editor/columns/breakdowns.tsx | 29 +++-- .../series_editor/columns/chart_types.tsx | 7 +- .../series_editor/columns/date_picker_col.tsx | 6 +- .../series_editor/columns/filter_expanded.tsx | 17 +-- .../columns/metric_selection.tsx | 5 +- .../series_editor/columns/remove_series.tsx | 2 +- .../series_editor/columns/series_filter.tsx | 29 ++--- .../series_editor/selected_filters.tsx | 32 ++++-- .../series_editor/series_editor.tsx | 103 ++++++++++-------- .../shared/exploratory_view/types.ts | 39 +++---- .../shared/field_value_selection.tsx | 4 +- .../public/context/has_data_context.tsx | 46 ++++---- .../utils/observability_Index_patterns.ts | 4 +- .../common/charts/ping_histogram.tsx | 2 +- 41 files changed, 665 insertions(+), 320 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/cpu_usage_config.ts create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/logs_frequency_config.ts create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/memory_usage_config.ts create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/network_activity_config.ts diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx index 3600b6d05f6dd2..536c64dbae2638 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx @@ -26,4 +26,5 @@ export const EmptyView = () => { const Wrapper = styled.div` text-align: center; opacity: 0.4; + height: 550pz; `; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts index 38de9699d4b9ad..d6c79740bc0bcd 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts @@ -7,12 +7,6 @@ import { AppDataType, ReportViewTypeId } from '../types'; -export const METRIC_TYPE = 'mt'; -export const REPORT_TYPE = 'rt'; -export const SERIES_TYPE = 'st'; -export const BREAK_DOWN = 'bd'; -export const FILTERS = 'ft'; - export const FieldLabels: Record = { 'user_agent.name': 'Browser family', 'user_agent.os.name': 'Operating system', @@ -24,6 +18,13 @@ export const FieldLabels: Record = { 'monitor.id': 'Monitor Id', 'monitor.status': 'Monitor Status', + + 'agent.hostname': 'Agent host', + 'host.hostname': 'Host name', + 'monitor.name': 'Monitor name', + 'monitor.type': 'Monitor Type', + 'url.port': 'Port', + tags: 'Tags', }; export const DataViewLabels: Record = { @@ -33,6 +34,10 @@ export const DataViewLabels: Record = { svl: 'APM Service latency', kpi: 'Business KPI over time', tpt: 'APM Service throughput', + cpu: 'System CPU Usage', + logs: 'Logs Frequency', + mem: 'System Memory Usage', + nwk: 'Network Activity', }; export const ReportToDataTypeMap: Record = { @@ -42,4 +47,8 @@ export const ReportToDataTypeMap: Record = { svl: 'apm', kpi: 'rum', pld: 'rum', + nwk: 'metrics', + mem: 'metrics', + logs: 'logs', + cpu: 'metrics', }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/cpu_usage_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/cpu_usage_config.ts new file mode 100644 index 00000000000000..0e68b691ca2467 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/cpu_usage_config.ts @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataSeries } from '../types'; +import { FieldLabels } from './constants'; + +interface Props { + seriesId: string; +} + +export function getCPUUsageLensConfig({ seriesId }: Props): DataSeries { + return { + id: seriesId, + reportType: 'cpu-usage', + defaultSeriesType: 'line', + seriesTypes: ['line', 'bar'], + xAxisColumn: { + sourceField: '@timestamp', + }, + yAxisColumn: { + operationType: 'avg', + sourceField: 'system.cpu.user.pct', + label: 'CPU Usage %', + }, + metricType: true, + defaultFilters: [], + breakdowns: ['host.hostname'], + filters: [], + labels: { ...FieldLabels, 'host.hostname': 'Host name' }, + reportDefinitions: [ + { + field: 'agent.hostname', + required: true, + }, + ], + }; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts index 651750cb8af4fc..2306425c905e13 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts @@ -12,6 +12,10 @@ import { getServiceLatencyLensConfig } from './service_latency_config'; import { getMonitorPingsConfig } from './monitor_pings_config'; import { getServiceThroughputLensConfig } from './service_throughput_config'; import { getKPITrendsLensConfig } from './kpi_trends_config'; +import { getCPUUsageLensConfig } from './cpu_usage_config'; +import { getMemoryUsageLensConfig } from './memory_usage_config'; +import { getNetworkActivityLensConfig } from './network_activity_config'; +import { getLogsFrequencyLensConfig } from './logs_frequency_config'; interface Props { reportType: keyof typeof ReportViewTypes; @@ -22,16 +26,24 @@ export const getDefaultConfigs = ({ reportType, seriesId }: Props) => { switch (ReportViewTypes[reportType]) { case 'page-load-dist': return getPageLoadDistLensConfig({ seriesId }); - case 'page-views': + case 'kpi-trends': return getKPITrendsLensConfig({ seriesId }); case 'uptime-duration': - return getMonitorDurationConfig(); + return getMonitorDurationConfig({ seriesId }); case 'uptime-pings': return getMonitorPingsConfig({ seriesId }); case 'service-latency': return getServiceLatencyLensConfig({ seriesId }); case 'service-throughput': return getServiceThroughputLensConfig({ seriesId }); + case 'cpu-usage': + return getCPUUsageLensConfig({ seriesId }); + case 'memory-usage': + return getMemoryUsageLensConfig({ seriesId }); + case 'network-activity': + return getNetworkActivityLensConfig({ seriesId }); + case 'logs-frequency': + return getLogsFrequencyLensConfig({ seriesId }); default: return getKPITrendsLensConfig({ seriesId }); } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts index 59f2fc0e82f4f4..00a46da508e8c1 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts @@ -17,7 +17,6 @@ export function getKPITrendsLensConfig({ seriesId }: Props): DataSeries { id: seriesId, defaultSeriesType: 'bar_stacked', reportType: 'kpi-trends', - indexPattern: 'apm_static_index_pattern_id', seriesTypes: ['bar', 'bar_stacked'], xAxisColumn: { sourceField: '@timestamp', @@ -44,7 +43,7 @@ export function getKPITrendsLensConfig({ seriesId }: Props): DataSeries { query: { match_phrase: { 'transaction.type': 'page-load' } }, }, ], - labels: { ...FieldLabels }, + labels: { ...FieldLabels, 'service.name': 'Web Application' }, reportDefinitions: [ { field: 'service.name', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts index a22eb88b0ab4cf..24843c5f859f29 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts @@ -115,7 +115,7 @@ export class LensAttributes { if (xAxisColumn) return { - label: 'Page load duration', + label: 'Page load duration (Seconds)', dataType: 'number', operationType: 'range', sourceField: 'transaction.duration.us', @@ -168,6 +168,7 @@ export class LensAttributes { accessors: ['y-axis-column'], layerId: 'layer1', seriesType: this.seriesType ?? 'line', + palette: this.reportViewConfig.palette ?? undefined, yConfig: [{ forAccessor: 'y-axis-column', color: 'green' }], xAccessor: 'x-axis-column', }, @@ -183,6 +184,9 @@ export class LensAttributes { values?.forEach((value) => { parsedFilters.push({ query: { match_phrase: { [field]: value } } }); }); + notValues?.forEach((value) => { + parsedFilters.push({ query: { match_phrase: { [field]: value } }, meta: { negate: true } }); + }); }); return parsedFilters; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/logs_frequency_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/logs_frequency_config.ts new file mode 100644 index 00000000000000..7c2db7a80e428a --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/logs_frequency_config.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataSeries } from '../types'; +import { FieldLabels } from './constants'; + +interface Props { + seriesId: string; +} + +export function getLogsFrequencyLensConfig({ seriesId }: Props): DataSeries { + return { + id: seriesId, + reportType: 'logs-frequency', + defaultSeriesType: 'line', + seriesTypes: ['line', 'bar'], + xAxisColumn: { + sourceField: '@timestamp', + }, + yAxisColumn: { + operationType: 'count', + }, + metricType: false, + defaultFilters: [], + breakdowns: ['agent.hostname'], + filters: [], + labels: { ...FieldLabels }, + reportDefinitions: [ + { + field: 'agent.hostname', + required: true, + }, + ], + }; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/memory_usage_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/memory_usage_config.ts new file mode 100644 index 00000000000000..7bb18b0dbf3169 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/memory_usage_config.ts @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataSeries } from '../types'; +import { FieldLabels } from './constants'; + +interface Props { + seriesId: string; +} + +export function getMemoryUsageLensConfig({ seriesId }: Props): DataSeries { + return { + id: seriesId, + reportType: 'memory-usage', + defaultSeriesType: 'line', + seriesTypes: ['line', 'bar'], + xAxisColumn: { + sourceField: '@timestamp', + }, + yAxisColumn: { + operationType: 'avg', + sourceField: 'system.memory.used.pct', + label: 'Memory Usage %', + }, + metricType: true, + defaultFilters: [], + breakdowns: ['host.hostname'], + filters: [], + labels: { ...FieldLabels, 'host.hostname': 'Host name' }, + reportDefinitions: [ + { + field: 'host.hostname', + required: true, + }, + ], + }; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_duration_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_duration_config.ts index e61b7b40a3f983..9e20a8ac92050e 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_duration_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_duration_config.ts @@ -8,12 +8,15 @@ import { DataSeries } from '../types'; import { FieldLabels } from './constants'; -export function getMonitorDurationConfig(): DataSeries { +interface Props { + seriesId: string; +} + +export function getMonitorDurationConfig({ seriesId }: Props): DataSeries { return { - id: 'elastic-co', + id: seriesId, reportType: 'uptime-duration', defaultSeriesType: 'line', - indexPattern: 'df32db00-819e-11eb-87f5-d7da22b1dde3', seriesTypes: ['line', 'bar_stacked'], xAxisColumn: { sourceField: '@timestamp', @@ -21,16 +24,19 @@ export function getMonitorDurationConfig(): DataSeries { yAxisColumn: { operationType: 'avg', sourceField: 'monitor.duration.us', - label: 'Monitor duration', + label: 'Monitor duration (ms)', }, metricType: true, - defaultFilters: ['observer.geo.name'], - breakdowns: ['observer.geo.name'], - filters: [ - { - query: { match_phrase: { 'monitor.id': 'android-homepage' } }, - }, + defaultFilters: ['monitor.type', 'observer.geo.name', 'tags'], + breakdowns: [ + 'observer.geo.name', + 'monitor.name', + 'monitor.id', + 'monitor.type', + 'tags', + 'url.port', ], + filters: [], reportDefinitions: [ { field: 'monitor.id', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_pings_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_pings_config.ts index 2c5e74191f1b91..be728387f97598 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_pings_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_pings_config.ts @@ -16,9 +16,9 @@ export function getMonitorPingsConfig({ seriesId }: Props): DataSeries { return { id: seriesId, reportType: 'uptime-pings', - defaultSeriesType: 'line', + defaultSeriesType: 'bar_stacked', indexPattern: 'df32db00-819e-11eb-87f5-d7da22b1dde3', - seriesTypes: ['bar', 'bar_stacked'], + seriesTypes: ['bar_stacked', 'bar'], xAxisColumn: { sourceField: '@timestamp', }, @@ -30,6 +30,7 @@ export function getMonitorPingsConfig({ seriesId }: Props): DataSeries { defaultFilters: ['observer.geo.name'], breakdowns: ['monitor.status', 'observer.geo.name'], filters: [], + palette: { type: 'palette', name: 'status' }, reportDefinitions: [ { field: 'monitor.id', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/network_activity_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/network_activity_config.ts new file mode 100644 index 00000000000000..3d9cb5f67c5129 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/network_activity_config.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataSeries } from '../types'; +import { FieldLabels } from './constants'; + +interface Props { + seriesId: string; +} + +export function getNetworkActivityLensConfig({ seriesId }: Props): DataSeries { + return { + id: seriesId, + reportType: 'network-activity', + defaultSeriesType: 'line', + seriesTypes: ['line', 'bar'], + xAxisColumn: { + sourceField: '@timestamp', + }, + yAxisColumn: { + operationType: 'avg', + sourceField: 'system.memory.used.pct', + }, + metricType: true, + defaultFilters: [], + breakdowns: ['host.hostname'], + filters: [], + labels: { ...FieldLabels, 'host.hostname': 'Host name' }, + reportDefinitions: [ + { + field: 'host.hostname', + required: true, + }, + ], + }; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts index d3c4bf3e39d4f7..f3db0f758a04d5 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts @@ -17,7 +17,6 @@ export function getPageLoadDistLensConfig({ seriesId }: Props): DataSeries { id: seriesId ?? 'unique-key', reportType: 'page-load-dist', defaultSeriesType: 'line', - indexPattern: 'apm_static_index_pattern_id', seriesTypes: ['line', 'bar'], xAxisColumn: { sourceField: 'transaction.duration.us', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts index 1b05757b3e04f7..a90dd2852e2bdf 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_latency_config.ts @@ -17,7 +17,6 @@ export function getServiceLatencyLensConfig({ seriesId }: Props): DataSeries { id: seriesId, reportType: 'service-latency', defaultSeriesType: 'line', - indexPattern: 'apm_static_index_pattern_id', seriesTypes: ['line', 'bar'], xAxisColumn: { sourceField: '@timestamp', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts index 5f62757a1ec027..1ffacfd8a18ce6 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/service_throughput_config.ts @@ -17,7 +17,6 @@ export function getServiceThroughputLensConfig({ seriesId }: Props): DataSeries id: seriesId, reportType: 'service-latency', defaultSeriesType: 'line', - indexPattern: 'apm_static_index_pattern_id', seriesTypes: ['line', 'bar'], xAxisColumn: { sourceField: '@timestamp', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx index 33c7a6508570f6..6d78c9e0267447 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import { EuiLoadingSpinner, EuiPanel } from '@elastic/eui'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; @@ -16,47 +16,54 @@ import { useUrlStorage } from './hooks/use_url_strorage'; import { useLensAttributes } from './hooks/use_lens_attributes'; import { EmptyView } from './components/empty_view'; import { useIndexPatternContext } from '../../../hooks/use_default_index_pattern'; +import { TypedLensByValueInput } from '../../../../../lens/public'; export function ExploratoryView() { const { services: { lens }, } = useKibana(); + const [lensAttributes, setLensAttributes] = useState( + null + ); + const { indexPattern } = useIndexPatternContext(); const LensComponent = lens.EmbeddableComponent; const { firstSeriesId: seriesId, firstSeries: series } = useUrlStorage(); - const lensAttributes = useLensAttributes({ + const lensAttributesT = useLensAttributes({ seriesId, indexPattern, }); + useEffect(() => { + setLensAttributes(lensAttributesT); + }, [JSON.stringify(lensAttributesT ?? {}), series?.reportType, series?.time?.from]); + return ( - {indexPattern ? ( - <> - - {lensAttributes && seriesId ? ( - {}} - onLoad={(val) => {}} - /> - ) : ( - - )} - - - ) : ( + + {!indexPattern && ( )} + + {lensAttributes && seriesId && series?.reportType && series?.time ? ( + {}} + onLoad={(val) => {}} + /> + ) : ( + + )} + ); } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx index f75fdfe5c9f2c4..daf9577f3fc1f5 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx @@ -29,7 +29,7 @@ export function ExploratoryViewHeader({ seriesId, lensAttributes }: Props) { -

{DataViewLabels[series[REPORT_TYPE]] ?? 'Exploratory view'}

+

{DataViewLabels[series.reportType] ?? 'Exploratory view'}

diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts index 08957c236bd444..7c2a5da895630c 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts @@ -10,8 +10,8 @@ import { useFetcher } from '../../../..'; import { IKbnUrlStateStorage } from '../../../../../../../../src/plugins/kibana_utils/public'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { ObservabilityClientPluginsStart } from '../../../../plugin'; -import { AllSeries } from './use_url_strorage'; -import { REPORT_TYPE, ReportToDataTypeMap } from '../configurations/constants'; +import { AllShortSeries } from './use_url_strorage'; +import { ReportToDataTypeMap } from '../configurations/constants'; import { ObservabilityIndexPatterns } from '../../../../utils/observability_Index_patterns'; export const useInitExploratoryView = (storage: IKbnUrlStateStorage) => { @@ -21,7 +21,7 @@ export const useInitExploratoryView = (storage: IKbnUrlStateStorage) => { const allSeriesKey = 'sr'; - const allSeries = storage.get(allSeriesKey) ?? {}; + const allSeries = storage.get(allSeriesKey) ?? {}; const allSeriesIds = Object.keys(allSeries); @@ -31,8 +31,10 @@ export const useInitExploratoryView = (storage: IKbnUrlStateStorage) => { const { data: indexPattern, status } = useFetcher(() => { const obsvIndexP = new ObservabilityIndexPatterns(data); - return obsvIndexP.getIndexPattern(ReportToDataTypeMap[firstSeries?.[REPORT_TYPE]] ?? 'apm'); - }, [firstSeries?.[REPORT_TYPE]]); + return obsvIndexP.getIndexPattern( + firstSeries?.rt ? ReportToDataTypeMap[firstSeries?.rt] : 'apm' + ); + }, [firstSeries?.rt]); return useMemo(() => { return indexPattern; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts index efd6ebb4c97e7c..bf23d7ee0d7946 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts @@ -5,19 +5,14 @@ * 2.0. */ +import { useMemo } from 'react'; import { TypedLensByValueInput } from '../../../../../../lens/public'; import { LensAttributes } from '../configurations/lens_attributes'; import { useUrlStorage } from './use_url_strorage'; -import { useMemo } from 'react'; import { getDefaultConfigs } from '../configurations/default_configs'; -import { - BREAK_DOWN, - FILTERS, - METRIC_TYPE, - REPORT_TYPE, - SERIES_TYPE, -} from '../configurations/constants'; + import { IIndexPattern } from '../../../../../../../../src/plugins/data/common'; +import { UrlFilter } from '../types'; interface Props { seriesId: string; @@ -30,21 +25,27 @@ export const useLensAttributes = ({ }: Props): TypedLensByValueInput['attributes'] | null => { const { series } = useUrlStorage(seriesId); + const { breakdown, seriesType, metric: metricType, reportType, reportDefinitions = {} } = + series ?? {}; + + const getFiltersFromDefs = () => { + return Object.entries(reportDefinitions).map(([field, value]) => ({ + field, + values: [value], + })) as UrlFilter[]; + }; + + const filters: UrlFilter[] = useMemo(() => { + return (series.filters ?? []).concat(getFiltersFromDefs()); + }, [series.filters, reportDefinitions]); + const dataViewConfig = getDefaultConfigs({ - reportType: series[REPORT_TYPE], - serviceName: series.serviceName, seriesId, + reportType, }); - const { - [FILTERS]: filters = [], - [BREAK_DOWN]: breakdown, - [SERIES_TYPE]: seriesType, - [METRIC_TYPE]: metricType, - } = series ?? {}; - return useMemo(() => { - if (!indexPattern) { + if (!indexPattern || !reportType) { return null; } @@ -61,5 +62,5 @@ export const useLensAttributes = ({ } return lensAttributes.getJSON(); - }, [indexPattern, breakdown, seriesType, filters, metricType]); + }, [indexPattern, breakdown, seriesType, filters, metricType, reportType]); }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx index 05ba7914588277..af65c78166841e 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx @@ -7,52 +7,99 @@ import React, { createContext, useContext, Context } from 'react'; import { IKbnUrlStateStorage } from '../../../../../../../../src/plugins/kibana_utils/public'; -import { NewSeriesUrl, SeriesUrl } from '../types'; +import { AppDataType, ReportViewTypeId, SeriesUrl, UrlFilter } from '../types'; export const UrlStorageContext = createContext(null); interface ProviderProps { storage: IKbnUrlStateStorage; } +const METRIC_TYPE = 'mt'; +const REPORT_TYPE = 'rt'; +const SERIES_TYPE = 'st'; +const BREAK_DOWN = 'bd'; +const FILTERS = 'ft'; export const UrlStorageContextProvider: React.FC = ({ children, storage }) => { return {children}; }; +function convertToShortUrl(newValue: SeriesUrl): ShortUrlSeries { + const { metric, seriesType, reportType, breakdown, filters, ...restSeries } = newValue; + return { + [METRIC_TYPE]: metric, + [REPORT_TYPE]: reportType, + [SERIES_TYPE]: seriesType, + [BREAK_DOWN]: breakdown, + [FILTERS]: filters, + ...restSeries, + }; +} + +function convertFromShortUrl(newValue: ShortUrlSeries): SeriesUrl { + const { mt, st, rt, bd, ft, time, ...restSeries } = newValue; + return { + metric: mt, + reportType: rt!, + seriesType: st, + breakdown: bd, + filters: ft!, + time: time!, + ...restSeries, + }; +} + +interface ShortUrlSeries { + [METRIC_TYPE]?: string; + [REPORT_TYPE]?: ReportViewTypeId; + [SERIES_TYPE]?: string; + [BREAK_DOWN]?: string; + [FILTERS]?: UrlFilter[]; + time?: { + to: string; + from: string; + }; + dataType?: AppDataType; + reportDefinitions?: Record; +} + +export type AllShortSeries = Record; export type AllSeries = Record; -export const NEW_SERIES_KEY = 'newSeries'; +export const NEW_SERIES_KEY = 'newSeriesKey'; export const useUrlStorage = (seriesId?: string) => { const allSeriesKey = 'sr'; const storage = useContext((UrlStorageContext as unknown) as Context); let series: SeriesUrl = {} as SeriesUrl; - const allSeries = storage.get(allSeriesKey) ?? {}; + const allShortSeries = storage.get(allSeriesKey) ?? {}; + + const allSeriesIds = Object.keys(allShortSeries); + + const allSeries: AllSeries = {}; + + allSeriesIds.forEach((seriesKey) => { + allSeries[seriesKey] = convertFromShortUrl(allShortSeries[seriesKey]); + }); if (seriesId) { series = allSeries?.[seriesId] ?? ({} as SeriesUrl); } - const setSeries = (seriesId: string, newValue: SeriesUrl) => { - allSeries[seriesId] = newValue; - storage.set(allSeriesKey, allSeries); + const setSeries = async (seriesIdN: string, newValue: SeriesUrl) => { + allShortSeries[seriesIdN] = convertToShortUrl(newValue); + allSeries[seriesIdN] = newValue; + return storage.set(allSeriesKey, allShortSeries); }; - const removeSeries = (seriesId: string) => { - delete allSeries[seriesId]; + const removeSeries = (seriesIdN: string) => { + delete allShortSeries[seriesIdN]; + delete allSeries[seriesIdN]; storage.set(allSeriesKey, allSeries); }; - const allSeriesIds = Object.keys(allSeries); - const firstSeriesId = allSeriesIds?.[0]; - const newSeries = storage.get('newSeries') ?? {}; - - const setNewSeries = (newValue: NewSeriesUrl) => { - storage.set(NEW_SERIES_KEY, newValue); - }; - return { storage, setSeries, @@ -61,8 +108,6 @@ export const useUrlStorage = (seriesId?: string) => { firstSeriesId, allSeries, allSeriesIds, - newSeries, - setNewSeries, firstSeries: allSeries?.[firstSeriesId], }; }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx index e241a97d309b20..3d7ef7e614b59d 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { AppDataType } from '../../types'; import { useIndexPatternContext } from '../../../../../hooks/use_default_index_pattern'; -import { useUrlStorage } from '../../hooks/use_url_strorage'; +import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_strorage'; const dataTypes: { id: AppDataType; label: string }[] = [ { id: 'synthetics', label: 'Synthetic Monitoring' }, @@ -18,8 +18,9 @@ const dataTypes: { id: AppDataType; label: string }[] = [ { id: 'metrics', label: 'Metrics' }, { id: 'apm', label: 'APM' }, ]; + export const DataTypesCol = () => { - const { newSeries, setNewSeries } = useUrlStorage(); + const { series, setSeries } = useUrlStorage(NEW_SERIES_KEY); const { loadIndexPattern } = useIndexPatternContext(); @@ -27,10 +28,10 @@ export const DataTypesCol = () => { if (dataType) { loadIndexPattern(dataType); } - setNewSeries({ dataType }); + setSeries(NEW_SERIES_KEY, { dataType } as any); }; - const selectedDataType = newSeries.dataType; + const selectedDataType = series.dataType; return ( diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.tsx index eced44f7475a98..57f1103f77d9e6 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.tsx @@ -6,9 +6,18 @@ */ import React from 'react'; +import { Breakdowns } from '../../series_editor/columns/breakdowns'; +import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_strorage'; +import { getDefaultConfigs } from '../../configurations/default_configs'; -interface Props {} +export const ReportBreakdowns = () => { + const { + series: { reportType }, + } = useUrlStorage(NEW_SERIES_KEY); -export const ReportBreakdowns = (props: Props) => { - return
; + const dataSeries = getDefaultConfigs({ + reportType: reportType!, + seriesId: NEW_SERIES_KEY, + }); + return ; }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx index 040104fd952f6c..74bb27e81e4bf8 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx @@ -10,14 +10,14 @@ import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FieldValueSelection } from '../../../field_value_selection'; import { useIndexPatternContext } from '../../../../../hooks/use_default_index_pattern'; import { getDefaultConfigs } from '../../configurations/default_configs'; -import { useUrlStorage } from '../../hooks/use_url_strorage'; +import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_strorage'; export const ReportDefinitionCol = () => { const { indexPattern } = useIndexPatternContext(); - const { newSeries, setNewSeries } = useUrlStorage(); + const { series, setSeries } = useUrlStorage(NEW_SERIES_KEY); - const { reportType, reportDefinitions: rtd = {} } = newSeries; + const { reportType, reportDefinitions: rtd = {} } = series; const { reportDefinitions, labels, filters } = getDefaultConfigs({ reportType: reportType!, @@ -25,37 +25,51 @@ export const ReportDefinitionCol = () => { }); const onChange = (field: string, value: string) => { - setNewSeries({ - ...newSeries, + setSeries(NEW_SERIES_KEY, { + ...series, reportDefinitions: { ...rtd, [field]: value }, }); }; + const onRemove = (field: string) => { + delete rtd[field]; + setSeries(NEW_SERIES_KEY, { + ...series, + reportDefinitions: rtd, + }); + }; + return ( - {reportDefinitions.map(({ field }) => ( - - - - onChange(field, val)} - filters={(filters ?? []).map(({ query }) => query)} - /> - - {rtd?.[field] && ( + {indexPattern && + reportDefinitions.map(({ field }) => ( + + - - {rtd?.[field]} - + onChange(field, val)} + filters={(filters ?? []).map(({ query }) => query)} + /> - )} - - - ))} + {rtd?.[field] && ( + + onRemove(field)} + > + {rtd?.[field]} + + + )} + + + ))} ); }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx index 35e6fa68a0e035..d6bff41bdc6808 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx @@ -6,8 +6,35 @@ */ import React from 'react'; +import { SeriesFilter } from '../../series_editor/columns/series_filter'; +import { NEW_SERIES_KEY } from '../../hooks/use_url_strorage'; +import { getDefaultConfigs } from '../../configurations/default_configs'; +import { DatePickerCol } from '../../series_editor/columns/date_picker_col'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { ReportViewTypeId } from '../../types'; -interface Props {} -export const ReportFilters = (props: Props) => { - return
; +interface Props { + reportType: ReportViewTypeId; +} +export const ReportFilters = ({ reportType }: Props) => { + const dataSeries = getDefaultConfigs({ + reportType: reportType!, + seriesId: NEW_SERIES_KEY, + }); + + return ( + + + + + + + + + ); }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx index d86dfe00f3e28e..82ab786b164184 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { ReportViewTypeId } from '../../types'; -import { useUrlStorage } from '../../hooks/use_url_strorage'; +import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_strorage'; interface Props { reportTypes: { id: ReportViewTypeId; label: string }[]; @@ -16,9 +16,9 @@ interface Props { export const ReportTypesCol = ({ reportTypes }: Props) => { const { - newSeries: { reportType: selectedReportType, ...restSeries }, - setNewSeries, - } = useUrlStorage(); + series: { reportType: selectedReportType, ...restSeries }, + setSeries, + } = useUrlStorage(NEW_SERIES_KEY); return reportTypes?.length > 0 ? ( @@ -30,12 +30,19 @@ export const ReportTypesCol = ({ reportTypes }: Props) => { iconType="arrowRight" color={selectedReportType === reportType ? 'primary' : 'text'} fill={selectedReportType === reportType} - onClick={() => - setNewSeries({ - ...restSeries, - reportType: reportType === selectedReportType ? undefined : reportType, - }) - } + onClick={() => { + if (reportType === selectedReportType) { + setSeries(NEW_SERIES_KEY, { + dataType: restSeries.dataType, + reportType: undefined, + }); + } else { + setSeries(NEW_SERIES_KEY, { + ...restSeries, + reportType: reportType, + }); + } + }} > {label}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx index 58f83092996689..ccdbca0a7037e4 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx @@ -9,14 +9,13 @@ import React, { useState } from 'react'; import { EuiButton, EuiBasicTable, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import styled from 'styled-components'; -import { AppDataType, ReportViewTypeId, SeriesUrl } from '../types'; +import { AppDataType, ReportViewTypeId, ReportViewTypes, SeriesUrl, UrlFilter } from '../types'; import { DataTypesCol } from './columns/data_types_col'; import { ReportTypesCol } from './columns/report_types_col'; import { ReportDefinitionCol } from './columns/report_definition_col'; import { ReportFilters } from './columns/report_filters'; import { ReportBreakdowns } from './columns/report_breakdowns'; -import { useUrlStorage } from '../hooks/use_url_strorage'; -import { FILTERS, REPORT_TYPE } from '../configurations/constants'; +import { NEW_SERIES_KEY, useUrlStorage } from '../hooks/use_url_strorage'; export const ReportTypes: Record> = { synthetics: [ @@ -31,16 +30,25 @@ export const ReportTypes: Record { - const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); + const { series, setSeries, allSeriesIds, removeSeries } = useUrlStorage(NEW_SERIES_KEY); - const { newSeries, setNewSeries, setSeries, allSeriesIds } = useUrlStorage(); + const { dataType, reportType, reportDefinitions = {}, filters = [] } = series; - const { dataType, reportType } = newSeries; + const [isFlyoutVisible, setIsFlyoutVisible] = useState(!!series.dataType); const columns = [ { @@ -63,7 +71,7 @@ export const SeriesBuilder = () => { { name: 'Filters', width: '25%', - render: (val: string) => (reportType ? : null), + render: (val: string) => (reportType ? : null), }, { name: 'Breakdowns', @@ -74,9 +82,7 @@ export const SeriesBuilder = () => { ]; const addSeries = () => { - const { reportDefinitions = {} } = newSeries; - - const getFiltersFromDefs = () => { + const getFiltersFromDefs = (): UrlFilter[] => { return Object.entries(reportDefinitions).map(([field, value]) => ({ field, values: [value], @@ -85,23 +91,24 @@ export const SeriesBuilder = () => { if (reportType) { const newSeriesId = `${ - reportDefinitions?.['service.name'] || reportDefinitions?.['monitor.id'] + reportDefinitions?.['service.name'] || + reportDefinitions?.['monitor.id'] || + ReportViewTypes[reportType] }`; const newSeriesN = { - [REPORT_TYPE]: reportType, + reportType: reportType, time: { from: 'now-30m', to: 'now' }, - [FILTERS]: getFiltersFromDefs(), + filters: getFiltersFromDefs().concat(filters), } as SeriesUrl; - setSeries(newSeriesId, newSeriesN); - - // reset state - setNewSeries({}); - setIsFlyoutVisible(false); + setSeries(newSeriesId, newSeriesN).then(() => { + removeSeries(NEW_SERIES_KEY); + setIsFlyoutVisible(false); + }); } }; - const items = [{ id: 'newSeries' }]; + const items = [{ id: NEW_SERIES_KEY }]; let flyout; @@ -109,7 +116,7 @@ export const SeriesBuilder = () => { flyout = ( @@ -125,7 +132,7 @@ export const SeriesBuilder = () => { iconType="cross" color="danger" onClick={() => { - setNewSeries({}); + removeSeries(NEW_SERIES_KEY); setIsFlyoutVisible(false); }} > @@ -139,16 +146,20 @@ export const SeriesBuilder = () => { return (
- setIsFlyoutVisible((prevState) => !prevState)} - disabled={allSeriesIds.length > 0} - > - {'Add series'} - - + {!isFlyoutVisible && ( + <> + setIsFlyoutVisible((prevState) => !prevState)} + disabled={allSeriesIds.length > 0} + > + {'Add series'} + + + + )} {flyout}
); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx index d81823f51205a7..c4ab9d4410a5e4 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx @@ -46,7 +46,7 @@ export function SeriesDatePicker({ seriesId }: Props) { useEffect(() => { if (!series || !series.time) { - setSeries(seriesId, { ...series, time: { from: 'now-15m', to: 'now' } }); + setSeries(seriesId, { ...series, time: { from: 'now-5h', to: 'now' } }); } }, []); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/actions_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/actions_col.tsx index 6658bbb716b79c..da8a1baf82fe6b 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/actions_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/actions_col.tsx @@ -21,9 +21,11 @@ export function ActionsCol({ series }: Props) { - - - + {series.metricType && ( + + + + )} ); } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx index 7592c01bed55d9..6296a7eb9b2dea 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx @@ -5,9 +5,9 @@ * 2.0. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { EuiSuperSelect } from '@elastic/eui'; -import { BREAK_DOWN, FieldLabels } from '../../configurations/constants'; +import { FieldLabels } from '../../configurations/constants'; import { useUrlStorage } from '../../hooks/use_url_strorage'; import { i18n } from '@kbn/i18n'; @@ -19,19 +19,26 @@ interface Props { export function Breakdowns({ seriesId, breakdowns = [] }: Props) { const items = breakdowns.map((breakdown) => ({ id: breakdown, label: FieldLabels[breakdown] })); - const [selectedBreakdown, setSelectedBreakdown] = useState(); + const { setSeries, series } = useUrlStorage(seriesId); + + const selectedBreakdown = series.breakdown; + const NO_BREAKDOWN = 'no_breakdown'; const onOptionChange = (optionId: string) => { - setSelectedBreakdown((prevState) => (prevState === optionId ? undefined : optionId)); + if (optionId === NO_BREAKDOWN) { + setSeries(seriesId, { + ...series, + breakdown: undefined, + }); + } else { + setSeries(seriesId, { + ...series, + breakdown: selectedBreakdown === optionId ? undefined : optionId, + }); + } }; - const { setSeries, series } = useUrlStorage(seriesId); - - useEffect(() => { - setSeries(seriesId, { ...series, [BREAK_DOWN]: selectedBreakdown }); - }, [selectedBreakdown]); - - const NO_BREAKDOWN = 'no_breakdown'; + useEffect(() => {}, [selectedBreakdown]); items.push({ id: NO_BREAKDOWN, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx index da5e8922739c91..ba4178d54a62e3 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx @@ -16,7 +16,6 @@ import styled from 'styled-components'; import { useUrlStorage } from '../../hooks/use_url_strorage'; import { LensIconChartBar } from '../../assets/chart_bar'; import { VisualizationType } from '../../../../../../../lens/public/types'; -import { SERIES_TYPE } from '../../configurations/constants'; import { LensIconChartBarHorizontal } from '../../assets/chart_bar_horizontal'; import { LensIconChartBarStacked } from '../../assets/chart_bar_stacked'; import { LensIconChartBarHorizontalStacked } from '../../assets/chart_bar_horizontal_stacked'; @@ -43,7 +42,7 @@ export function ChartTypes({ const [isOpen, setIsOpen] = useState(false); - const seriesType = series?.[SERIES_TYPE] ?? defaultChartType; + const seriesType = series?.seriesType ?? defaultChartType; return ( { Object.keys(allSeries).forEach((seriesKey) => { const series = allSeries[seriesKey]; - setSeries(seriesKey, { ...series, [SERIES_TYPE]: seriesType }); + setSeries(seriesKey, { ...series, seriesType }); }); }} /> diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/date_picker_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/date_picker_col.tsx index 8051635ef1a24e..8c99de51978a7c 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/date_picker_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/date_picker_col.tsx @@ -9,12 +9,12 @@ import React from 'react'; import { SeriesDatePicker } from '../../series_date_picker'; interface Props { - series: any; + seriesId: string; } -export function DatePickerCol({ series }: Props) { +export function DatePickerCol({ seriesId }: Props) { return (
- +
); } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx index cea6fdd6e0d96d..4580d6e1c8ac70 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx @@ -20,7 +20,6 @@ import { useKibana } from '../../../../../../../../../src/plugins/kibana_react/p import { useIndexPatternContext } from '../../../../../hooks/use_default_index_pattern'; import { useUrlStorage } from '../../hooks/use_url_strorage'; import { UrlFilter } from '../../types'; -import { FILTERS } from '../../configurations/constants'; interface Props { seriesId: string; @@ -49,7 +48,7 @@ export function FilterExpanded({ seriesId, field, label, goBack }: Props) { }); }, [field]); - const filters = series?.[FILTERS] ?? []; + const filters = series?.filters ?? []; let currFilter: UrlFilter | undefined = filters.find(({ field: fd }) => field === fd); @@ -62,11 +61,11 @@ export function FilterExpanded({ seriesId, field, label, goBack }: Props) { currFilter.values = [id]; } if (filters.length === 0) { - setSeries(seriesId, { ...series, [FILTERS]: [currFilter] }); + setSeries(seriesId, { ...series, filters: [currFilter] }); } else { setSeries(seriesId, { ...series, - [FILTERS]: [currFilter, ...filters.filter((ft) => ft.field != field)], + filters: [currFilter, ...filters.filter((ft) => ft.field != field)], }); } return; @@ -89,9 +88,9 @@ export function FilterExpanded({ seriesId, field, label, goBack }: Props) { currFilter.values = values.length > 0 ? values : undefined; if (notValues.length > 0 || values.length > 0) { - setSeries(seriesId, { ...series, [FILTERS]: [currFilter] }); + setSeries(seriesId, { ...series, filters: [currFilter] }); } else { - setSeries(seriesId, { ...series, [FILTERS]: undefined }); + setSeries(seriesId, { ...series, filters: undefined }); } } }; @@ -109,7 +108,11 @@ export function FilterExpanded({ seriesId, field, label, goBack }: Props) { }} /> - {status === 'loading' && } + {status === 'loading' && ( +
+ +
+ )} {(values || []) .filter((opt) => opt.toLowerCase().includes(value.toLowerCase())) .map((opt) => ( diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.tsx index 7d5312d8933d5e..934a291b1f9948 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.tsx @@ -8,7 +8,6 @@ import React, { useState } from 'react'; import { EuiButton, EuiButtonGroup, EuiPopover } from '@elastic/eui'; import { useUrlStorage } from '../../hooks/use_url_strorage'; -import { METRIC_TYPE } from '../../configurations/constants'; const toggleButtons = [ { @@ -40,7 +39,7 @@ export function MetricSelection({ const [isOpen, setIsOpen] = useState(false); - const [toggleIdSelected, setToggleIdSelected] = useState(series?.[METRIC_TYPE] ?? 'avg'); + const [toggleIdSelected, setToggleIdSelected] = useState(series?.metric ?? 'avg'); const onChange = (optionId: string) => { setToggleIdSelected(optionId); @@ -48,7 +47,7 @@ export function MetricSelection({ Object.keys(allSeries).forEach((seriesKey) => { const seriesN = allSeries[seriesKey]; - setSeries(seriesKey, { ...seriesN, [METRIC_TYPE]: optionId }); + setSeries(seriesKey, { ...seriesN, metric: optionId }); }); }; const button = ( diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/remove_series.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/remove_series.tsx index 7b80e418daeea1..dc1ffd29985c64 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/remove_series.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/remove_series.tsx @@ -20,5 +20,5 @@ export function RemoveSeries({ series }: Props) { const onClick = () => { removeSeries(series.id); }; - return ; + return ; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx index 4cee3c556da9e0..030b3caa65ffbe 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx @@ -18,11 +18,13 @@ import { FilterExpanded } from './filter_expanded'; import { DataSeries } from '../../types'; import { FieldLabels } from '../../configurations/constants'; import { SelectedFilters } from '../selected_filters'; +import { NEW_SERIES_KEY } from '../../hooks/use_url_strorage'; interface Props { seriesId: string; defaultFilters: DataSeries['defaultFilters']; series: DataSeries; + isNew?: boolean; } export interface Field { @@ -30,12 +32,13 @@ export interface Field { field: string; } -export function SeriesFilter({ series, seriesId, defaultFilters = [] }: Props) { +export function SeriesFilter({ series, isNew, seriesId, defaultFilters = [] }: Props) { const [isPopoverVisible, setIsPopoverVisible] = useState(false); const [selectedField, setSelectedField] = useState(null); const options = defaultFilters.map((field) => ({ label: FieldLabels[field], field })); + const disabled = seriesId === NEW_SERIES_KEY && !isNew; const button = ( { setIsPopoverVisible(true); }} + isDisabled={disabled} + size="s" > Add filter @@ -88,20 +93,16 @@ export function SeriesFilter({ series, seriesId, defaultFilters = [] }: Props) { return ( - + {!disabled && } - - - - {!selectedField ? mainPanel : childPanel} - - - + + {!selectedField ? mainPanel : childPanel} + ); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx index d81876accb2090..9052a7c165c9ee 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx @@ -5,21 +5,35 @@ * 2.0. */ -import React, { Fragment } from 'react'; -import { useUrlStorage } from '../hooks/use_url_strorage'; +import React, { Fragment, useMemo } from 'react'; +import { NEW_SERIES_KEY, useUrlStorage } from '../hooks/use_url_strorage'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { FILTERS } from '../configurations/constants'; import { FilterLabel } from '../components/filter_label'; -import { DataSeries } from '../types'; +import { DataSeries, UrlFilter } from '../types'; +import { useIndexPatternContext } from '../../../../hooks/use_default_index_pattern'; interface Props { seriesId: string; series: DataSeries; + isNew?: boolean; } -export const SelectedFilters = ({ seriesId, series: { labels } }: Props) => { +export const SelectedFilters = ({ seriesId, isNew, series: { labels } }: Props) => { const { series, setSeries } = useUrlStorage(seriesId); - const filters = series?.[FILTERS] ?? []; + const { reportDefinitions = {} } = series; + const getFiltersFromDefs = () => { + return Object.entries(reportDefinitions).map(([field, value]) => ({ + field, + values: [value], + })) as UrlFilter[]; + }; + + const filters: UrlFilter[] = useMemo(() => { + if (seriesId === NEW_SERIES_KEY && isNew) { + return series.filters ?? []; + } + return (series.filters ?? []).concat(getFiltersFromDefs()); + }, [series.filters, reportDefinitions]); const removeFilter = (field: string, value: string, notVal: boolean) => { const filtersN = filters.map((filter) => { @@ -35,10 +49,12 @@ export const SelectedFilters = ({ seriesId, series: { labels } }: Props) => { return filter; }); - setSeries(seriesId, { ...series, [FILTERS]: filtersN }); + setSeries(seriesId, { ...series, filters: filtersN }); }; - return filters.length > 0 ? ( + const { indexPattern } = useIndexPatternContext(); + + return filters.length > 0 && indexPattern ? ( {filters.map(({ field, values, notValues }) => ( diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx index c174ac5fb40e6a..8d5eb73d8817e6 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx @@ -12,13 +12,14 @@ import { ActionsCol } from './columns/actions_col'; import { Breakdowns } from './columns/breakdowns'; import { DataSeries } from '../types'; import { SeriesBuilder } from '../series_builder/series_builder'; -import { useUrlStorage } from '../hooks/use_url_strorage'; +import { NEW_SERIES_KEY, useUrlStorage } from '../hooks/use_url_strorage'; import { getDefaultConfigs } from '../configurations/default_configs'; -import { REPORT_TYPE } from '../configurations/constants'; import { DatePickerCol } from './columns/date_picker_col'; import { RemoveSeries } from './columns/remove_series'; export const SeriesEditor = () => { + const { allSeries, firstSeriesId } = useUrlStorage(); + const columns = [ { name: 'Name', @@ -26,58 +27,66 @@ export const SeriesEditor = () => { width: '20%', render: (val: string) => ( - {val} + {' '} + {val === NEW_SERIES_KEY ? 'new-series-preview' : val} ), }, - { - name: 'Filter', - field: 'defaultFilters', - width: '20%', - render: (defaultFilters: string[], series: DataSeries) => ( - - ), - }, - { - name: 'Breakdowns', - field: 'breakdowns', - width: '15%', - render: (val: string[], item: DataSeries) => ( - - ), - }, - { - name: '', - align: 'center' as const, - width: '15%', - field: 'id', - render: (val: string, item: DataSeries) => , - }, - { - name: 'Time', - width: '20%', - field: 'id', - render: (val: string, item: DataSeries) => , - }, - { - name: 'Actions', - align: 'center' as const, - width: '5%', - field: 'id', - render: (val: string, item: DataSeries) => , - }, + ...(firstSeriesId !== NEW_SERIES_KEY + ? [ + { + name: 'Filter', + field: 'defaultFilters', + width: '20%', + render: (defaultFilters: string[], series: DataSeries) => ( + + ), + }, + { + name: 'Breakdowns', + field: 'breakdowns', + width: '15%', + render: (val: string[], item: DataSeries) => ( + + ), + }, + { + name: '', + align: 'center' as const, + width: '15%', + field: 'id', + render: (val: string, item: DataSeries) => , + }, + { + name: 'Time', + width: '20%', + field: 'id', + render: (val: string, item: DataSeries) => , + }, + { + name: 'Actions', + align: 'center' as const, + width: '5%', + field: 'id', + render: (val: string, item: DataSeries) => , + }, + ] + : []), ]; - const { allSeries } = useUrlStorage(); - const allSeriesKeys = Object.keys(allSeries); - const items: DataSeries[] = allSeriesKeys.map((seriesKey) => { + const items: DataSeries[] = []; + allSeriesKeys.forEach((seriesKey) => { const series = allSeries[seriesKey]; - return getDefaultConfigs({ - reportType: series[REPORT_TYPE], - seriesId: seriesKey, - }); + if (series.reportType) { + items.push( + getDefaultConfigs({ + reportType: series.reportType, + seriesId: seriesKey, + }) + ); + } }); return ( @@ -87,7 +96,7 @@ export const SeriesEditor = () => { items={items} rowHeader="firstName" columns={columns} - rowProps={() => ({ height: 100 })} + rowProps={() => (firstSeriesId === NEW_SERIES_KEY ? {} : { height: 100 })} noItemsMessage={'No series found, please add a series.'} cellProps={{ style: { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts index d411c18ae53c79..5a438105a58833 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts @@ -11,13 +11,7 @@ import { DateHistogramIndexPatternColumn, LastValueIndexPatternColumn, } from '../../../../../lens/public'; -import { - BREAK_DOWN, - FILTERS, - METRIC_TYPE, - REPORT_TYPE, - SERIES_TYPE, -} from './configurations/constants'; + import { ESFilter } from '../../../../../../../typings/elasticsearch'; export const ReportViewTypes = { @@ -27,20 +21,20 @@ export const ReportViewTypes = { upp: 'uptime-pings', svl: 'service-latency', tpt: 'service-throughput', -}; + logs: 'logs-frequency', + cpu: 'cpu-usage', + mem: 'memory-usage', + nwk: 'network-activity', +} as const; + +type ValueOf = T[keyof T]; export type ReportViewTypeId = keyof typeof ReportViewTypes; -export type ReportViewType = - | 'page-load-dist' - | 'kpi-trends' - | 'uptime-duration' - | 'uptime-pings' - | 'service-latency'; +export type ReportViewType = ValueOf; export interface DataSeries { reportType: ReportViewType; - indexPattern: string; id: string; xAxisColumn: Partial | Partial; yAxisColumn: @@ -59,6 +53,7 @@ export interface DataSeries { }[]; labels: Record; metricType: boolean; + palette?: Record; } export interface SeriesUrl { @@ -66,16 +61,12 @@ export interface SeriesUrl { to: string; from: string; }; - [BREAK_DOWN]?: string; - [FILTERS]?: UrlFilter[]; - [SERIES_TYPE]?: string; - [REPORT_TYPE]: ReportViewTypeId; - [METRIC_TYPE]?: string; -} - -export interface NewSeriesUrl { + breakdown?: string; + filters?: UrlFilter[]; + seriesType?: string; + reportType: ReportViewTypeId; + metric?: string; dataType?: AppDataType; - reportType?: ReportViewTypeId; reportDefinitions?: Record; } diff --git a/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx b/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx index faa4b2e16120a4..ae30d0ef86d408 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx +++ b/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx @@ -65,7 +65,7 @@ export const FieldValueSelection = ({ }; const onValueChange = (evt: FormEvent) => { - setQuery(evt.target.value); + setQuery((evt.target as HTMLInputElement).value); }; const button = ( @@ -97,7 +97,7 @@ export const FieldValueSelection = ({ compressed: true, onInput: onValueChange, }} - options={options} + options={options as any} onChange={onChange} isLoading={loading} > diff --git a/x-pack/plugins/observability/public/context/has_data_context.tsx b/x-pack/plugins/observability/public/context/has_data_context.tsx index 085b7fd7ba028e..3117996b399717 100644 --- a/x-pack/plugins/observability/public/context/has_data_context.tsx +++ b/x-pack/plugins/observability/public/context/has_data_context.tsx @@ -14,6 +14,7 @@ import { usePluginContext } from '../hooks/use_plugin_context'; import { useTimeRange } from '../hooks/use_time_range'; import { getObservabilityAlerts } from '../services/get_observability_alerts'; import { ObservabilityFetchDataPlugins, UXHasDataResponse } from '../typings/fetch_overview_data'; +import { useRouteMatch } from 'react-router'; type DataContextApps = ObservabilityFetchDataPlugins | 'alert'; @@ -41,35 +42,38 @@ export function HasDataContextProvider({ children }: { children: React.ReactNode const [hasData, setHasData] = useState({}); + const isExploratoryView = useRouteMatch('/exploratory-view'); + useEffect( () => { - apps.forEach(async (app) => { - try { - if (app !== 'alert') { - const params = - app === 'ux' - ? { absoluteTime: { start: absoluteStart, end: absoluteEnd } } - : undefined; - - const result = await getDataHandler(app)?.hasData(params); + if (!isExploratoryView) + apps.forEach(async (app) => { + try { + if (app !== 'alert') { + const params = + app === 'ux' + ? { absoluteTime: { start: absoluteStart, end: absoluteEnd } } + : undefined; + + const result = await getDataHandler(app)?.hasData(params); + setHasData((prevState) => ({ + ...prevState, + [app]: { + hasData: result, + status: FETCH_STATUS.SUCCESS, + }, + })); + } + } catch (e) { setHasData((prevState) => ({ ...prevState, [app]: { - hasData: result, - status: FETCH_STATUS.SUCCESS, + hasData: undefined, + status: FETCH_STATUS.FAILURE, }, })); } - } catch (e) { - setHasData((prevState) => ({ - ...prevState, - [app]: { - hasData: undefined, - status: FETCH_STATUS.FAILURE, - }, - })); - } - }); + }); }, // eslint-disable-next-line react-hooks/exhaustive-deps [] diff --git a/x-pack/plugins/observability/public/utils/observability_Index_patterns.ts b/x-pack/plugins/observability/public/utils/observability_Index_patterns.ts index 42694940c9ce47..be4c36b26c15b8 100644 --- a/x-pack/plugins/observability/public/utils/observability_Index_patterns.ts +++ b/x-pack/plugins/observability/public/utils/observability_Index_patterns.ts @@ -21,8 +21,8 @@ const appToPatternMap: Record = { synthetics: 'heartbeat-*', apm: 'apm-*', rum: 'apm-*', - logs: 'logs-*', - metrics: 'metrics-*', + logs: 'logs-*,filebeat-*', + metrics: 'metrics-*,metricbeat-*', }; export class ObservabilityIndexPatterns { diff --git a/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx b/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx index 04e3c851e8b3e2..effdd9ce8e5c53 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx +++ b/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx @@ -183,7 +183,7 @@ export const PingHistogramComponent: React.FC = ({ } const analyzeHref = { - ['uptime-pings-pgv']: { + ['uptime-pings-histogram']: { rt: 'upp', time: { from: dateRangeStart, to: dateRangeEnd }, bd: 'monitor.status', From 1d5a7c885b82f01aefc2971ea9ee9a35d2e6a9b3 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Sun, 21 Mar 2021 21:56:03 +0100 Subject: [PATCH 11/53] update --- src/plugins/data/public/index.ts | 1 - .../components/filter_label.tsx | 3 +- .../configurations/constants.ts | 1 + .../configurations/monitor_pings_config.ts | 6 +- .../configurations/page_load_dist_config.ts | 5 +- .../hooks/use_series_filters.ts | 76 ++++++++++++-- .../hooks/use_url_strorage.tsx | 4 +- .../series_builder/columns/report_filters.tsx | 21 ++-- .../series_editor/columns/filter_expanded.tsx | 80 ++++----------- .../columns/filter_value_btn.tsx | 98 +++++++++++++++++++ .../series_editor/columns/series_filter.tsx | 9 +- .../series_editor/selected_filters.tsx | 25 ++--- .../series_editor/series_editor.tsx | 18 ++-- .../shared/exploratory_view/types.ts | 2 +- .../shared/field_value_selection.tsx | 24 +++-- .../public/hooks/use_values_list.ts | 30 +++++- .../utils/observability_Index_patterns.ts | 2 +- .../common/charts/ping_histogram.tsx | 2 +- 18 files changed, 275 insertions(+), 132 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 1d040cc2ff246e..1838ca43e8c231 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -486,7 +486,6 @@ export { APPLY_FILTER_TRIGGER } from './triggers'; */ import { DataPublicPlugin } from './plugin'; -import { FilterItem } from './ui/filter_bar'; export function plugin(initializerContext: PluginInitializerContext) { return new DataPublicPlugin(initializerContext); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx index 71cfd3a48b1d25..e97b26e69fc4ce 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx @@ -10,7 +10,6 @@ import { esFilters, Filter } from '../../../../../../../../src/plugins/data/publ import { useIndexPatternContext } from '../../../../hooks/use_default_index_pattern'; import { injectI18n } from '@kbn/i18n/react'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; -import { FieldLabels } from '../configurations/constants'; import { useSeriesFilters } from '../hooks/use_series_filters'; interface Props { @@ -53,7 +52,7 @@ export const FilterLabel = ({ label, seriesId, field, value, negate, removeFilte }} onUpdate={(filterN: Filter) => { if (filterN.meta.negate !== negate) { - invertFilter(field, value, negate); + invertFilter({ field, value, negate }); } }} uiSettings={uiSettings!} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts index d6c79740bc0bcd..b36bd94d1af18d 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts @@ -9,6 +9,7 @@ import { AppDataType, ReportViewTypeId } from '../types'; export const FieldLabels: Record = { 'user_agent.name': 'Browser family', + 'user_agent.version': 'Browser version', 'user_agent.os.name': 'Operating system', 'client.geo.country_name': 'Location', 'user_agent.device.name': 'Device', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_pings_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_pings_config.ts index be728387f97598..2df1c901f60d9b 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_pings_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/monitor_pings_config.ts @@ -17,7 +17,6 @@ export function getMonitorPingsConfig({ seriesId }: Props): DataSeries { id: seriesId, reportType: 'uptime-pings', defaultSeriesType: 'bar_stacked', - indexPattern: 'df32db00-819e-11eb-87f5-d7da22b1dde3', seriesTypes: ['bar_stacked', 'bar'], xAxisColumn: { sourceField: '@timestamp', @@ -28,13 +27,16 @@ export function getMonitorPingsConfig({ seriesId }: Props): DataSeries { }, metricType: false, defaultFilters: ['observer.geo.name'], - breakdowns: ['monitor.status', 'observer.geo.name'], + breakdowns: ['monitor.status', 'observer.geo.name', 'monitor.type'], filters: [], palette: { type: 'palette', name: 'status' }, reportDefinitions: [ { field: 'monitor.id', }, + { + field: 'url.full', + }, ], labels: { ...FieldLabels }, }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts index f3db0f758a04d5..1f139c3b3b3cef 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts @@ -27,10 +27,13 @@ export function getPageLoadDistLensConfig({ seriesId }: Props): DataSeries { }, metricType: false, defaultFilters: [ - 'user_agent.name', 'user_agent.os.name', 'client.geo.country_name', 'user_agent.device.name', + { + field: 'user_agent.name', + nested: 'user_agent.version', + }, ], breakdowns: [ 'user_agent.name', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_filters.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_filters.ts index 0a149467f08d52..ee477ef8383d5b 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_filters.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_filters.ts @@ -6,19 +6,61 @@ */ import { useUrlStorage } from './use_url_strorage'; -import { FILTERS } from '../configurations/constants'; import { UrlFilter } from '../types'; interface Props { seriesId: string; } + +interface UpdateFilter { + field: string; + value: string; + negate?: boolean; +} + export const useSeriesFilters = ({ seriesId }: Props) => { const { series, setSeries } = useUrlStorage(seriesId); - const filters = series[FILTERS] ?? []; + const filters = series.filters ?? []; + + const removeFilter = ({ field, value, negate }: UpdateFilter) => { + const filtersN = filters.map((filter) => { + if (filter.field === field) { + if (negate) { + const notValuesN = filter.notValues?.filter((val) => val !== value); + return { ...filter, notValues: notValuesN }; + } else { + const valuesN = filter.values?.filter((val) => val !== value); + return { ...filter, values: valuesN }; + } + } + + return filter; + }); + setSeries(seriesId, { ...series, filters: filtersN }); + }; + + const addFilter = ({ field, value, negate }: UpdateFilter) => { + const currFilter: UrlFilter = { field }; + if (negate) { + currFilter.notValues = [value]; + } else { + currFilter.values = [value]; + } + if (filters.length === 0) { + setSeries(seriesId, { ...series, filters: [currFilter] }); + } else { + setSeries(seriesId, { + ...series, + filters: [currFilter, ...filters.filter((ft) => ft.field != field)], + }); + } + }; - const invertFilter = (field: string, value: string, negate: boolean) => { - let currFilter: UrlFilter | undefined = filters.find(({ field: fd }) => field === fd)!; + const updateFilter = ({ field, value, negate }: UpdateFilter) => { + const currFilter: UrlFilter | undefined = filters.find(({ field: fd }) => field === fd) ?? { + field, + }; const currNotValues = currFilter.notValues ?? []; const currValues = currFilter.values ?? []; @@ -27,20 +69,36 @@ export const useSeriesFilters = ({ seriesId }: Props) => { const values = currValues.filter((val) => val !== value); if (negate) { - values.push(value); - } else { notValues.push(value); + } else { + values.push(value); } currFilter.notValues = notValues.length > 0 ? notValues : undefined; currFilter.values = values.length > 0 ? values : undefined; + const otherFilters = filters.filter(({ field: fd }) => fd !== field); + if (notValues.length > 0 || values.length > 0) { - setSeries(seriesId, { ...series, [FILTERS]: [currFilter] }); + setSeries(seriesId, { ...series, filters: [...otherFilters, currFilter] }); + } else { + setSeries(seriesId, { ...series, filters: otherFilters }); + } + }; + + const setFilter = ({ field, value, negate }: UpdateFilter) => { + let currFilter: UrlFilter | undefined = filters.find(({ field: fd }) => field === fd); + + if (!currFilter) { + addFilter({ field, value, negate }); } else { - setSeries(seriesId, { ...series, [FILTERS]: undefined }); + updateFilter({ field, value, negate }); } }; - return { invertFilter }; + const invertFilter = ({ field, value, negate }: UpdateFilter) => { + updateFilter({ field, value, negate: !negate }); + }; + + return { invertFilter, setFilter, removeFilter }; }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx index af65c78166841e..c8625c1c20dcb9 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx @@ -68,7 +68,7 @@ export type AllSeries = Record; export const NEW_SERIES_KEY = 'newSeriesKey'; -export const useUrlStorage = (seriesId?: string) => { +export function useUrlStorage(seriesId?: string) { const allSeriesKey = 'sr'; const storage = useContext((UrlStorageContext as unknown) as Context); let series: SeriesUrl = {} as SeriesUrl; @@ -110,4 +110,4 @@ export const useUrlStorage = (seriesId?: string) => { allSeriesIds, firstSeries: allSeries?.[firstSeriesId], }; -}; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx index d6bff41bdc6808..c2607ba6b628bc 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx @@ -9,8 +9,6 @@ import React from 'react'; import { SeriesFilter } from '../../series_editor/columns/series_filter'; import { NEW_SERIES_KEY } from '../../hooks/use_url_strorage'; import { getDefaultConfigs } from '../../configurations/default_configs'; -import { DatePickerCol } from '../../series_editor/columns/date_picker_col'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { ReportViewTypeId } from '../../types'; interface Props { @@ -23,18 +21,11 @@ export const ReportFilters = ({ reportType }: Props) => { }); return ( - - - - - - - - + ); }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx index 4580d6e1c8ac70..d3cb899c54e8a6 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx @@ -11,7 +11,6 @@ import { EuiSpacer, EuiButtonEmpty, EuiLoadingSpinner, - EuiFilterButton, EuiFilterGroup, } from '@elastic/eui'; import { ObservabilityClientPluginsStart } from '../../../../../plugin'; @@ -20,15 +19,17 @@ import { useKibana } from '../../../../../../../../../src/plugins/kibana_react/p import { useIndexPatternContext } from '../../../../../hooks/use_default_index_pattern'; import { useUrlStorage } from '../../hooks/use_url_strorage'; import { UrlFilter } from '../../types'; +import { FilterValueButton } from './filter_value_btn'; interface Props { seriesId: string; label: string; field: string; goBack: () => void; + nestedField?: string; } -export function FilterExpanded({ seriesId, field, label, goBack }: Props) { +export function FilterExpanded({ seriesId, field, label, goBack, nestedField }: Props) { const { indexPattern } = useIndexPatternContext(); const [value, setValue] = useState(''); @@ -37,7 +38,7 @@ export function FilterExpanded({ seriesId, field, label, goBack }: Props) { services: { data }, } = useKibana(); - const { series, setSeries } = useUrlStorage(seriesId); + const { series } = useUrlStorage(seriesId); const { data: values, status } = useFetcher>(() => { return data.autocomplete.getValueSuggestions({ @@ -52,49 +53,6 @@ export function FilterExpanded({ seriesId, field, label, goBack }: Props) { let currFilter: UrlFilter | undefined = filters.find(({ field: fd }) => field === fd); - const onChange = (id: string, not?: boolean) => { - if (!currFilter) { - currFilter = { field }; - if (not) { - currFilter.notValues = [id]; - } else { - currFilter.values = [id]; - } - if (filters.length === 0) { - setSeries(seriesId, { ...series, filters: [currFilter] }); - } else { - setSeries(seriesId, { - ...series, - filters: [currFilter, ...filters.filter((ft) => ft.field != field)], - }); - } - return; - } - - if (currFilter) { - const currNotValues = currFilter.notValues ?? []; - const currValues = currFilter.values ?? []; - - const notValues = currNotValues.filter((val) => val !== id); - const values = currValues.filter((val) => val !== id); - - if (not && !currNotValues.includes(id)) { - notValues.push(id); - } else if (!currValues.includes(id)) { - values.push(id); - } - - currFilter.notValues = notValues.length > 0 ? notValues : undefined; - currFilter.values = values.length > 0 ? values : undefined; - - if (notValues.length > 0 || values.length > 0) { - setSeries(seriesId, { ...series, filters: [currFilter] }); - } else { - setSeries(seriesId, { ...series, filters: undefined }); - } - } - }; - return ( <> goBack()}> @@ -118,20 +76,22 @@ export function FilterExpanded({ seriesId, field, label, goBack }: Props) { .map((opt) => ( - onChange(opt, true)} - color="danger" - > - Not {opt} - - onChange(opt)} - color="primary" - > - {opt} - + + diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx new file mode 100644 index 00000000000000..7c8d21ec6380c8 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; +import { EuiFilterButton, hexToRgb } from '@elastic/eui'; +import { useIndexPatternContext } from '../../../../../hooks/use_default_index_pattern'; +import { useUrlStorage } from '../../hooks/use_url_strorage'; +import { FieldValueSelection } from '../../../field_value_selection'; +import { useSeriesFilters } from '../../hooks/use_series_filters'; +import styled from 'styled-components'; +import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common'; + +interface Props { + value: string; + field: string; + allValues?: string[]; + negate?: boolean; + nestedField?: string; + seriesId: string; +} + +export const FilterValueButton = ({ + value, + field, + allValues, + negate, + seriesId, + nestedField, +}: Props) => { + const { series } = useUrlStorage(seriesId); + + const [isOpen, setIsOpen] = useState(false); + + const [selected, setSelected] = useState('any'); + + const { indexPattern } = useIndexPatternContext(); + + const { setFilter, removeFilter } = useSeriesFilters({ seriesId }); + + const hasActiveFilters = (allValues ?? []).includes(value); + + const button = ( + { + if (hasActiveFilters) { + removeFilter({ field, value, negate }); + } else { + setFilter({ field, value, negate }); + } + if (!hasActiveFilters) { + setIsOpen((prevState) => !prevState); + } else { + setIsOpen(false); + } + }} + className="" + > + {negate ? `Not ${value}` : value} + + ); + + const onNestedChange = (val: string) => { + console.log(val); + }; + + return nestedField ? ( + + ) : ( + button + ); +}; + +const FilterButton = euiStyled(EuiFilterButton)` + background-color: rgba(${(props) => { + const color = props.hasActiveFilters + ? props.color === 'danger' + ? hexToRgb(props.theme.eui.euiColorDanger) + : hexToRgb(props.theme.eui.euiColorPrimary) + : 'initial'; + return `${color[0]}, ${color[1]}, ${color[2]}, 0.1`; + }}); +`; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx index 030b3caa65ffbe..9616d790a1eb9f 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx @@ -30,6 +30,7 @@ interface Props { export interface Field { label: string; field: string; + nested: string; } export function SeriesFilter({ series, isNew, seriesId, defaultFilters = [] }: Props) { @@ -37,7 +38,12 @@ export function SeriesFilter({ series, isNew, seriesId, defaultFilters = [] }: P const [selectedField, setSelectedField] = useState(null); - const options = defaultFilters.map((field) => ({ label: FieldLabels[field], field })); + const options = defaultFilters.map((field) => { + if (typeof field == 'string') { + return { label: FieldLabels[field], field }; + } + return { label: FieldLabels[field.field], field: field.field, nested: field.nested }; + }); const disabled = seriesId === NEW_SERIES_KEY && !isNew; const button = ( @@ -80,6 +86,7 @@ export function SeriesFilter({ series, isNew, seriesId, defaultFilters = [] }: P seriesId={seriesId} field={selectedField.field} label={selectedField.label} + nestedField={selectedField.nested} goBack={() => { setSelectedField(null); }} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx index 9052a7c165c9ee..d0af9ff96a0e78 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx @@ -11,6 +11,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FilterLabel } from '../components/filter_label'; import { DataSeries, UrlFilter } from '../types'; import { useIndexPatternContext } from '../../../../hooks/use_default_index_pattern'; +import { useSeriesFilters } from '../hooks/use_series_filters'; interface Props { seriesId: string; @@ -18,9 +19,10 @@ interface Props { isNew?: boolean; } export const SelectedFilters = ({ seriesId, isNew, series: { labels } }: Props) => { - const { series, setSeries } = useUrlStorage(seriesId); + const { series } = useUrlStorage(seriesId); const { reportDefinitions = {} } = series; + const getFiltersFromDefs = () => { return Object.entries(reportDefinitions).map(([field, value]) => ({ field, @@ -35,22 +37,7 @@ export const SelectedFilters = ({ seriesId, isNew, series: { labels } }: Props) return (series.filters ?? []).concat(getFiltersFromDefs()); }, [series.filters, reportDefinitions]); - const removeFilter = (field: string, value: string, notVal: boolean) => { - const filtersN = filters.map((filter) => { - if (filter.field === field) { - if (notVal) { - const notValuesN = filter.notValues?.filter((val) => val !== value); - return { ...filter, notValues: notValuesN }; - } else { - const valuesN = filter.values?.filter((val) => val !== value); - return { ...filter, values: valuesN }; - } - } - - return filter; - }); - setSeries(seriesId, { ...series, filters: filtersN }); - }; + const { removeFilter } = useSeriesFilters({ seriesId }); const { indexPattern } = useIndexPatternContext(); @@ -66,7 +53,7 @@ export const SelectedFilters = ({ seriesId, isNew, series: { labels } }: Props) field={field} label={labels[field]} value={val} - removeFilter={() => removeFilter(field, val, false)} + removeFilter={() => removeFilter({ field, value: val, negate: false })} negate={false} /> @@ -79,7 +66,7 @@ export const SelectedFilters = ({ seriesId, isNew, series: { labels } }: Props) label={labels[field]} value={val} negate={true} - removeFilter={() => removeFilter(field, val, true)} + removeFilter={() => removeFilter({ field, value: val, negate: true })} /> ))} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx index 8d5eb73d8817e6..a4b2d149578967 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx @@ -57,12 +57,18 @@ export const SeriesEditor = () => { field: 'id', render: (val: string, item: DataSeries) => , }, - { - name: 'Time', - width: '20%', - field: 'id', - render: (val: string, item: DataSeries) => , - }, + ] + : []), + { + name:
Time
, + width: '20%', + field: 'id', + align: 'right' as const, + render: (val: string, item: DataSeries) => , + }, + + ...(firstSeriesId !== NEW_SERIES_KEY + ? [ { name: 'Actions', align: 'center' as const, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts index 5a438105a58833..005a7e6a7b0d3e 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts @@ -43,7 +43,7 @@ export interface DataSeries { | Partial; breakdowns: string[]; defaultSeriesType: string; - defaultFilters: string[]; + defaultFilters: Array; seriesTypes: string[]; filters?: ESFilter[]; reportDefinitions: { diff --git a/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx b/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx index ae30d0ef86d408..5b669791437265 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx +++ b/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx @@ -16,6 +16,7 @@ import { import { useValuesList } from '../../hooks/use_values_list'; import { IIndexPattern } from '../../../../../../src/plugins/data/common'; import { ESFilter } from '../../../../../../typings/elasticsearch'; +import { PopoverAnchorPosition } from '@elastic/eui/src/components/popover/popover'; interface Option { id: string; @@ -28,8 +29,12 @@ interface Props { label: string; indexPattern: IIndexPattern; sourceField: string; - onChange: (val: string) => void; + onChange: (val?: string) => void; filters: ESFilter[]; + anchorPosition?: PopoverAnchorPosition; + time?: { from: string; to: string }; + forceOpen?: boolean; + button?: JSX.Element; } export const FieldValueSelection = ({ @@ -38,10 +43,14 @@ export const FieldValueSelection = ({ value, label, filters, + button, + time, + forceOpen, + anchorPosition, onChange: onSelectionChange, }: Props) => { const [query, setQuery] = useState(''); - const { values, loading } = useValuesList({ indexPattern, query, sourceField, filters }); + const { values, loading } = useValuesList({ indexPattern, query, sourceField, filters, time }); const [options, setOptions] = useState([]); const [isPopoverOpen, setIsPopoverOpen] = useState(false); @@ -68,7 +77,7 @@ export const FieldValueSelection = ({ setQuery((evt.target as HTMLInputElement).value); }; - const button = ( + const anchorButton = ( opt?.checked === 'on')} onClick={() => { - const selected = options.find((opt) => opt?.checked === 'on')!; - onSelectionChange(selected.label); + const selected = options.find((opt) => opt?.checked === 'on'); + onSelectionChange(selected?.label); setIsPopoverOpen(false); }} > diff --git a/x-pack/plugins/observability/public/hooks/use_values_list.ts b/x-pack/plugins/observability/public/hooks/use_values_list.ts index af3ff883aa3246..281dd6eac96345 100644 --- a/x-pack/plugins/observability/public/hooks/use_values_list.ts +++ b/x-pack/plugins/observability/public/hooks/use_values_list.ts @@ -16,22 +16,44 @@ interface Props { query?: string; indexPattern: IIndexPattern; filters: ESFilter[]; + time?: { from: string; to: string }; } -export const useValuesList = ({ sourceField, indexPattern, query, filters }: Props) => { +export const useValuesList = ({ + sourceField, + indexPattern, + query, + filters = [], + time, +}: Props): { values: string[]; loading: boolean } => { const { services: { data }, } = useKibana(); const { data: values, status } = useFetcher(() => { + if (!sourceField || !indexPattern) { + return []; + } return data.autocomplete.getValueSuggestions({ indexPattern, query: query || '', - // useTimeRange: false, + useTimeRange: !time, field: indexPattern.fields.find(({ name }) => name === sourceField)!, - boolFilter: filters, + boolFilter: time + ? [ + ...filters, + { + range: { + '@timestamp': { + gte: time.from, + lte: time.to, + }, + }, + }, + ] + : filters, }); }, [sourceField, query]); - return { values, loading: status === 'loading' || status === 'pending' }; + return { values: values as string[], loading: status === 'loading' || status === 'pending' }; }; diff --git a/x-pack/plugins/observability/public/utils/observability_Index_patterns.ts b/x-pack/plugins/observability/public/utils/observability_Index_patterns.ts index be4c36b26c15b8..715105d0419b4f 100644 --- a/x-pack/plugins/observability/public/utils/observability_Index_patterns.ts +++ b/x-pack/plugins/observability/public/utils/observability_Index_patterns.ts @@ -34,7 +34,7 @@ export class ObservabilityIndexPatterns { async createIndexPattern(app: DataType) { if (!this.data) { - throw 'data is not defined'; + throw new Error('data is not defined'); } const pattern = appToPatternMap[app]; diff --git a/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx b/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx index effdd9ce8e5c53..578870e7fdaa9d 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx +++ b/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx @@ -16,6 +16,7 @@ import { XYChartElementEvent, ElementClickListener, } from '@elastic/charts'; +import rison from 'rison-node'; import { EuiTitle, EuiSpacer, EuiFlexItem, EuiFlexGroup, EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useContext } from 'react'; @@ -30,7 +31,6 @@ import { useGetUrlParams, useUrlParams } from '../../../hooks'; import { ChartEmptyState } from './chart_empty_state'; import { getDateRangeFromChartElement } from './utils'; import { STATUS_DOWN_LABEL, STATUS_UP_LABEL } from '../translations'; -import rison from 'rison-node'; export interface PingHistogramComponentProps { /** From 2ec3924bb42ee40fd54c8cfc33f26c694a0bfff7 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 22 Mar 2021 14:44:00 +0100 Subject: [PATCH 12/53] update filters --- .../app/RumDashboard/PageViewsTrend/index.tsx | 4 +- .../configurations/constants.ts | 12 ++++ .../configurations/default_configs.ts | 4 +- .../configurations/kpi_trends_config.ts | 16 ++++- .../configurations/lens_attributes.ts | 64 +++++++++++++++---- ...t_config.ts => performance_dist_config.ts} | 30 ++++++++- .../exploratory_view/exploratory_view.tsx | 4 +- .../hooks/use_lens_attributes.ts | 44 +++++++++---- .../columns/report_definition_col.tsx | 61 ++++++++++-------- .../columns/report_types_col.tsx | 9 +-- .../series_builder/custom_report_field.tsx | 45 +++++++++++++ .../series_editor/columns/filter_expanded.tsx | 8 ++- .../columns/filter_value_btn.tsx | 43 ++++++++----- .../series_editor/columns/series_filter.tsx | 26 ++++++-- .../series_editor/series_editor.tsx | 8 +-- .../shared/exploratory_view/types.ts | 14 ++-- .../public/hooks/use_values_list.ts | 2 +- 17 files changed, 295 insertions(+), 99 deletions(-) rename x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/{page_load_dist_config.ts => performance_dist_config.ts} (51%) create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/custom_report_field.tsx diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx index 74e5974fa34604..be0284f8295417 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx @@ -56,8 +56,8 @@ export function PageViewsTrend() { ); const analyzeHref = { - [serviceName + '-pgv']: { - rt: 'pgv', + [serviceName + '-kpi']: { + rt: 'kpi', serviceName, time: { from: start, to: end }, }, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts index b36bd94d1af18d..c7864c8df52432 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants.ts @@ -6,6 +6,7 @@ */ import { AppDataType, ReportViewTypeId } from '../types'; +import { CLS_FIELD, FCP_FIELD, FID_FIELD, LCP_FIELD, TBT_FIELD } from './performance_dist_config'; export const FieldLabels: Record = { 'user_agent.name': 'Browser family', @@ -17,6 +18,12 @@ export const FieldLabels: Record = { 'service.name': 'Service Name', 'service.environment': 'Environment', + [LCP_FIELD]: 'Largest contentful paint', + [FCP_FIELD]: 'First contentful paint', + [TBT_FIELD]: 'Total blocking time', + [FID_FIELD]: 'First input delay', + [CLS_FIELD]: 'Cumulative layout shift', + 'monitor.id': 'Monitor Id', 'monitor.status': 'Monitor Status', @@ -26,6 +33,11 @@ export const FieldLabels: Record = { 'monitor.type': 'Monitor Type', 'url.port': 'Port', tags: 'Tags', + + // custom + + 'performance.metric': 'Metric', + 'Business.KPI': 'KPI', }; export const DataViewLabels: Record = { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts index 2306425c905e13..71564df9297835 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts @@ -6,7 +6,7 @@ */ import { ReportViewTypes } from '../types'; -import { getPageLoadDistLensConfig } from './page_load_dist_config'; +import { getPerformanceDistLensConfig } from './performance_dist_config'; import { getMonitorDurationConfig } from './monitor_duration_config'; import { getServiceLatencyLensConfig } from './service_latency_config'; import { getMonitorPingsConfig } from './monitor_pings_config'; @@ -25,7 +25,7 @@ interface Props { export const getDefaultConfigs = ({ reportType, seriesId }: Props) => { switch (ReportViewTypes[reportType]) { case 'page-load-dist': - return getPageLoadDistLensConfig({ seriesId }); + return getPerformanceDistLensConfig({ seriesId }); case 'kpi-trends': return getKPITrendsLensConfig({ seriesId }); case 'uptime-duration': diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts index 00a46da508e8c1..a6bda3e4f139e1 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/kpi_trends_config.ts @@ -27,10 +27,13 @@ export function getKPITrendsLensConfig({ seriesId }: Props): DataSeries { }, metricType: false, defaultFilters: [ - 'user_agent.name', 'user_agent.os.name', 'client.geo.country_name', 'user_agent.device.name', + { + field: 'user_agent.name', + nested: 'user_agent.version', + }, ], breakdowns: [ 'user_agent.name', @@ -52,6 +55,17 @@ export function getKPITrendsLensConfig({ seriesId }: Props): DataSeries { { field: 'service.environment', }, + { + field: 'Business.KPI', + custom: true, + defaultValue: 'Records', + options: [ + { + field: 'Records', + label: 'Page views', + }, + ], + }, ], }; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts index 24843c5f859f29..66159ca5fa21c1 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts @@ -10,9 +10,14 @@ import { TypedLensByValueInput, XYState, } from '../../../../../../lens/public'; -import { IIndexPattern } from '../../../../../../../../src/plugins/data/common'; +import { + buildPhraseFilter, + buildPhrasesFilter, + IIndexPattern, +} from '../../../../../../../../src/plugins/data/common'; import { FieldLabels } from './constants'; import { DataSeries, UrlFilter } from '../types'; +import { ESFilter } from '../../../../../../../../typings/elasticsearch'; export class LensAttributes { indexPattern: IIndexPattern; @@ -21,17 +26,20 @@ export class LensAttributes { filters: UrlFilter[]; seriesType: string; reportViewConfig: DataSeries; + reportDefinitions: Record; constructor( indexPattern: IIndexPattern, reportViewConfig: DataSeries, seriesType: string, filters: UrlFilter[], - metricType?: string + metricType?: string, + reportDefinitions?: Record ) { this.indexPattern = indexPattern; this.layers = {}; this.filters = filters ?? []; + this.reportDefinitions = reportDefinitions ?? {}; if (typeof reportViewConfig.yAxisColumn.operationType !== undefined && metricType) { reportViewConfig.yAxisColumn.operationType = metricType; @@ -75,7 +83,7 @@ export class LensAttributes { getNumberColumn(sourceField: string) { return { sourceField, - label: FieldLabels[sourceField], + label: this.reportViewConfig.labels[sourceField], dataType: 'number', operationType: 'range', isBucketed: true, @@ -103,13 +111,24 @@ export class LensAttributes { getXAxis() { const { xAxisColumn } = this.reportViewConfig; - if (xAxisColumn.sourceField) { - const fieldMeta = this.indexPattern.fields.find( - (field) => field.name === xAxisColumn.sourceField - ); + let xAxisField = xAxisColumn.sourceField; + + if (xAxisField) { + const rdf = this.reportViewConfig.reportDefinitions ?? []; + + const customField = rdf.find(({ field }) => field === xAxisField); + + if (customField && this.reportDefinitions[xAxisField]) { + xAxisField = this.reportDefinitions[xAxisField]; + } + + const fieldMeta = this.indexPattern.fields.find((field) => field.name === xAxisField); if (fieldMeta?.type === 'date') { - return this.getDateHistogramColumn(xAxisColumn.sourceField); + return this.getDateHistogramColumn(xAxisField); + } + if (fieldMeta?.type === 'number') { + return this.getNumberColumn(xAxisField); } } @@ -181,12 +200,29 @@ export class LensAttributes { const parsedFilters = this.reportViewConfig.filters ? [...defaultFilters] : []; this.filters.forEach(({ field, values = [], notValues = [] }) => { - values?.forEach((value) => { - parsedFilters.push({ query: { match_phrase: { [field]: value } } }); - }); - notValues?.forEach((value) => { - parsedFilters.push({ query: { match_phrase: { [field]: value } }, meta: { negate: true } }); - }); + const fieldMeta = this.indexPattern.fields.find((fieldT) => fieldT.name === field)!; + + if (values?.length > 0) { + if (values?.length > 1) { + const multiFilter = buildPhrasesFilter(fieldMeta, values, this.indexPattern); + parsedFilters.push(multiFilter as ESFilter); + } else { + const filter = buildPhraseFilter(fieldMeta, values[0], this.indexPattern); + parsedFilters.push(filter as ESFilter); + } + } + + if (notValues?.length > 0) { + if (notValues?.length > 1) { + const multiFilter = buildPhrasesFilter(fieldMeta, notValues, this.indexPattern); + multiFilter.meta.negate = true; + parsedFilters.push(multiFilter as ESFilter); + } else { + const filter = buildPhraseFilter(fieldMeta, notValues[0], this.indexPattern); + filter.meta.negate = true; + parsedFilters.push(filter as ESFilter); + } + } }); return parsedFilters; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/performance_dist_config.ts similarity index 51% rename from x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts rename to x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/performance_dist_config.ts index 1f139c3b3b3cef..e349cf819b067e 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/page_load_dist_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/performance_dist_config.ts @@ -8,18 +8,25 @@ import { DataSeries } from '../types'; import { FieldLabels } from './constants'; +export const TRANSACTION_DURATION = 'transaction.duration.us'; +export const FCP_FIELD = 'transaction.marks.agent.firstContentfulPaint'; +export const LCP_FIELD = 'transaction.marks.agent.largestContentfulPaint'; +export const TBT_FIELD = 'transaction.experience.tbt'; +export const FID_FIELD = 'transaction.experience.fid'; +export const CLS_FIELD = 'transaction.experience.cls'; + interface Props { seriesId: string; } -export function getPageLoadDistLensConfig({ seriesId }: Props): DataSeries { +export function getPerformanceDistLensConfig({ seriesId }: Props): DataSeries { return { id: seriesId ?? 'unique-key', reportType: 'page-load-dist', defaultSeriesType: 'line', seriesTypes: ['line', 'bar'], xAxisColumn: { - sourceField: 'transaction.duration.us', + sourceField: 'performance.metric', }, yAxisColumn: { operationType: 'count', @@ -49,11 +56,28 @@ export function getPageLoadDistLensConfig({ seriesId }: Props): DataSeries { { field: 'service.environment', }, + { + field: 'performance.metric', + custom: true, + defaultValue: TRANSACTION_DURATION, + options: [ + { label: 'Page load time', field: TRANSACTION_DURATION }, + { label: 'First contentful paint', field: FCP_FIELD }, + { label: 'Total blocking time', field: TBT_FIELD }, + { label: 'Largest contentful paint', field: LCP_FIELD, description: 'Core web vital' }, + { label: 'First input delay', field: FID_FIELD, description: 'Core web vital' }, + { label: 'Cumulative layout shift', field: CLS_FIELD, description: 'Core web vital' }, + ], + }, ], filters: [ { query: { match_phrase: { 'transaction.type': 'page-load' } } }, { query: { match_phrase: { 'processor.event': 'transaction' } } }, ], - labels: { ...FieldLabels, 'service.name': 'Web Application' }, + labels: { + ...FieldLabels, + 'service.name': 'Web Application', + [TRANSACTION_DURATION]: 'Page load time', + }, }; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx index 6d78c9e0267447..f90651f1d943fd 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx @@ -43,7 +43,7 @@ export function ExploratoryView() { }, [JSON.stringify(lensAttributesT ?? {}), series?.reportType, series?.time?.from]); return ( - + {!indexPattern && ( @@ -57,8 +57,6 @@ export function ExploratoryView() { style={{ height: 550 }} timeRange={series?.time} attributes={lensAttributes} - onBrushEnd={(data) => {}} - onLoad={(val) => {}} /> ) : ( diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts index bf23d7ee0d7946..28030cd5b3e356 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts @@ -28,22 +28,30 @@ export const useLensAttributes = ({ const { breakdown, seriesType, metric: metricType, reportType, reportDefinitions = {} } = series ?? {}; - const getFiltersFromDefs = () => { - return Object.entries(reportDefinitions).map(([field, value]) => ({ - field, - values: [value], - })) as UrlFilter[]; - }; - - const filters: UrlFilter[] = useMemo(() => { - return (series.filters ?? []).concat(getFiltersFromDefs()); - }, [series.filters, reportDefinitions]); - const dataViewConfig = getDefaultConfigs({ seriesId, reportType, }); + const filters: UrlFilter[] = useMemo(() => { + const getFiltersFromDefs = () => { + const rdfFilters = Object.entries(reportDefinitions).map(([field, value]) => { + return { + field, + values: [value], + }; + }) as UrlFilter[]; + + // let's filter out custom fields + return rdfFilters.filter(({ field }) => { + const rdf = dataViewConfig.reportDefinitions.find(({ field: fd }) => field === fd); + return !rdf?.custom; + }); + }; + + return (series.filters ?? []).concat(getFiltersFromDefs()); + }, [series.filters, reportDefinitions]); + return useMemo(() => { if (!indexPattern || !reportType) { return null; @@ -54,7 +62,8 @@ export const useLensAttributes = ({ dataViewConfig, seriesType!, filters, - metricType + metricType, + reportDefinitions ); if (breakdown) { @@ -62,5 +71,14 @@ export const useLensAttributes = ({ } return lensAttributes.getJSON(); - }, [indexPattern, breakdown, seriesType, filters, metricType, reportType]); + }, [ + indexPattern, + breakdown, + seriesType, + filters, + metricType, + reportType, + reportDefinitions, + dataViewConfig, + ]); }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx index 74bb27e81e4bf8..da94ea5b786ac7 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx @@ -11,8 +11,9 @@ import { FieldValueSelection } from '../../../field_value_selection'; import { useIndexPatternContext } from '../../../../../hooks/use_default_index_pattern'; import { getDefaultConfigs } from '../../configurations/default_configs'; import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_strorage'; +import { CustomReportField } from '../custom_report_field'; -export const ReportDefinitionCol = () => { +export function ReportDefinitionCol() { const { indexPattern } = useIndexPatternContext(); const { series, setSeries } = useUrlStorage(NEW_SERIES_KEY); @@ -42,34 +43,44 @@ export const ReportDefinitionCol = () => { return ( {indexPattern && - reportDefinitions.map(({ field }) => ( + reportDefinitions.map(({ field, custom, options, defaultValue }) => ( - - - onChange(field, val)} - filters={(filters ?? []).map(({ query }) => query)} - /> - - {rtd?.[field] && ( + {!custom ? ( + - onRemove(field)} - > - {rtd?.[field]} - + onChange(field, val)} + filters={(filters ?? []).map(({ query }) => query)} + time={series.time} + /> - )} - + {rtd?.[field] && ( + + onRemove(field)} + > + {rtd?.[field]} + + + )} + + ) : ( + + )} ))} ); -}; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx index 82ab786b164184..e125c0e1de0920 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx @@ -11,10 +11,10 @@ import { ReportViewTypeId } from '../../types'; import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_strorage'; interface Props { - reportTypes: { id: ReportViewTypeId; label: string }[]; + reportTypes: Array<{ id: ReportViewTypeId; label: string }>; } -export const ReportTypesCol = ({ reportTypes }: Props) => { +export function ReportTypesCol({ reportTypes }: Props) { const { series: { reportType: selectedReportType, ...restSeries }, setSeries, @@ -39,7 +39,8 @@ export const ReportTypesCol = ({ reportTypes }: Props) => { } else { setSeries(NEW_SERIES_KEY, { ...restSeries, - reportType: reportType, + reportType, + reportDefinitions: {}, }); } }} @@ -52,4 +53,4 @@ export const ReportTypesCol = ({ reportTypes }: Props) => { ) : ( Select a data type to start building a series. ); -}; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/custom_report_field.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/custom_report_field.tsx new file mode 100644 index 00000000000000..8c2bf413bb8908 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/custom_report_field.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiSuperSelect } from '@elastic/eui'; +import { useUrlStorage } from '../hooks/use_url_strorage'; +import { ReportDefinition } from '../types'; + +interface Props { + field: string; + seriesId: string; + defaultValue?: string; + options: ReportDefinition['options']; +} + +export function CustomReportField({ field, seriesId, options: opts, defaultValue }: Props) { + const { series, setSeries } = useUrlStorage(seriesId); + + const onChange = (value: string) => { + setSeries(seriesId, { ...series, reportDefinitions: { [field]: value } }); + }; + + const { reportDefinitions } = series; + + const NO_SELECT = 'no_select'; + + const options = [{ label: 'Select metric', field: NO_SELECT }, ...opts]; + + return ( +
+ ({ + value: fd, + inputDisplay: label, + }))} + valueOfSelected={reportDefinitions?.[field] || defaultValue || NO_SELECT} + onChange={(value) => onChange(value)} + /> +
+ ); +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx index d3cb899c54e8a6..d06c24b57f7756 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx @@ -34,6 +34,8 @@ export function FilterExpanded({ seriesId, field, label, goBack, nestedField }: const [value, setValue] = useState(''); + const [isOpen, setIsOpen] = useState({ value: '', negate: false }); + const { services: { data }, } = useKibana(); @@ -51,7 +53,7 @@ export function FilterExpanded({ seriesId, field, label, goBack, nestedField }: const filters = series?.filters ?? []; - let currFilter: UrlFilter | undefined = filters.find(({ field: fd }) => field === fd); + const currFilter: UrlFilter | undefined = filters.find(({ field: fd }) => field === fd); return ( <> @@ -83,6 +85,8 @@ export function FilterExpanded({ seriesId, field, label, goBack, nestedField }: negate={true} nestedField={nestedField} seriesId={seriesId} + isOpen={isOpen} + setIsOpen={setIsOpen} /> diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx index 7c8d21ec6380c8..7ae79f7d010e0b 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx @@ -5,38 +5,40 @@ * 2.0. */ -import React, { useState } from 'react'; +import React from 'react'; import { EuiFilterButton, hexToRgb } from '@elastic/eui'; import { useIndexPatternContext } from '../../../../../hooks/use_default_index_pattern'; import { useUrlStorage } from '../../hooks/use_url_strorage'; import { FieldValueSelection } from '../../../field_value_selection'; import { useSeriesFilters } from '../../hooks/use_series_filters'; -import styled from 'styled-components'; import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common'; interface Props { value: string; field: string; allValues?: string[]; - negate?: boolean; + negate: boolean; nestedField?: string; seriesId: string; + isOpen: { + value: string; + negate: boolean; + }; + setIsOpen: (val: { value: string; negate: boolean }) => void; } -export const FilterValueButton = ({ +export function FilterValueButton({ + isOpen, + setIsOpen, value, field, allValues, negate, seriesId, nestedField, -}: Props) => { +}: Props) { const { series } = useUrlStorage(seriesId); - const [isOpen, setIsOpen] = useState(false); - - const [selected, setSelected] = useState('any'); - const { indexPattern } = useIndexPatternContext(); const { setFilter, removeFilter } = useSeriesFilters({ seriesId }); @@ -54,9 +56,9 @@ export const FilterValueButton = ({ setFilter({ field, value, negate }); } if (!hasActiveFilters) { - setIsOpen((prevState) => !prevState); + setIsOpen({ value, negate }); } else { - setIsOpen(false); + setIsOpen({ value: '', negate }); } }} className="" @@ -65,26 +67,33 @@ export const FilterValueButton = ({ ); - const onNestedChange = (val: string) => { - console.log(val); + const onNestedChange = (val?: string) => { + setFilter({ field: nestedField!, value: val }); + setIsOpen({ value: '', negate }); }; return nestedField ? ( ) : ( button ); -}; +} const FilterButton = euiStyled(EuiFilterButton)` background-color: rgba(${(props) => { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx index 9616d790a1eb9f..3c59e1fc29e021 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx @@ -18,7 +18,7 @@ import { FilterExpanded } from './filter_expanded'; import { DataSeries } from '../../types'; import { FieldLabels } from '../../configurations/constants'; import { SelectedFilters } from '../selected_filters'; -import { NEW_SERIES_KEY } from '../../hooks/use_url_strorage'; +import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_strorage'; interface Props { seriesId: string; @@ -39,13 +39,15 @@ export function SeriesFilter({ series, isNew, seriesId, defaultFilters = [] }: P const [selectedField, setSelectedField] = useState(null); const options = defaultFilters.map((field) => { - if (typeof field == 'string') { + if (typeof field === 'string') { return { label: FieldLabels[field], field }; } return { label: FieldLabels[field.field], field: field.field, nested: field.nested }; }); const disabled = seriesId === NEW_SERIES_KEY && !isNew; + const { setSeries, series: urlSeries } = useUrlStorage(seriesId); + const button = ( + {!disabled && } {!selectedField ? mainPanel : childPanel} + {(urlSeries.filters ?? []).length > 0 && ( + + { + setSeries(seriesId, { ...urlSeries, filters: undefined }); + }} + isDisabled={disabled} + size="s" + > + Clear filters + + + )} ); } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx index a4b2d149578967..59f318a76e436c 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx @@ -17,14 +17,14 @@ import { getDefaultConfigs } from '../configurations/default_configs'; import { DatePickerCol } from './columns/date_picker_col'; import { RemoveSeries } from './columns/remove_series'; -export const SeriesEditor = () => { +export function SeriesEditor() { const { allSeries, firstSeriesId } = useUrlStorage(); const columns = [ { name: 'Name', field: 'id', - width: '20%', + width: '15%', render: (val: string) => ( {' '} @@ -37,7 +37,7 @@ export const SeriesEditor = () => { { name: 'Filter', field: 'defaultFilters', - width: '20%', + width: '25%', render: (defaultFilters: string[], series: DataSeries) => ( ), @@ -114,4 +114,4 @@ export const SeriesEditor = () => { ); -}; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts index 005a7e6a7b0d3e..41d885de5edb41 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts @@ -33,6 +33,14 @@ export type ReportViewTypeId = keyof typeof ReportViewTypes; export type ReportViewType = ValueOf; +export interface ReportDefinition { + field: string; + required?: boolean; + custom?: boolean; + defaultValue?: string; + options?: Array<{ field: string; label: string; description?: string }>; +} + export interface DataSeries { reportType: ReportViewType; id: string; @@ -46,11 +54,7 @@ export interface DataSeries { defaultFilters: Array; seriesTypes: string[]; filters?: ESFilter[]; - reportDefinitions: { - field: string; - required?: boolean; - custom?: boolean; - }[]; + reportDefinitions: ReportDefinition[]; labels: Record; metricType: boolean; palette?: Record; diff --git a/x-pack/plugins/observability/public/hooks/use_values_list.ts b/x-pack/plugins/observability/public/hooks/use_values_list.ts index 281dd6eac96345..846a438a42cc56 100644 --- a/x-pack/plugins/observability/public/hooks/use_values_list.ts +++ b/x-pack/plugins/observability/public/hooks/use_values_list.ts @@ -53,7 +53,7 @@ export const useValuesList = ({ ] : filters, }); - }, [sourceField, query]); + }, [sourceField, query, time]); return { values: values as string[], loading: status === 'loading' || status === 'pending' }; }; From 4f8f118296dbd5db1e3838381b718f2eadfa5f3b Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 22 Mar 2021 15:42:30 +0100 Subject: [PATCH 13/53] update --- .../hooks/use_init_exploratory_view.ts | 17 +++++++++++++---- .../exploratory_view/hooks/use_url_strorage.tsx | 6 +++--- .../series_builder/custom_report_field.tsx | 2 +- .../series_builder/series_builder.tsx | 7 ++++--- .../utils/observability_Index_patterns.ts | 8 ++++---- 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts index 7c2a5da895630c..6efa420e8db6f0 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts @@ -12,7 +12,10 @@ import { useKibana } from '../../../../../../../../src/plugins/kibana_react/publ import { ObservabilityClientPluginsStart } from '../../../../plugin'; import { AllShortSeries } from './use_url_strorage'; import { ReportToDataTypeMap } from '../configurations/constants'; -import { ObservabilityIndexPatterns } from '../../../../utils/observability_Index_patterns'; +import { + DataType, + ObservabilityIndexPatterns, +} from '../../../../utils/observability_Index_patterns'; export const useInitExploratoryView = (storage: IKbnUrlStateStorage) => { const { @@ -31,9 +34,15 @@ export const useInitExploratoryView = (storage: IKbnUrlStateStorage) => { const { data: indexPattern, status } = useFetcher(() => { const obsvIndexP = new ObservabilityIndexPatterns(data); - return obsvIndexP.getIndexPattern( - firstSeries?.rt ? ReportToDataTypeMap[firstSeries?.rt] : 'apm' - ); + let reportType: DataType = 'apm'; + if (firstSeries?.rt) { + reportType = ReportToDataTypeMap[firstSeries?.rt]; + } + if (firstSeries?.reportType) { + reportType = ReportToDataTypeMap[firstSeries?.reportType]; + } + + return obsvIndexP.getIndexPattern(reportType); }, [firstSeries?.rt]); return useMemo(() => { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx index c8625c1c20dcb9..d32d4d4bf3d0aa 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_strorage.tsx @@ -20,9 +20,9 @@ const SERIES_TYPE = 'st'; const BREAK_DOWN = 'bd'; const FILTERS = 'ft'; -export const UrlStorageContextProvider: React.FC = ({ children, storage }) => { +export function UrlStorageContextProvider({ children, storage }: React.FC) { return {children}; -}; +} function convertToShortUrl(newValue: SeriesUrl): ShortUrlSeries { const { metric, seriesType, reportType, breakdown, filters, ...restSeries } = newValue; @@ -95,7 +95,7 @@ export function useUrlStorage(seriesId?: string) { const removeSeries = (seriesIdN: string) => { delete allShortSeries[seriesIdN]; delete allSeries[seriesIdN]; - storage.set(allSeriesKey, allSeries); + storage.set(allSeriesKey, allShortSeries); }; const firstSeriesId = allSeriesIds?.[0]; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/custom_report_field.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/custom_report_field.tsx index 8c2bf413bb8908..2b9c21b7af7915 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/custom_report_field.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/custom_report_field.tsx @@ -28,7 +28,7 @@ export function CustomReportField({ field, seriesId, options: opts, defaultValue const NO_SELECT = 'no_select'; - const options = [{ label: 'Select metric', field: NO_SELECT }, ...opts]; + const options = [{ label: 'Select metric', field: NO_SELECT }, ...(opts ?? [])]; return (
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx index ccdbca0a7037e4..ebf952ff7dde90 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx @@ -43,7 +43,7 @@ export const ReportTypes: Record { +export function SeriesBuilder() { const { series, setSeries, allSeriesIds, removeSeries } = useUrlStorage(NEW_SERIES_KEY); const { dataType, reportType, reportDefinitions = {}, filters = [] } = series; @@ -97,10 +97,11 @@ export const SeriesBuilder = () => { }`; const newSeriesN = { - reportType: reportType, + reportType, time: { from: 'now-30m', to: 'now' }, filters: getFiltersFromDefs().concat(filters), } as SeriesUrl; + setSeries(newSeriesId, newSeriesN).then(() => { removeSeries(NEW_SERIES_KEY); setIsFlyoutVisible(false); @@ -163,7 +164,7 @@ export const SeriesBuilder = () => { {flyout}
); -}; +} const BottomFlyout = styled.div` height: 300px; diff --git a/x-pack/plugins/observability/public/utils/observability_Index_patterns.ts b/x-pack/plugins/observability/public/utils/observability_Index_patterns.ts index 715105d0419b4f..9bdb2c07807969 100644 --- a/x-pack/plugins/observability/public/utils/observability_Index_patterns.ts +++ b/x-pack/plugins/observability/public/utils/observability_Index_patterns.ts @@ -7,7 +7,7 @@ import { DataPublicPluginStart, IIndexPattern } from '../../../../../src/plugins/data/public'; -type DataType = 'synthetics' | 'apm' | 'logs' | 'metrics' | 'rum'; +export type DataType = 'synthetics' | 'apm' | 'logs' | 'metrics' | 'rum'; const indexPatternList: Record = { synthetics: 'synthetics_static_index_pattern_id', @@ -52,12 +52,12 @@ export class ObservabilityIndexPatterns { async getIndexPattern(app: DataType): Promise { if (!this.data) { - throw 'data is not defined'; + throw new Error('data is not defined'); } try { - return await this.data?.indexPatterns.get(indexPatternList[app]); + return await this.data?.indexPatterns.get(indexPatternList[app || 'apm']); } catch (e) { - return await this.createIndexPattern(app); + return await this.createIndexPattern(app || 'apm'); } } } From b2fbc2b7a94284963472c6a75c5a4d5227870825 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 23 Mar 2021 09:28:21 +0100 Subject: [PATCH 14/53] wip --- .../hooks/use_lens_attributes.ts | 37 ++++++++++--------- .../series_builder/custom_report_field.tsx | 4 +- .../series_builder/series_builder.tsx | 21 +++++++---- .../series_editor/selected_filters.tsx | 12 ++---- 4 files changed, 41 insertions(+), 33 deletions(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts index 28030cd5b3e356..7b79d96b269138 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts @@ -12,13 +12,31 @@ import { useUrlStorage } from './use_url_strorage'; import { getDefaultConfigs } from '../configurations/default_configs'; import { IIndexPattern } from '../../../../../../../../src/plugins/data/common'; -import { UrlFilter } from '../types'; +import { DataSeries, SeriesUrl, UrlFilter } from '../types'; interface Props { seriesId: string; indexPattern?: IIndexPattern | null; } +export const getFiltersFromDefs = ( + reportDefinitions: SeriesUrl['reportDefinitions'], + dataViewConfig: DataSeries +) => { + const rdfFilters = Object.entries(reportDefinitions ?? {}).map(([field, value]) => { + return { + field, + values: [value], + }; + }) as UrlFilter[]; + + // let's filter out custom fields + return rdfFilters.filter(({ field }) => { + const rdf = dataViewConfig.reportDefinitions.find(({ field: fd }) => field === fd); + return !rdf?.custom; + }); +}; + export const useLensAttributes = ({ seriesId, indexPattern, @@ -34,22 +52,7 @@ export const useLensAttributes = ({ }); const filters: UrlFilter[] = useMemo(() => { - const getFiltersFromDefs = () => { - const rdfFilters = Object.entries(reportDefinitions).map(([field, value]) => { - return { - field, - values: [value], - }; - }) as UrlFilter[]; - - // let's filter out custom fields - return rdfFilters.filter(({ field }) => { - const rdf = dataViewConfig.reportDefinitions.find(({ field: fd }) => field === fd); - return !rdf?.custom; - }); - }; - - return (series.filters ?? []).concat(getFiltersFromDefs()); + return (series.filters ?? []).concat(getFiltersFromDefs(reportDefinitions, dataViewConfig)); }, [series.filters, reportDefinitions]); return useMemo(() => { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/custom_report_field.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/custom_report_field.tsx index 2b9c21b7af7915..6039fd4cba2804 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/custom_report_field.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/custom_report_field.tsx @@ -20,8 +20,10 @@ interface Props { export function CustomReportField({ field, seriesId, options: opts, defaultValue }: Props) { const { series, setSeries } = useUrlStorage(seriesId); + const { reportDefinitions: rtd = {} } = series; + const onChange = (value: string) => { - setSeries(seriesId, { ...series, reportDefinitions: { [field]: value } }); + setSeries(seriesId, { ...series, reportDefinitions: { ...rtd, [field]: value } }); }; const { reportDefinitions } = series; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx index ebf952ff7dde90..641d8f11d19770 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx @@ -82,12 +82,18 @@ export function SeriesBuilder() { ]; const addSeries = () => { - const getFiltersFromDefs = (): UrlFilter[] => { - return Object.entries(reportDefinitions).map(([field, value]) => ({ - field, - values: [value], - })); - }; + // const getFiltersFromDefs = (): UrlFilter[] => { + // return Object.entries(reportDefinitions).map(([field, value]) => ({ + // field, + // values: [value], + // })); + // + // // let's filter out custom fields + // return rdfFilters.filter(({ field }) => { + // const rdf = dataViewConfig.reportDefinitions.find(({ field: fd }) => field === fd); + // return !rdf?.custom; + // }); + // }; if (reportType) { const newSeriesId = `${ @@ -99,7 +105,8 @@ export function SeriesBuilder() { const newSeriesN = { reportType, time: { from: 'now-30m', to: 'now' }, - filters: getFiltersFromDefs().concat(filters), + filters: filters, + reportDefinitions, } as SeriesUrl; setSeries(newSeriesId, newSeriesN).then(() => { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx index d0af9ff96a0e78..ba85a796d67411 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx @@ -12,29 +12,25 @@ import { FilterLabel } from '../components/filter_label'; import { DataSeries, UrlFilter } from '../types'; import { useIndexPatternContext } from '../../../../hooks/use_default_index_pattern'; import { useSeriesFilters } from '../hooks/use_series_filters'; +import { getFiltersFromDefs } from '../hooks/use_lens_attributes'; interface Props { seriesId: string; series: DataSeries; isNew?: boolean; } -export const SelectedFilters = ({ seriesId, isNew, series: { labels } }: Props) => { +export const SelectedFilters = ({ seriesId, isNew, series: dataSeries }: Props) => { const { series } = useUrlStorage(seriesId); const { reportDefinitions = {} } = series; - const getFiltersFromDefs = () => { - return Object.entries(reportDefinitions).map(([field, value]) => ({ - field, - values: [value], - })) as UrlFilter[]; - }; + const { labels } = dataSeries; const filters: UrlFilter[] = useMemo(() => { if (seriesId === NEW_SERIES_KEY && isNew) { return series.filters ?? []; } - return (series.filters ?? []).concat(getFiltersFromDefs()); + return (series.filters ?? []).concat(getFiltersFromDefs(reportDefinitions, dataSeries)); }, [series.filters, reportDefinitions]); const { removeFilter } = useSeriesFilters({ seriesId }); From e5c1e594e0cb98a2f6eb3bfe48948e21c8b3563f Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 23 Mar 2021 13:23:39 +0100 Subject: [PATCH 15/53] update --- x-pack/plugins/translations/translations/ja-JP.json | 1 - x-pack/plugins/translations/translations/zh-CN.json | 1 - 2 files changed, 2 deletions(-) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 53245fde2796fd..11f67ba13cdcac 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -12237,7 +12237,6 @@ "xpack.lens.xyChart.valueLabelsVisibility.auto": "非表示", "xpack.lens.xyChart.valueLabelsVisibility.inside": "表示", "xpack.lens.xyChart.valuesHistogramDisabledHelpText": "この設定はヒストグラムで変更できません。", - "xpack.lens.xyChart.valuesLabel": "値", "xpack.lens.xyChart.valuesPercentageDisabledHelpText": "この設定は割合エリアグラフで変更できません。", "xpack.lens.xyChart.valuesStackedDisabledHelpText": "この設定は積み上げ棒グラフまたは割合棒グラフで変更できません", "xpack.lens.xyChart.verticalAxisLabel": "縦軸", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index d26a0111fe3822..5b516be39cd3c1 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -12396,7 +12396,6 @@ "xpack.lens.xyChart.valueLabelsVisibility.auto": "隐藏", "xpack.lens.xyChart.valueLabelsVisibility.inside": "显示", "xpack.lens.xyChart.valuesHistogramDisabledHelpText": "不能在直方图上更改此设置。", - "xpack.lens.xyChart.valuesLabel": "值", "xpack.lens.xyChart.valuesPercentageDisabledHelpText": "不能在百分比面积图上更改此设置。", "xpack.lens.xyChart.valuesStackedDisabledHelpText": "不能在堆积图或百分比条形图上更改此设置", "xpack.lens.xyChart.verticalAxisLabel": "垂直轴", From c14a2e5bf0b98e3477b400a0c8f9076789764ff3 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 24 Mar 2021 09:38:45 +0100 Subject: [PATCH 16/53] update asset --- x-pack/plugins/lens/public/index.ts | 1 + .../shared_components/chart_types/index.tsx | 19 ++ .../chart_types/xy_chart_types.tsx | 101 ++++++++++ .../exploratory_view/assets/axis_bottom.tsx | 31 --- .../exploratory_view/assets/axis_left.tsx | 32 --- .../exploratory_view/assets/axis_right.tsx | 32 --- .../exploratory_view/assets/axis_top.tsx | 35 ---- .../exploratory_view/assets/chart_area.tsx | 31 --- .../assets/chart_area_percentage.tsx | 35 ---- .../assets/chart_area_stacked.tsx | 35 ---- .../exploratory_view/assets/chart_bar.tsx | 31 --- .../assets/chart_bar_horizontal.tsx | 35 ---- .../chart_bar_horizontal_percentage.tsx | 35 ---- .../assets/chart_bar_horizontal_stacked.tsx | 35 ---- .../assets/chart_bar_percentage.tsx | 35 ---- .../assets/chart_bar_stacked.tsx | 35 ---- .../assets/chart_datatable.tsx | 35 ---- .../exploratory_view/assets/chart_donut.tsx | 31 --- .../exploratory_view/assets/chart_line.tsx | 31 --- .../exploratory_view/assets/chart_metric.tsx | 31 --- .../assets/chart_mixed_xy.tsx | 35 ---- .../exploratory_view/assets/chart_pie.tsx | 31 --- .../exploratory_view/assets/chart_treemap.tsx | 35 ---- .../assets/drop_illustration.tsx | 49 ----- .../shared/exploratory_view/assets/legend.tsx | 40 ---- .../assets/lens_app_graphic_dark_2x.png | Bin 82733 -> 0 bytes .../assets/lens_app_graphic_light_2x.png | Bin 94444 -> 0 bytes .../series_editor/columns/actions_col.tsx | 4 +- .../series_editor/columns/chart_types.tsx | 186 ++---------------- 29 files changed, 139 insertions(+), 927 deletions(-) create mode 100644 x-pack/plugins/lens/public/shared_components/chart_types/index.tsx create mode 100644 x-pack/plugins/lens/public/shared_components/chart_types/xy_chart_types.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_bottom.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_left.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_right.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_top.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_area.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_area_percentage.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_area_stacked.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_horizontal.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_horizontal_percentage.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_horizontal_stacked.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_percentage.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_stacked.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_datatable.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_donut.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_line.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_metric.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_mixed_xy.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_pie.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_treemap.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/drop_illustration.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/legend.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/lens_app_graphic_dark_2x.png delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/assets/lens_app_graphic_light_2x.png diff --git a/x-pack/plugins/lens/public/index.ts b/x-pack/plugins/lens/public/index.ts index fa5a9f9289e92a..6ffcbe318ec0b6 100644 --- a/x-pack/plugins/lens/public/index.ts +++ b/x-pack/plugins/lens/public/index.ts @@ -11,6 +11,7 @@ export { EmbeddableComponentProps, TypedLensByValueInput, } from './editor_frame_service/embeddable/embeddable_component'; +export { XYChartTypes } from './shared_components/chart_types'; export type { XYState, AxesSettingsConfig, diff --git a/x-pack/plugins/lens/public/shared_components/chart_types/index.tsx b/x-pack/plugins/lens/public/shared_components/chart_types/index.tsx new file mode 100644 index 00000000000000..0bf69b5c7314bc --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/chart_types/index.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { lazy, Suspense } from 'react'; +import type { ChartTypesProps } from './xy_chart_types'; + +const ChartTypesComponent = lazy(() => import('./xy_chart_types')); + +export const XYChartTypes = (props: ChartTypesProps) => { + return ( + Loading...
}> + + + ); +}; diff --git a/x-pack/plugins/lens/public/shared_components/chart_types/xy_chart_types.tsx b/x-pack/plugins/lens/public/shared_components/chart_types/xy_chart_types.tsx new file mode 100644 index 00000000000000..67b6dca4cdca77 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/chart_types/xy_chart_types.tsx @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; +import { EuiButton, EuiButtonGroup, EuiButtonIcon, EuiPopover } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import styled from 'styled-components'; +import { visualizationTypes } from '../../xy_visualization/types'; + +const ButtonGroup = styled(EuiButtonGroup)` + &&& { + .euiButtonGroupButton-isSelected { + background-color: #a5a9b1 !important; + } + } +`; +export interface XYChartTypesProps { + onChange: (value: string) => void; + value: string; + label?: string; + includeChartTypes?: string[]; + excludeChartTypes?: string[]; +} + +function XYChartTypes({ + onChange, + value, + label, + includeChartTypes, + excludeChartTypes, +}: XYChartTypesProps) { + const [isOpen, setIsOpen] = useState(false); + + let vizTypes = visualizationTypes; + + if ((excludeChartTypes ?? []).length > 0) { + vizTypes = visualizationTypes.filter(({ id }) => !excludeChartTypes?.includes(id)); + } + + if ((includeChartTypes ?? []).length > 0) { + vizTypes = visualizationTypes.filter(({ id }) => includeChartTypes?.includes(id)); + } + + return ( + id === value)?.icon} + onClick={() => { + setIsOpen((prevState) => !prevState); + }} + > + {label} + + ) : ( + id === value)?.icon!} + onClick={() => { + setIsOpen((prevState) => !prevState); + }} + /> + ) + } + closePopover={() => setIsOpen(false)} + > + ({ + id: t.id, + label: t.label, + title: t.label, + iconType: t.icon || 'empty', + 'data-test-subj': `lnsXY_seriesType-${t.id}`, + }))} + idSelected={value} + onChange={(valueN: string) => { + onChange(valueN); + }} + /> + + ); +} + +// eslint-disable-next-line import/no-default-export +export default XYChartTypes; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_bottom.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_bottom.tsx deleted file mode 100644 index 309d41bf242214..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_bottom.tsx +++ /dev/null @@ -1,31 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as React from 'react'; - -export const EuiIconAxisBottom = ({ - title, - titleId, - ...props -}: { - title: string; - titleId: string; -}) => ( - - {title ? {title} : null} - - - -); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_left.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_left.tsx deleted file mode 100644 index 9a39a2f43a74df..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_left.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as React from 'react'; - -export const EuiIconAxisLeft = ({ - title, - titleId, - ...props -}: { - title: string; - titleId: string; -}) => ( - - {title ? {title} : null} - - - - -); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_right.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_right.tsx deleted file mode 100644 index 4db6fc06d82fa7..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_right.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as React from 'react'; - -export const EuiIconAxisRight = ({ - title, - titleId, - ...props -}: { - title: string; - titleId: string; -}) => ( - - {title ? {title} : null} - - - - -); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_top.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_top.tsx deleted file mode 100644 index 2c3dc0744c0684..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/axis_top.tsx +++ /dev/null @@ -1,35 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as React from 'react'; - -export const EuiIconAxisTop = ({ - title, - titleId, - ...props -}: { - title: string; - titleId: string; -}) => ( - - {title ? {title} : null} - - - - - - - -); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_area.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_area.tsx deleted file mode 100644 index 664735205d97e0..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_area.tsx +++ /dev/null @@ -1,31 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiIconProps } from '@elastic/eui'; - -export const LensIconChartArea = ({ title, titleId, ...props }: Omit) => ( - - {title ? {title} : null} - - - -); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_area_percentage.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_area_percentage.tsx deleted file mode 100644 index 910d5dc8172899..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_area_percentage.tsx +++ /dev/null @@ -1,35 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiIconProps } from '@elastic/eui'; - -export const LensIconChartAreaPercentage = ({ - title, - titleId, - ...props -}: Omit) => ( - - {title ? {title} : null} - - - -); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_area_stacked.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_area_stacked.tsx deleted file mode 100644 index 16e1ff849b6091..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_area_stacked.tsx +++ /dev/null @@ -1,35 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiIconProps } from '@elastic/eui'; - -export const LensIconChartAreaStacked = ({ - title, - titleId, - ...props -}: Omit) => ( - - {title ? {title} : null} - - - -); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar.tsx deleted file mode 100644 index 991c9a6c74ced4..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar.tsx +++ /dev/null @@ -1,31 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiIconProps } from '@elastic/eui'; - -export const LensIconChartBar = ({ title, titleId, ...props }: Omit) => ( - - {title ? {title} : null} - - - -); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_horizontal.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_horizontal.tsx deleted file mode 100644 index dfd25158cc2958..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_horizontal.tsx +++ /dev/null @@ -1,35 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiIconProps } from '@elastic/eui'; - -export const LensIconChartBarHorizontal = ({ - title, - titleId, - ...props -}: Omit) => ( - - {title ? {title} : null} - - - -); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_horizontal_percentage.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_horizontal_percentage.tsx deleted file mode 100644 index a3c79991f2affc..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_horizontal_percentage.tsx +++ /dev/null @@ -1,35 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiIconProps } from '@elastic/eui'; - -export const LensIconChartBarHorizontalPercentage = ({ - title, - titleId, - ...props -}: Omit) => ( - - {title ? {title} : null} - - - -); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_horizontal_stacked.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_horizontal_stacked.tsx deleted file mode 100644 index d6abbaa37aaa0f..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_horizontal_stacked.tsx +++ /dev/null @@ -1,35 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiIconProps } from '@elastic/eui'; - -export const LensIconChartBarHorizontalStacked = ({ - title, - titleId, - ...props -}: Omit) => ( - - {title ? {title} : null} - - - -); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_percentage.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_percentage.tsx deleted file mode 100644 index c297ee831f27cc..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_percentage.tsx +++ /dev/null @@ -1,35 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiIconProps } from '@elastic/eui'; - -export const LensIconChartBarPercentage = ({ - title, - titleId, - ...props -}: Omit) => ( - - {title ? {title} : null} - - - -); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_stacked.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_stacked.tsx deleted file mode 100644 index c5775ce4bf859d..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_bar_stacked.tsx +++ /dev/null @@ -1,35 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiIconProps } from '@elastic/eui'; - -export const LensIconChartBarStacked = ({ - title, - titleId, - ...props -}: Omit) => ( - - {title ? {title} : null} - - - -); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_datatable.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_datatable.tsx deleted file mode 100644 index d6a5cb733e1e14..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_datatable.tsx +++ /dev/null @@ -1,35 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiIconProps } from '@elastic/eui'; - -export const LensIconChartDatatable = ({ - title, - titleId, - ...props -}: Omit) => ( - - {title ? {title} : null} - - - -); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_donut.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_donut.tsx deleted file mode 100644 index f2ce65a1f2e196..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_donut.tsx +++ /dev/null @@ -1,31 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiIconProps } from '@elastic/eui'; - -export const LensIconChartDonut = ({ title, titleId, ...props }: Omit) => ( - - {title ? {title} : null} - - - -); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_line.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_line.tsx deleted file mode 100644 index 82df68d8fd43cb..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_line.tsx +++ /dev/null @@ -1,31 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiIconProps } from '@elastic/eui'; - -export const LensIconChartLine = ({ title, titleId, ...props }: Omit) => ( - - {title ? {title} : null} - - - -); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_metric.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_metric.tsx deleted file mode 100644 index d15be760eec605..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_metric.tsx +++ /dev/null @@ -1,31 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiIconProps } from '@elastic/eui'; - -export const LensIconChartMetric = ({ title, titleId, ...props }: Omit) => ( - - {title ? {title} : null} - - - -); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_mixed_xy.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_mixed_xy.tsx deleted file mode 100644 index bb213217a9fa9c..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_mixed_xy.tsx +++ /dev/null @@ -1,35 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiIconProps } from '@elastic/eui'; - -export const LensIconChartMixedXy = ({ title, titleId, ...props }: Omit) => ( - - {title ? {title} : null} - - - - -); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_pie.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_pie.tsx deleted file mode 100644 index 56a18b9d616240..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_pie.tsx +++ /dev/null @@ -1,31 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiIconProps } from '@elastic/eui'; - -export const LensIconChartPie = ({ title, titleId, ...props }: Omit) => ( - - {title ? {title} : null} - - - -); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_treemap.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_treemap.tsx deleted file mode 100644 index c56fdedfebdf7c..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/chart_treemap.tsx +++ /dev/null @@ -1,35 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiIconProps } from '@elastic/eui'; - -export const LensIconChartTreemap = ({ title, titleId, ...props }: Omit) => ( - - {title ? {title} : null} - - - - -); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/drop_illustration.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/drop_illustration.tsx deleted file mode 100644 index acd6d7bf313241..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/drop_illustration.tsx +++ /dev/null @@ -1,49 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as React from 'react'; -import { EuiIconProps } from '@elastic/eui'; - -export const DropIllustration = ({ title, titleId, ...props }: Omit) => ( - - {title ? {title} : null} - - - - - - - - -); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/legend.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/legend.tsx deleted file mode 100644 index cde39b222cdd40..00000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/legend.tsx +++ /dev/null @@ -1,40 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as React from 'react'; - -export const EuiIconLegend = ({ title, titleId, ...props }: { title: string; titleId: string }) => ( - - {title ? {title} : null} - - - - - - - -); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/lens_app_graphic_dark_2x.png b/x-pack/plugins/observability/public/components/shared/exploratory_view/assets/lens_app_graphic_dark_2x.png deleted file mode 100644 index 2c2c71b82180a7574427c284cfffa91771a2296a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 82733 zcma%DWmwbg_urTdfelo;5mZvTa|jA5DIg%tNa=3aNTmgoPDzpO9w4JbLQ+DcYc$A! zKTr7lp6A{F1s4}zzPr!4&pG$!l6mzL!8E@pU z-b1h|-F*B7XaD1y8*-{`VFjePYF4*z3L@eWhf%k}a^A(n80^M!H1b~^^zZ1cJm7Ei zc5n1<^d2HqY1#E|y|k%!pYvSR9gTSd3M)Zx2qIVb^iV0pP$O6)NaY!+lm}GDhepD#~#KONR3c@08$MHFY8vm z{r9@@A|SrZ=UCja{r@aL(1(DmweJ^C{igrtvd>blm$-k4^ZM+XqyMoNYz70n7@vK! z`Onk)gV#&k@dsA>{A(L;knMb`YLn{%DSvJ2&qW%9KzQ^XibHjp(jsBOa+Q^?+G$H7 z7xHuxekWyO8?WJ#6ho<^zDgR+hxPDlQag2H3jZS2-3|Mw?q4?=pZKBNI1{-O_;+x+YV2>8bXD|o82Fz}6$N;`CmcdN z5+N9>Hz)nW6n+by)fj9FOTdKZ=Z61VqonD7=y_F-KkVO6<9Ytj^J(Yk$iMhx1QQJX zp=alk|C-OU#6R?m{b9BKFLJj)!4|Mp7)84C0PFv_Mk;_gJI=lc4R{AYX=f72#tJ$A z_s@Sb@SE#Dt@;1;2O|l{JP-Vd;-5W3;M?W?Edve_pvoWZ5&GZH+bVavKNVA78?>qu zWQ{Z`*1Mo)Gq zMAd)xo7;0OcBjJzrDTD^*Ng4r;*)umf!Br!8C?s_s+DV^eUYDUoTxdhm6WGuEAnl# zwDs3vw39$}$nAev{(~mtwd7|Mj^C8a+t~^T?V3#P>C=u*;)3_Ncs{r%wK^>GPsHWikf0tmp)^xsSeV%gK>w(0*SwH+fN((WxNEdIH2#xTu-TDuz z9)&dWzWUskRR1w>zH0-o+I<|1H%05Xu;`uWhJ&t<+<8Ye
ME7m@#R)t9&q^| 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

D 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

s4&bcDbHgys`Q8|Ssnb#9qa?* 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 - + {series.metricType && ( diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx index ba4178d54a62e3..f94e12db967bbb 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx @@ -5,33 +5,12 @@ * 2.0. */ -import React, { useState } from 'react'; -import { EuiButton, EuiButtonGroup, EuiPopover } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; +import React from 'react'; -// import { LensIconChartBarPercentage } from '../assets/chart_bar_percentage'; -// import { LensIconChartBarHorizontalPercentage } from '../assets/chart_bar_horizontal_percentage'; -// import { LensIconChartAreaPercentage } from '../assets/chart_area_percentage'; -import styled from 'styled-components'; import { useUrlStorage } from '../../hooks/use_url_strorage'; -import { LensIconChartBar } from '../../assets/chart_bar'; -import { VisualizationType } from '../../../../../../../lens/public/types'; -import { LensIconChartBarHorizontal } from '../../assets/chart_bar_horizontal'; -import { LensIconChartBarStacked } from '../../assets/chart_bar_stacked'; -import { LensIconChartBarHorizontalStacked } from '../../assets/chart_bar_horizontal_stacked'; -import { LensIconChartArea } from '../../assets/chart_area'; -import { LensIconChartAreaStacked } from '../../assets/chart_area_stacked'; -import { LensIconChartLine } from '../../assets/chart_line'; +import { XYChartTypes } from '../../../../../../../lens/public'; -const ButtonGroup = styled(EuiButtonGroup)` - &&& { - .euiButtonGroupButton-isSelected { - background-color: #a5a9b1 !important; - } - } -`; - -export function ChartTypes({ +export function SeriesChartTypes({ seriesId, defaultChartType, }: { @@ -40,154 +19,21 @@ export function ChartTypes({ }) { const { series, setSeries, allSeries } = useUrlStorage(seriesId); - const [isOpen, setIsOpen] = useState(false); - const seriesType = series?.seriesType ?? defaultChartType; - return ( - id == seriesType)?.icon} - onClick={() => { - setIsOpen((prevState) => !prevState); - }} - > - Chart type - - } - closePopover={() => setIsOpen(false)} - > - ({ - id: t.id, - label: t.label, - iconType: t.icon || 'empty', - 'data-test-subj': `lnsXY_seriesType-${t.id}`, - }))} - idSelected={series?.seriesType ?? seriesType} - onChange={(seriesType: string) => { - Object.keys(allSeries).forEach((seriesKey) => { - const series = allSeries[seriesKey]; + const onChange = (value: string) => { + Object.keys(allSeries).forEach((seriesKey) => { + const seriesN = allSeries[seriesKey]; + + setSeries(seriesKey, { ...seriesN, seriesType: value }); + }); + }; - setSeries(seriesKey, { ...series, seriesType }); - }); - }} - /> - + return ( + ); } - -const groupLabelForBar = i18n.translate('xpack.lens.xyVisualization.barGroupLabel', { - defaultMessage: 'Bar', -}); - -const groupLabelForLineAndArea = i18n.translate('xpack.lens.xyVisualization.lineGroupLabel', { - defaultMessage: 'Line and area', -}); - -export const visualizationTypes: VisualizationType[] = [ - { - id: 'bar', - icon: LensIconChartBar, - label: i18n.translate('xpack.lens.xyVisualization.barLabel', { - defaultMessage: 'Bar vertical', - }), - groupLabel: groupLabelForBar, - }, - { - id: 'bar_horizontal', - icon: LensIconChartBarHorizontal, - label: i18n.translate('xpack.lens.xyVisualization.barHorizontalLabel', { - defaultMessage: 'H. Bar', - }), - fullLabel: i18n.translate('xpack.lens.xyVisualization.barHorizontalFullLabel', { - defaultMessage: 'Bar horizontal', - }), - groupLabel: groupLabelForBar, - }, - { - id: 'bar_stacked', - icon: LensIconChartBarStacked, - label: i18n.translate('xpack.lens.xyVisualization.stackedBarLabel', { - defaultMessage: 'Bar vertical stacked', - }), - groupLabel: groupLabelForBar, - }, - // { - // id: 'bar_percentage_stacked', - // icon: LensIconChartBarPercentage, - // label: i18n.translate('xpack.lens.xyVisualization.stackedPercentageBarLabel', { - // defaultMessage: 'Bar vertical percentage', - // }), - // groupLabel: groupLabelForBar, - // }, - { - id: 'bar_horizontal_stacked', - icon: LensIconChartBarHorizontalStacked, - label: i18n.translate('xpack.lens.xyVisualization.stackedBarHorizontalLabel', { - defaultMessage: 'H. Stacked bar', - }), - fullLabel: i18n.translate('xpack.lens.xyVisualization.stackedBarHorizontalFullLabel', { - defaultMessage: 'Bar horizontal stacked', - }), - groupLabel: groupLabelForBar, - }, - // { - // id: 'bar_horizontal_percentage_stacked', - // icon: LensIconChartBarHorizontalPercentage, - // label: i18n.translate('xpack.lens.xyVisualization.stackedPercentageBarHorizontalLabel', { - // defaultMessage: 'H. Percentage bar', - // }), - // fullLabel: i18n.translate( - // 'xpack.lens.xyVisualization.stackedPercentageBarHorizontalFullLabel', - // { - // defaultMessage: 'Bar horizontal percentage', - // } - // ), - // groupLabel: groupLabelForBar, - // }, - { - id: 'area', - icon: LensIconChartArea, - label: i18n.translate('xpack.lens.xyVisualization.areaLabel', { - defaultMessage: 'Area', - }), - groupLabel: groupLabelForLineAndArea, - }, - { - id: 'area_stacked', - icon: LensIconChartAreaStacked, - label: i18n.translate('xpack.lens.xyVisualization.stackedAreaLabel', { - defaultMessage: 'Area stacked', - }), - groupLabel: groupLabelForLineAndArea, - }, - // { - // id: 'area_percentage_stacked', - // icon: LensIconChartAreaPercentage, - // label: i18n.translate('xpack.lens.xyVisualization.stackedPercentageAreaLabel', { - // defaultMessage: 'Area percentage', - // }), - // groupLabel: groupLabelForLineAndArea, - // }, - { - id: 'line', - icon: LensIconChartLine, - label: i18n.translate('xpack.lens.xyVisualization.lineLabel', { - defaultMessage: 'Line', - }), - groupLabel: groupLabelForLineAndArea, - }, -]; From 165a540eec33597b17697bca7e73967c7dee72b8 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 24 Mar 2021 10:12:34 +0100 Subject: [PATCH 17/53] update --- .../exploratory_view/series_editor/columns/chart_types.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx index f94e12db967bbb..047b68473fba46 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx @@ -34,6 +34,7 @@ export function SeriesChartTypes({ onChange={onChange} value={seriesType} excludeChartTypes={['bar_percentage_stacked']} + label={'Chart types'} /> ); } From e3b3eb55ec3962015f5e1959d78dc54ddeaf35cb Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 24 Mar 2021 10:56:43 +0100 Subject: [PATCH 18/53] update eslint --- .../exploratory_view/components/empty_view.tsx | 6 +++--- .../exploratory_view/components/filter_label.tsx | 6 +++--- .../exploratory_view/hooks/use_series_filters.ts | 4 ++-- .../components/shared/exploratory_view/index.tsx | 10 +++++----- .../series_builder/columns/data_types_col.tsx | 6 +++--- .../series_builder/columns/report_breakdowns.tsx | 4 ++-- .../series_builder/columns/report_filters.tsx | 4 ++-- .../series_builder/series_builder.tsx | 15 +-------------- .../series_editor/columns/breakdowns.tsx | 2 +- .../series_editor/selected_filters.tsx | 6 +++--- .../components/shared/field_value_selection.tsx | 8 ++++---- .../public/context/has_data_context.tsx | 2 +- .../observability/public/hooks/use_breadcrumbs.ts | 2 +- .../public/hooks/use_default_index_pattern.tsx | 7 ++++--- 14 files changed, 35 insertions(+), 47 deletions(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx index 536c64dbae2638..2d4d42733ba5b5 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx @@ -7,10 +7,10 @@ import React from 'react'; import { EuiImage } from '@elastic/eui'; -import { usePluginContext } from '../../../../hooks/use_plugin_context'; import styled from 'styled-components'; +import { usePluginContext } from '../../../../hooks/use_plugin_context'; -export const EmptyView = () => { +export function EmptyView() { const { core } = usePluginContext(); return ( @@ -21,7 +21,7 @@ export const EmptyView = () => { /> ); -}; +} const Wrapper = styled.div` text-align: center; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx index e97b26e69fc4ce..7052492cc1a3dd 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx @@ -6,9 +6,9 @@ */ import React from 'react'; +import { injectI18n } from '@kbn/i18n/react'; import { esFilters, Filter } from '../../../../../../../../src/plugins/data/public'; import { useIndexPatternContext } from '../../../../hooks/use_default_index_pattern'; -import { injectI18n } from '@kbn/i18n/react'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { useSeriesFilters } from '../hooks/use_series_filters'; @@ -20,7 +20,7 @@ interface Props { negate: boolean; removeFilter: (field: string, value: string, notVal: boolean) => void; } -export const FilterLabel = ({ label, seriesId, field, value, negate, removeFilter }: Props) => { +export function FilterLabel({ label, seriesId, field, value, negate, removeFilter }: Props) { const FilterItem = injectI18n(esFilters.FilterItem); const { indexPattern } = useIndexPatternContext(); @@ -59,4 +59,4 @@ export const FilterLabel = ({ label, seriesId, field, value, negate, removeFilte hiddenPanelOptions={['pinFilter', 'editFilter', 'disableFilter']} /> ); -}; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_filters.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_filters.ts index ee477ef8383d5b..1016fc2025b85c 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_filters.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_filters.ts @@ -52,7 +52,7 @@ export const useSeriesFilters = ({ seriesId }: Props) => { } else { setSeries(seriesId, { ...series, - filters: [currFilter, ...filters.filter((ft) => ft.field != field)], + filters: [currFilter, ...filters.filter((ft) => ft.field !== field)], }); } }; @@ -87,7 +87,7 @@ export const useSeriesFilters = ({ seriesId }: Props) => { }; const setFilter = ({ field, value, negate }: UpdateFilter) => { - let currFilter: UrlFilter | undefined = filters.find(({ field: fd }) => field === fd); + const currFilter: UrlFilter | undefined = filters.find(({ field: fd }) => field === fd); if (!currFilter) { addFilter({ field, value, negate }); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx index 4135502eb43b21..ae475e21e47cc0 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx @@ -6,14 +6,14 @@ */ import React, { useContext } from 'react'; +import { i18n } from '@kbn/i18n'; +import { useHistory } from 'react-router-dom'; +import { ThemeContext } from 'styled-components'; import { ExploratoryView } from './exploratory_view'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { ObservabilityClientPluginsStart } from '../../../plugin'; import { useBreadcrumbs } from '../../../hooks/use_breadcrumbs'; -import { i18n } from '@kbn/i18n'; import { IndexPatternContextProvider } from '../../../hooks/use_default_index_pattern'; -import { useHistory } from 'react-router-dom'; -import { ThemeContext } from 'styled-components'; import { createKbnUrlStateStorage, withNotifyOnErrors, @@ -22,7 +22,7 @@ import { UrlStorageContextProvider } from './hooks/use_url_strorage'; import { useInitExploratoryView } from './hooks/use_init_exploratory_view'; import { WithHeaderLayout } from '../../app/layout/with_header'; -export const ExploratoryViewPage = () => { +export function ExploratoryViewPage() { useBreadcrumbs([ { text: i18n.translate('xpack.observability.overview.exploratoryView', { @@ -59,4 +59,4 @@ export const ExploratoryViewPage = () => { ); -}; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx index 3d7ef7e614b59d..d8694415446e40 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx @@ -11,7 +11,7 @@ import { AppDataType } from '../../types'; import { useIndexPatternContext } from '../../../../../hooks/use_default_index_pattern'; import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_strorage'; -const dataTypes: { id: AppDataType; label: string }[] = [ +const dataTypes: Array<{ id: AppDataType; label: string }> = [ { id: 'synthetics', label: 'Synthetic Monitoring' }, { id: 'rum', label: 'User Experience(RUM)' }, { id: 'logs', label: 'Logs' }, @@ -19,7 +19,7 @@ const dataTypes: { id: AppDataType; label: string }[] = [ { id: 'apm', label: 'APM' }, ]; -export const DataTypesCol = () => { +export function DataTypesCol() { const { series, setSeries } = useUrlStorage(NEW_SERIES_KEY); const { loadIndexPattern } = useIndexPatternContext(); @@ -53,4 +53,4 @@ export const DataTypesCol = () => { ))} ); -}; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.tsx index 57f1103f77d9e6..e22e34ab36ea5c 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.tsx @@ -10,7 +10,7 @@ import { Breakdowns } from '../../series_editor/columns/breakdowns'; import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_strorage'; import { getDefaultConfigs } from '../../configurations/default_configs'; -export const ReportBreakdowns = () => { +export function ReportBreakdowns() { const { series: { reportType }, } = useUrlStorage(NEW_SERIES_KEY); @@ -20,4 +20,4 @@ export const ReportBreakdowns = () => { seriesId: NEW_SERIES_KEY, }); return ; -}; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx index c2607ba6b628bc..d08614e0675b7f 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx @@ -14,7 +14,7 @@ import { ReportViewTypeId } from '../../types'; interface Props { reportType: ReportViewTypeId; } -export const ReportFilters = ({ reportType }: Props) => { +export function ReportFilters({ reportType }: Props) { const dataSeries = getDefaultConfigs({ reportType: reportType!, seriesId: NEW_SERIES_KEY, @@ -28,4 +28,4 @@ export const ReportFilters = ({ reportType }: Props) => { isNew={true} /> ); -}; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx index 641d8f11d19770..6955f2bf9fe78e 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx @@ -82,19 +82,6 @@ export function SeriesBuilder() { ]; const addSeries = () => { - // const getFiltersFromDefs = (): UrlFilter[] => { - // return Object.entries(reportDefinitions).map(([field, value]) => ({ - // field, - // values: [value], - // })); - // - // // let's filter out custom fields - // return rdfFilters.filter(({ field }) => { - // const rdf = dataViewConfig.reportDefinitions.find(({ field: fd }) => field === fd); - // return !rdf?.custom; - // }); - // }; - if (reportType) { const newSeriesId = `${ reportDefinitions?.['service.name'] || @@ -105,7 +92,7 @@ export function SeriesBuilder() { const newSeriesN = { reportType, time: { from: 'now-30m', to: 'now' }, - filters: filters, + filters, reportDefinitions, } as SeriesUrl; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx index 6296a7eb9b2dea..4d10b464249a3b 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx @@ -7,9 +7,9 @@ import React, { useEffect } from 'react'; import { EuiSuperSelect } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { FieldLabels } from '../../configurations/constants'; import { useUrlStorage } from '../../hooks/use_url_strorage'; -import { i18n } from '@kbn/i18n'; interface Props { seriesId: string; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx index ba85a796d67411..b38131c3707d29 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx @@ -6,8 +6,8 @@ */ import React, { Fragment, useMemo } from 'react'; -import { NEW_SERIES_KEY, useUrlStorage } from '../hooks/use_url_strorage'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { NEW_SERIES_KEY, useUrlStorage } from '../hooks/use_url_strorage'; import { FilterLabel } from '../components/filter_label'; import { DataSeries, UrlFilter } from '../types'; import { useIndexPatternContext } from '../../../../hooks/use_default_index_pattern'; @@ -19,7 +19,7 @@ interface Props { series: DataSeries; isNew?: boolean; } -export const SelectedFilters = ({ seriesId, isNew, series: dataSeries }: Props) => { +export function SelectedFilters({ seriesId, isNew, series: dataSeries }: Props) { const { series } = useUrlStorage(seriesId); const { reportDefinitions = {} } = series; @@ -71,4 +71,4 @@ export const SelectedFilters = ({ seriesId, isNew, series: dataSeries }: Props) ) : null; -}; +} diff --git a/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx b/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx index 5b669791437265..839545c8b96a4c 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx +++ b/x-pack/plugins/observability/public/components/shared/field_value_selection.tsx @@ -13,10 +13,10 @@ import { EuiPopoverTitle, EuiSelectable, } from '@elastic/eui'; +import { PopoverAnchorPosition } from '@elastic/eui/src/components/popover/popover'; import { useValuesList } from '../../hooks/use_values_list'; import { IIndexPattern } from '../../../../../../src/plugins/data/common'; import { ESFilter } from '../../../../../../typings/elasticsearch'; -import { PopoverAnchorPosition } from '@elastic/eui/src/components/popover/popover'; interface Option { id: string; @@ -37,7 +37,7 @@ interface Props { button?: JSX.Element; } -export const FieldValueSelection = ({ +export function FieldValueSelection({ sourceField, indexPattern, value, @@ -48,7 +48,7 @@ export const FieldValueSelection = ({ forceOpen, anchorPosition, onChange: onSelectionChange, -}: Props) => { +}: Props) { const [query, setQuery] = useState(''); const { values, loading } = useValuesList({ indexPattern, query, sourceField, filters, time }); @@ -134,4 +134,4 @@ export const FieldValueSelection = ({ ); -}; +} diff --git a/x-pack/plugins/observability/public/context/has_data_context.tsx b/x-pack/plugins/observability/public/context/has_data_context.tsx index 3117996b399717..a2628d37828a44 100644 --- a/x-pack/plugins/observability/public/context/has_data_context.tsx +++ b/x-pack/plugins/observability/public/context/has_data_context.tsx @@ -7,6 +7,7 @@ import { uniqueId } from 'lodash'; import React, { createContext, useEffect, useState } from 'react'; +import { useRouteMatch } from 'react-router-dom'; import { Alert } from '../../../alerting/common'; import { getDataHandler } from '../data_handler'; import { FETCH_STATUS } from '../hooks/use_fetcher'; @@ -14,7 +15,6 @@ import { usePluginContext } from '../hooks/use_plugin_context'; import { useTimeRange } from '../hooks/use_time_range'; import { getObservabilityAlerts } from '../services/get_observability_alerts'; import { ObservabilityFetchDataPlugins, UXHasDataResponse } from '../typings/fetch_overview_data'; -import { useRouteMatch } from 'react-router'; type DataContextApps = ObservabilityFetchDataPlugins | 'alert'; diff --git a/x-pack/plugins/observability/public/hooks/use_breadcrumbs.ts b/x-pack/plugins/observability/public/hooks/use_breadcrumbs.ts index f34809ce4f8eb6..5ce71438d0a241 100644 --- a/x-pack/plugins/observability/public/hooks/use_breadcrumbs.ts +++ b/x-pack/plugins/observability/public/hooks/use_breadcrumbs.ts @@ -9,9 +9,9 @@ import { ChromeBreadcrumb } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { MouseEvent, useEffect } from 'react'; import { EuiBreadcrumb } from '@elastic/eui'; +import { stringify } from 'query-string'; import { useKibana } from '../../../../../src/plugins/kibana_react/public'; import { useQueryParams } from './use_query_params'; -import { stringify } from 'query-string'; const EMPTY_QUERY = '?'; diff --git a/x-pack/plugins/observability/public/hooks/use_default_index_pattern.tsx b/x-pack/plugins/observability/public/hooks/use_default_index_pattern.tsx index 0735b964908d82..0b744d1ae4e52a 100644 --- a/x-pack/plugins/observability/public/hooks/use_default_index_pattern.tsx +++ b/x-pack/plugins/observability/public/hooks/use_default_index_pattern.tsx @@ -21,12 +21,13 @@ export const IndexPatternContext = createContext>( interface ProviderProps { indexPattern: IIndexPattern; + children: JSX.Element; } -export const IndexPatternContextProvider: React.FC = ({ +export function IndexPatternContextProvider({ children, indexPattern: initialIndexPattern, -}) => { +}: ProviderProps) { const [indexPattern, setIndexPattern] = useState(initialIndexPattern); useEffect(() => { @@ -51,7 +52,7 @@ export const IndexPatternContextProvider: React.FC = ({ }, [indexPattern]); return {children}; -}; +} export const useIndexPatternContext = () => { return useContext((IndexPatternContext as unknown) as Context); From 84c2abe18eefa0be94fba103fa71d18530cfb75b Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 24 Mar 2021 11:21:26 +0100 Subject: [PATCH 19/53] udate --- .../plugins/observability/public/hooks/use_values_list.ts | 2 +- x-pack/plugins/observability/public/plugin.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/observability/public/hooks/use_values_list.ts b/x-pack/plugins/observability/public/hooks/use_values_list.ts index 4c9c028d7a5c80..0373f54cfe98ad 100644 --- a/x-pack/plugins/observability/public/hooks/use_values_list.ts +++ b/x-pack/plugins/observability/public/hooks/use_values_list.ts @@ -53,7 +53,7 @@ export const useValuesList = ({ ] : filters, }); - }, [sourceField, query, time]); + }, [sourceField, query, time, data.autocomplete, indexPattern, filters]); return { values: values as string[], loading: status === 'loading' || status === 'pending' }; }; diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index 6834ae69762f6d..2881b59eb6cae1 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -21,6 +21,7 @@ import { HomePublicPluginSetup } from '../../../../src/plugins/home/public'; import { registerDataHandler } from './data_handler'; import { toggleOverviewLinkInNav } from './toggle_overview_link_in_nav'; import { LensPublicStart } from '../../lens/public'; +import { ObservabilityPluginSetupDeps } from '../target/types/public/plugin'; export interface ObservabilityPluginSetup { dashboard: { register: typeof registerDataHandler }; @@ -52,7 +53,10 @@ export class Plugin constructor(context: PluginInitializerContext) {} - public setup(core: CoreSetup, plugins: ObservabilityPluginSetupDeps) { + public setup( + core: CoreSetup, + plugins: ObservabilityPluginSetupDeps + ) { const category = DEFAULT_APP_CATEGORIES.observability; const euiIconType = 'logo-observability'; const mount = async (params: AppMountParameters) => { From 75b87e1eb30381ebae9fc83db2cb8ddd7ff74814 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 24 Mar 2021 11:23:59 +0100 Subject: [PATCH 20/53] wip --- x-pack/plugins/observability/public/plugin.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index 2881b59eb6cae1..117e29f5763876 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -63,9 +63,9 @@ export class Plugin // Load application bundle const { renderApp } = await import('./application'); // Get start services - const [coreStart] = await core.getStartServices(); + const [coreStart, startPlugins] = await core.getStartServices(); - return renderApp(coreStart, plugins, params); + return renderApp(coreStart, startPlugins, params); }; const updater$ = this.appUpdater$; From 533698a0e2cb6c0a32133dd27bfae817b8b667a0 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 24 Mar 2021 14:07:03 +0100 Subject: [PATCH 21/53] update plugin start/setup --- .../public/application/application.test.tsx | 4 +-- .../public/application/index.tsx | 4 +-- .../components/app/section/apm/index.test.tsx | 4 +-- .../components/app/section/ux/index.test.tsx | 4 +-- .../public/context/plugin_context.tsx | 4 +-- .../public/hooks/use_time_range.test.ts | 6 ++-- .../pages/overview/overview.stories.tsx | 4 +-- x-pack/plugins/observability/public/plugin.ts | 36 ++++++++++++++----- .../public/utils/test_helper.tsx | 4 +-- x-pack/plugins/observability/tsconfig.json | 1 + 10 files changed, 45 insertions(+), 26 deletions(-) diff --git a/x-pack/plugins/observability/public/application/application.test.tsx b/x-pack/plugins/observability/public/application/application.test.tsx index 33cc6d24397d60..0eac688cd3e4bf 100644 --- a/x-pack/plugins/observability/public/application/application.test.tsx +++ b/x-pack/plugins/observability/public/application/application.test.tsx @@ -9,7 +9,7 @@ import { createMemoryHistory } from 'history'; import React from 'react'; import { Observable } from 'rxjs'; import { AppMountParameters, CoreStart } from 'src/core/public'; -import { ObservabilityPluginSetupDeps } from '../plugin'; +import { ObservabilityClientPluginsStart } from '../plugin'; import { renderApp } from './'; describe('renderApp', () => { @@ -32,7 +32,7 @@ describe('renderApp', () => { }, }, }, - } as unknown) as ObservabilityPluginSetupDeps; + } as unknown) as ObservabilityClientPluginsStart; const core = ({ application: { currentAppId$: new Observable(), navigateToUrl: () => {} }, chrome: { diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index 9628a5cc61ba93..f711cfbb82b338 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -18,7 +18,7 @@ import { import { PluginContext } from '../context/plugin_context'; import { usePluginContext } from '../hooks/use_plugin_context'; import { useRouteParams } from '../hooks/use_route_params'; -import { ObservabilityPluginSetupDeps } from '../plugin'; +import { ObservabilityClientPluginsStart } from '../plugin'; import { HasDataContextProvider } from '../context/has_data_context'; import { Breadcrumbs, routes } from '../routes'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; @@ -68,7 +68,7 @@ function App() { export const renderApp = ( core: CoreStart, - plugins: ObservabilityPluginSetupDeps, + plugins: ObservabilityClientPluginsStart, appMountParameters: AppMountParameters ) => { const { element, history } = appMountParameters; diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx index 8f7961e13f80b3..6873dd33e6f78e 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx @@ -14,7 +14,7 @@ import * as hasDataHook from '../../../../hooks/use_has_data'; import * as pluginContext from '../../../../hooks/use_plugin_context'; import { HasDataContextValue } from '../../../../context/has_data_context'; import { AppMountParameters, CoreStart } from 'kibana/public'; -import { ObservabilityPluginSetupDeps } from '../../../../plugin'; +import { ObservabilityClientPluginsStart } from '../../../../plugin'; jest.mock('react-router-dom', () => ({ useLocation: () => ({ @@ -53,7 +53,7 @@ describe('APMSection', () => { }, }, }, - } as unknown) as ObservabilityPluginSetupDeps, + } as unknown) as ObservabilityClientPluginsStart, })); }); it('renders with transaction series and stats', () => { diff --git a/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx b/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx index 3d0e1618d0c3e2..a586a8aa87bc49 100644 --- a/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx @@ -11,7 +11,7 @@ import { HasDataContextValue } from '../../../../context/has_data_context'; import * as fetcherHook from '../../../../hooks/use_fetcher'; import * as hasDataHook from '../../../../hooks/use_has_data'; import * as pluginContext from '../../../../hooks/use_plugin_context'; -import { ObservabilityPluginSetupDeps } from '../../../../plugin'; +import { ObservabilityClientPluginsStart } from '../../../../plugin'; import { render } from '../../../../utils/test_helper'; import { UXSection } from './'; import { response } from './mock_data/ux.mock'; @@ -52,7 +52,7 @@ describe('UXSection', () => { }, }, }, - } as unknown) as ObservabilityPluginSetupDeps, + } as unknown) as ObservabilityClientPluginsStart, })); }); it('renders with core web vitals', () => { diff --git a/x-pack/plugins/observability/public/context/plugin_context.tsx b/x-pack/plugins/observability/public/context/plugin_context.tsx index d47915feb7b485..e0eed2a1470a3b 100644 --- a/x-pack/plugins/observability/public/context/plugin_context.tsx +++ b/x-pack/plugins/observability/public/context/plugin_context.tsx @@ -7,12 +7,12 @@ import { createContext } from 'react'; import { AppMountParameters, CoreStart } from 'kibana/public'; -import { ObservabilityPluginSetupDeps } from '../plugin'; +import { ObservabilityClientPluginsStart } from '../plugin'; export interface PluginContextValue { appMountParameters: AppMountParameters; core: CoreStart; - plugins: ObservabilityPluginSetupDeps; + plugins: ObservabilityClientPluginsStart; } export const PluginContext = createContext({} as PluginContextValue); diff --git a/x-pack/plugins/observability/public/hooks/use_time_range.test.ts b/x-pack/plugins/observability/public/hooks/use_time_range.test.ts index 7e065efbf2937d..833221ebdefbd0 100644 --- a/x-pack/plugins/observability/public/hooks/use_time_range.test.ts +++ b/x-pack/plugins/observability/public/hooks/use_time_range.test.ts @@ -8,7 +8,7 @@ import { useTimeRange } from './use_time_range'; import * as pluginContext from './use_plugin_context'; import { AppMountParameters, CoreStart } from 'kibana/public'; -import { ObservabilityPluginSetupDeps } from '../plugin'; +import { ObservabilityClientPluginsStart } from '../plugin'; import * as kibanaUISettings from './use_kibana_ui_settings'; jest.mock('react-router-dom', () => ({ @@ -36,7 +36,7 @@ describe('useTimeRange', () => { }, }, }, - } as unknown) as ObservabilityPluginSetupDeps, + } as unknown) as ObservabilityClientPluginsStart, })); jest.spyOn(kibanaUISettings, 'useKibanaUISettings').mockImplementation(() => ({ from: '2020-10-08T05:00:00.000Z', @@ -76,7 +76,7 @@ describe('useTimeRange', () => { }, }, }, - } as unknown) as ObservabilityPluginSetupDeps, + } as unknown) as ObservabilityClientPluginsStart, })); }); it('returns ranges and absolute times from kibana default settings', () => { diff --git a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx index b5aaeea3367c1c..6c09d4cde4add6 100644 --- a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx +++ b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx @@ -15,7 +15,7 @@ import { UI_SETTINGS } from '../../../../../../src/plugins/data/public'; import { HasDataContextProvider } from '../../context/has_data_context'; import { PluginContext } from '../../context/plugin_context'; import { registerDataHandler, unregisterDataHandler } from '../../data_handler'; -import { ObservabilityPluginSetupDeps } from '../../plugin'; +import { ObservabilityClientPluginsStart } from '../../plugin'; import { OverviewPage } from './'; import { alertsFetchData } from './mock/alerts.mock'; import { emptyResponse as emptyAPMResponse, fetchApmData } from './mock/apm.mock'; @@ -51,7 +51,7 @@ const withCore = makeDecorator({ timefilter: { timefilter: { setTime: () => {}, getTime: () => ({}) } }, }, }, - } as unknown) as ObservabilityPluginSetupDeps, + } as unknown) as ObservabilityClientPluginsStart, }} > diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index 81c174932914ba..cd6ebc661045da 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -7,7 +7,7 @@ import { BehaviorSubject } from 'rxjs'; import { i18n } from '@kbn/i18n'; -import { DataPublicPluginSetup } from '../../../../src/plugins/data/public'; +import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public'; import { AppMountParameters, AppUpdater, @@ -17,36 +17,54 @@ import { PluginInitializerContext, CoreStart, } from '../../../../src/core/public'; -import { HomePublicPluginSetup } from '../../../../src/plugins/home/public'; +import { HomePublicPluginSetup, HomePublicPluginStart } from '../../../../src/plugins/home/public'; import { registerDataHandler } from './data_handler'; import { toggleOverviewLinkInNav } from './toggle_overview_link_in_nav'; +import { LensPublicStart } from '../../lens/public'; export interface ObservabilityPluginSetup { dashboard: { register: typeof registerDataHandler }; } -export interface ObservabilityPluginSetupDeps { - home?: HomePublicPluginSetup; +export interface ObservabilityClientPluginsSetup { data: DataPublicPluginSetup; + home?: HomePublicPluginSetup; +} + +export interface ObservabilityClientPluginsStart { + home?: HomePublicPluginStart; + data: DataPublicPluginStart; + lens: LensPublicStart; } -export type ObservabilityPluginStart = void; +export type ObservabilityClientSetup = void; +export type ObservabilityClientStart = void; -export class Plugin implements PluginClass { +export class Plugin + implements + PluginClass< + ObservabilityClientSetup, + ObservabilityClientStart, + ObservabilityClientPluginsSetup, + ObservabilityClientPluginsStart + > { private readonly appUpdater$ = new BehaviorSubject(() => ({})); constructor(context: PluginInitializerContext) {} - public setup(core: CoreSetup, plugins: ObservabilityPluginSetupDeps) { + public setup( + core: CoreSetup, + plugins: ObservabilityClientPluginsSetup + ) { const category = DEFAULT_APP_CATEGORIES.observability; const euiIconType = 'logo-observability'; const mount = async (params: AppMountParameters) => { // Load application bundle const { renderApp } = await import('./application'); // Get start services - const [coreStart] = await core.getStartServices(); + const [coreStart, startPlugins] = await core.getStartServices(); - return renderApp(coreStart, plugins, params); + return renderApp(coreStart, startPlugins, params); }; const updater$ = this.appUpdater$; diff --git a/x-pack/plugins/observability/public/utils/test_helper.tsx b/x-pack/plugins/observability/public/utils/test_helper.tsx index b7dd70acb91bde..7c516f9b5fb34a 100644 --- a/x-pack/plugins/observability/public/utils/test_helper.tsx +++ b/x-pack/plugins/observability/public/utils/test_helper.tsx @@ -13,7 +13,7 @@ import { of } from 'rxjs'; import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; import translations from '../../../translations/translations/ja-JP.json'; import { PluginContext } from '../context/plugin_context'; -import { ObservabilityPluginSetupDeps } from '../plugin'; +import { ObservabilityClientPluginsStart } from '../plugin'; import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common'; const appMountParameters = ({ setHeaderActionMenu: () => {} } as unknown) as AppMountParameters; @@ -32,7 +32,7 @@ export const core = ({ const plugins = ({ data: { query: { timefilter: { timefilter: { setTime: jest.fn() } } } }, -} as unknown) as ObservabilityPluginSetupDeps; +} as unknown) as ObservabilityClientPluginsStart; export const render = (component: React.ReactNode) => { return testLibRender( diff --git a/x-pack/plugins/observability/tsconfig.json b/x-pack/plugins/observability/tsconfig.json index 5c7528610a0b1f..6833948b86b18d 100644 --- a/x-pack/plugins/observability/tsconfig.json +++ b/x-pack/plugins/observability/tsconfig.json @@ -17,6 +17,7 @@ { "path": "../../../src/plugins/usage_collection/tsconfig.json" }, { "path": "../alerting/tsconfig.json" }, { "path": "../licensing/tsconfig.json" }, + { "path": "../lens/tsconfig.json" }, { "path": "../translations/tsconfig.json" } ] } From 805f0621aaedf409fc5d3147c198bd5f84ba0c30 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 24 Mar 2021 14:55:08 +0100 Subject: [PATCH 22/53] update --- x-pack/plugins/lens/public/index.ts | 1 - x-pack/plugins/lens/public/plugin.ts | 11 +- .../shared_components/chart_types/index.tsx | 19 ---- .../chart_types/xy_chart_types.tsx | 101 ------------------ .../series_editor/columns/chart_types.tsx | 101 +++++++++++++++++- 5 files changed, 109 insertions(+), 124 deletions(-) delete mode 100644 x-pack/plugins/lens/public/shared_components/chart_types/index.tsx delete mode 100644 x-pack/plugins/lens/public/shared_components/chart_types/xy_chart_types.tsx diff --git a/x-pack/plugins/lens/public/index.ts b/x-pack/plugins/lens/public/index.ts index 6ffcbe318ec0b6..fa5a9f9289e92a 100644 --- a/x-pack/plugins/lens/public/index.ts +++ b/x-pack/plugins/lens/public/index.ts @@ -11,7 +11,6 @@ export { EmbeddableComponentProps, TypedLensByValueInput, } from './editor_frame_service/embeddable/embeddable_component'; -export { XYChartTypes } from './shared_components/chart_types'; export type { XYState, AxesSettingsConfig, diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index fc7e4464624f4c..455ed12d6b5181 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -42,7 +42,7 @@ import { VISUALIZE_FIELD_TRIGGER, } from '../../../../src/plugins/ui_actions/public'; import { APP_ID, getEditPath, NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../common'; -import { EditorFrameStart } from './types'; +import { EditorFrameStart, VisualizationType } from './types'; import { getLensAliasConfig } from './vis_type_alias'; import { visualizeFieldAction } from './trigger_actions/visualize_field_actions'; import { getSearchProvider } from './search_provider'; @@ -101,6 +101,11 @@ export interface LensPublicStart { * Method which returns true if the user has permission to use Lens as defined by application capabilities. */ canUseEditor: () => boolean; + + /** + * Method which returns xy VisualizationTypes array keeping this async as to not impact page load bundle + */ + getXyVisTypes: () => Promise; } export class LensPlugin { @@ -257,6 +262,10 @@ export class LensPlugin { canUseEditor: () => { return Boolean(core.application.capabilities.visualize?.show); }, + getXyVisTypes: async () => { + const { visualizationTypes } = await import('./xy_visualization/types'); + return visualizationTypes; + }, }; } diff --git a/x-pack/plugins/lens/public/shared_components/chart_types/index.tsx b/x-pack/plugins/lens/public/shared_components/chart_types/index.tsx deleted file mode 100644 index 0bf69b5c7314bc..00000000000000 --- a/x-pack/plugins/lens/public/shared_components/chart_types/index.tsx +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { lazy, Suspense } from 'react'; -import type { ChartTypesProps } from './xy_chart_types'; - -const ChartTypesComponent = lazy(() => import('./xy_chart_types')); - -export const XYChartTypes = (props: ChartTypesProps) => { - return ( - Loading...