Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Observability] [Exploratory View] add percentile ranks, show legend always, and fix field labels #113765

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { OperationType } from '../../../../../../../lens/public';
import { ReportViewType } from '../../types';
import {
CLS_FIELD,
Expand All @@ -13,6 +13,7 @@ import {
LCP_FIELD,
TBT_FIELD,
TRANSACTION_TIME_TO_FIRST_BYTE,
TRANSACTION_DURATION,
} from './elasticsearch_fieldnames';
import {
AGENT_HOST_LABEL,
Expand Down Expand Up @@ -45,6 +46,8 @@ import {
TBT_LABEL,
URL_LABEL,
BACKEND_TIME_LABEL,
MONITORS_DURATION_LABEL,
PAGE_LOAD_TIME_LABEL,
LABELS_FIELD,
} from './labels';

Expand All @@ -69,9 +72,11 @@ export const FieldLabels: Record<string, string> = {
[FID_FIELD]: FID_LABEL,
[CLS_FIELD]: CLS_LABEL,
[TRANSACTION_TIME_TO_FIRST_BYTE]: BACKEND_TIME_LABEL,
[TRANSACTION_DURATION]: PAGE_LOAD_TIME_LABEL,

'monitor.id': MONITOR_ID_LABEL,
'monitor.status': MONITOR_STATUS_LABEL,
'monitor.duration.us': MONITORS_DURATION_LABEL,

'agent.hostname': AGENT_HOST_LABEL,
'host.hostname': HOST_NAME_LABEL,
Expand All @@ -86,6 +91,7 @@ export const FieldLabels: Record<string, string> = {
'performance.metric': METRIC_LABEL,
'Business.KPI': KPI_LABEL,
'http.request.method': REQUEST_METHOD,
percentile: 'Percentile',
LABEL_FIELDS_FILTER: LABELS_FIELD,
LABEL_FIELDS_BREAKDOWN: 'Labels field',
};
Expand Down Expand Up @@ -114,8 +120,16 @@ export const USE_BREAK_DOWN_COLUMN = 'USE_BREAK_DOWN_COLUMN';
export const FILTER_RECORDS = 'FILTER_RECORDS';
export const TERMS_COLUMN = 'TERMS_COLUMN';
export const OPERATION_COLUMN = 'operation';
export const PERCENTILE = 'percentile';

export const REPORT_METRIC_FIELD = 'REPORT_METRIC_FIELD';

export const PERCENTILE_RANKS = [
'99th' as OperationType,
'95th' as OperationType,
'90th' as OperationType,
'75th' as OperationType,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should add 50th as well.

'50th' as OperationType,
];
export const LABEL_FIELDS_FILTER = 'LABEL_FIELDS_FILTER';
export const LABEL_FIELDS_BREAKDOWN = 'LABEL_FIELDS_BREAKDOWN';
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
} from './constants/elasticsearch_fieldnames';
import { buildExistsFilter, buildPhrasesFilter } from './utils';
import { sampleAttributeKpi } from './test_data/sample_attribute_kpi';
import { RECORDS_FIELD, REPORT_METRIC_FIELD, ReportTypes } from './constants';
import { RECORDS_FIELD, REPORT_METRIC_FIELD, PERCENTILE_RANKS, ReportTypes } from './constants';

describe('Lens Attribute', () => {
mockAppIndexPattern();
Expand Down Expand Up @@ -75,6 +75,63 @@ describe('Lens Attribute', () => {
expect(lnsAttrKpi.getJSON()).toEqual(sampleAttributeKpi);
});

it('should return expected json for percentile breakdowns', function () {
const seriesConfigKpi = getDefaultConfigs({
reportType: ReportTypes.KPI,
dataType: 'ux',
indexPattern: mockIndexPattern,
});

const lnsAttrKpi = new LensAttributes([
{
filters: [],
seriesConfig: seriesConfigKpi,
time: {
from: 'now-1h',
to: 'now',
},
indexPattern: mockIndexPattern,
name: 'ux-series-1',
breakdown: 'percentile',
reportDefinitions: {},
selectedMetricField: 'transaction.duration.us',
color: '#54b399',
},
]);

expect(lnsAttrKpi.getJSON().state.datasourceStates.indexpattern.layers.layer0.columns).toEqual({
'x-axis-column-layer0': {
dataType: 'date',
isBucketed: true,
label: '@timestamp',
operationType: 'date_histogram',
params: {
interval: 'auto',
},
scale: 'interval',
sourceField: '@timestamp',
},
...PERCENTILE_RANKS.reduce((acc: Record<string, any>, rank, index) => {
acc[`y-axis-column-${index === 0 ? 'layer' + index : index}`] = {
dataType: 'number',
filter: {
language: 'kuery',
query: 'transaction.type: page-load and processor.event: transaction',
},
isBucketed: false,
label: `${rank} percentile of page load time`,
operationType: 'percentile',
params: {
percentile: Number(rank.slice(0, 2)),
},
scale: 'ratio',
sourceField: 'transaction.duration.us',
};
return acc;
}, {}),
});
});

it('should return main y axis', function () {
expect(lnsAttr.getMainYAxis(layerConfig, 'layer0', '')).toEqual({
dataType: 'number',
Expand Down Expand Up @@ -413,7 +470,7 @@ describe('Lens Attribute', () => {
yConfig: [{ color: 'green', forAccessor: 'y-axis-column-layer0' }],
},
],
legend: { isVisible: true, position: 'right' },
legend: { isVisible: true, showSingleSeries: true, position: 'right' },
preferredSeriesType: 'line',
tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true },
valueLabels: 'hide',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import {
REPORT_METRIC_FIELD,
RECORDS_FIELD,
RECORDS_PERCENTAGE_FIELD,
PERCENTILE,
PERCENTILE_RANKS,
ReportTypes,
} from './constants';
import { ColumnFilter, SeriesConfig, UrlFilter, URLReportDefinition } from '../types';
Expand Down Expand Up @@ -249,6 +251,30 @@ export class LensAttributes {
};
}

getPercentileBreakdowns(
layerConfig: LayerConfig,
columnFilter?: string
): Record<string, FieldBasedIndexPatternColumn> {
const yAxisColumns = layerConfig.seriesConfig.yAxisColumns;
const { sourceField: mainSourceField, label: mainLabel } = yAxisColumns[0];
const lensColumns: Record<string, FieldBasedIndexPatternColumn> = {};

// start at 1, because main y axis will have the first percentile breakdown
for (let i = 1; i < PERCENTILE_RANKS.length; i++) {
lensColumns[`y-axis-column-${i}`] = {
...this.getColumnBasedOnType({
sourceField: mainSourceField!,
operationType: PERCENTILE_RANKS[i],
label: mainLabel,
layerConfig,
colIndex: i,
}),
filter: { query: columnFilter || '', language: 'kuery' },
};
}
return lensColumns;
}

getPercentileNumberColumn(
sourceField: string,
percentileValue: string,
Expand All @@ -258,7 +284,7 @@ export class LensAttributes {
...buildNumberColumn(sourceField),
label: i18n.translate('xpack.observability.expView.columns.label', {
defaultMessage: '{percentileValue} percentile of {sourceField}',
values: { sourceField: seriesConfig.labels[sourceField], percentileValue },
values: { sourceField: seriesConfig.labels[sourceField]?.toLowerCase(), percentileValue },
}),
operationType: 'percentile',
params: { percentile: Number(percentileValue.split('th')[0]) },
Expand Down Expand Up @@ -328,6 +354,7 @@ export class LensAttributes {
layerConfig: LayerConfig;
colIndex?: number;
}) {
const { breakdown, seriesConfig } = layerConfig;
const { fieldMeta, columnType, fieldName, columnLabel, timeScale, columnFilters } =
this.getFieldMeta(sourceField, layerConfig);

Expand All @@ -348,6 +375,18 @@ export class LensAttributes {
if (fieldType === 'date') {
return this.getDateHistogramColumn(fieldName);
}

if (fieldType === 'number' && breakdown === PERCENTILE) {
return {
...this.getPercentileNumberColumn(
fieldName,
operationType || PERCENTILE_RANKS[0],
seriesConfig!
),
filter: colIndex !== undefined ? columnFilters?.[colIndex] : undefined,
};
}

if (fieldType === 'number') {
return this.getNumberColumn({
sourceField: fieldName,
Expand Down Expand Up @@ -395,6 +434,7 @@ export class LensAttributes {
}

getMainYAxis(layerConfig: LayerConfig, layerId: string, columnFilter: string) {
const { breakdown } = layerConfig;
const { sourceField, operationType, label } = layerConfig.seriesConfig.yAxisColumns[0];

if (sourceField === RECORDS_PERCENTAGE_FIELD) {
Expand All @@ -407,14 +447,15 @@ export class LensAttributes {

return this.getColumnBasedOnType({
sourceField,
operationType,
operationType: breakdown === PERCENTILE ? PERCENTILE_RANKS[0] : operationType,
label,
layerConfig,
colIndex: 0,
});
}

getChildYAxises(layerConfig: LayerConfig, layerId?: string, columnFilter?: string) {
const { breakdown } = layerConfig;
const lensColumns: Record<string, FieldBasedIndexPatternColumn | SumIndexPatternColumn> = {};
const yAxisColumns = layerConfig.seriesConfig.yAxisColumns;
const { sourceField: mainSourceField, label: mainLabel } = yAxisColumns[0];
Expand All @@ -424,7 +465,10 @@ export class LensAttributes {
.supportingColumns;
}

// 1 means there is only main y axis
if (yAxisColumns.length === 1 && breakdown === PERCENTILE) {
return this.getPercentileBreakdowns(layerConfig, columnFilter);
}

if (yAxisColumns.length === 1) {
return lensColumns;
}
Expand Down Expand Up @@ -574,7 +618,7 @@ export class LensAttributes {
layers[layerId] = {
columnOrder: [
`x-axis-column-${layerId}`,
...(breakdown && sourceField !== USE_BREAK_DOWN_COLUMN
...(breakdown && sourceField !== USE_BREAK_DOWN_COLUMN && breakdown !== PERCENTILE
? [`breakdown-column-${layerId}`]
: []),
`y-axis-column-${layerId}`,
Expand All @@ -588,7 +632,7 @@ export class LensAttributes {
filter: { query: columnFilter, language: 'kuery' },
...(timeShift ? { timeShift } : {}),
},
...(breakdown && sourceField !== USE_BREAK_DOWN_COLUMN
...(breakdown && sourceField !== USE_BREAK_DOWN_COLUMN && breakdown !== PERCENTILE
? // do nothing since this will be used a x axis source
{
[`breakdown-column-${layerId}`]: this.getBreakdownColumn({
Expand All @@ -610,7 +654,7 @@ export class LensAttributes {

getXyState(): XYState {
return {
legend: { isVisible: true, position: 'right' },
legend: { isVisible: true, showSingleSeries: true, position: 'right' },
valueLabels: 'hide',
fittingFunction: 'Linear',
curveType: 'CURVE_MONOTONE_X' as XYCurveType,
Expand All @@ -636,6 +680,7 @@ export class LensAttributes {
],
xAccessor: `x-axis-column-layer${index}`,
...(layerConfig.breakdown &&
layerConfig.breakdown !== PERCENTILE &&
layerConfig.seriesConfig.xAxisColumn.sourceField !== USE_BREAK_DOWN_COLUMN
? { splitAccessor: `breakdown-column-layer${index}` }
: {}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
OPERATION_COLUMN,
RECORDS_FIELD,
REPORT_METRIC_FIELD,
PERCENTILE,
ReportTypes,
} from '../constants';
import { buildPhraseFilter } from '../utils';
Expand Down Expand Up @@ -81,6 +82,7 @@ export function getKPITrendsLensConfig({ indexPattern }: ConfigProps): SeriesCon
USER_AGENT_OS,
CLIENT_GEO_COUNTRY_NAME,
USER_AGENT_DEVICE,
PERCENTILE,
LABEL_FIELDS_BREAKDOWN,
],
baseFilters: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@
*/

import { ConfigProps, SeriesConfig } from '../../types';
import { FieldLabels, OPERATION_COLUMN, REPORT_METRIC_FIELD, ReportTypes } from '../constants';
import {
FieldLabels,
OPERATION_COLUMN,
REPORT_METRIC_FIELD,
PERCENTILE,
ReportTypes,
} from '../constants';
import {
CLS_LABEL,
DCL_LABEL,
Expand Down Expand Up @@ -44,7 +50,7 @@ export function getSyntheticsKPIConfig({ indexPattern }: ConfigProps): SeriesCon
],
hasOperationType: false,
filterFields: ['observer.geo.name', 'monitor.type', 'tags'],
breakdownFields: ['observer.geo.name', 'monitor.type', 'monitor.name'],
breakdownFields: ['observer.geo.name', 'monitor.type', 'monitor.name', PERCENTILE],
baseFilters: [],
palette: { type: 'palette', name: 'status' },
definitionFields: ['monitor.name', 'url.full'],
Expand Down Expand Up @@ -98,6 +104,6 @@ export function getSyntheticsKPIConfig({ indexPattern }: ConfigProps): SeriesCon
columnType: OPERATION_COLUMN,
},
],
labels: { ...FieldLabels },
labels: { ...FieldLabels, [SUMMARY_UP]: UP_LABEL, [SUMMARY_DOWN]: DOWN_LABEL },
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ export const sampleAttribute = {
],
legend: {
isVisible: true,
showSingleSeries: true,
position: 'right',
},
preferredSeriesType: 'line',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ export const sampleAttributeCoreWebVital = {
],
legend: {
isVisible: true,
showSingleSeries: true,
position: 'right',
},
preferredSeriesType: 'line',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export const sampleAttributeKpi = {
],
legend: {
isVisible: true,
showSingleSeries: true,
position: 'right',
},
preferredSeriesType: 'line',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { fireEvent, screen } from '@testing-library/react';
import { Breakdowns } from './breakdowns';
import { mockIndexPattern, mockUxSeries, render } from '../../rtl_helpers';
import { getDefaultConfigs } from '../../configurations/default_configs';
import { RECORDS_FIELD } from '../../configurations/constants';
import { USER_AGENT_OS } from '../../configurations/constants/elasticsearch_fieldnames';

describe('Breakdowns', function () {
Expand Down Expand Up @@ -56,6 +57,26 @@ describe('Breakdowns', function () {
expect(setSeries).toHaveBeenCalledTimes(1);
});

it('does not show percentile breakdown for records metrics', function () {
const kpiConfig = getDefaultConfigs({
reportType: 'kpi-over-time',
indexPattern: mockIndexPattern,
dataType: 'ux',
});

render(
<Breakdowns
seriesId={0}
seriesConfig={kpiConfig}
series={{ ...mockUxSeries, selectedMetricField: RECORDS_FIELD }}
/>
);

fireEvent.click(screen.getByTestId('seriesBreakdown'));

expect(screen.queryByText('Percentile')).not.toBeInTheDocument();
});

it('should disable breakdowns when a different series has a breakdown', function () {
const initSeries = {
data: [mockUxSeries, { ...mockUxSeries, breakdown: undefined }],
Expand Down
Loading