From 52d5b9d51df349eccf471c6da4f90f32b73bbd1d Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Wed, 23 Jun 2021 09:58:51 -0700 Subject: [PATCH 01/17] [docker] Removes setting with hyphen (#103085) This setting is causing an error to be throw as it's being used in an environment variable. Created https://github.com/elastic/kibana/issues/103084 Signed-off-by: Tyler Smalley --- .../docker_generator/resources/base/bin/kibana-docker | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index a9b2dd6aefdda6..9ea6e8960e3734 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -69,7 +69,6 @@ kibana_vars=( logging.appenders logging.appenders.console logging.appenders.file - logging.appenders.rolling-file logging.dest logging.json logging.loggers From 293dc95f8a54d234877a836191af4fac4e04649c Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 23 Jun 2021 19:07:57 +0200 Subject: [PATCH 02/17] [Exploratory view] Refactor code for multi series (#101157) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../app/RumDashboard/ActionMenu/index.tsx | 5 +- .../PageLoadDistribution/index.tsx | 2 +- .../app/RumDashboard/PageViewsTrend/index.tsx | 2 +- .../components/empty_view.tsx | 8 +- .../components/filter_label.test.tsx | 4 + .../components/filter_label.tsx | 6 +- .../configurations/constants/constants.ts | 22 +- .../configurations/default_configs.ts | 14 +- .../configurations/lens_attributes.test.ts | 303 +++++----- .../configurations/lens_attributes.ts | 530 +++++++++++------- .../mobile/device_distribution_config.ts | 2 +- .../synthetics/data_distribution_config.ts | 4 +- .../test_data/sample_attribute.ts | 28 +- .../exploratory_view/configurations/utils.ts | 40 +- .../exploratory_view.test.tsx | 3 +- .../exploratory_view/exploratory_view.tsx | 105 +++- .../exploratory_view/header/header.test.tsx | 2 +- .../shared/exploratory_view/header/header.tsx | 7 +- .../hooks/use_app_index_pattern.tsx | 35 +- .../hooks/use_lens_attributes.ts | 79 ++- .../hooks/use_series_storage.tsx | 29 +- .../shared/exploratory_view/index.tsx | 4 +- .../shared/exploratory_view/rtl_helpers.tsx | 11 +- .../series_builder/columns/chart_types.tsx | 8 +- .../columns/data_types_col.test.tsx | 11 +- .../series_builder/columns/data_types_col.tsx | 6 +- .../columns/date_picker_col.tsx | 11 +- .../columns/operation_type_select.test.tsx | 8 +- .../columns/report_breakdowns.test.tsx | 6 +- .../columns/report_definition_col.test.tsx | 6 +- .../columns/report_definition_col.tsx | 32 +- .../columns/report_definition_field.tsx | 26 +- .../columns/report_filters.test.tsx | 2 +- .../columns/report_types_col.test.tsx | 11 +- .../columns/report_types_col.tsx | 28 +- .../series_builder/last_updated.tsx | 37 ++ .../series_builder/series_builder.tsx | 285 +++++++--- .../series_date_picker/date_range_picker.tsx | 113 ++++ .../series_date_picker/index.tsx | 2 +- .../series_date_picker.test.tsx | 10 +- .../series_editor/columns/breakdowns.test.tsx | 4 +- .../series_editor/columns/date_picker_col.tsx | 11 +- .../series_editor/columns/filter_expanded.tsx | 8 +- .../columns/filter_value_btn.test.tsx | 4 +- .../columns/filter_value_btn.tsx | 4 +- .../series_editor/columns/remove_series.tsx | 4 +- .../series_editor/columns/series_actions.tsx | 92 ++- .../series_editor/selected_filters.test.tsx | 2 +- .../series_editor/selected_filters.tsx | 5 +- .../series_editor/series_editor.tsx | 128 ++--- .../shared/exploratory_view/types.ts | 7 +- .../utils/stringify_kueries.test.ts | 148 +++++ .../utils/stringify_kueries.ts | 37 ++ .../observability/public/routes/index.tsx | 14 + .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../common/charts/ping_histogram.tsx | 2 +- .../common/header/action_menu_content.tsx | 5 +- .../monitor_duration_container.tsx | 2 +- 59 files changed, 1556 insertions(+), 770 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/last_updated.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/date_range_picker.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/utils/stringify_kueries.test.ts create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/utils/stringify_kueries.ts diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ActionMenu/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ActionMenu/index.tsx index 20d930d28599f9..63ba7047696cac 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ActionMenu/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ActionMenu/index.tsx @@ -47,10 +47,11 @@ export function UXActionMenu({ const uxExploratoryViewLink = createExploratoryViewUrl( { - 'ux-series': { + 'ux-series': ({ dataType: 'ux', + isNew: true, time: { from: rangeFrom, to: rangeTo }, - } as SeriesUrl, + } as unknown) as SeriesUrl, }, http?.basePath.get() ); diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx index e0486af6cd6efc..5c63cc24b6fdf9 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx @@ -89,7 +89,7 @@ export function PageLoadDistribution() { { [`${serviceName}-page-views`]: { dataType: 'ux', - reportType: 'dist', + reportType: 'data-distribution', time: { from: rangeFrom!, to: rangeTo! }, reportDefinitions: { 'service.name': serviceName as string[], 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 c45637e5d3c825..667d0b5e4b4dbc 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 @@ -64,7 +64,7 @@ export function PageViewsTrend() { { [`${serviceName}-page-views`]: { dataType: 'ux', - reportType: 'kpi', + reportType: 'kpi-over-time', time: { from: rangeFrom!, to: rangeTo! }, reportDefinitions: { 'service.name': serviceName as string[], 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 ea69a371cedaeb..3566835b1701ca 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 @@ -38,6 +38,12 @@ export function EmptyView({ emptyMessage = SELECTED_DATA_TYPE_FOR_REPORT; } + if (!series) { + emptyMessage = i18n.translate('xpack.observability.expView.seriesEditor.notFound', { + defaultMessage: 'No series found. Please add a series.', + }); + } + return ( {loading && ( @@ -77,7 +83,7 @@ export const EMPTY_LABEL = i18n.translate('xpack.observability.expView.seriesBui export const CHOOSE_REPORT_DEFINITION = i18n.translate( 'xpack.observability.expView.seriesBuilder.emptyReportDefinition', { - defaultMessage: 'Select a report type to create a visualization.', + defaultMessage: 'Select a report definition to create a visualization.', } ); 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 af64e74bca89c6..fe2953edd36d6a 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 @@ -29,6 +29,7 @@ describe('FilterLabel', function () { negate={false} seriesId={'kpi-over-time'} removeFilter={jest.fn()} + indexPattern={mockIndexPattern} /> ); @@ -52,6 +53,7 @@ describe('FilterLabel', function () { negate={false} seriesId={'kpi-over-time'} removeFilter={removeFilter} + indexPattern={mockIndexPattern} /> ); @@ -74,6 +76,7 @@ describe('FilterLabel', function () { negate={false} seriesId={'kpi-over-time'} removeFilter={removeFilter} + indexPattern={mockIndexPattern} /> ); @@ -99,6 +102,7 @@ describe('FilterLabel', function () { negate={true} seriesId={'kpi-over-time'} removeFilter={jest.fn()} + indexPattern={mockIndexPattern} /> ); 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 3d4ba6dc08c377..a08e777c5ea715 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,7 +6,7 @@ */ import React from 'react'; -import { useAppIndexPatternContext } from '../hooks/use_app_index_pattern'; +import { IndexPattern } from '../../../../../../../../src/plugins/data/public'; import { useSeriesFilters } from '../hooks/use_series_filters'; import { FilterValueLabel } from '../../filter_value_label/filter_value_label'; @@ -17,6 +17,7 @@ interface Props { seriesId: string; negate: boolean; definitionFilter?: boolean; + indexPattern: IndexPattern; removeFilter: (field: string, value: string, notVal: boolean) => void; } @@ -26,11 +27,10 @@ export function FilterLabel({ field, value, negate, + indexPattern, removeFilter, definitionFilter, }: Props) { - const { indexPattern } = useAppIndexPatternContext(); - const { invertFilter } = useSeriesFilters({ seriesId }); return indexPattern ? ( diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts index e119507860c5ca..01e8d023ae96bd 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts @@ -5,8 +5,15 @@ * 2.0. */ -import { ReportViewTypeId } from '../../types'; -import { CLS_FIELD, FCP_FIELD, FID_FIELD, LCP_FIELD, TBT_FIELD } from './elasticsearch_fieldnames'; +import { ReportViewType } from '../../types'; +import { + CLS_FIELD, + FCP_FIELD, + FID_FIELD, + LCP_FIELD, + TBT_FIELD, + TRANSACTION_TIME_TO_FIRST_BYTE, +} from './elasticsearch_fieldnames'; import { AGENT_HOST_LABEL, BROWSER_FAMILY_LABEL, @@ -58,6 +65,7 @@ export const FieldLabels: Record = { [TBT_FIELD]: TBT_LABEL, [FID_FIELD]: FID_LABEL, [CLS_FIELD]: CLS_LABEL, + [TRANSACTION_TIME_TO_FIRST_BYTE]: 'Page load time', 'monitor.id': MONITOR_ID_LABEL, 'monitor.status': MONITOR_STATUS_LABEL, @@ -77,11 +85,11 @@ export const FieldLabels: Record = { 'http.request.method': REQUEST_METHOD, }; -export const DataViewLabels: Record = { - dist: PERF_DIST_LABEL, - kpi: KPI_OVER_TIME_LABEL, - cwv: CORE_WEB_VITALS_LABEL, - mdd: DEVICE_DISTRIBUTION_LABEL, +export const DataViewLabels: Record = { + 'data-distribution': PERF_DIST_LABEL, + 'kpi-over-time': KPI_OVER_TIME_LABEL, + 'core-web-vitals': CORE_WEB_VITALS_LABEL, + 'device-data-distribution': DEVICE_DISTRIBUTION_LABEL, }; export const USE_BREAK_DOWN_COLUMN = 'USE_BREAK_DOWN_COLUMN'; 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 07342d976cbea9..574a9f6a2bc103 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,7 +5,7 @@ * 2.0. */ -import { AppDataType, ReportViewTypes } from '../types'; +import { AppDataType, ReportViewType } from '../types'; import { getRumDistributionConfig } from './rum/data_distribution_config'; import { getSyntheticsDistributionConfig } from './synthetics/data_distribution_config'; import { getSyntheticsKPIConfig } from './synthetics/kpi_over_time_config'; @@ -17,7 +17,7 @@ import { getMobileKPIDistributionConfig } from './mobile/distribution_config'; import { getMobileDeviceDistributionConfig } from './mobile/device_distribution_config'; interface Props { - reportType: keyof typeof ReportViewTypes; + reportType: ReportViewType; indexPattern: IndexPattern; dataType: AppDataType; } @@ -25,23 +25,23 @@ interface Props { export const getDefaultConfigs = ({ reportType, dataType, indexPattern }: Props) => { switch (dataType) { case 'ux': - if (reportType === 'dist') { + if (reportType === 'data-distribution') { return getRumDistributionConfig({ indexPattern }); } - if (reportType === 'cwv') { + if (reportType === 'core-web-vitals') { return getCoreWebVitalsConfig({ indexPattern }); } return getKPITrendsLensConfig({ indexPattern }); case 'synthetics': - if (reportType === 'dist') { + if (reportType === 'data-distribution') { return getSyntheticsDistributionConfig({ indexPattern }); } return getSyntheticsKPIConfig({ indexPattern }); case 'mobile': - if (reportType === 'dist') { + if (reportType === 'data-distribution') { return getMobileKPIDistributionConfig({ indexPattern }); } - if (reportType === 'mdd') { + if (reportType === 'device-data-distribution') { return getMobileDeviceDistributionConfig({ indexPattern }); } return getMobileKPIConfig({ indexPattern }); 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 8b21df64a3c910..5189a529bda8f5 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 @@ -5,25 +5,37 @@ * 2.0. */ -import { LensAttributes } from './lens_attributes'; +import { LayerConfig, LensAttributes } from './lens_attributes'; import { mockAppIndexPattern, mockIndexPattern } from '../rtl_helpers'; import { getDefaultConfigs } from './default_configs'; import { sampleAttribute } from './test_data/sample_attribute'; -import { LCP_FIELD, SERVICE_NAME, USER_AGENT_NAME } from './constants/elasticsearch_fieldnames'; +import { LCP_FIELD, USER_AGENT_NAME } from './constants/elasticsearch_fieldnames'; +import { buildExistsFilter, buildPhrasesFilter } from './utils'; describe('Lens Attribute', () => { mockAppIndexPattern(); const reportViewConfig = getDefaultConfigs({ - reportType: 'dist', + reportType: 'data-distribution', dataType: 'ux', indexPattern: mockIndexPattern, }); + reportViewConfig.filters?.push(...buildExistsFilter('transaction.type', mockIndexPattern)); + let lnsAttr: LensAttributes; + const layerConfig: LayerConfig = { + reportConfig: reportViewConfig, + seriesType: 'line', + operationType: 'count', + indexPattern: mockIndexPattern, + reportDefinitions: {}, + time: { from: 'now-15m', to: 'now' }, + }; + beforeEach(() => { - lnsAttr = new LensAttributes(mockIndexPattern, reportViewConfig, 'line', [], 'count', {}); + lnsAttr = new LensAttributes([layerConfig]); }); it('should return expected json', function () { @@ -31,7 +43,7 @@ describe('Lens Attribute', () => { }); it('should return main y axis', function () { - expect(lnsAttr.getMainYAxis()).toEqual({ + expect(lnsAttr.getMainYAxis(layerConfig)).toEqual({ dataType: 'number', isBucketed: false, label: 'Pages loaded', @@ -42,7 +54,7 @@ describe('Lens Attribute', () => { }); it('should return expected field type', function () { - expect(JSON.stringify(lnsAttr.getFieldMeta('transaction.type'))).toEqual( + expect(JSON.stringify(lnsAttr.getFieldMeta('transaction.type', layerConfig))).toEqual( JSON.stringify({ fieldMeta: { count: 0, @@ -60,7 +72,7 @@ describe('Lens Attribute', () => { }); it('should return expected field type for custom field with default value', function () { - expect(JSON.stringify(lnsAttr.getFieldMeta('performance.metric'))).toEqual( + expect(JSON.stringify(lnsAttr.getFieldMeta('performance.metric', layerConfig))).toEqual( JSON.stringify({ fieldMeta: { count: 0, @@ -79,11 +91,18 @@ describe('Lens Attribute', () => { }); it('should return expected field type for custom field with passed value', function () { - lnsAttr = new LensAttributes(mockIndexPattern, reportViewConfig, 'line', [], 'count', { - 'performance.metric': [LCP_FIELD], - }); + const layerConfig1: LayerConfig = { + reportConfig: reportViewConfig, + seriesType: 'line', + operationType: 'count', + indexPattern: mockIndexPattern, + reportDefinitions: { 'performance.metric': [LCP_FIELD] }, + time: { from: 'now-15m', to: 'now' }, + }; - expect(JSON.stringify(lnsAttr.getFieldMeta('performance.metric'))).toEqual( + lnsAttr = new LensAttributes([layerConfig1]); + + expect(JSON.stringify(lnsAttr.getFieldMeta('performance.metric', layerConfig1))).toEqual( JSON.stringify({ fieldMeta: { count: 0, @@ -102,7 +121,7 @@ describe('Lens Attribute', () => { }); it('should return expected number range column', function () { - expect(lnsAttr.getNumberRangeColumn('transaction.duration.us')).toEqual({ + expect(lnsAttr.getNumberRangeColumn('transaction.duration.us', reportViewConfig)).toEqual({ dataType: 'number', isBucketed: true, label: 'Page load time', @@ -124,7 +143,7 @@ describe('Lens Attribute', () => { }); it('should return expected number operation column', function () { - expect(lnsAttr.getNumberRangeColumn('transaction.duration.us')).toEqual({ + expect(lnsAttr.getNumberRangeColumn('transaction.duration.us', reportViewConfig)).toEqual({ dataType: 'number', isBucketed: true, label: 'Page load time', @@ -160,7 +179,7 @@ describe('Lens Attribute', () => { }); it('should return main x axis', function () { - expect(lnsAttr.getXAxis()).toEqual({ + expect(lnsAttr.getXAxis(layerConfig, 'layer0')).toEqual({ dataType: 'number', isBucketed: true, label: 'Page load time', @@ -182,38 +201,45 @@ describe('Lens Attribute', () => { }); 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', + expect(lnsAttr.getLayers()).toEqual({ + layer0: { + columnOrder: ['x-axis-column-layer0', 'y-axis-column-layer0'], + columns: { + 'x-axis-column-layer0': { + 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-layer0': { + dataType: 'number', + isBucketed: false, + label: 'Pages loaded', + operationType: 'count', + scale: 'ratio', + sourceField: 'Records', + filter: { + language: 'kuery', + query: + 'transaction.type: page-load and processor.event: transaction and transaction.type : *', + }, }, - scale: 'interval', - sourceField: 'transaction.duration.us', - }, - 'y-axis-column': { - dataType: 'number', - isBucketed: false, - label: 'Pages loaded', - operationType: 'count', - scale: 'ratio', - sourceField: 'Records', }, + incompleteColumns: {}, }, - incompleteColumns: {}, }); }); @@ -225,12 +251,12 @@ describe('Lens Attribute', () => { gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, layers: [ { - accessors: ['y-axis-column'], - layerId: 'layer1', + accessors: ['y-axis-column-layer0'], + layerId: 'layer0', palette: undefined, seriesType: 'line', - xAccessor: 'x-axis-column', - yConfig: [{ color: 'green', forAccessor: 'y-axis-column' }], + xAccessor: 'x-axis-column-layer0', + yConfig: [{ forAccessor: 'y-axis-column-layer0' }], }, ], legend: { isVisible: true, position: 'right' }, @@ -240,108 +266,52 @@ describe('Lens Attribute', () => { }); }); - 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', - {} - ); + describe('Layer breakdowns', function () { + it('should return breakdown column', function () { + const layerConfig1: LayerConfig = { + reportConfig: reportViewConfig, + seriesType: 'line', + operationType: 'count', + indexPattern: mockIndexPattern, + reportDefinitions: { 'performance.metric': [LCP_FIELD] }, + breakdown: USER_AGENT_NAME, + time: { from: 'now-15m', to: 'now' }, + }; - 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', - }, - }, - }, - ]); - }); - }); + lnsAttr = new LensAttributes([layerConfig1]); - describe('Layer breakdowns', function () { - it('should add breakdown column', function () { - lnsAttr.addBreakdown(USER_AGENT_NAME); + lnsAttr.getBreakdownColumn({ + sourceField: USER_AGENT_NAME, + layerId: 'layer0', + indexPattern: mockIndexPattern, + }); expect(lnsAttr.visualization.layers).toEqual([ { - accessors: ['y-axis-column'], - layerId: 'layer1', + accessors: ['y-axis-column-layer0'], + layerId: 'layer0', palette: undefined, seriesType: 'line', - splitAccessor: 'break-down-column', - xAccessor: 'x-axis-column', - yConfig: [{ color: 'green', forAccessor: 'y-axis-column' }], + splitAccessor: 'breakdown-column-layer0', + xAccessor: 'x-axis-column-layer0', + yConfig: [{ forAccessor: 'y-axis-column-layer0' }], }, ]); - expect(lnsAttr.layers.layer1).toEqual({ - columnOrder: ['x-axis-column', 'break-down-column', 'y-axis-column'], + expect(lnsAttr.layers.layer0).toEqual({ + columnOrder: ['x-axis-column-layer0', 'breakdown-column-layer0', 'y-axis-column-layer0'], columns: { - 'break-down-column': { + 'breakdown-column-layer0': { dataType: 'string', isBucketed: true, label: 'Top values of Browser family', operationType: 'terms', params: { missingBucket: false, - orderBy: { columnId: 'y-axis-column', type: 'column' }, + orderBy: { + columnId: 'y-axis-column-layer0', + type: 'column', + }, orderDirection: 'desc', otherBucket: true, size: 10, @@ -349,10 +319,10 @@ describe('Lens Attribute', () => { scale: 'ordinal', sourceField: 'user_agent.name', }, - 'x-axis-column': { + 'x-axis-column-layer0': { dataType: 'number', isBucketed: true, - label: 'Page load time', + label: 'Largest contentful paint', operationType: 'range', params: { maxBars: 'auto', @@ -360,62 +330,47 @@ describe('Lens Attribute', () => { type: 'histogram', }, scale: 'interval', - sourceField: 'transaction.duration.us', + sourceField: 'transaction.marks.agent.largestContentfulPaint', }, - 'y-axis-column': { + 'y-axis-column-layer0': { dataType: 'number', isBucketed: false, label: 'Pages loaded', operationType: 'count', scale: 'ratio', sourceField: 'Records', + filter: { + language: 'kuery', + query: + 'transaction.type: page-load and processor.event: transaction and transaction.type : *', + }, }, }, incompleteColumns: {}, }); }); + }); - it('should remove breakdown column', function () { - lnsAttr.addBreakdown(USER_AGENT_NAME); - - lnsAttr.removeBreakdown(); + describe('Layer Filters', function () { + it('should return expected filters', function () { + reportViewConfig.filters?.push( + ...buildPhrasesFilter('service.name', ['elastic', 'kibana'], mockIndexPattern) + ); - 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' }], - }, - ]); + const layerConfig1: LayerConfig = { + reportConfig: reportViewConfig, + seriesType: 'line', + operationType: 'count', + indexPattern: mockIndexPattern, + reportDefinitions: { 'performance.metric': [LCP_FIELD] }, + time: { from: 'now-15m', to: 'now' }, + }; - expect(lnsAttr.layers.layer1.columnOrder).toEqual(['x-axis-column', 'y-axis-column']); + const filters = lnsAttr.getLayerFilters(layerConfig1, 2); - 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', - }, - }); + expect(filters).toEqual( + '@timestamp >= now-15m and @timestamp <= now and transaction.type: page-load and processor.event: transaction and transaction.type : * and service.name: (elastic or kibana)' + ); }); }); }); 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 22ad18c663b322..208e8d8ba43c28 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,13 +27,12 @@ import { TermsIndexPatternColumn, CardinalityIndexPatternColumn, } from '../../../../../../lens/public'; -import { - buildPhraseFilter, - buildPhrasesFilter, - IndexPattern, -} from '../../../../../../../../src/plugins/data/common'; +import { urlFiltersToKueryString } from '../utils/stringify_kueries'; +import { ExistsFilter, IndexPattern } from '../../../../../../../../src/plugins/data/common'; import { FieldLabels, FILTER_RECORDS, USE_BREAK_DOWN_COLUMN, TERMS_COLUMN } from './constants'; import { ColumnFilter, DataSeries, UrlFilter, URLReportDefinition } from '../types'; +import { PersistableFilter } from '../../../../../../lens/common'; +import { parseAbsoluteDate } from '../series_date_picker/date_range_picker'; function getLayerReferenceName(layerId: string) { return `indexpattern-datasource-layer-${layerId}`; @@ -87,46 +86,50 @@ export const parseCustomFieldName = ( return { fieldName, columnType, columnFilters, timeScale, columnLabel }; }; -export class LensAttributes { +export interface LayerConfig { + filters?: UrlFilter[]; + reportConfig: DataSeries; + breakdown?: string; + seriesType?: SeriesType; + operationType?: OperationType; + reportDefinitions: URLReportDefinition; + time: { to: string; from: string }; indexPattern: IndexPattern; +} + +export class LensAttributes { layers: Record; visualization: XYState; - filters: UrlFilter[]; - seriesType: SeriesType; - reportViewConfig: DataSeries; - reportDefinitions: URLReportDefinition; - breakdownSource?: string; + layerConfigs: LayerConfig[]; - constructor( - indexPattern: IndexPattern, - reportViewConfig: DataSeries, - seriesType?: SeriesType, - filters?: UrlFilter[], - operationType?: OperationType, - reportDefinitions?: URLReportDefinition, - breakdownSource?: string - ) { - this.indexPattern = indexPattern; + constructor(layerConfigs: LayerConfig[]) { this.layers = {}; - this.filters = filters ?? []; - this.reportDefinitions = reportDefinitions ?? {}; - this.breakdownSource = breakdownSource; - - if (operationType) { - reportViewConfig.yAxisColumns.forEach((yAxisColumn) => { - if (typeof yAxisColumn.operationType !== undefined) { - yAxisColumn.operationType = operationType as FieldBasedIndexPatternColumn['operationType']; - } - }); - } - this.seriesType = seriesType ?? reportViewConfig.defaultSeriesType; - this.reportViewConfig = reportViewConfig; - this.layers.layer1 = this.getLayer(); + + layerConfigs.forEach(({ reportConfig, operationType }) => { + if (operationType) { + reportConfig.yAxisColumns.forEach((yAxisColumn) => { + if (typeof yAxisColumn.operationType !== undefined) { + yAxisColumn.operationType = operationType as FieldBasedIndexPatternColumn['operationType']; + } + }); + } + }); + + this.layerConfigs = layerConfigs; + this.layers = this.getLayers(); this.visualization = this.getXyState(); } - getBreakdownColumn(sourceField: string): TermsIndexPatternColumn { - const fieldMeta = this.indexPattern.getFieldByName(sourceField); + getBreakdownColumn({ + sourceField, + layerId, + indexPattern, + }: { + sourceField: string; + layerId: string; + indexPattern: IndexPattern; + }): TermsIndexPatternColumn { + const fieldMeta = indexPattern.getFieldByName(sourceField); return { sourceField, @@ -136,8 +139,8 @@ export class LensAttributes { scale: 'ordinal', isBucketed: true, params: { + orderBy: { type: 'column', columnId: `y-axis-column-${layerId}` }, size: 10, - orderBy: { type: 'column', columnId: 'y-axis-column' }, orderDirection: 'desc', otherBucket: true, missingBucket: false, @@ -145,36 +148,14 @@ export class LensAttributes { }; } - addBreakdown(sourceField: string) { - const { xAxisColumn } = this.reportViewConfig; - if (xAxisColumn?.sourceField === USE_BREAK_DOWN_COLUMN) { - // do nothing since this will be used a x axis source - return; - } - this.layers.layer1.columns['break-down-column'] = this.getBreakdownColumn(sourceField); - - this.layers.layer1.columnOrder = [ - 'x-axis-column', - 'break-down-column', - 'y-axis-column', - ...Object.keys(this.getChildYAxises()), - ]; - - this.visualization.layers[0].splitAccessor = 'break-down-column'; - } - - removeBreakdown() { - delete this.layers.layer1.columns['break-down-column']; - - this.layers.layer1.columnOrder = ['x-axis-column', 'y-axis-column']; - - this.visualization.layers[0].splitAccessor = undefined; - } - - getNumberRangeColumn(sourceField: string, label?: string): RangeIndexPatternColumn { + getNumberRangeColumn( + sourceField: string, + reportViewConfig: DataSeries, + label?: string + ): RangeIndexPatternColumn { return { sourceField, - label: this.reportViewConfig.labels[sourceField] ?? label, + label: reportViewConfig.labels[sourceField] ?? label, dataType: 'number', operationType: 'range', isBucketed: true, @@ -187,16 +168,36 @@ export class LensAttributes { }; } - getCardinalityColumn(sourceField: string, label?: string) { - return this.getNumberOperationColumn(sourceField, 'unique_count', label); + getCardinalityColumn({ + sourceField, + label, + reportViewConfig, + }: { + sourceField: string; + label?: string; + reportViewConfig: DataSeries; + }) { + return this.getNumberOperationColumn({ + sourceField, + operationType: 'unique_count', + label, + reportViewConfig, + }); } - getNumberColumn( - sourceField: string, - columnType?: string, - operationType?: string, - label?: string - ) { + getNumberColumn({ + reportViewConfig, + label, + sourceField, + columnType, + operationType, + }: { + sourceField: string; + columnType?: string; + operationType?: string; + label?: string; + reportViewConfig: DataSeries; + }) { if (columnType === 'operation' || operationType) { if ( operationType === 'median' || @@ -204,48 +205,58 @@ export class LensAttributes { operationType === 'sum' || operationType === 'unique_count' ) { - return this.getNumberOperationColumn(sourceField, operationType, label); + return this.getNumberOperationColumn({ + sourceField, + operationType, + label, + reportViewConfig, + }); } if (operationType?.includes('th')) { - return this.getPercentileNumberColumn(sourceField, operationType); + return this.getPercentileNumberColumn(sourceField, operationType, reportViewConfig!); } } - return this.getNumberRangeColumn(sourceField, label); + return this.getNumberRangeColumn(sourceField, reportViewConfig!, label); } - getNumberOperationColumn( - sourceField: string, - operationType: 'average' | 'median' | 'sum' | 'unique_count', - label?: string - ): + getNumberOperationColumn({ + sourceField, + label, + reportViewConfig, + operationType, + }: { + sourceField: string; + operationType: 'average' | 'median' | 'sum' | 'unique_count'; + label?: string; + reportViewConfig: DataSeries; + }): | AvgIndexPatternColumn | MedianIndexPatternColumn | SumIndexPatternColumn | CardinalityIndexPatternColumn { return { ...buildNumberColumn(sourceField), - label: - label || - i18n.translate('xpack.observability.expView.columns.operation.label', { - defaultMessage: '{operationType} of {sourceField}', - values: { - sourceField: this.reportViewConfig.labels[sourceField], - operationType: capitalize(operationType), - }, - }), + label: i18n.translate('xpack.observability.expView.columns.operation.label', { + defaultMessage: '{operationType} of {sourceField}', + values: { + sourceField: label || reportViewConfig.labels[sourceField], + operationType: capitalize(operationType), + }, + }), operationType, }; } getPercentileNumberColumn( sourceField: string, - percentileValue: string + percentileValue: string, + reportViewConfig: DataSeries ): PercentileIndexPatternColumn { return { ...buildNumberColumn(sourceField), label: i18n.translate('xpack.observability.expView.columns.label', { defaultMessage: '{percentileValue} percentile of {sourceField}', - values: { sourceField: this.reportViewConfig.labels[sourceField], percentileValue }, + values: { sourceField: reportViewConfig.labels[sourceField], percentileValue }, }), operationType: 'percentile', params: { percentile: Number(percentileValue.split('th')[0]) }, @@ -268,7 +279,7 @@ export class LensAttributes { return { operationType: 'terms', sourceField, - label: label || 'Top values of ' + sourceField, + label: 'Top values of ' + label || sourceField, dataType: 'string', isBucketed: true, scale: 'ordinal', @@ -283,30 +294,45 @@ export class LensAttributes { }; } - getXAxis() { - const { xAxisColumn } = this.reportViewConfig; + getXAxis(layerConfig: LayerConfig, layerId: string) { + const { xAxisColumn } = layerConfig.reportConfig; if (xAxisColumn?.sourceField === USE_BREAK_DOWN_COLUMN) { - return this.getBreakdownColumn(this.breakdownSource || this.reportViewConfig.breakdowns[0]); + return this.getBreakdownColumn({ + layerId, + indexPattern: layerConfig.indexPattern, + sourceField: layerConfig.breakdown || layerConfig.reportConfig.breakdowns[0], + }); } - return this.getColumnBasedOnType(xAxisColumn.sourceField!, undefined, xAxisColumn.label); + return this.getColumnBasedOnType({ + layerConfig, + label: xAxisColumn.label, + sourceField: xAxisColumn.sourceField!, + }); } - getColumnBasedOnType( - sourceField: string, - operationType?: OperationType, - label?: string, - colIndex?: number - ) { + getColumnBasedOnType({ + sourceField, + label, + layerConfig, + operationType, + colIndex, + }: { + sourceField: string; + operationType?: OperationType; + label?: string; + layerConfig: LayerConfig; + colIndex?: number; + }) { const { fieldMeta, columnType, fieldName, - columnFilters, - timeScale, columnLabel, - } = this.getFieldMeta(sourceField); + timeScale, + columnFilters, + } = this.getFieldMeta(sourceField, layerConfig); const { type: fieldType } = fieldMeta ?? {}; if (columnType === TERMS_COLUMN) { @@ -325,47 +351,76 @@ export class LensAttributes { return this.getDateHistogramColumn(fieldName); } if (fieldType === 'number') { - return this.getNumberColumn(fieldName, columnType, operationType, columnLabel || label); + return this.getNumberColumn({ + sourceField: fieldName, + columnType, + operationType, + label: columnLabel || label, + reportViewConfig: layerConfig.reportConfig, + }); } if (operationType === 'unique_count') { - return this.getCardinalityColumn(fieldName, columnLabel || label); + return this.getCardinalityColumn({ + sourceField: fieldName, + label: columnLabel || label, + reportViewConfig: layerConfig.reportConfig, + }); } // FIXME review my approach again return this.getDateHistogramColumn(fieldName); } - getCustomFieldName(sourceField: string) { - return parseCustomFieldName(sourceField, this.reportViewConfig, this.reportDefinitions); + getCustomFieldName({ + sourceField, + layerConfig, + }: { + sourceField: string; + layerConfig: LayerConfig; + }) { + return parseCustomFieldName( + sourceField, + layerConfig.reportConfig, + layerConfig.reportDefinitions + ); } - getFieldMeta(sourceField: string) { + getFieldMeta(sourceField: string, layerConfig: LayerConfig) { const { fieldName, columnType, + columnLabel, columnFilters, timeScale, - columnLabel, - } = this.getCustomFieldName(sourceField); + } = this.getCustomFieldName({ + sourceField, + layerConfig, + }); - const fieldMeta = this.indexPattern.getFieldByName(fieldName); + const fieldMeta = layerConfig.indexPattern.getFieldByName(fieldName); - return { fieldMeta, fieldName, columnType, columnFilters, timeScale, columnLabel }; + return { fieldMeta, fieldName, columnType, columnLabel, columnFilters, timeScale }; } - getMainYAxis() { - const { sourceField, operationType, label } = this.reportViewConfig.yAxisColumns[0]; + getMainYAxis(layerConfig: LayerConfig) { + const { sourceField, operationType, label } = layerConfig.reportConfig.yAxisColumns[0]; if (sourceField === 'Records' || !sourceField) { return this.getRecordsColumn(label); } - return this.getColumnBasedOnType(sourceField!, operationType, label, 0); + return this.getColumnBasedOnType({ + sourceField, + operationType, + label, + layerConfig, + colIndex: 0, + }); } - getChildYAxises() { + getChildYAxises(layerConfig: LayerConfig) { const lensColumns: Record = {}; - const yAxisColumns = this.reportViewConfig.yAxisColumns; + const yAxisColumns = layerConfig.reportConfig.yAxisColumns; // 1 means there is only main y axis if (yAxisColumns.length === 1) { return lensColumns; @@ -373,12 +428,13 @@ export class LensAttributes { for (let i = 1; i < yAxisColumns.length; i++) { const { sourceField, operationType, label } = yAxisColumns[i]; - lensColumns[`y-axis-column-${i}`] = this.getColumnBasedOnType( - sourceField!, + lensColumns[`y-axis-column-${i}`] = this.getColumnBasedOnType({ + sourceField: sourceField!, operationType, label, - i - ); + layerConfig, + colIndex: i, + }); } return lensColumns; } @@ -396,20 +452,139 @@ export class LensAttributes { scale: 'ratio', sourceField: 'Records', filter: columnFilter, - timeScale, + ...(timeScale ? { timeScale } : {}), } as CountIndexPatternColumn; } - getLayer() { - return { - columnOrder: ['x-axis-column', 'y-axis-column', ...Object.keys(this.getChildYAxises())], - columns: { - 'x-axis-column': this.getXAxis(), - 'y-axis-column': this.getMainYAxis(), - ...this.getChildYAxises(), - }, - incompleteColumns: {}, - }; + getLayerFilters(layerConfig: LayerConfig, totalLayers: number) { + const { + filters, + time: { from, to }, + reportConfig: { filters: layerFilters, reportType }, + } = layerConfig; + let baseFilters = ''; + if (reportType !== 'kpi-over-time' && totalLayers > 1) { + // for kpi over time, we don't need to add time range filters + // since those are essentially plotted along the x-axis + baseFilters += `@timestamp >= ${from} and @timestamp <= ${to}`; + } + + layerFilters?.forEach((filter: PersistableFilter | ExistsFilter) => { + const qFilter = filter as PersistableFilter; + if (qFilter.query?.match_phrase) { + const fieldName = Object.keys(qFilter.query.match_phrase)[0]; + const kql = `${fieldName}: ${qFilter.query.match_phrase[fieldName]}`; + if (baseFilters.length > 0) { + baseFilters += ` and ${kql}`; + } else { + baseFilters += kql; + } + } + if (qFilter.query?.bool?.should) { + const values: string[] = []; + let fieldName = ''; + qFilter.query?.bool.should.forEach((ft: PersistableFilter['query']['match_phrase']) => { + if (ft.match_phrase) { + fieldName = Object.keys(ft.match_phrase)[0]; + values.push(ft.match_phrase[fieldName]); + } + }); + + const kueryString = `${fieldName}: (${values.join(' or ')})`; + + if (baseFilters.length > 0) { + baseFilters += ` and ${kueryString}`; + } else { + baseFilters += kueryString; + } + } + const existFilter = filter as ExistsFilter; + + if (existFilter.exists) { + const fieldName = existFilter.exists.field; + const kql = `${fieldName} : *`; + if (baseFilters.length > 0) { + baseFilters += ` and ${kql}`; + } else { + baseFilters += kql; + } + } + }); + + const rFilters = urlFiltersToKueryString(filters ?? []); + if (!baseFilters) { + return rFilters; + } + if (!rFilters) { + return baseFilters; + } + return `${rFilters} and ${baseFilters}`; + } + + getTimeShift(mainLayerConfig: LayerConfig, layerConfig: LayerConfig, index: number) { + if (index === 0 || mainLayerConfig.reportConfig.reportType !== 'kpi-over-time') { + return null; + } + + const { + time: { from: mainFrom }, + } = mainLayerConfig; + + const { + time: { from }, + } = layerConfig; + + const inDays = parseAbsoluteDate(mainFrom).diff(parseAbsoluteDate(from), 'days'); + if (inDays > 1) { + return inDays + 'd'; + } + const inHours = parseAbsoluteDate(mainFrom).diff(parseAbsoluteDate(from), 'hours'); + return inHours + 'h'; + } + + getLayers() { + const layers: Record = {}; + const layerConfigs = this.layerConfigs; + + layerConfigs.forEach((layerConfig, index) => { + const { breakdown } = layerConfig; + + const layerId = `layer${index}`; + const columnFilter = this.getLayerFilters(layerConfig, layerConfigs.length); + const timeShift = this.getTimeShift(this.layerConfigs[0], layerConfig, index); + const mainYAxis = this.getMainYAxis(layerConfig); + layers[layerId] = { + columnOrder: [ + `x-axis-column-${layerId}`, + ...(breakdown ? [`breakdown-column-${layerId}`] : []), + `y-axis-column-${layerId}`, + ...Object.keys(this.getChildYAxises(layerConfig)), + ], + columns: { + [`x-axis-column-${layerId}`]: this.getXAxis(layerConfig, layerId), + [`y-axis-column-${layerId}`]: { + ...mainYAxis, + label: timeShift ? `${mainYAxis.label}(${timeShift})` : mainYAxis.label, + filter: { query: columnFilter, language: 'kuery' }, + ...(timeShift ? { timeShift } : {}), + }, + ...(breakdown && breakdown !== USE_BREAK_DOWN_COLUMN + ? // do nothing since this will be used a x axis source + { + [`breakdown-column-${layerId}`]: this.getBreakdownColumn({ + layerId, + sourceField: breakdown, + indexPattern: layerConfig.indexPattern, + }), + } + : {}), + ...this.getChildYAxises(layerConfig), + }, + incompleteColumns: {}, + }; + }); + + return layers; } getXyState(): XYState { @@ -422,71 +597,48 @@ export class LensAttributes { tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true }, gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, preferredSeriesType: 'line', - layers: [ - { - accessors: ['y-axis-column', ...Object.keys(this.getChildYAxises())], - layerId: 'layer1', - seriesType: this.seriesType ?? 'line', - palette: this.reportViewConfig.palette, - yConfig: this.reportViewConfig.yConfig || [ - { forAccessor: 'y-axis-column', color: 'green' }, - ], - xAccessor: 'x-axis-column', - }, - ], - ...(this.reportViewConfig.yTitle ? { yTitle: this.reportViewConfig.yTitle } : {}), + layers: this.layerConfigs.map((layerConfig, index) => ({ + accessors: [ + `y-axis-column-layer${index}`, + ...Object.keys(this.getChildYAxises(layerConfig)), + ], + layerId: `layer${index}`, + seriesType: layerConfig.seriesType || layerConfig.reportConfig.defaultSeriesType, + palette: layerConfig.reportConfig.palette, + yConfig: layerConfig.reportConfig.yConfig || [ + { forAccessor: `y-axis-column-layer${index}` }, + ], + xAccessor: `x-axis-column-layer${index}`, + ...(layerConfig.breakdown ? { splitAccessor: `breakdown-column-layer${index}` } : {}), + })), + ...(this.layerConfigs[0].reportConfig.yTitle + ? { yTitle: this.layerConfigs[0].reportConfig.yTitle } + : {}), }; } - parseFilters() { - const defaultFilters = this.reportViewConfig.filters ?? []; - const parsedFilters = this.reportViewConfig.filters ? [...defaultFilters] : []; - - this.filters.forEach(({ field, values = [], notValues = [] }) => { - 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); - } else { - const filter = buildPhraseFilter(fieldMeta, values[0], this.indexPattern); - parsedFilters.push(filter); - } - } - - if (notValues?.length > 0) { - if (notValues?.length > 1) { - const multiFilter = buildPhrasesFilter(fieldMeta, notValues, this.indexPattern); - multiFilter.meta.negate = true; - parsedFilters.push(multiFilter); - } else { - const filter = buildPhraseFilter(fieldMeta, notValues[0], this.indexPattern); - filter.meta.negate = true; - parsedFilters.push(filter); - } - } - }); - - return parsedFilters; - } + parseFilters() {} getJSON(): TypedLensByValueInput['attributes'] { + const uniqueIndexPatternsIds = Array.from( + new Set([...this.layerConfigs.map(({ indexPattern }) => indexPattern.id)]) + ); + return { title: 'Prefilled from exploratory view app', description: '', visualizationType: 'lnsXY', references: [ - { - id: this.indexPattern.id!, + ...uniqueIndexPatternsIds.map((patternId) => ({ + id: patternId!, name: 'indexpattern-datasource-current-indexpattern', type: 'index-pattern', - }, - { - id: this.indexPattern.id!, - name: getLayerReferenceName('layer1'), + })), + ...this.layerConfigs.map(({ indexPattern }, index) => ({ + id: indexPattern.id!, + name: getLayerReferenceName(`layer${index}`), type: 'index-pattern', - }, + })), ], state: { datasourceStates: { @@ -496,7 +648,7 @@ export class LensAttributes { }, visualization: this.visualization, query: { query: '', language: 'kuery' }, - filters: this.parseFilters(), + filters: [], }, }; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/device_distribution_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/device_distribution_config.ts index 6f9806660e4895..e1cb5a0370fb22 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/device_distribution_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/device_distribution_config.ts @@ -14,7 +14,7 @@ import { MobileFields } from './mobile_fields'; export function getMobileDeviceDistributionConfig({ indexPattern }: ConfigProps): DataSeries { return { - reportType: 'mobile-device-distribution', + reportType: 'device-data-distribution', defaultSeriesType: 'bar', seriesTypes: ['bar', 'bar_horizontal'], xAxisColumn: { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/data_distribution_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/data_distribution_config.ts index 854f844db047d9..b958c0dd715286 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/data_distribution_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/data_distribution_config.ts @@ -10,10 +10,10 @@ import { FieldLabels, RECORDS_FIELD } from '../constants'; import { buildExistsFilter } from '../utils'; import { MONITORS_DURATION_LABEL, PINGS_LABEL } from '../constants/labels'; -export function getSyntheticsDistributionConfig({ indexPattern }: ConfigProps): DataSeries { +export function getSyntheticsDistributionConfig({ series, indexPattern }: ConfigProps): DataSeries { return { reportType: 'data-distribution', - defaultSeriesType: 'line', + defaultSeriesType: series?.seriesType || 'line', seriesTypes: [], xAxisColumn: { sourceField: 'performance.metric', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts index 9b299e7d70bcc4..edf2a424158205 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts @@ -10,16 +10,16 @@ export const sampleAttribute = { visualizationType: 'lnsXY', references: [ { id: 'apm-*', name: 'indexpattern-datasource-current-indexpattern', type: 'index-pattern' }, - { id: 'apm-*', name: 'indexpattern-datasource-layer-layer1', type: 'index-pattern' }, + { id: 'apm-*', name: 'indexpattern-datasource-layer-layer0', type: 'index-pattern' }, ], state: { datasourceStates: { indexpattern: { layers: { - layer1: { - columnOrder: ['x-axis-column', 'y-axis-column'], + layer0: { + columnOrder: ['x-axis-column-layer0', 'y-axis-column-layer0'], columns: { - 'x-axis-column': { + 'x-axis-column-layer0': { sourceField: 'transaction.duration.us', label: 'Page load time', dataType: 'number', @@ -32,13 +32,18 @@ export const sampleAttribute = { maxBars: 'auto', }, }, - 'y-axis-column': { + 'y-axis-column-layer0': { dataType: 'number', isBucketed: false, label: 'Pages loaded', operationType: 'count', scale: 'ratio', sourceField: 'Records', + filter: { + language: 'kuery', + query: + 'transaction.type: page-load and processor.event: transaction and transaction.type : *', + }, }, }, incompleteColumns: {}, @@ -57,18 +62,15 @@ export const sampleAttribute = { preferredSeriesType: 'line', layers: [ { - accessors: ['y-axis-column'], - layerId: 'layer1', + accessors: ['y-axis-column-layer0'], + layerId: 'layer0', seriesType: 'line', - yConfig: [{ forAccessor: 'y-axis-column', color: 'green' }], - xAccessor: 'x-axis-column', + yConfig: [{ forAccessor: 'y-axis-column-layer0' }], + xAccessor: 'x-axis-column-layer0', }, ], }, query: { query: '', language: 'kuery' }, - filters: [ - { meta: { index: 'apm-*' }, query: { match_phrase: { 'transaction.type': 'page-load' } } }, - { meta: { index: 'apm-*' }, query: { match_phrase: { 'processor.event': 'transaction' } } }, - ], + filters: [], }, }; 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 fc60800bc4403e..9b1e7ec141ca2a 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 @@ -5,11 +5,12 @@ * 2.0. */ import rison, { RisonValue } from 'rison-node'; +import type { SeriesUrl, UrlFilter } from '../types'; import type { AllSeries, AllShortSeries } from '../hooks/use_series_storage'; -import type { SeriesUrl } from '../types'; import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns'; -import { esFilters } from '../../../../../../../../src/plugins/data/public'; +import { esFilters, ExistsFilter } from '../../../../../../../../src/plugins/data/public'; import { URL_KEYS } from './constants/url_constants'; +import { PersistableFilter } from '../../../../../../lens/common'; export function convertToShortUrl(series: SeriesUrl) { const { @@ -51,7 +52,7 @@ export function createExploratoryViewUrl(allSeries: AllSeries, baseHref = '') { } export function buildPhraseFilter(field: string, value: string, indexPattern: IIndexPattern) { - const fieldMeta = indexPattern.fields.find((fieldT) => fieldT.name === field); + const fieldMeta = indexPattern?.fields.find((fieldT) => fieldT.name === field); if (fieldMeta) { return [esFilters.buildPhraseFilter(fieldMeta, value, indexPattern)]; } @@ -59,7 +60,7 @@ export function buildPhraseFilter(field: string, value: string, indexPattern: II } export function buildPhrasesFilter(field: string, value: string[], indexPattern: IIndexPattern) { - const fieldMeta = indexPattern.fields.find((fieldT) => fieldT.name === field); + const fieldMeta = indexPattern?.fields.find((fieldT) => fieldT.name === field); if (fieldMeta) { return [esFilters.buildPhrasesFilter(fieldMeta, value, indexPattern)]; } @@ -67,9 +68,38 @@ export function buildPhrasesFilter(field: string, value: string[], indexPattern: } export function buildExistsFilter(field: string, indexPattern: IIndexPattern) { - const fieldMeta = indexPattern.fields.find((fieldT) => fieldT.name === field); + const fieldMeta = indexPattern?.fields.find((fieldT) => fieldT.name === field); if (fieldMeta) { return [esFilters.buildExistsFilter(fieldMeta, indexPattern)]; } return []; } + +type FiltersType = PersistableFilter[] | ExistsFilter[]; + +export function urlFilterToPersistedFilter({ + urlFilters, + initFilters, + indexPattern, +}: { + urlFilters: UrlFilter[]; + initFilters: FiltersType; + indexPattern: IIndexPattern; +}) { + const parsedFilters: FiltersType = initFilters ? [...initFilters] : []; + + urlFilters.forEach(({ field, values = [], notValues = [] }) => { + if (values?.length > 0) { + const filter = buildPhrasesFilter(field, values, indexPattern); + parsedFilters.push(...filter); + } + + if (notValues?.length > 0) { + const filter = buildPhrasesFilter(field, notValues, indexPattern)[0]; + filter.meta.negate = true; + parsedFilters.push(filter); + } + }); + + return parsedFilters; +} 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 779049601bd6d5..989ebf17c20622 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 @@ -51,8 +51,9 @@ describe('ExploratoryView', () => { const initSeries = { data: { 'ux-series': { + isNew: true, dataType: 'ux' as const, - reportType: 'dist' as const, + reportType: 'data-distribution' as const, breakdown: 'user_agent .name', reportDefinitions: { 'service.name': ['elastic-co'] }, time: { from: 'now-15m', to: 'now' }, 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 329ed20ffed3d4..ad85ecab968b21 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,9 +5,10 @@ * 2.0. */ import { i18n } from '@kbn/i18n'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState, useCallback } from 'react'; import { EuiPanel, EuiTitle } from '@elastic/eui'; import styled from 'styled-components'; +import { isEmpty } from 'lodash'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { ObservabilityPublicPluginsStart } from '../../../plugin'; import { ExploratoryViewHeader } from './header/header'; @@ -17,10 +18,37 @@ import { EmptyView } from './components/empty_view'; import { TypedLensByValueInput } from '../../../../../lens/public'; import { useAppIndexPatternContext } from './hooks/use_app_index_pattern'; import { SeriesBuilder } from './series_builder/series_builder'; +import { SeriesUrl } from './types'; + +export const combineTimeRanges = ( + allSeries: Record, + firstSeries?: SeriesUrl +) => { + let to: string = ''; + let from: string = ''; + if (firstSeries?.reportType === 'kpi-over-time') { + return firstSeries.time; + } + Object.values(allSeries ?? {}).forEach((series) => { + if (series.dataType && series.reportType && !isEmpty(series.reportDefinitions)) { + const seriesTo = new Date(series.time.to); + const seriesFrom = new Date(series.time.from); + if (!to || seriesTo > new Date(to)) { + to = series.time.to; + } + if (!from || seriesFrom < new Date(from)) { + from = series.time.from; + } + } + }); + return { to, from }; +}; export function ExploratoryView({ saveAttributes, + multiSeries, }: { + multiSeries?: boolean; saveAttributes?: (attr: TypedLensByValueInput['attributes'] | null) => void; }) { const { @@ -33,6 +61,8 @@ export function ExploratoryView({ const [height, setHeight] = useState('100vh'); const [seriesId, setSeriesId] = useState(''); + const [lastUpdated, setLastUpdated] = useState(); + const [lensAttributes, setLensAttributes] = useState( null ); @@ -47,9 +77,7 @@ export function ExploratoryView({ setSeriesId(firstSeriesId); }, [allSeries, firstSeriesId]); - const lensAttributesT = useLensAttributes({ - seriesId, - }); + const lensAttributesT = useLensAttributes(); const setHeightOffset = () => { if (seriesBuilderRef?.current && wrapperRef.current) { @@ -60,10 +88,12 @@ export function ExploratoryView({ }; useEffect(() => { - if (series?.dataType) { - loadIndexPattern({ dataType: series?.dataType }); - } - }, [series?.dataType, loadIndexPattern]); + Object.values(allSeries).forEach((seriesT) => { + loadIndexPattern({ + dataType: seriesT.dataType, + }); + }); + }, [allSeries, loadIndexPattern]); useEffect(() => { setLensAttributes(lensAttributesT); @@ -72,47 +102,62 @@ export function ExploratoryView({ } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [JSON.stringify(lensAttributesT ?? {}), series?.reportType, series?.time?.from]); + }, [JSON.stringify(lensAttributesT ?? {})]); useEffect(() => { setHeightOffset(); }); + const timeRange = combineTimeRanges(allSeries, series); + + const onLensLoad = useCallback(() => { + setLastUpdated(Date.now()); + }, []); + + const onBrushEnd = useCallback( + ({ range }: { range: number[] }) => { + if (series?.reportType !== 'data-distribution') { + setSeries(seriesId, { + ...series, + time: { + from: new Date(range[0]).toISOString(), + to: new Date(range[1]).toISOString(), + }, + }); + } else { + notifications?.toasts.add( + i18n.translate('xpack.observability.exploratoryView.noBrusing', { + defaultMessage: 'Zoom by brush selection is only available on time series charts.', + }) + ); + } + }, + [notifications?.toasts, series, seriesId, setSeries] + ); + return ( {lens ? ( <> - {lensAttributes && seriesId && series?.reportType && series?.time ? ( + {lensAttributes && timeRange.to && timeRange.from ? ( { - if (series?.reportType !== 'dist') { - setSeries(seriesId, { - ...series, - time: { - from: new Date(range[0]).toISOString(), - to: new Date(range[1]).toISOString(), - }, - }); - } else { - notifications?.toasts.add( - i18n.translate('xpack.observability.exploratoryView.noBrusing', { - defaultMessage: - 'Zoom by brush selection is only available on time series charts.', - }) - ); - } - }} + onLoad={onLensLoad} + onBrushEnd={onBrushEnd} /> ) : ( )} - + ) : ( 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 1dedc4142f1747..8cd8977fcf7418 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 @@ -26,7 +26,7 @@ describe('ExploratoryViewHeader', function () { data: { 'uptime-pings-histogram': { dataType: 'synthetics' as const, - reportType: 'kpi' as const, + reportType: 'kpi-over-time' as const, breakdown: 'monitor.status', time: { from: 'now-15m', to: 'now' }, }, 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 3e02207e262725..dbe9cd163451da 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 @@ -13,6 +13,7 @@ import { useKibana } from '../../../../../../../../src/plugins/kibana_react/publ import { DataViewLabels } from '../configurations/constants'; import { ObservabilityAppServices } from '../../../../application/types'; import { useSeriesStorage } from '../hooks/use_series_storage'; +import { combineTimeRanges } from '../exploratory_view'; interface Props { seriesId: string; @@ -24,7 +25,7 @@ export function ExploratoryViewHeader({ seriesId, lensAttributes }: Props) { const { lens } = kServices; - const { getSeries } = useSeriesStorage(); + const { getSeries, allSeries } = useSeriesStorage(); const series = getSeries(seriesId); @@ -32,6 +33,8 @@ export function ExploratoryViewHeader({ seriesId, lensAttributes }: Props) { const LensSaveModalComponent = lens.SaveModalComponent; + const timeRange = combineTimeRanges(allSeries, series); + return ( <> @@ -63,7 +66,7 @@ export function ExploratoryViewHeader({ seriesId, lensAttributes }: Props) { lens.navigateToPrefilledEditor( { id: '', - timeRange: series.time, + timeRange, attributes: lensAttributes, }, true diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx index 4259bb778e5112..7a5f12a72b1f0c 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx @@ -15,7 +15,6 @@ import { getDataHandler } from '../../../../data_handler'; export interface IIndexPatternContext { loading: boolean; - selectedApp: AppDataType; indexPatterns: IndexPatternState; hasAppData: HasAppDataState; loadIndexPattern: (params: { dataType: AppDataType }) => void; @@ -29,10 +28,10 @@ interface ProviderProps { type HasAppDataState = Record; type IndexPatternState = Record; +type LoadingState = Record; export function IndexPatternContextProvider({ children }: ProviderProps) { - const [loading, setLoading] = useState(false); - const [selectedApp, setSelectedApp] = useState(); + const [loading, setLoading] = useState({} as LoadingState); const [indexPatterns, setIndexPatterns] = useState({} as IndexPatternState); const [hasAppData, setHasAppData] = useState({ infra_metrics: null, @@ -49,10 +48,9 @@ export function IndexPatternContextProvider({ children }: ProviderProps) { const loadIndexPattern: IIndexPatternContext['loadIndexPattern'] = useCallback( async ({ dataType }) => { - setSelectedApp(dataType); + if (hasAppData[dataType] === null && !loading[dataType]) { + setLoading((prevState) => ({ ...prevState, [dataType]: true })); - if (hasAppData[dataType] === null) { - setLoading(true); try { let hasDataT = false; let indices: string | undefined = ''; @@ -78,23 +76,22 @@ export function IndexPatternContextProvider({ children }: ProviderProps) { setIndexPatterns((prevState) => ({ ...prevState, [dataType]: indPattern })); } - setLoading(false); + setLoading((prevState) => ({ ...prevState, [dataType]: false })); } catch (e) { - setLoading(false); + setLoading((prevState) => ({ ...prevState, [dataType]: false })); } } }, - [data, hasAppData] + [data, hasAppData, loading] ); return ( loadingT), }} > {children} @@ -102,19 +99,23 @@ export function IndexPatternContextProvider({ children }: ProviderProps) { ); } -export const useAppIndexPatternContext = () => { - const { selectedApp, loading, hasAppData, loadIndexPattern, indexPatterns } = useContext( +export const useAppIndexPatternContext = (dataType?: AppDataType) => { + const { loading, hasAppData, loadIndexPattern, indexPatterns } = useContext( (IndexPatternContext as unknown) as Context ); + if (dataType && !indexPatterns?.[dataType] && !loading) { + loadIndexPattern({ dataType }); + } + return useMemo(() => { return { hasAppData, - selectedApp, loading, - indexPattern: indexPatterns?.[selectedApp], - hasData: hasAppData?.[selectedApp], + indexPatterns, + indexPattern: dataType ? indexPatterns?.[dataType] : undefined, + hasData: dataType ? hasAppData?.[dataType] : undefined, loadIndexPattern, }; - }, [hasAppData, indexPatterns, loadIndexPattern, loading, selectedApp]); + }, [dataType, hasAppData, indexPatterns, loadIndexPattern, loading]); }; 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 1c85bc5089b2af..11487afe28e966 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 @@ -8,17 +8,13 @@ import { useMemo } from 'react'; import { isEmpty } from 'lodash'; import { TypedLensByValueInput } from '../../../../../../lens/public'; -import { LensAttributes } from '../configurations/lens_attributes'; +import { LayerConfig, LensAttributes } from '../configurations/lens_attributes'; import { useSeriesStorage } from './use_series_storage'; import { getDefaultConfigs } from '../configurations/default_configs'; import { DataSeries, SeriesUrl, UrlFilter } from '../types'; import { useAppIndexPatternContext } from './use_app_index_pattern'; -interface Props { - seriesId: string; -} - export const getFiltersFromDefs = ( reportDefinitions: SeriesUrl['reportDefinitions'], dataViewConfig: DataSeries @@ -37,54 +33,51 @@ export const getFiltersFromDefs = ( }); }; -export const useLensAttributes = ({ - seriesId, -}: Props): TypedLensByValueInput['attributes'] | null => { - const { getSeries } = useSeriesStorage(); - const series = getSeries(seriesId); - const { breakdown, seriesType, operationType, reportType, dataType, reportDefinitions = {} } = - series ?? {}; +export const useLensAttributes = (): TypedLensByValueInput['attributes'] | null => { + const { allSeriesIds, allSeries } = useSeriesStorage(); - const { indexPattern } = useAppIndexPatternContext(); + const { indexPatterns } = useAppIndexPatternContext(); return useMemo(() => { - if (!indexPattern || !reportType || isEmpty(reportDefinitions)) { + if (isEmpty(indexPatterns) || isEmpty(allSeriesIds)) { return null; } - const dataViewConfig = getDefaultConfigs({ - reportType, - dataType, - indexPattern, - }); + const layerConfigs: LayerConfig[] = []; + + allSeriesIds.forEach((seriesIdT) => { + const seriesT = allSeries[seriesIdT]; + const indexPattern = indexPatterns?.[seriesT?.dataType]; + if (indexPattern && seriesT.reportType && !isEmpty(seriesT.reportDefinitions)) { + const reportViewConfig = getDefaultConfigs({ + reportType: seriesT.reportType, + dataType: seriesT.dataType, + indexPattern, + }); - const filters: UrlFilter[] = (series.filters ?? []).concat( - getFiltersFromDefs(reportDefinitions, dataViewConfig) - ); + const filters: UrlFilter[] = (seriesT.filters ?? []).concat( + getFiltersFromDefs(seriesT.reportDefinitions, reportViewConfig) + ); - const lensAttributes = new LensAttributes( - indexPattern, - dataViewConfig, - seriesType, - filters, - operationType, - reportDefinitions, - breakdown - ); + layerConfigs.push({ + filters, + indexPattern, + reportConfig: reportViewConfig, + breakdown: seriesT.breakdown, + operationType: seriesT.operationType, + seriesType: seriesT.seriesType, + reportDefinitions: seriesT.reportDefinitions ?? {}, + time: seriesT.time, + }); + } + }); - if (breakdown) { - lensAttributes.addBreakdown(breakdown); + if (layerConfigs.length < 1) { + return null; } + const lensAttributes = new LensAttributes(layerConfigs); + return lensAttributes.getJSON(); - }, [ - indexPattern, - reportType, - reportDefinitions, - dataType, - series.filters, - seriesType, - operationType, - breakdown, - ]); + }, [indexPatterns, allSeriesIds, allSeries]); }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_storage.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_storage.tsx index fac75f910a93fc..e9ae43950d47d3 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_storage.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_storage.tsx @@ -12,7 +12,7 @@ import { } from '../../../../../../../../src/plugins/kibana_utils/public'; import type { AppDataType, - ReportViewTypeId, + ReportViewType, SeriesUrl, UrlFilter, URLReportDefinition, @@ -36,6 +36,16 @@ interface ProviderProps { storage: IKbnUrlStateStorage | ISessionStorageStateStorage; } +function convertAllShortSeries(allShortSeries: AllShortSeries) { + const allSeriesIds = Object.keys(allShortSeries); + const allSeriesN: AllSeries = {}; + allSeriesIds.forEach((seriesKey) => { + allSeriesN[seriesKey] = convertFromShortUrl(allShortSeries[seriesKey]); + }); + + return allSeriesN; +} + export function UrlStorageContextProvider({ children, storage, @@ -45,15 +55,14 @@ export function UrlStorageContextProvider({ const [allShortSeries, setAllShortSeries] = useState( () => storage.get(allSeriesKey) ?? {} ); - const [allSeries, setAllSeries] = useState({}); + const [allSeries, setAllSeries] = useState(() => + convertAllShortSeries(storage.get(allSeriesKey) ?? {}) + ); const [firstSeriesId, setFirstSeriesId] = useState(''); useEffect(() => { const allSeriesIds = Object.keys(allShortSeries); - const allSeriesN: AllSeries = {}; - allSeriesIds.forEach((seriesKey) => { - allSeriesN[seriesKey] = convertFromShortUrl(allShortSeries[seriesKey]); - }); + const allSeriesN: AllSeries = convertAllShortSeries(allShortSeries ?? {}); setAllSeries(allSeriesN); setFirstSeriesId(allSeriesIds?.[0]); @@ -68,8 +77,10 @@ export function UrlStorageContextProvider({ }; const removeSeries = (seriesIdN: string) => { - delete allShortSeries[seriesIdN]; - delete allSeries[seriesIdN]; + setAllShortSeries((prevState) => { + delete prevState[seriesIdN]; + return { ...prevState }; + }); }; const allSeriesIds = Object.keys(allShortSeries); @@ -115,7 +126,7 @@ function convertFromShortUrl(newValue: ShortUrlSeries): SeriesUrl { interface ShortUrlSeries { [URL_KEYS.OPERATION_TYPE]?: OperationType; - [URL_KEYS.REPORT_TYPE]?: ReportViewTypeId; + [URL_KEYS.REPORT_TYPE]?: ReportViewType; [URL_KEYS.DATA_TYPE]?: AppDataType; [URL_KEYS.SERIES_TYPE]?: SeriesType; [URL_KEYS.BREAK_DOWN]?: string; 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 3de29b02853e8c..e55752ceb62ba3 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 @@ -25,9 +25,11 @@ import { TypedLensByValueInput } from '../../../../../lens/public'; export function ExploratoryViewPage({ saveAttributes, + multiSeries = false, useSessionStorage = false, }: { useSessionStorage?: boolean; + multiSeries?: boolean; saveAttributes?: (attr: TypedLensByValueInput['attributes'] | null) => void; }) { useTrackPageview({ app: 'observability-overview', path: 'exploratory-view' }); @@ -59,7 +61,7 @@ export function ExploratoryViewPage({ - + 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 8e54ab7629d26b..972e3beb4b7220 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 @@ -35,8 +35,11 @@ import { getStubIndexPattern } from '../../../../../../../src/plugins/data/publi import indexPatternData from './configurations/test_data/test_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'; +import { + IndexPattern, + IndexPatternsContract, +} from '../../../../../../../src/plugins/data/common/index_patterns/index_patterns'; +import { AppDataType, UrlFilter } from './types'; import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; import { ListItem } from '../../../hooks/use_values_list'; @@ -232,11 +235,11 @@ export const mockAppIndexPattern = () => { const loadIndexPattern = jest.fn(); const spy = jest.spyOn(useAppIndexPatternHook, 'useAppIndexPatternContext').mockReturnValue({ indexPattern: mockIndexPattern, - selectedApp: 'ux', hasData: true, loading: false, hasAppData: { ux: true } as any, loadIndexPattern, + indexPatterns: ({ ux: mockIndexPattern } as unknown) as Record, }); return { spy, loadIndexPattern }; }; @@ -260,7 +263,7 @@ function mockSeriesStorageContext({ }) { const mockDataSeries = data || { 'performance-distribution': { - reportType: 'dist', + reportType: 'data-distribution', dataType: 'ux', breakdown: breakdown || 'user_agent.name', time: { from: 'now-15m', to: 'now' }, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.tsx index 9ae8b68bf3e8c7..50c2f91e6067d0 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.tsx @@ -27,18 +27,14 @@ export function SeriesChartTypesSelect({ seriesTypes?: SeriesType[]; defaultChartType: SeriesType; }) { - const { getSeries, setSeries, allSeries } = useSeriesStorage(); + const { getSeries, setSeries } = useSeriesStorage(); const series = getSeries(seriesId); const seriesType = series?.seriesType ?? defaultChartType; const onChange = (value: SeriesType) => { - Object.keys(allSeries).forEach((seriesKey) => { - const seriesN = allSeries[seriesKey]; - - setSeries(seriesKey, { ...seriesN, seriesType: value }); - }); + setSeries(seriesId, { ...series, seriesType: value }); }; return ( 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 e3c1666c533ef0..b10702ebded579 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 @@ -29,7 +29,14 @@ describe('DataTypesCol', function () { fireEvent.click(screen.getByText(/user experience \(rum\)/i)); expect(setSeries).toHaveBeenCalledTimes(1); - expect(setSeries).toHaveBeenCalledWith(seriesId, { dataType: 'ux' }); + expect(setSeries).toHaveBeenCalledWith(seriesId, { + dataType: 'ux', + isNew: true, + time: { + from: 'now-15m', + to: 'now', + }, + }); }); it('should set series on change on already selected', function () { @@ -37,7 +44,7 @@ describe('DataTypesCol', function () { data: { [seriesId]: { dataType: 'synthetics' as const, - reportType: 'kpi' as const, + reportType: 'kpi-over-time' as const, breakdown: 'monitor.status', time: { from: 'now-15m', to: 'now' }, }, 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 985afdf8888686..f386f62d9ed73e 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 @@ -31,7 +31,11 @@ export function DataTypesCol({ seriesId }: { seriesId: string }) { if (!dataType) { removeSeries(seriesId); } else { - setSeries(seriesId || `${dataType}-series`, { dataType } as any); + setSeries(seriesId || `${dataType}-series`, { + dataType, + isNew: true, + time: series.time, + } as any); } }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/date_picker_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/date_picker_col.tsx index 175fbea9445c1b..6be78084ae195d 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/date_picker_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/date_picker_col.tsx @@ -8,14 +8,23 @@ import React from 'react'; import styled from 'styled-components'; import { SeriesDatePicker } from '../../series_date_picker'; +import { DateRangePicker } from '../../series_date_picker/date_range_picker'; +import { useSeriesStorage } from '../../hooks/use_series_storage'; interface Props { seriesId: string; } export function DatePickerCol({ seriesId }: Props) { + const { firstSeriesId, getSeries } = useSeriesStorage(); + const { reportType } = getSeries(firstSeriesId); + return ( - + {firstSeriesId === seriesId || reportType !== 'kpi-over-time' ? ( + + ) : ( + + )} ); } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.test.tsx index c262a94f968be0..516f04e3812ba0 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.test.tsx @@ -22,7 +22,7 @@ describe('OperationTypeSelect', function () { data: { 'performance-distribution': { dataType: 'ux' as const, - reportType: 'kpi' as const, + reportType: 'kpi-over-time' as const, operationType: 'median' as const, time: { from: 'now-15m', to: 'now' }, }, @@ -39,7 +39,7 @@ describe('OperationTypeSelect', function () { data: { 'series-id': { dataType: 'ux' as const, - reportType: 'kpi' as const, + reportType: 'kpi-over-time' as const, operationType: 'median' as const, time: { from: 'now-15m', to: 'now' }, }, @@ -53,7 +53,7 @@ describe('OperationTypeSelect', function () { expect(setSeries).toHaveBeenCalledWith('series-id', { operationType: 'median', dataType: 'ux', - reportType: 'kpi', + reportType: 'kpi-over-time', time: { from: 'now-15m', to: 'now' }, }); @@ -61,7 +61,7 @@ describe('OperationTypeSelect', function () { expect(setSeries).toHaveBeenCalledWith('series-id', { operationType: '95th', dataType: 'ux', - reportType: 'kpi', + reportType: 'kpi-over-time', time: { from: 'now-15m', to: 'now' }, }); }); 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 805186e877d570..203382afc16243 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 @@ -15,7 +15,7 @@ import { USER_AGENT_OS } from '../../configurations/constants/elasticsearch_fiel describe('Series Builder ReportBreakdowns', function () { const seriesId = 'test-series-id'; const dataViewSeries = getDefaultConfigs({ - reportType: 'dist', + reportType: 'data-distribution', dataType: 'ux', indexPattern: mockIndexPattern, }); @@ -45,7 +45,7 @@ describe('Series Builder ReportBreakdowns', function () { expect(setSeries).toHaveBeenCalledWith(seriesId, { breakdown: USER_AGENT_OS, dataType: 'ux', - reportType: 'dist', + reportType: 'data-distribution', time: { from: 'now-15m', to: 'now' }, }); }); @@ -67,7 +67,7 @@ describe('Series Builder ReportBreakdowns', function () { expect(setSeries).toHaveBeenCalledWith(seriesId, { breakdown: undefined, dataType: 'ux', - reportType: 'dist', + reportType: 'data-distribution', 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 index e947961fb43006..2e5c674b9fad88 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 @@ -22,7 +22,7 @@ describe('Series Builder ReportDefinitionCol', function () { const seriesId = 'test-series-id'; const dataViewSeries = getDefaultConfigs({ - reportType: 'dist', + reportType: 'data-distribution', indexPattern: mockIndexPattern, dataType: 'ux', }); @@ -31,7 +31,7 @@ describe('Series Builder ReportDefinitionCol', function () { data: { [seriesId]: { dataType: 'ux' as const, - reportType: 'dist' as const, + reportType: 'data-distribution' as const, time: { from: 'now-30d', to: 'now' }, reportDefinitions: { [SERVICE_NAME]: ['elastic-co'] }, }, @@ -81,7 +81,7 @@ describe('Series Builder ReportDefinitionCol', function () { expect(setSeries).toHaveBeenCalledWith(seriesId, { dataType: 'ux', reportDefinitions: {}, - reportType: 'dist', + reportType: 'data-distribution', time: { from: 'now-30d', to: 'now' }, }); }); 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 338f5d52c26fae..47962af0d4bc46 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,7 +8,6 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui'; import styled from 'styled-components'; -import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern'; import { useSeriesStorage } from '../../hooks/use_series_storage'; import { CustomReportField } from '../custom_report_field'; import { DataSeries, URLReportDefinition } from '../../types'; @@ -36,8 +35,6 @@ export function ReportDefinitionCol({ dataViewSeries: DataSeries; seriesId: string; }) { - const { indexPattern } = useAppIndexPatternContext(); - const { getSeries, setSeries } = useSeriesStorage(); const series = getSeries(seriesId); @@ -69,21 +66,20 @@ export function ReportDefinitionCol({ - {indexPattern && - reportDefinitions.map(({ field, custom, options }) => ( - - {!custom ? ( - - ) : ( - - )} - - ))} + {reportDefinitions.map(({ field, custom, options }) => ( + + {!custom ? ( + + ) : ( + + )} + + ))} {(hasOperationType || columnType === 'operation') && ( { - if (!custom && selectedReportDefinitions?.[fieldT] && fieldT !== field) { + if (!custom && indexPattern && selectedReportDefinitions?.[fieldT] && fieldT !== field) { const values = selectedReportDefinitions?.[fieldT]; const valueFilter = buildPhrasesFilter(fieldT, values, indexPattern)[0]; filtersN.push(valueFilter.query); @@ -64,16 +64,18 @@ export function ReportDefinitionField({ seriesId, field, dataSeries, onChange }: return ( - onChange(field, val)} - filters={queryFilters} - time={series.time} - fullWidth={true} - /> + {indexPattern && ( + onChange(field, val)} + filters={queryFilters} + time={series.time} + fullWidth={true} + /> + )} ); 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 index 7ca947fed0bc9f..f35639388aac5e 100644 --- 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 @@ -15,7 +15,7 @@ describe('Series Builder ReportFilters', function () { const seriesId = 'test-series-id'; const dataViewSeries = getDefaultConfigs({ - reportType: 'dist', + reportType: 'data-distribution', indexPattern: mockIndexPattern, dataType: 'ux', }); 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 f36d64ca5bbbd8..f7cfe06c0d9280 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 @@ -11,10 +11,9 @@ import { mockAppIndexPattern, render } from '../../rtl_helpers'; import { ReportTypesCol, SELECTED_DATA_TYPE_FOR_REPORT } from './report_types_col'; import { ReportTypes } from '../series_builder'; import { DEFAULT_TIME } from '../../configurations/constants'; -import { NEW_SERIES_KEY } from '../../hooks/use_series_storage'; describe('ReportTypesCol', function () { - const seriesId = 'test-series-id'; + const seriesId = 'performance-distribution'; mockAppIndexPattern(); @@ -40,7 +39,7 @@ describe('ReportTypesCol', function () { breakdown: 'user_agent.name', dataType: 'ux', reportDefinitions: {}, - reportType: 'kpi', + reportType: 'kpi-over-time', time: { from: 'now-15m', to: 'now' }, }); expect(setSeries).toHaveBeenCalledTimes(1); @@ -49,11 +48,12 @@ describe('ReportTypesCol', function () { it('should set selected as filled', function () { const initSeries = { data: { - [NEW_SERIES_KEY]: { + [seriesId]: { dataType: 'synthetics' as const, - reportType: 'kpi' as const, + reportType: 'kpi-over-time' as const, breakdown: 'monitor.status', time: { from: 'now-15m', to: 'now' }, + isNew: true, }, }, }; @@ -74,6 +74,7 @@ describe('ReportTypesCol', function () { expect(setSeries).toHaveBeenCalledWith(seriesId, { dataType: 'synthetics', time: DEFAULT_TIME, + isNew: true, }); }); }); 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 9fff8dae14a47c..64c7b48c668b89 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,27 +7,33 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; +import { map } from 'lodash'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import styled from 'styled-components'; -import { ReportViewTypeId, SeriesUrl } from '../../types'; +import { ReportViewType, SeriesUrl } from '../../types'; import { useSeriesStorage } from '../../hooks/use_series_storage'; import { DEFAULT_TIME } from '../../configurations/constants'; import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern'; +import { ReportTypeItem, SELECT_DATA_TYPE } from '../series_builder'; interface Props { seriesId: string; - reportTypes: Array<{ id: ReportViewTypeId; label: string }>; + reportTypes: ReportTypeItem[]; } export function ReportTypesCol({ seriesId, reportTypes }: Props) { - const { setSeries, getSeries } = useSeriesStorage(); + const { setSeries, getSeries, firstSeries, firstSeriesId } = useSeriesStorage(); const { reportType: selectedReportType, ...restSeries } = getSeries(seriesId); - const { loading, hasData, selectedApp } = useAppIndexPatternContext(); + const { loading, hasData } = useAppIndexPatternContext(restSeries.dataType); - if (!loading && !hasData && selectedApp) { + if (!restSeries.dataType) { + return {SELECT_DATA_TYPE}; + } + + if (!loading && !hasData) { return ( firstSeriesId !== seriesId && reportType !== firstSeries.reportType + ), + 'reportType' + ); + return reportTypes?.length > 0 ? ( - {reportTypes.map(({ id: reportType, label }) => ( + {reportTypes.map(({ reportType, label }) => ( - -
+ +
+
+ +
+ + + +
+ - -
+
+
-
- - - -
+ recent 2 + + + + + +
- -
+
+
- - - -
-
- + + + +
+
+ +
-
- + + + + + +

+ Analytics +

+
+
+ + } + className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" data-test-subj="collapsibleNavGroup-kibana" - iconType="logoKibana" + id="generated-id" initialIsOpen={true} - isCollapsible={true} - key="kibana" + isLoading={false} + isLoadingMessage={false} onToggle={[Function]} - title="Analytics" + paddingSize="none" > - - - - - - -

- Analytics -

-
-
- - } - className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" +
-
- -
-
+ +
+ +
+ + + +
+
+ - -
+
+
-
- - - -
+ dashboard + + + + + +
- -
+
+
-
-
- + + + + + + + + + +

+ Observability +

+
+
+ + } + className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" data-test-subj="collapsibleNavGroup-observability" - iconType="logoObservability" + id="generated-id" initialIsOpen={true} - isCollapsible={true} - key="observability" + isLoading={false} + isLoadingMessage={false} onToggle={[Function]} - title="Observability" + paddingSize="none" > - - - - - - -

- Observability -

-
-
- - } - className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" +
-
- -
-
+ +
+ +
+ + + +
+
+ - -
+
+
-
- - - -
+ logs + + + + + +
- -
+
+
-
-
- + + + + + + + + + +

+ Security +

+
+
+ + } + className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" data-test-subj="collapsibleNavGroup-securitySolution" - iconType="logoSecurity" + id="generated-id" initialIsOpen={true} - isCollapsible={true} - key="securitySolution" + isLoading={false} + isLoadingMessage={false} onToggle={[Function]} - title="Security" + paddingSize="none" > - - - - - - -

- Security -

-
-
- - } - className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" +
-
- -
-
+ +
+ +
+ + + +
+
+ - -
+
+
-
- - - -
+ siem + + + + + +
- -
+
+
-
-
- + + + + + + + + + +

+ Management +

+
+
+ + } + className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" data-test-subj="collapsibleNavGroup-management" - iconType="managementApp" + id="generated-id" initialIsOpen={true} - isCollapsible={true} - key="management" + isLoading={false} + isLoadingMessage={false} onToggle={[Function]} - title="Management" + paddingSize="none" > - - - - - - -

- Management -

-
-
- - } - className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" +
-
- -
-
+ +
+ +
+ + + +
+
+ - -
+
+
-
- - - -
+ monitoring + + + + + +
- -
+
+
-
-
- + + + +
-
- - - -
+ canvas + + + + + +
- - - +
+
+ + +
-
- -
    - - - - Dock navigation - - , - } - } - color="subdued" - data-test-subj="collapsible-nav-lock" - iconType="lockOpen" - label="Dock navigation" - onClick={[Function]} - size="xs" - > -
  • - -
  • -
    -
-
-
+ , + } + } + color="subdued" + data-test-subj="collapsible-nav-lock" + iconType="lockOpen" + label="Dock navigation" + onClick={[Function]} + size="xs" + > +
  • + +
  • + + +
    - - -
    - - - - - - - -
    - +
    + + + +
    + + `; @@ -2770,42 +2706,57 @@ exports[`CollapsibleNav renders the default nav 3`] = ` isOpen={false} onClose={[Function]} > - - -
    -
    +
    + + + + +

    + Recently viewed +

    +
    +
    + + } + className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading" data-test-subj="collapsibleNavGroup-recentlyViewed" + id="generated-id" initialIsOpen={true} - isCollapsible={true} - key="recentlyViewed" + isLoading={false} + isLoadingMessage={false} onToggle={[Function]} - title="Recently viewed" + paddingSize="none" > - - - -

    - Recently viewed -

    -
    -
    - - } - className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading" +
    -
    - -
    -
    + +
    + +
    + + + +
    +
    + - -
    +
    +
    -
    - -
    - -
    -

    - No recently viewed items -

    -
    -
    -
    -
    -
    +

    + No recently viewed items +

    +
    + +
    +
    -
    -
    + + -
    -
    - -
    -
    - + + + +
    +
    + +
    -
    - - + +
    -
    - -
      - - - - Undock navigation - - , - } - } - color="subdued" - data-test-subj="collapsible-nav-lock" - iconType="lock" - label="Undock navigation" - onClick={[Function]} - size="xs" - > -
    • - -
    • -
      -
    -
    -
    + , + } + } + color="subdued" + data-test-subj="collapsible-nav-lock" + iconType="lock" + label="Undock navigation" + onClick={[Function]} + size="xs" + > +
  • + +
  • + + +
    - - -
    - - - - - - - -
    - +
    + + + +
    + + `; diff --git a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap index 6ad1e2d3a1cc62..5aee9ca1b7c08d 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap @@ -4947,42 +4947,57 @@ exports[`Header renders 1`] = ` isOpen={false} onClose={[Function]} > - - -
    -
    +
    + +
    +
    + +
    -
    -
    -
    - - - -
    + data-euiicon-type="home" + /> + + + Home + + + + + +
    -
    -
    - - + +
    +
    + + + + +

    + Recently viewed +

    +
    +
    + + } + className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading" data-test-subj="collapsibleNavGroup-recentlyViewed" + id="mockId" initialIsOpen={true} - isCollapsible={true} - key="recentlyViewed" + isLoading={false} + isLoadingMessage={false} onToggle={[Function]} - title="Recently viewed" + paddingSize="none" > - - - -

    - Recently viewed -

    -
    -
    - - } - className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading" +
    -
    - -
    -
    + +
    + +
    + + + +
    +
    + - -
    +
    +
    -
    - - - -
    + dashboard + + + + + +
    - -
    +
    +
    -
    -
    - -
    -
    - + + + +
    +
    + +
    -
    - +
    + +
      + +
    • + +
    • +
      +
    +
    +
    +
    + + +
    + + + Undock navigation + + , + } + } + color="subdued" + data-test-subj="collapsible-nav-lock" + iconType="lock" + label="Undock navigation" onClick={[Function]} - size="s" + size="xs" >
  • @@ -5445,163 +5540,11 @@ exports[`Header renders 1`] = `
    - - -
    -
    - -
      - - - - Undock navigation - - , - } - } - color="subdued" - data-test-subj="collapsible-nav-lock" - iconType="lock" - label="Undock navigation" - onClick={[Function]} - size="xs" - > -
    • - -
    • -
      -
    -
    -
    -
    -
    -
    -
    -
    - - - - - - - - + + +
    + + diff --git a/src/core/public/chrome/ui/header/collapsible_nav.test.tsx b/src/core/public/chrome/ui/header/collapsible_nav.test.tsx index 7f338a859e7b42..460770744d53a3 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.test.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.test.tsx @@ -16,10 +16,6 @@ import { httpServiceMock } from '../../../http/http_service.mock'; import { ChromeRecentlyAccessedHistoryItem } from '../../recently_accessed'; import { CollapsibleNav } from './collapsible_nav'; -jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ - htmlIdGenerator: () => () => 'mockId', -})); - const { kibana, observability, security, management } = DEFAULT_APP_CATEGORIES; function mockLink({ title = 'discover', category }: Partial) { diff --git a/src/core/public/chrome/ui/header/header.test.tsx b/src/core/public/chrome/ui/header/header.test.tsx index fdbdde8556eebf..a3a0197b4017e0 100644 --- a/src/core/public/chrome/ui/header/header.test.tsx +++ b/src/core/public/chrome/ui/header/header.test.tsx @@ -99,7 +99,7 @@ describe('Header', () => { act(() => isLocked$.next(true)); component.update(); - expect(component.find('nav[aria-label="Primary"]').exists()).toBeTruthy(); + expect(component.find('[data-test-subj="collapsibleNav"]').exists()).toBeTruthy(); expect(component).toMatchSnapshot(); act(() => diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index 67cdd24aae8487..246ca83ef5adeb 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -87,6 +87,7 @@ export function Header({ const isVisible = useObservable(observables.isVisible$, false); const isLocked = useObservable(observables.isLocked$, false); const [isNavOpen, setIsNavOpen] = useState(false); + const [navId] = useState(htmlIdGenerator()()); const breadcrumbsAppendExtension = useObservable(breadcrumbsAppendExtension$); if (!isVisible) { @@ -99,7 +100,6 @@ export function Header({ } const toggleCollapsibleNavRef = createRef void }>(); - const navId = htmlIdGenerator()(); const className = classnames('hide-for-sharing', 'headerGlobalNav'); const Breadcrumbs = ( diff --git a/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap b/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap index f5a1c51ccbe158..fbd09f30968542 100644 --- a/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap +++ b/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap @@ -26,7 +26,7 @@ Array [ ] `; -exports[`FlyoutService openFlyout() renders a flyout to the DOM 2`] = `"
    Flyout content
    "`; +exports[`FlyoutService openFlyout() renders a flyout to the DOM 2`] = `"
    Flyout content
    "`; exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 1`] = ` Array [ @@ -59,4 +59,4 @@ Array [ ] `; -exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 2`] = `"
    Flyout content 2
    "`; +exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 2`] = `"
    Flyout content 2
    "`; diff --git a/src/core/public/styles/_base.scss b/src/core/public/styles/_base.scss index 3386fa73f328aa..de138cdf402e6e 100644 --- a/src/core/public/styles/_base.scss +++ b/src/core/public/styles/_base.scss @@ -26,7 +26,7 @@ } .euiBody--collapsibleNavIsDocked .euiBottomBar { - margin-left: $euiCollapsibleNavWidth; + margin-left: 320px; // Hard-coded for now -- @cchaos } // Temporary fix for EuiPageHeader with a bottom border but no tabs or padding diff --git a/src/plugins/console/public/application/components/welcome_panel.tsx b/src/plugins/console/public/application/components/welcome_panel.tsx index eb746e313d228a..8514d41c04a51c 100644 --- a/src/plugins/console/public/application/components/welcome_panel.tsx +++ b/src/plugins/console/public/application/components/welcome_panel.tsx @@ -27,7 +27,7 @@ interface Props { export function WelcomePanel(props: Props) { return ( - +

    diff --git a/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap index 9f56740fdac221..afe339f3f43a23 100644 --- a/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap +++ b/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap @@ -603,7 +603,7 @@ exports[`DashboardEmptyScreen renders correctly with readonly mode 1`] = ` } > -
    -
    +
    @@ -950,7 +950,7 @@ exports[`DashboardEmptyScreen renders correctly with view mode 1`] = ` } > -
    -
    +
    diff --git a/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap b/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap index a0a7e54d275322..0ab3f8a4e34668 100644 --- a/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap +++ b/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap @@ -176,27 +176,27 @@ exports[`Inspector Data View component should render empty state 1`] = `
    + +

    + + No data available + +

    +
    - -

    - - No data available - -

    -
    diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.test.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.test.tsx index 60841799b1398b..50be2473a441e2 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.test.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.test.tsx @@ -144,7 +144,9 @@ describe('Discover flyout', function () { expect(props.setExpandedDoc.mock.calls[0][0]._id).toBe('4'); }); - it('allows navigating with arrow keys through documents', () => { + // EuiFlyout is mocked in Jest environments. + // EUI team to reinstate `onKeyDown`: https://github.com/elastic/eui/issues/4883 + it.skip('allows navigating with arrow keys through documents', () => { const props = getProps(); const component = mountWithIntl(); findTestSubject(component, 'docTableDetailsFlyout').simulate('keydown', { key: 'ArrowRight' }); diff --git a/src/plugins/discover/public/application/components/source_viewer/__snapshots__/source_viewer.test.tsx.snap b/src/plugins/discover/public/application/components/source_viewer/__snapshots__/source_viewer.test.tsx.snap index f40dbbbae1f877..68786871825ac3 100644 --- a/src/plugins/discover/public/application/components/source_viewer/__snapshots__/source_viewer.test.tsx.snap +++ b/src/plugins/discover/public/application/components/source_viewer/__snapshots__/source_viewer.test.tsx.snap @@ -147,27 +147,27 @@ exports[`Source Viewer component renders error state 1`] = ` />
    + +

    + An Error Occurred +

    +
    - -

    - An Error Occurred -

    -
    diff --git a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap index 40170c39942e52..79c1a11cfef84a 100644 --- a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap @@ -153,7 +153,7 @@ exports[`UrlFormatEditor should render normally 1`] = ` class="euiFormControlLayout__childrenWrapper" > diff --git a/src/plugins/inspector/public/ui/__snapshots__/inspector_panel.test.tsx.snap b/src/plugins/inspector/public/ui/__snapshots__/inspector_panel.test.tsx.snap index 5ad82053651469..67d2cf72c53756 100644 --- a/src/plugins/inspector/public/ui/__snapshots__/inspector_panel.test.tsx.snap +++ b/src/plugins/inspector/public/ui/__snapshots__/inspector_panel.test.tsx.snap @@ -329,6 +329,7 @@ exports[`InspectorPanel should render as expected 1`] = ` >
    & { +export type KibanaPageTemplateSolutionNavProps = Partial> & { /** * Name of the solution, i.e. "Observability" */ diff --git a/src/plugins/presentation_util/public/components/labs/labs_flyout.tsx b/src/plugins/presentation_util/public/components/labs/labs_flyout.tsx index 5b424c7e95f18b..1af85da9830851 100644 --- a/src/plugins/presentation_util/public/components/labs/labs_flyout.tsx +++ b/src/plugins/presentation_util/public/components/labs/labs_flyout.tsx @@ -20,7 +20,6 @@ import { EuiFlexItem, EuiFlexGroup, EuiIcon, - EuiOverlayMask, } from '@elastic/eui'; import { SolutionName, ProjectStatus, ProjectID, Project, EnvironmentName } from '../../../common'; @@ -124,30 +123,32 @@ export const LabsFlyout = (props: Props) => { ); return ( - onClose()} headerZindexLocation="below"> - - - -

    - - - - - {strings.getTitleLabel()} - -

    -
    - - -

    {strings.getDescriptionMessage()}

    -
    -
    - - - - {footer} -
    -
    + + + +

    + + + + + {strings.getTitleLabel()} + +

    +
    + + +

    {strings.getDescriptionMessage()}

    +
    +
    + + + + {footer} +
    ); }; diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/intro.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/intro.test.tsx.snap index 5239a925435399..5a8cd06b8ecc07 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/intro.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/intro.test.tsx.snap @@ -47,20 +47,30 @@ exports[`Intro component renders correctly 1`] = `
    -
    - +
    - Modifying objects is for advanced users only. Object properties are not validated and invalid objects could cause errors, data loss, or worse. Unless someone with intimate knowledge of the code told you to be in here, you probably shouldn’t be. - -
    +
    + + Modifying objects is for advanced users only. Object properties are not validated and invalid objects could cause errors, data loss, or worse. Unless someone with intimate knowledge of the code told you to be in here, you probably shouldn’t be. + +
    +
    +
    diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/not_found_errors.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/not_found_errors.test.tsx.snap index bddfe000008d42..f977c17df41d31 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/not_found_errors.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/not_found_errors.test.tsx.snap @@ -49,29 +49,39 @@ exports[`NotFoundErrors component renders correctly for index-pattern type 1`] =
    -
    - - The index pattern associated with this object no longer exists. - -
    -
    - +
    - If you know what this error means, go ahead and fix it — otherwise click the delete button above. - -
    +
    + + The index pattern associated with this object no longer exists. + +
    +
    + + If you know what this error means, go ahead and fix it — otherwise click the delete button above. + +
    +
    +
    @@ -128,29 +138,39 @@ exports[`NotFoundErrors component renders correctly for index-pattern-field type
    -
    - - A field associated with this object no longer exists in the index pattern. - -
    -
    - +
    - If you know what this error means, go ahead and fix it — otherwise click the delete button above. - -
    +
    + + A field associated with this object no longer exists in the index pattern. + +
    +
    + + If you know what this error means, go ahead and fix it — otherwise click the delete button above. + +
    +
    +
    @@ -207,29 +227,39 @@ exports[`NotFoundErrors component renders correctly for search type 1`] = `
    -
    - - The saved search associated with this object no longer exists. - -
    -
    - +
    - If you know what this error means, go ahead and fix it — otherwise click the delete button above. - -
    +
    + + The saved search associated with this object no longer exists. + +
    +
    + + If you know what this error means, go ahead and fix it — otherwise click the delete button above. + +
    +
    +
    @@ -286,21 +316,31 @@ exports[`NotFoundErrors component renders correctly for unknown type 1`] = `
    -
    -
    - +
    - If you know what this error means, go ahead and fix it — otherwise click the delete button above. - -
    +
    +
    + + If you know what this error means, go ahead and fix it — otherwise click the delete button above. + +
    +
    +
    diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/flyout.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/flyout.test.tsx.snap index a68e8891b5ad19..bd97f2e6bffb13 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/flyout.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/flyout.test.tsx.snap @@ -2,6 +2,7 @@ exports[`Flyout conflicts should allow conflict resolution 1`] = ` @@ -277,6 +278,7 @@ exports[`Flyout conflicts should allow conflict resolution 2`] = ` exports[`Flyout legacy conflicts should allow conflict resolution 1`] = ` @@ -548,6 +550,7 @@ Array [ exports[`Flyout should render import step 1`] = ` diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx index 62e0cd0504e8e2..f6c8d5fb694087 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx @@ -960,7 +960,7 @@ export class Flyout extends Component { } return ( - +

    diff --git a/src/plugins/vis_type_timeseries/public/application/components/color_picker.test.tsx b/src/plugins/vis_type_timeseries/public/application/components/color_picker.test.tsx index 8e975f99042562..50d3e8c38e389f 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/color_picker.test.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/color_picker.test.tsx @@ -36,7 +36,7 @@ describe('ColorPicker', () => { const props = { ...defaultProps, value: '#68BC00' }; component = mount(); component.find('.tvbColorPicker button').simulate('click'); - const input = findTestSubject(component, 'topColorPickerInput'); + const input = findTestSubject(component, 'euiColorPickerInput_top'); expect(input.props().value).toBe('#68BC00'); }); @@ -44,7 +44,7 @@ describe('ColorPicker', () => { const props = { ...defaultProps, value: 'rgba(85,66,177,1)' }; component = mount(); component.find('.tvbColorPicker button').simulate('click'); - const input = findTestSubject(component, 'topColorPickerInput'); + const input = findTestSubject(component, 'euiColorPickerInput_top'); expect(input.props().value).toBe('85,66,177,1'); }); diff --git a/src/plugins/visualizations/public/components/__snapshots__/visualization_noresults.test.js.snap b/src/plugins/visualizations/public/components/__snapshots__/visualization_noresults.test.js.snap index 25ec05c83a8c6a..56e2cb1b60f3c7 100644 --- a/src/plugins/visualizations/public/components/__snapshots__/visualization_noresults.test.js.snap +++ b/src/plugins/visualizations/public/components/__snapshots__/visualization_noresults.test.js.snap @@ -14,7 +14,7 @@ exports[`VisualizationNoResults should render according to snapshot 1`] = ` data-euiicon-type="visualizeApp" />
    { await PageObjects.settings.clickEditFieldFormat(); await a11y.testAppSnapshot(); + await PageObjects.settings.clickCloseEditFieldFormatFlyout(); }); it('Advanced settings', async () => { diff --git a/test/functional/apps/management/_import_objects.ts b/test/functional/apps/management/_import_objects.ts index 0278955c577a10..6ef0bfd5a09e8a 100644 --- a/test/functional/apps/management/_import_objects.ts +++ b/test/functional/apps/management/_import_objects.ts @@ -419,14 +419,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'index-pattern-test-1' ); - await testSubjects.click('pagination-button-next'); + const flyout = await testSubjects.find('importSavedObjectsFlyout'); + + await (await flyout.findByTestSubject('pagination-button-next')).click(); await PageObjects.savedObjects.setOverriddenIndexPatternValue( 'missing-index-pattern-7', 'index-pattern-test-2' ); - await testSubjects.click('pagination-button-previous'); + await (await flyout.findByTestSubject('pagination-button-previous')).click(); const selectedIdForMissingIndexPattern1 = await testSubjects.getAttribute( 'managementChangeIndexSelection-missing-index-pattern-1', @@ -435,7 +437,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(selectedIdForMissingIndexPattern1).to.eql('f1e4c910-a2e6-11e7-bb30-233be9be6a20'); - await testSubjects.click('pagination-button-next'); + await (await flyout.findByTestSubject('pagination-button-next')).click(); const selectedIdForMissingIndexPattern7 = await testSubjects.getAttribute( 'managementChangeIndexSelection-missing-index-pattern-7', diff --git a/test/functional/page_objects/settings_page.ts b/test/functional/page_objects/settings_page.ts index 88951bb04c956a..cb8f1981770174 100644 --- a/test/functional/page_objects/settings_page.ts +++ b/test/functional/page_objects/settings_page.ts @@ -739,6 +739,10 @@ export class SettingsPageObject extends FtrService { await this.testSubjects.click('editFieldFormat'); } + async clickCloseEditFieldFormatFlyout() { + await this.testSubjects.click('euiFlyoutCloseButton'); + } + async associateIndexPattern(oldIndexPatternId: string, newIndexPatternTitle: string) { await this.find.clickByCssSelector( `select[data-test-subj="managementChangeIndexSelection-${oldIndexPatternId}"] > diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index 6e263dd1cdbbf5..7f1ea64bcd9792 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -563,7 +563,7 @@ export class VisualBuilderPageObject extends FtrService { public async checkColorPickerPopUpIsPresent(): Promise { this.log.debug(`Check color picker popup is present`); - await this.testSubjects.existOrFail('colorPickerPopover', { timeout: 5000 }); + await this.testSubjects.existOrFail('euiColorPickerPopover', { timeout: 5000 }); } public async changePanelPreview(nth: number = 0): Promise { diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/ResponsiveFlyout.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/ResponsiveFlyout.tsx index 8549f09bba2482..09fbf07b8ecbd7 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/ResponsiveFlyout.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/ResponsiveFlyout.tsx @@ -5,10 +5,21 @@ * 2.0. */ +import { ReactNode } from 'react'; +import { StyledComponent } from 'styled-components'; import { EuiFlyout } from '@elastic/eui'; -import { euiStyled } from '../../../../../../../../../../src/plugins/kibana_react/common'; +import { + euiStyled, + EuiTheme, +} from '../../../../../../../../../../src/plugins/kibana_react/common'; -export const ResponsiveFlyout = euiStyled(EuiFlyout)` +// TODO: EUI team follow up on complex types and styled-components `styled` +// https://github.com/elastic/eui/issues/4855 +export const ResponsiveFlyout: StyledComponent< + typeof EuiFlyout, + EuiTheme, + { children?: ReactNode } +> = euiStyled(EuiFlyout)` width: 100%; @media (min-width: 800px) { diff --git a/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset_manager.stories.storyshot b/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset_manager.stories.storyshot index 34b6b333f3ef50..d567d3cf85f13b 100644 --- a/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset_manager.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset_manager.stories.storyshot @@ -116,20 +116,13 @@ exports[`Storyshots components/Assets/AssetManager no assets 1`] = ` size="xxl" />
    - -

    - Import your assets to get started -

    -
    - + Import your assets to get started +

    diff --git a/x-pack/plugins/canvas/public/components/custom_element_modal/__stories__/__snapshots__/custom_element_modal.stories.storyshot b/x-pack/plugins/canvas/public/components/custom_element_modal/__stories__/__snapshots__/custom_element_modal.stories.storyshot index 18f86aca243027..dc66eef8090508 100644 --- a/x-pack/plugins/canvas/public/components/custom_element_modal/__stories__/__snapshots__/custom_element_modal.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/custom_element_modal/__stories__/__snapshots__/custom_element_modal.stories.storyshot @@ -80,7 +80,7 @@ exports[`Storyshots components/Elements/CustomElementModal with description 1`] className="euiFormControlLayout__childrenWrapper" >
    40 characters remaining
    @@ -119,7 +119,7 @@ exports[`Storyshots components/Elements/CustomElementModal with description 1`] className="euiFormRow__fieldWrapper" >