From 6f48dbea4df03bdccf1d52f6dabcb346f7b59eda Mon Sep 17 00:00:00 2001 From: shahzad31 Date: Tue, 19 Oct 2021 16:58:34 +0200 Subject: [PATCH] Added percentile refernce lines --- .../configurations/lens_attributes.test.ts | 240 +++++-------- .../configurations/lens_attributes.ts | 225 +++++++++--- .../{overall_column.tsx => overall_column.ts} | 25 +- .../rum/data_distribution_config.ts | 7 +- .../synthetics/data_distribution_config.ts | 1 + .../test_data/sample_attribute.ts | 155 ++++++++- .../sample_attribute_with_reference_lines.ts | 323 ++++++++++++++++++ .../shared/exploratory_view/types.ts | 1 + 8 files changed, 748 insertions(+), 229 deletions(-) rename x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_columns/{overall_column.tsx => overall_column.ts} (81%) create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_with_reference_lines.ts 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 139f9fe67c751..8b102a25a3004 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 @@ -17,6 +17,7 @@ import { import { buildExistsFilter, buildPhrasesFilter } from './utils'; import { sampleAttributeKpi } from './test_data/sample_attribute_kpi'; import { RECORDS_FIELD, REPORT_METRIC_FIELD, PERCENTILE_RANKS, ReportTypes } from './constants'; +import { sampleAttributeWithReferenceLines } from './test_data/sample_attribute_with_reference_lines'; describe('Lens Attribute', () => { mockAppIndexPattern(); @@ -113,6 +114,7 @@ describe('Lens Attribute', () => { }, ...PERCENTILE_RANKS.reduce((acc: Record, rank, index) => { acc[`y-axis-column-${index === 0 ? 'layer' + index : index}`] = { + customLabel: true, dataType: 'number', filter: { language: 'kuery', @@ -134,6 +136,7 @@ describe('Lens Attribute', () => { it('should return main y axis', function () { expect(lnsAttr.getMainYAxis(layerConfig, 'layer0', '')).toEqual({ + customLabel: true, dataType: 'number', isBucketed: false, label: 'Pages loaded', @@ -148,7 +151,7 @@ describe('Lens Attribute', () => { formula: 'count() / overall_sum(count())', isFormulaBroken: false, }, - references: ['y-axis-column-layer0X4'], + references: ['y-axis-column-layer0X3'], scale: 'ratio', }); }); @@ -186,6 +189,7 @@ describe('Lens Attribute', () => { }, fieldName: 'transaction.duration.us', columnLabel: 'Page load time', + showPercentileAnnotations: true, }) ); }); @@ -219,6 +223,7 @@ describe('Lens Attribute', () => { }, fieldName: TRANSACTION_DURATION, columnLabel: 'Page load time', + showPercentileAnnotations: true, }) ); }); @@ -322,135 +327,7 @@ describe('Lens Attribute', () => { }); it('should return first layer', function () { - expect(lnsAttr.getLayers()).toEqual({ - layer0: { - columnOrder: [ - 'x-axis-column-layer0', - 'y-axis-column-layer0', - 'y-axis-column-layer0X0', - 'y-axis-column-layer0X1', - 'y-axis-column-layer0X2', - 'y-axis-column-layer0X3', - 'y-axis-column-layer0X4', - ], - 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', - filter: { - language: 'kuery', - query: - 'transaction.type: page-load and processor.event: transaction and transaction.type : *', - }, - isBucketed: false, - label: 'Pages loaded', - operationType: 'formula', - params: { - format: { - id: 'percent', - params: { - decimals: 0, - }, - }, - formula: - "count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *') / overall_sum(count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *'))", - isFormulaBroken: false, - }, - references: ['y-axis-column-layer0X4'], - scale: 'ratio', - }, - 'y-axis-column-layer0X0': { - customLabel: true, - dataType: 'number', - filter: { - language: 'kuery', - query: - 'transaction.type: page-load and processor.event: transaction and transaction.type : *', - }, - isBucketed: false, - label: 'Part of count() / overall_sum(count())', - operationType: 'count', - scale: 'ratio', - sourceField: 'Records', - }, - 'y-axis-column-layer0X1': { - customLabel: true, - dataType: 'number', - filter: { - language: 'kuery', - query: - 'transaction.type: page-load and processor.event: transaction and transaction.type : *', - }, - isBucketed: false, - label: 'Part of count() / overall_sum(count())', - operationType: 'count', - scale: 'ratio', - sourceField: 'Records', - }, - 'y-axis-column-layer0X2': { - customLabel: true, - dataType: 'number', - isBucketed: false, - label: 'Part of count() / overall_sum(count())', - operationType: 'math', - params: { - tinymathAst: 'y-axis-column-layer0X1', - }, - references: ['y-axis-column-layer0X1'], - scale: 'ratio', - }, - 'y-axis-column-layer0X3': { - customLabel: true, - dataType: 'number', - isBucketed: false, - label: 'Part of count() / overall_sum(count())', - operationType: 'overall_sum', - references: ['y-axis-column-layer0X2'], - scale: 'ratio', - }, - 'y-axis-column-layer0X4': { - customLabel: true, - dataType: 'number', - isBucketed: false, - label: 'Part of count() / overall_sum(count())', - operationType: 'math', - params: { - tinymathAst: { - args: ['y-axis-column-layer0X0', 'y-axis-column-layer0X3'], - location: { - max: 30, - min: 0, - }, - name: 'divide', - text: "count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *') / overall_sum(count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *'))", - type: 'function', - }, - }, - references: ['y-axis-column-layer0X0', 'y-axis-column-layer0X3'], - scale: 'ratio', - }, - }, - incompleteColumns: {}, - }, - }); + expect(lnsAttr.getLayers()).toEqual(sampleAttribute.state.datasourceStates.indexpattern.layers); }); it('should return expected XYState', function () { @@ -469,6 +346,60 @@ describe('Lens Attribute', () => { xAccessor: 'x-axis-column-layer0', yConfig: [{ color: 'green', forAccessor: 'y-axis-column-layer0' }], }, + { + accessors: [ + '50th-percentile-reference-line-layer0-reference-lines', + '75th-percentile-reference-line-layer0-reference-lines', + '90th-percentile-reference-line-layer0-reference-lines', + '95th-percentile-reference-line-layer0-reference-lines', + '99th-percentile-reference-line-layer0-reference-lines', + ], + layerId: 'layer0-reference-lines', + layerType: 'referenceLine', + seriesType: 'line', + yConfig: [ + { + axisMode: 'bottom', + color: '#6092C0', + forAccessor: '50th-percentile-reference-line-layer0-reference-lines', + lineStyle: 'solid', + lineWidth: 2, + textVisibility: true, + }, + { + axisMode: 'bottom', + color: '#6092C0', + forAccessor: '75th-percentile-reference-line-layer0-reference-lines', + lineStyle: 'solid', + lineWidth: 2, + textVisibility: true, + }, + { + axisMode: 'bottom', + color: '#6092C0', + forAccessor: '90th-percentile-reference-line-layer0-reference-lines', + lineStyle: 'solid', + lineWidth: 2, + textVisibility: true, + }, + { + axisMode: 'bottom', + color: '#6092C0', + forAccessor: '95th-percentile-reference-line-layer0-reference-lines', + lineStyle: 'solid', + lineWidth: 2, + textVisibility: true, + }, + { + axisMode: 'bottom', + color: '#6092C0', + forAccessor: '99th-percentile-reference-line-layer0-reference-lines', + lineStyle: 'solid', + lineWidth: 2, + textVisibility: true, + }, + ], + }, ], legend: { isVisible: true, showSingleSeries: true, position: 'right' }, preferredSeriesType: 'line', @@ -489,7 +420,7 @@ describe('Lens Attribute', () => { time: { from: 'now-15m', to: 'now' }, color: 'green', name: 'test-series', - selectedMetricField: TRANSACTION_DURATION, + selectedMetricField: LCP_FIELD, }; lnsAttr = new LensAttributes([layerConfig1]); @@ -523,7 +454,6 @@ describe('Lens Attribute', () => { 'y-axis-column-layer0X1', 'y-axis-column-layer0X2', 'y-axis-column-layer0X3', - 'y-axis-column-layer0X4', ], columns: { 'breakdown-column-layer0': { @@ -547,7 +477,7 @@ describe('Lens Attribute', () => { 'x-axis-column-layer0': { dataType: 'number', isBucketed: true, - label: 'Page load time', + label: 'Largest contentful paint', operationType: 'range', params: { maxBars: 'auto', @@ -561,9 +491,10 @@ describe('Lens Attribute', () => { type: 'histogram', }, scale: 'interval', - sourceField: 'transaction.duration.us', + sourceField: LCP_FIELD, }, 'y-axis-column-layer0': { + customLabel: true, dataType: 'number', filter: { language: 'kuery', @@ -584,7 +515,7 @@ describe('Lens Attribute', () => { "count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *') / overall_sum(count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *'))", isFormulaBroken: false, }, - references: ['y-axis-column-layer0X4'], + references: ['y-axis-column-layer0X3'], scale: 'ratio', }, 'y-axis-column-layer0X0': { @@ -620,23 +551,11 @@ describe('Lens Attribute', () => { dataType: 'number', isBucketed: false, label: 'Part of count() / overall_sum(count())', - operationType: 'math', - params: { - tinymathAst: 'y-axis-column-layer0X1', - }, + operationType: 'overall_sum', references: ['y-axis-column-layer0X1'], scale: 'ratio', }, 'y-axis-column-layer0X3': { - customLabel: true, - dataType: 'number', - isBucketed: false, - label: 'Part of count() / overall_sum(count())', - operationType: 'overall_sum', - references: ['y-axis-column-layer0X2'], - scale: 'ratio', - }, - 'y-axis-column-layer0X4': { customLabel: true, dataType: 'number', isBucketed: false, @@ -644,7 +563,7 @@ describe('Lens Attribute', () => { operationType: 'math', params: { tinymathAst: { - args: ['y-axis-column-layer0X0', 'y-axis-column-layer0X3'], + args: ['y-axis-column-layer0X0', 'y-axis-column-layer0X2'], location: { max: 30, min: 0, @@ -654,7 +573,7 @@ describe('Lens Attribute', () => { type: 'function', }, }, - references: ['y-axis-column-layer0X0', 'y-axis-column-layer0X3'], + references: ['y-axis-column-layer0X0', 'y-axis-column-layer0X2'], scale: 'ratio', }, }, @@ -688,4 +607,25 @@ describe('Lens Attribute', () => { ); }); }); + + describe('Reference line layers', function () { + it('should return expected reference lines', function () { + const layerConfig1: LayerConfig = { + seriesConfig: reportViewConfig, + seriesType: 'line', + indexPattern: mockIndexPattern, + reportDefinitions: {}, + time: { from: 'now-15m', to: 'now' }, + color: 'green', + name: 'test-series', + selectedMetricField: TRANSACTION_DURATION, + }; + + lnsAttr = new LensAttributes([layerConfig1]); + + const attributes = lnsAttr.getJSON(); + + expect(attributes).toEqual(sampleAttributeWithReferenceLines); + }); + }); }); 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 a31bef7c9c214..f93dcd74b19e8 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 @@ -9,34 +9,34 @@ import { i18n } from '@kbn/i18n'; import { capitalize } from 'lodash'; import { ExistsFilter, isExistsFilter } from '@kbn/es-query'; import { + AvgIndexPatternColumn, + CardinalityIndexPatternColumn, CountIndexPatternColumn, + DataType, DateHistogramIndexPatternColumn, - AvgIndexPatternColumn, + FieldBasedIndexPatternColumn, MedianIndexPatternColumn, - PercentileIndexPatternColumn, + OperationMetadata, OperationType, + PercentileIndexPatternColumn, PersistedIndexPatternLayer, RangeIndexPatternColumn, SeriesType, - TypedLensByValueInput, - XYState, - XYCurveType, - DataType, - OperationMetadata, - FieldBasedIndexPatternColumn, SumIndexPatternColumn, TermsIndexPatternColumn, - CardinalityIndexPatternColumn, + TypedLensByValueInput, + XYCurveType, + XYState, } from '../../../../../../lens/public'; import { urlFiltersToKueryString } from '../utils/stringify_kueries'; import { IndexPattern } from '../../../../../../../../src/plugins/data/common'; import { FILTER_RECORDS, - USE_BREAK_DOWN_COLUMN, - TERMS_COLUMN, - REPORT_METRIC_FIELD, RECORDS_FIELD, RECORDS_PERCENTAGE_FIELD, + REPORT_METRIC_FIELD, + TERMS_COLUMN, + USE_BREAK_DOWN_COLUMN, PERCENTILE, PERCENTILE_RANKS, ReportTypes, @@ -64,6 +64,7 @@ export const parseCustomFieldName = (seriesConfig: SeriesConfig, selectedMetricF let columnFilters; let timeScale; let columnLabel; + let showPercentileAnnotations; const metricOptions = seriesConfig.metricOptions ?? []; @@ -76,10 +77,18 @@ export const parseCustomFieldName = (seriesConfig: SeriesConfig, selectedMetricF columnFilters = currField?.columnFilters; timeScale = currField?.timeScale; columnLabel = currField?.label; + showPercentileAnnotations = currField?.showPercentileAnnotations; } } - return { fieldName: selectedMetricField!, columnType, columnFilters, timeScale, columnLabel }; + return { + fieldName: selectedMetricField!, + columnType, + columnFilters, + timeScale, + columnLabel, + showPercentileAnnotations, + }; }; export interface LayerConfig { @@ -98,12 +107,21 @@ export interface LayerConfig { export class LensAttributes { layers: Record; + seriesReferenceLines: Record< + string, + { + layerData: PersistedIndexPatternLayer; + layerState: XYState['layers']; + indexPattern: IndexPattern; + } + >; visualization: XYState; layerConfigs: LayerConfig[]; isMultiSeries: boolean; constructor(layerConfigs: LayerConfig[]) { this.layers = {}; + this.seriesReferenceLines = {}; layerConfigs.forEach(({ seriesConfig, operationType }) => { if (operationType) { @@ -259,6 +277,7 @@ export class LensAttributes { getPercentileBreakdowns( layerConfig: LayerConfig, + layerId: string, columnFilter?: string ): Record { const yAxisColumns = layerConfig.seriesConfig.yAxisColumns; @@ -273,6 +292,7 @@ export class LensAttributes { operationType: PERCENTILE_RANKS[i], label: mainLabel, layerConfig, + layerId, colIndex: i, }), filter: { query: columnFilter || '', language: 'kuery' }, @@ -294,6 +314,7 @@ export class LensAttributes { }), operationType: 'percentile', params: { percentile: Number(percentileValue.split('th')[0]) }, + customLabel: true, }; } @@ -342,6 +363,7 @@ export class LensAttributes { return this.getColumnBasedOnType({ layerConfig, + layerId, label: xAxisColumn.label, sourceField: xAxisColumn.sourceField!, }); @@ -353,16 +375,29 @@ export class LensAttributes { layerConfig, operationType, colIndex, + layerId, }: { sourceField: string; operationType?: OperationType; label?: string; + layerId: string; layerConfig: LayerConfig; colIndex?: number; }) { const { breakdown, seriesConfig } = layerConfig; - const { fieldMeta, columnType, fieldName, columnLabel, timeScale, columnFilters } = - this.getFieldMeta(sourceField, layerConfig); + const { + fieldMeta, + columnType, + fieldName, + columnLabel, + timeScale, + columnFilters, + showPercentileAnnotations, + } = this.getFieldMeta(sourceField, layerConfig); + + if (showPercentileAnnotations) { + this.addThresholdLayer(fieldName, layerId, layerConfig); + } const { type: fieldType } = fieldMeta ?? {}; @@ -427,12 +462,24 @@ export class LensAttributes { getFieldMeta(sourceField: string, layerConfig: LayerConfig) { if (sourceField === REPORT_METRIC_FIELD) { - const { fieldName, columnType, columnLabel, columnFilters, timeScale } = parseCustomFieldName( - layerConfig.seriesConfig, - layerConfig.selectedMetricField - ); + const { + fieldName, + columnType, + columnLabel, + columnFilters, + timeScale, + showPercentileAnnotations, + } = parseCustomFieldName(layerConfig.seriesConfig, layerConfig.selectedMetricField); const fieldMeta = layerConfig.indexPattern.getFieldByName(fieldName!); - return { fieldMeta, fieldName, columnType, columnLabel, columnFilters, timeScale }; + return { + fieldMeta, + fieldName, + columnType, + columnLabel, + columnFilters, + timeScale, + showPercentileAnnotations, + }; } else { const fieldMeta = layerConfig.indexPattern.getFieldByName(sourceField); @@ -458,22 +505,28 @@ export class LensAttributes { layerConfig, colIndex: 0, operationType: breakdown === PERCENTILE ? PERCENTILE_RANKS[0] : operationType, + layerId, }); } - getChildYAxises(layerConfig: LayerConfig, layerId?: string, columnFilter?: string) { + getChildYAxises( + layerConfig: LayerConfig, + layerId: string, + columnFilter?: string, + forAccessorsKeys?: boolean + ) { const { breakdown } = layerConfig; const lensColumns: Record = {}; const yAxisColumns = layerConfig.seriesConfig.yAxisColumns; const { sourceField: mainSourceField, label: mainLabel } = yAxisColumns[0]; - if (mainSourceField === RECORDS_PERCENTAGE_FIELD && layerId) { + if (mainSourceField === RECORDS_PERCENTAGE_FIELD && layerId && !forAccessorsKeys) { return getDistributionInPercentageColumn({ label: mainLabel, layerId, columnFilter }) .supportingColumns; } if (yAxisColumns.length === 1 && breakdown === PERCENTILE) { - return this.getPercentileBreakdowns(layerConfig, columnFilter); + return this.getPercentileBreakdowns(layerConfig, layerId, columnFilter); } if (yAxisColumns.length === 1) { @@ -490,6 +543,7 @@ export class LensAttributes { label, layerConfig, colIndex: i, + layerId, }); } return lensColumns; @@ -661,6 +715,10 @@ export class LensAttributes { }; }); + Object.entries(this.seriesReferenceLines).forEach(([id, { layerData }]) => { + layers[id] = layerData; + }); + return layers; } @@ -678,29 +736,104 @@ export class LensAttributes { tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true }, gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, preferredSeriesType: 'line', - layers: this.layerConfigs.map((layerConfig, index) => ({ - accessors: [ - `y-axis-column-layer${index}`, - ...Object.keys(this.getChildYAxises(layerConfig)), - ], - layerId: `layer${index}`, - layerType: 'data', - seriesType: layerConfig.seriesType || layerConfig.seriesConfig.defaultSeriesType, - palette: layerConfig.seriesConfig.palette, - yConfig: layerConfig.seriesConfig.yConfig || [ - { forAccessor: `y-axis-column-layer${index}`, color: layerConfig.color }, - ], - xAccessor: `x-axis-column-layer${index}`, - ...(layerConfig.breakdown && - layerConfig.breakdown !== PERCENTILE && - layerConfig.seriesConfig.xAxisColumn.sourceField !== USE_BREAK_DOWN_COLUMN - ? { splitAccessor: `breakdown-column-layer${index}` } - : {}), - })), + layers: this.getDataLayers(), + }; + } + + getDataLayers(): XYState['layers'] { + const dataLayers = this.layerConfigs.map((layerConfig, index) => ({ + accessors: [ + `y-axis-column-layer${index}`, + ...Object.keys(this.getChildYAxises(layerConfig, `layer${index}`, undefined, true)), + ], + layerId: `layer${index}`, + layerType: 'data' as any, + seriesType: layerConfig.seriesType || layerConfig.seriesConfig.defaultSeriesType, + palette: layerConfig.seriesConfig.palette, + yConfig: layerConfig.seriesConfig.yConfig || [ + { forAccessor: `y-axis-column-layer${index}`, color: layerConfig.color }, + ], + xAccessor: `x-axis-column-layer${index}`, + ...(layerConfig.breakdown && + layerConfig.breakdown !== PERCENTILE && + layerConfig.seriesConfig.xAxisColumn.sourceField !== USE_BREAK_DOWN_COLUMN + ? { splitAccessor: `breakdown-column-layer${index}` } + : {}), ...(this.layerConfigs[0].seriesConfig.yTitle ? { yTitle: this.layerConfigs[0].seriesConfig.yTitle } : {}), + })); + + const referenceLineLayers: XYState['layers'] = []; + + Object.entries(this.seriesReferenceLines).forEach(([_id, { layerState }]) => { + referenceLineLayers.push(layerState[0]); + }); + + return [...dataLayers, ...referenceLineLayers]; + } + + addThresholdLayer( + fieldName: string, + layerId: string, + { seriesConfig, indexPattern }: LayerConfig + ) { + const referenceLineLayerId = `${layerId}-reference-lines`; + + const referenceLineColumns = this.getThresholdColumns( + fieldName, + referenceLineLayerId, + seriesConfig + ); + + const layerData = { + columnOrder: Object.keys(referenceLineColumns), + columns: referenceLineColumns, + incompleteColumns: {}, }; + + const layerState = this.getThresholdLayer(fieldName, referenceLineLayerId, seriesConfig); + + this.seriesReferenceLines[referenceLineLayerId] = { layerData, layerState, indexPattern }; + } + + getThresholdLayer( + fieldName: string, + referenceLineLayerId: string, + seriesConfig: SeriesConfig + ): XYState['layers'] { + const columns = this.getThresholdColumns(fieldName, referenceLineLayerId, seriesConfig); + + return [ + { + layerId: referenceLineLayerId, + accessors: Object.keys(columns), + layerType: 'referenceLine', + seriesType: 'line', + yConfig: Object.keys(columns).map((columnId) => ({ + axisMode: 'bottom', + color: '#6092C0', + forAccessor: columnId, + lineStyle: 'solid', + lineWidth: 2, + textVisibility: true, + })), + }, + ]; + } + + getThresholdColumns(fieldName: string, layerId: string, seriesConfig: SeriesConfig) { + const referenceLines = ['50th', '75th', '90th', '95th', '99th']; + const columns: Record = {}; + + referenceLines.forEach((referenceLine) => { + columns[`${referenceLine}-percentile-reference-line-${layerId}`] = { + ...this.getPercentileNumberColumn(fieldName, referenceLine, seriesConfig), + label: referenceLine, + }; + }); + + return columns; } getJSON(refresh?: number): TypedLensByValueInput['attributes'] { @@ -709,6 +842,13 @@ export class LensAttributes { ); const query = this.layerConfigs[0].seriesConfig.query; + const referenceLineIndexReferences = Object.entries(this.seriesReferenceLines).map( + ([id, { indexPattern }]) => ({ + id: indexPattern.id!, + name: getLayerReferenceName(id), + type: 'index-pattern', + }) + ); return { title: 'Prefilled from exploratory view app', @@ -725,6 +865,7 @@ export class LensAttributes { name: getLayerReferenceName(`layer${index}`), type: 'index-pattern', })), + ...referenceLineIndexReferences, ], state: { datasourceStates: { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_columns/overall_column.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_columns/overall_column.ts similarity index 81% rename from x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_columns/overall_column.tsx rename to x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_columns/overall_column.ts index 44b40944dfd07..32268dcd8e440 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_columns/overall_column.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_columns/overall_column.ts @@ -31,6 +31,7 @@ export function getDistributionInPercentageColumn({ } const main: FormulaIndexPatternColumn = { + customLabel: true, label: label || 'Percentage of records', dataType: 'number' as DataType, operationType: 'formula', @@ -41,7 +42,7 @@ export function getDistributionInPercentageColumn({ isFormulaBroken: false, format: { id: 'percent', params: { decimals: 0 } }, }, - references: [`${yAxisColId}X4`], + references: [`${yAxisColId}X3`], }; const countColumn: CountIndexPatternColumn = { @@ -55,24 +56,13 @@ export function getDistributionInPercentageColumn({ filter: { query: columnFilter ?? '', language: 'kuery' }, }; - const mathColumn: MathIndexPatternColumn = { - label: 'Part of count() / overall_sum(count())', - dataType: 'number', - operationType: 'math', - isBucketed: false, - scale: 'ratio', - params: { tinymathAst: `${yAxisColId}X1` }, - references: [`${yAxisColId}X1`], - customLabel: true, - }; - const overAllSumColumn: OverallSumIndexPatternColumn = { label: 'Part of count() / overall_sum(count())', dataType: 'number', operationType: 'overall_sum', isBucketed: false, scale: 'ratio', - references: [`${yAxisColId}X2`], + references: [`${yAxisColId}X1`], customLabel: true, }; @@ -86,12 +76,12 @@ export function getDistributionInPercentageColumn({ tinymathAst: { type: 'function', name: 'divide', - args: [`${yAxisColId}X0`, `${yAxisColId}X3`], + args: [`${yAxisColId}X0`, `${yAxisColId}X2`], location: { min: 0, max: 30 }, text: lensFormula, } as unknown as TinymathAST, }, - references: [`${yAxisColId}X0`, `${yAxisColId}X3`], + references: [`${yAxisColId}X0`, `${yAxisColId}X2`], customLabel: true, }; @@ -101,9 +91,8 @@ export function getDistributionInPercentageColumn({ > = { [`${yAxisColId}X0`]: countColumn, [`${yAxisColId}X1`]: countColumn, - [`${yAxisColId}X2`]: mathColumn, - [`${yAxisColId}X3`]: overAllSumColumn, - [`${yAxisColId}X4`]: tinyMathColumn, + [`${yAxisColId}X2`]: overAllSumColumn, + [`${yAxisColId}X3`]: tinyMathColumn, }; return { main, supportingColumns }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/data_distribution_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/data_distribution_config.ts index 7796b381423bf..fd731fecd4687 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/data_distribution_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/data_distribution_config.ts @@ -77,7 +77,12 @@ export function getRumDistributionConfig({ indexPattern }: ConfigProps): SeriesC breakdownFields: [USER_AGENT_NAME, USER_AGENT_OS, CLIENT_GEO_COUNTRY_NAME, USER_AGENT_DEVICE], definitionFields: [SERVICE_NAME, SERVICE_ENVIRONMENT], metricOptions: [ - { label: PAGE_LOAD_TIME_LABEL, id: TRANSACTION_DURATION, field: TRANSACTION_DURATION }, + { + label: PAGE_LOAD_TIME_LABEL, + id: TRANSACTION_DURATION, + field: TRANSACTION_DURATION, + showPercentileAnnotations: true, + }, { label: BACKEND_TIME_LABEL, id: TRANSACTION_TIME_TO_FIRST_BYTE, 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 fb44da8e4327f..bcb8372954dfd 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 @@ -68,6 +68,7 @@ export function getSyntheticsDistributionConfig({ label: MONITORS_DURATION_LABEL, id: MONITOR_DURATION_US, field: MONITOR_DURATION_US, + showPercentileAnnotations: true, }, { label: LCP_LABEL, 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 8254a5a816921..8dc10fd318bcf 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 @@ -17,6 +17,11 @@ export const sampleAttribute = { name: 'indexpattern-datasource-layer-layer0', type: 'index-pattern', }, + { + id: 'apm-*', + name: 'indexpattern-datasource-layer-layer0-reference-lines', + type: 'index-pattern', + }, ], state: { datasourceStates: { @@ -30,7 +35,6 @@ export const sampleAttribute = { 'y-axis-column-layer0X1', 'y-axis-column-layer0X2', 'y-axis-column-layer0X3', - 'y-axis-column-layer0X4', ], columns: { 'x-axis-column-layer0': { @@ -53,6 +57,7 @@ export const sampleAttribute = { sourceField: 'transaction.duration.us', }, 'y-axis-column-layer0': { + customLabel: true, dataType: 'number', filter: { language: 'kuery', @@ -73,7 +78,7 @@ export const sampleAttribute = { "count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *') / overall_sum(count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *'))", isFormulaBroken: false, }, - references: ['y-axis-column-layer0X4'], + references: ['y-axis-column-layer0X3'], scale: 'ratio', }, 'y-axis-column-layer0X0': { @@ -109,23 +114,11 @@ export const sampleAttribute = { dataType: 'number', isBucketed: false, label: 'Part of count() / overall_sum(count())', - operationType: 'math', - params: { - tinymathAst: 'y-axis-column-layer0X1', - }, + operationType: 'overall_sum', references: ['y-axis-column-layer0X1'], scale: 'ratio', }, 'y-axis-column-layer0X3': { - customLabel: true, - dataType: 'number', - isBucketed: false, - label: 'Part of count() / overall_sum(count())', - operationType: 'overall_sum', - references: ['y-axis-column-layer0X2'], - scale: 'ratio', - }, - 'y-axis-column-layer0X4': { customLabel: true, dataType: 'number', isBucketed: false, @@ -133,7 +126,7 @@ export const sampleAttribute = { operationType: 'math', params: { tinymathAst: { - args: ['y-axis-column-layer0X0', 'y-axis-column-layer0X3'], + args: ['y-axis-column-layer0X0', 'y-axis-column-layer0X2'], location: { max: 30, min: 0, @@ -143,8 +136,80 @@ export const sampleAttribute = { type: 'function', }, }, - references: ['y-axis-column-layer0X0', 'y-axis-column-layer0X3'], + references: ['y-axis-column-layer0X0', 'y-axis-column-layer0X2'], + scale: 'ratio', + }, + }, + incompleteColumns: {}, + }, + 'layer0-reference-lines': { + columnOrder: [ + '50th-percentile-reference-line-layer0-reference-lines', + '75th-percentile-reference-line-layer0-reference-lines', + '90th-percentile-reference-line-layer0-reference-lines', + '95th-percentile-reference-line-layer0-reference-lines', + '99th-percentile-reference-line-layer0-reference-lines', + ], + columns: { + '50th-percentile-reference-line-layer0-reference-lines': { + customLabel: true, + dataType: 'number', + isBucketed: false, + label: '50th', + operationType: 'percentile', + params: { + percentile: 50, + }, + scale: 'ratio', + sourceField: 'transaction.duration.us', + }, + '75th-percentile-reference-line-layer0-reference-lines': { + customLabel: true, + dataType: 'number', + isBucketed: false, + label: '75th', + operationType: 'percentile', + params: { + percentile: 75, + }, + scale: 'ratio', + sourceField: 'transaction.duration.us', + }, + '90th-percentile-reference-line-layer0-reference-lines': { + customLabel: true, + dataType: 'number', + isBucketed: false, + label: '90th', + operationType: 'percentile', + params: { + percentile: 90, + }, + scale: 'ratio', + sourceField: 'transaction.duration.us', + }, + '95th-percentile-reference-line-layer0-reference-lines': { + customLabel: true, + dataType: 'number', + isBucketed: false, + label: '95th', + operationType: 'percentile', + params: { + percentile: 95, + }, + scale: 'ratio', + sourceField: 'transaction.duration.us', + }, + '99th-percentile-reference-line-layer0-reference-lines': { + customLabel: true, + dataType: 'number', + isBucketed: false, + label: '99th', + operationType: 'percentile', + params: { + percentile: 99, + }, scale: 'ratio', + sourceField: 'transaction.duration.us', }, }, incompleteColumns: {}, @@ -184,11 +249,65 @@ export const sampleAttribute = { }, ], }, + { + accessors: [ + '50th-percentile-reference-line-layer0-reference-lines', + '75th-percentile-reference-line-layer0-reference-lines', + '90th-percentile-reference-line-layer0-reference-lines', + '95th-percentile-reference-line-layer0-reference-lines', + '99th-percentile-reference-line-layer0-reference-lines', + ], + layerId: 'layer0-reference-lines', + layerType: 'referenceLine', + seriesType: 'line', + yConfig: [ + { + axisMode: 'bottom', + color: '#6092C0', + forAccessor: '50th-percentile-reference-line-layer0-reference-lines', + lineStyle: 'solid', + lineWidth: 2, + textVisibility: true, + }, + { + axisMode: 'bottom', + color: '#6092C0', + forAccessor: '75th-percentile-reference-line-layer0-reference-lines', + lineStyle: 'solid', + lineWidth: 2, + textVisibility: true, + }, + { + axisMode: 'bottom', + color: '#6092C0', + forAccessor: '90th-percentile-reference-line-layer0-reference-lines', + lineStyle: 'solid', + lineWidth: 2, + textVisibility: true, + }, + { + axisMode: 'bottom', + color: '#6092C0', + forAccessor: '95th-percentile-reference-line-layer0-reference-lines', + lineStyle: 'solid', + lineWidth: 2, + textVisibility: true, + }, + { + axisMode: 'bottom', + color: '#6092C0', + forAccessor: '99th-percentile-reference-line-layer0-reference-lines', + lineStyle: 'solid', + lineWidth: 2, + textVisibility: true, + }, + ], + }, ], legend: { isVisible: true, - showSingleSeries: true, position: 'right', + showSingleSeries: true, }, preferredSeriesType: 'line', tickLabelsVisibilitySettings: { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_with_reference_lines.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_with_reference_lines.ts new file mode 100644 index 0000000000000..ea68dd080a4e4 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_with_reference_lines.ts @@ -0,0 +1,323 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export const sampleAttributeWithReferenceLines = { + description: 'undefined', + references: [ + { + id: 'apm-*', + name: 'indexpattern-datasource-current-indexpattern', + type: 'index-pattern', + }, + { + id: 'apm-*', + name: 'indexpattern-datasource-layer-layer0', + type: 'index-pattern', + }, + { + id: 'apm-*', + name: 'indexpattern-datasource-layer-layer0-reference-lines', + type: 'index-pattern', + }, + ], + state: { + datasourceStates: { + indexpattern: { + layers: { + layer0: { + columnOrder: [ + 'x-axis-column-layer0', + 'y-axis-column-layer0', + 'y-axis-column-layer0X0', + 'y-axis-column-layer0X1', + 'y-axis-column-layer0X2', + 'y-axis-column-layer0X3', + ], + 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': { + customLabel: true, + dataType: 'number', + filter: { + language: 'kuery', + query: + 'transaction.type: page-load and processor.event: transaction and transaction.type : * and service.name: (elastic or kibana)', + }, + isBucketed: false, + label: 'Pages loaded', + operationType: 'formula', + params: { + format: { + id: 'percent', + params: { + decimals: 0, + }, + }, + formula: + "count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : * and service.name: (elastic or kibana)') / overall_sum(count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : * and service.name: (elastic or kibana)'))", + isFormulaBroken: false, + }, + references: ['y-axis-column-layer0X3'], + scale: 'ratio', + }, + 'y-axis-column-layer0X0': { + customLabel: true, + dataType: 'number', + filter: { + language: 'kuery', + query: + 'transaction.type: page-load and processor.event: transaction and transaction.type : * and service.name: (elastic or kibana)', + }, + isBucketed: false, + label: 'Part of count() / overall_sum(count())', + operationType: 'count', + scale: 'ratio', + sourceField: 'Records', + }, + 'y-axis-column-layer0X1': { + customLabel: true, + dataType: 'number', + filter: { + language: 'kuery', + query: + 'transaction.type: page-load and processor.event: transaction and transaction.type : * and service.name: (elastic or kibana)', + }, + isBucketed: false, + label: 'Part of count() / overall_sum(count())', + operationType: 'count', + scale: 'ratio', + sourceField: 'Records', + }, + 'y-axis-column-layer0X2': { + customLabel: true, + dataType: 'number', + isBucketed: false, + label: 'Part of count() / overall_sum(count())', + operationType: 'overall_sum', + references: ['y-axis-column-layer0X1'], + scale: 'ratio', + }, + 'y-axis-column-layer0X3': { + customLabel: true, + dataType: 'number', + isBucketed: false, + label: 'Part of count() / overall_sum(count())', + operationType: 'math', + params: { + tinymathAst: { + args: ['y-axis-column-layer0X0', 'y-axis-column-layer0X2'], + location: { + max: 30, + min: 0, + }, + name: 'divide', + text: "count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : * and service.name: (elastic or kibana)') / overall_sum(count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : * and service.name: (elastic or kibana)'))", + type: 'function', + }, + }, + references: ['y-axis-column-layer0X0', 'y-axis-column-layer0X2'], + scale: 'ratio', + }, + }, + incompleteColumns: {}, + }, + 'layer0-reference-lines': { + columnOrder: [ + '50th-percentile-reference-line-layer0-reference-lines', + '75th-percentile-reference-line-layer0-reference-lines', + '90th-percentile-reference-line-layer0-reference-lines', + '95th-percentile-reference-line-layer0-reference-lines', + '99th-percentile-reference-line-layer0-reference-lines', + ], + columns: { + '50th-percentile-reference-line-layer0-reference-lines': { + customLabel: true, + dataType: 'number', + isBucketed: false, + label: '50th', + operationType: 'percentile', + params: { + percentile: 50, + }, + scale: 'ratio', + sourceField: 'transaction.duration.us', + }, + '75th-percentile-reference-line-layer0-reference-lines': { + customLabel: true, + dataType: 'number', + isBucketed: false, + label: '75th', + operationType: 'percentile', + params: { + percentile: 75, + }, + scale: 'ratio', + sourceField: 'transaction.duration.us', + }, + '90th-percentile-reference-line-layer0-reference-lines': { + customLabel: true, + dataType: 'number', + isBucketed: false, + label: '90th', + operationType: 'percentile', + params: { + percentile: 90, + }, + scale: 'ratio', + sourceField: 'transaction.duration.us', + }, + '95th-percentile-reference-line-layer0-reference-lines': { + customLabel: true, + dataType: 'number', + isBucketed: false, + label: '95th', + operationType: 'percentile', + params: { + percentile: 95, + }, + scale: 'ratio', + sourceField: 'transaction.duration.us', + }, + '99th-percentile-reference-line-layer0-reference-lines': { + customLabel: true, + dataType: 'number', + isBucketed: false, + label: '99th', + operationType: 'percentile', + params: { + percentile: 99, + }, + scale: 'ratio', + sourceField: 'transaction.duration.us', + }, + }, + incompleteColumns: {}, + }, + }, + }, + }, + filters: [], + query: { + language: 'kuery', + query: 'transaction.duration.us < 60000000', + }, + visualization: { + axisTitlesVisibilitySettings: { + x: true, + yLeft: true, + yRight: true, + }, + curveType: 'CURVE_MONOTONE_X', + fittingFunction: 'Linear', + gridlinesVisibilitySettings: { + x: true, + yLeft: true, + yRight: true, + }, + layers: [ + { + accessors: ['y-axis-column-layer0'], + layerId: 'layer0', + layerType: 'data', + seriesType: 'line', + xAccessor: 'x-axis-column-layer0', + yConfig: [ + { + color: 'green', + forAccessor: 'y-axis-column-layer0', + }, + ], + }, + { + accessors: [ + '50th-percentile-reference-line-layer0-reference-lines', + '75th-percentile-reference-line-layer0-reference-lines', + '90th-percentile-reference-line-layer0-reference-lines', + '95th-percentile-reference-line-layer0-reference-lines', + '99th-percentile-reference-line-layer0-reference-lines', + ], + layerId: 'layer0-reference-lines', + layerType: 'referenceLine', + seriesType: 'line', + yConfig: [ + { + axisMode: 'bottom', + color: '#6092C0', + forAccessor: '50th-percentile-reference-line-layer0-reference-lines', + lineStyle: 'solid', + lineWidth: 2, + textVisibility: true, + }, + { + axisMode: 'bottom', + color: '#6092C0', + forAccessor: '75th-percentile-reference-line-layer0-reference-lines', + lineStyle: 'solid', + lineWidth: 2, + textVisibility: true, + }, + { + axisMode: 'bottom', + color: '#6092C0', + forAccessor: '90th-percentile-reference-line-layer0-reference-lines', + lineStyle: 'solid', + lineWidth: 2, + textVisibility: true, + }, + { + axisMode: 'bottom', + color: '#6092C0', + forAccessor: '95th-percentile-reference-line-layer0-reference-lines', + lineStyle: 'solid', + lineWidth: 2, + textVisibility: true, + }, + { + axisMode: 'bottom', + color: '#6092C0', + forAccessor: '99th-percentile-reference-line-layer0-reference-lines', + lineStyle: 'solid', + lineWidth: 2, + textVisibility: true, + }, + ], + }, + ], + legend: { + isVisible: true, + position: 'right', + showSingleSeries: true, + }, + preferredSeriesType: 'line', + tickLabelsVisibilitySettings: { + x: true, + yLeft: true, + yRight: true, + }, + valueLabels: 'hide', + }, + }, + title: 'Prefilled from exploratory view app', + visualizationType: 'lnsXY', +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts index acd49fc25588e..9112be6187d33 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts @@ -45,6 +45,7 @@ export interface MetricOption { columnType?: 'range' | 'operation' | 'FILTER_RECORDS' | 'TERMS_COLUMN' | 'unique_count'; columnFilters?: ColumnFilter[]; timeScale?: string; + showPercentileAnnotations?: boolean; } export interface SeriesConfig {