[Exploratory view] Core web vitals (#100320) (#101144)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

Co-authored-by: Shahzad <shahzad.muhammad@elastic.co>
This commit is contained in:
Kibana Machine 2021-06-02 08:33:23 -04:00 committed by GitHub
parent 4202cb42dc
commit 54b8d288b3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 354 additions and 74 deletions

View file

@ -20,6 +20,7 @@ export type {
ValueLabelConfig,
YAxisMode,
XYCurveType,
YConfig,
} from './xy_visualization/types';
export type { DataType, OperationMetadata } from './types';
export type {

View file

@ -12,6 +12,7 @@ import {
BROWSER_FAMILY_LABEL,
BROWSER_VERSION_LABEL,
CLS_LABEL,
CORE_WEB_VITALS_LABEL,
CPU_USAGE_LABEL,
DEVICE_LABEL,
ENVIRONMENT_LABEL,
@ -92,6 +93,7 @@ export const DataViewLabels: Record<ReportViewTypeId, string> = {
logs: LOGS_FREQUENCY_LABEL,
mem: MEMORY_USAGE_LABEL,
nwk: NETWORK_ACTIVITY_LABEL,
cwv: CORE_WEB_VITALS_LABEL,
};
export const ReportToDataTypeMap: Record<ReportViewTypeId, AppDataType> = {
@ -105,4 +107,9 @@ export const ReportToDataTypeMap: Record<ReportViewTypeId, AppDataType> = {
mem: 'infra_metrics',
logs: 'infra_logs',
cpu: 'infra_metrics',
cwv: 'ux',
};
export const USE_BREAK_DOWN_COLUMN = 'USE_BREAK_DOWN_COLUMN';
export const FILTER_RECORDS = 'FILTER_RECORDS';
export const OPERATION_COLUMN = 'operation';

View file

@ -119,6 +119,7 @@ export const TRANSACTION_URL = 'url.full';
export const CLIENT_GEO = 'client.geo';
export const USER_AGENT_DEVICE = 'user_agent.device.name';
export const USER_AGENT_OS = 'user_agent.os.name';
export const USER_AGENT_OS_VERSION = 'user_agent.os.version';
export const TRANSACTION_TIME_TO_FIRST_BYTE = 'transaction.marks.agent.timeToFirstByte';
export const TRANSACTION_DOM_INTERACTIVE = 'transaction.marks.agent.domInteractive';

View file

@ -200,6 +200,14 @@ export const NETWORK_ACTIVITY_LABEL = i18n.translate(
defaultMessage: 'Network activity',
}
);
export const CORE_WEB_VITALS_LABEL = i18n.translate(
'xpack.observability.expView.fieldLabels.coreWebVitals',
{
defaultMessage: 'Core web vitals',
}
);
export const MEMORY_USAGE_LABEL = i18n.translate(
'xpack.observability.expView.fieldLabels.memoryUsage',
{

View file

@ -17,6 +17,7 @@ import { getMemoryUsageLensConfig } from './metrics/memory_usage_config';
import { getNetworkActivityLensConfig } from './metrics/network_activity_config';
import { getLogsFrequencyLensConfig } from './logs/logs_frequency_config';
import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns';
import { getCoreWebVitalsConfig } from './rum/core_web_vitals_config';
interface Props {
reportType: keyof typeof ReportViewTypes;
@ -30,6 +31,8 @@ export const getDefaultConfigs = ({ reportType, seriesId, indexPattern }: Props)
return getPerformanceDistLensConfig({ seriesId, indexPattern });
case 'kpi-trends':
return getKPITrendsLensConfig({ seriesId, indexPattern });
case 'core-web-vitals':
return getCoreWebVitalsConfig({ seriesId, indexPattern });
case 'uptime-duration':
return getMonitorDurationConfig({ seriesId, indexPattern });
case 'uptime-pings':

View file

@ -342,7 +342,7 @@ describe('Lens Attribute', () => {
orderBy: { columnId: 'y-axis-column', type: 'column' },
orderDirection: 'desc',
otherBucket: true,
size: 3,
size: 10,
},
scale: 'ordinal',
sourceField: 'user_agent.name',

View file

@ -24,14 +24,15 @@ import {
OperationMetadata,
FieldBasedIndexPatternColumn,
SumIndexPatternColumn,
TermsIndexPatternColumn,
} from '../../../../../../lens/public';
import {
buildPhraseFilter,
buildPhrasesFilter,
IndexPattern,
} from '../../../../../../../../src/plugins/data/common';
import { FieldLabels } from './constants';
import { DataSeries, UrlFilter, URLReportDefinition } from '../types';
import { FieldLabels, FILTER_RECORDS, USE_BREAK_DOWN_COLUMN } from './constants';
import { ColumnFilter, DataSeries, UrlFilter, URLReportDefinition } from '../types';
function getLayerReferenceName(layerId: string) {
return `indexpattern-datasource-layer-${layerId}`;
@ -53,6 +54,7 @@ export const parseCustomFieldName = (
) => {
let fieldName = sourceField;
let columnType;
let columnFilters;
const rdf = reportViewConfig.reportDefinitions ?? [];
@ -61,17 +63,21 @@ export const parseCustomFieldName = (
if (customField) {
if (selectedDefinitions[fieldName]) {
fieldName = selectedDefinitions[fieldName][0];
if (customField?.options)
columnType = customField?.options?.find(({ field }) => field === fieldName)?.columnType;
} else if (customField.defaultValue) {
fieldName = customField.defaultValue;
} else if (customField.options?.[0].field) {
fieldName = customField.options?.[0].field;
if (customField?.options) {
const currField = customField?.options?.find(
({ field, id }) => field === fieldName || id === fieldName
);
columnType = currField?.columnType;
columnFilters = currField?.columnFilters;
}
} else if (customField.options?.[0].field || customField.options?.[0].id) {
fieldName = customField.options?.[0].field || customField.options?.[0].id;
columnType = customField.options?.[0].columnType;
columnFilters = customField.options?.[0].columnFilters;
}
}
return { fieldName, columnType };
return { fieldName, columnType, columnFilters };
};
export class LensAttributes {
@ -82,6 +88,7 @@ export class LensAttributes {
seriesType: SeriesType;
reportViewConfig: DataSeries;
reportDefinitions: URLReportDefinition;
breakdownSource?: string;
constructor(
indexPattern: IndexPattern,
@ -89,12 +96,14 @@ export class LensAttributes {
seriesType?: SeriesType,
filters?: UrlFilter[],
operationType?: OperationType,
reportDefinitions?: URLReportDefinition
reportDefinitions?: URLReportDefinition,
breakdownSource?: string
) {
this.indexPattern = indexPattern;
this.layers = {};
this.filters = filters ?? [];
this.reportDefinitions = reportDefinitions ?? {};
this.breakdownSource = breakdownSource;
if (operationType) {
reportViewConfig.yAxisColumns.forEach((yAxisColumn) => {
@ -109,10 +118,10 @@ export class LensAttributes {
this.visualization = this.getXyState();
}
addBreakdown(sourceField: string) {
getBreakdownColumn(sourceField: string): TermsIndexPatternColumn {
const fieldMeta = this.indexPattern.getFieldByName(sourceField);
this.layers.layer1.columns['break-down-column'] = {
return {
sourceField,
label: `Top values of ${FieldLabels[sourceField]}`,
dataType: fieldMeta?.type as DataType,
@ -120,13 +129,22 @@ export class LensAttributes {
scale: 'ordinal',
isBucketed: true,
params: {
size: 3,
size: 10,
orderBy: { type: 'column', columnId: 'y-axis-column' },
orderDirection: 'desc',
otherBucket: true,
missingBucket: false,
},
};
}
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',
@ -229,15 +247,27 @@ export class LensAttributes {
getXAxis() {
const { xAxisColumn } = this.reportViewConfig;
if (xAxisColumn?.sourceField === USE_BREAK_DOWN_COLUMN) {
return this.getBreakdownColumn(this.breakdownSource || this.reportViewConfig.breakdowns[0]);
}
return this.getColumnBasedOnType(xAxisColumn.sourceField!, undefined, xAxisColumn.label);
}
getColumnBasedOnType(sourceField: string, operationType?: OperationType, label?: string) {
const { fieldMeta, columnType, fieldName } = this.getFieldMeta(sourceField);
getColumnBasedOnType(
sourceField: string,
operationType?: OperationType,
label?: string,
colIndex?: number
) {
const { fieldMeta, columnType, fieldName, columnFilters } = this.getFieldMeta(sourceField);
const { type: fieldType } = fieldMeta ?? {};
if (fieldName === 'Records') {
return this.getRecordsColumn();
if (fieldName === 'Records' || columnType === FILTER_RECORDS) {
return this.getRecordsColumn(
label,
colIndex !== undefined ? columnFilters?.[colIndex] : undefined
);
}
if (fieldType === 'date') {
@ -256,11 +286,11 @@ export class LensAttributes {
}
getFieldMeta(sourceField: string) {
const { fieldName, columnType } = this.getCustomFieldName(sourceField);
const { fieldName, columnType, columnFilters } = this.getCustomFieldName(sourceField);
const fieldMeta = this.indexPattern.getFieldByName(fieldName);
return { fieldMeta, fieldName, columnType };
return { fieldMeta, fieldName, columnType, columnFilters };
}
getMainYAxis() {
@ -270,7 +300,7 @@ export class LensAttributes {
return this.getRecordsColumn(label);
}
return this.getColumnBasedOnType(sourceField!, operationType, label);
return this.getColumnBasedOnType(sourceField!, operationType, label, 0);
}
getChildYAxises() {
@ -286,13 +316,14 @@ export class LensAttributes {
lensColumns[`y-axis-column-${i}`] = this.getColumnBasedOnType(
sourceField!,
operationType,
label
label,
i
);
}
return lensColumns;
}
getRecordsColumn(label?: string): CountIndexPatternColumn {
getRecordsColumn(label?: string, columnFilter?: ColumnFilter): CountIndexPatternColumn {
return {
dataType: 'number',
isBucketed: false,
@ -300,6 +331,7 @@ export class LensAttributes {
operationType: 'count',
scale: 'ratio',
sourceField: 'Records',
filter: columnFilter,
} as CountIndexPatternColumn;
}
@ -331,7 +363,9 @@ export class LensAttributes {
layerId: 'layer1',
seriesType: this.seriesType ?? 'line',
palette: this.reportViewConfig.palette,
yConfig: [{ forAccessor: 'y-axis-column', color: 'green' }],
yConfig: this.reportViewConfig.yConfig || [
{ forAccessor: 'y-axis-column', color: 'green' },
],
xAccessor: 'x-axis-column',
},
],

View file

@ -0,0 +1,164 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { euiPaletteForStatus } from '@elastic/eui';
import { ConfigProps, DataSeries } from '../../types';
import { FieldLabels, FILTER_RECORDS, USE_BREAK_DOWN_COLUMN } from '../constants';
import { buildPhraseFilter } from '../utils';
import {
CLIENT_GEO_COUNTRY_NAME,
CLS_FIELD,
FID_FIELD,
LCP_FIELD,
PROCESSOR_EVENT,
SERVICE_NAME,
TRANSACTION_TYPE,
USER_AGENT_DEVICE,
USER_AGENT_NAME,
USER_AGENT_OS,
USER_AGENT_VERSION,
TRANSACTION_URL,
USER_AGENT_OS_VERSION,
URL_FULL,
SERVICE_ENVIRONMENT,
} from '../constants/elasticsearch_fieldnames';
export function getCoreWebVitalsConfig({ seriesId, indexPattern }: ConfigProps): DataSeries {
const statusPallete = euiPaletteForStatus(3);
return {
id: seriesId,
defaultSeriesType: 'bar_horizontal_percentage_stacked',
reportType: 'kpi-trends',
seriesTypes: ['bar_horizontal_percentage_stacked'],
xAxisColumn: {
sourceField: USE_BREAK_DOWN_COLUMN,
},
yAxisColumns: [
{
sourceField: 'core.web.vitals',
label: 'Good',
},
{
sourceField: 'core.web.vitals',
label: 'Average',
},
{
sourceField: 'core.web.vitals',
label: 'Poor',
},
],
hasOperationType: false,
defaultFilters: [
{
field: TRANSACTION_URL,
isNegated: false,
},
SERVICE_NAME,
{
field: USER_AGENT_OS,
nested: USER_AGENT_OS_VERSION,
},
CLIENT_GEO_COUNTRY_NAME,
USER_AGENT_DEVICE,
{
field: USER_AGENT_NAME,
nested: USER_AGENT_VERSION,
},
],
breakdowns: [
SERVICE_NAME,
USER_AGENT_NAME,
USER_AGENT_OS,
CLIENT_GEO_COUNTRY_NAME,
USER_AGENT_DEVICE,
URL_FULL,
],
filters: [
...buildPhraseFilter(TRANSACTION_TYPE, 'page-load', indexPattern),
...buildPhraseFilter(PROCESSOR_EVENT, 'transaction', indexPattern),
],
labels: { ...FieldLabels, [SERVICE_NAME]: 'Web Application' },
reportDefinitions: [
{
field: SERVICE_NAME,
required: true,
},
{
field: SERVICE_ENVIRONMENT,
},
{
field: 'core.web.vitals',
custom: true,
options: [
{
id: LCP_FIELD,
label: 'Largest contentful paint',
columnType: FILTER_RECORDS,
columnFilters: [
{
language: 'kuery',
query: `${LCP_FIELD} < 2500`,
},
{
language: 'kuery',
query: `${LCP_FIELD} > 2500 and ${LCP_FIELD} < 4000`,
},
{
language: 'kuery',
query: `${LCP_FIELD} > 4000`,
},
],
},
{
label: 'First input delay',
id: FID_FIELD,
columnType: FILTER_RECORDS,
columnFilters: [
{
language: 'kuery',
query: `${FID_FIELD} < 100`,
},
{
language: 'kuery',
query: `${FID_FIELD} > 100 and ${FID_FIELD} < 300`,
},
{
language: 'kuery',
query: `${FID_FIELD} > 300`,
},
],
},
{
label: 'Cumulative layout shift',
id: CLS_FIELD,
columnType: FILTER_RECORDS,
columnFilters: [
{
language: 'kuery',
query: `${CLS_FIELD} < 0.1`,
},
{
language: 'kuery',
query: `${CLS_FIELD} > 0.1 and ${CLS_FIELD} < 0.25`,
},
{
language: 'kuery',
query: `${CLS_FIELD} > 0.25`,
},
],
},
],
},
],
yConfig: [
{ color: statusPallete[0], forAccessor: 'y-axis-column' },
{ color: statusPallete[1], forAccessor: 'y-axis-column-1' },
{ color: statusPallete[2], forAccessor: 'y-axis-column-2' },
],
};
}

View file

@ -6,7 +6,7 @@
*/
import { ConfigProps, DataSeries } from '../../types';
import { FieldLabels, RECORDS_FIELD } from '../constants';
import { FieldLabels, OPERATION_COLUMN, RECORDS_FIELD } from '../constants';
import { buildPhraseFilter } from '../utils';
import {
CLIENT_GEO_COUNTRY_NAME,
@ -85,20 +85,25 @@ export function getKPITrendsLensConfig({ seriesId, indexPattern }: ConfigProps):
{
field: 'business.kpi',
custom: true,
defaultValue: RECORDS_FIELD,
options: [
{ field: RECORDS_FIELD, label: PAGE_VIEWS_LABEL },
{ label: PAGE_LOAD_TIME_LABEL, field: TRANSACTION_DURATION, columnType: 'operation' },
{ field: RECORDS_FIELD, id: RECORDS_FIELD, label: PAGE_VIEWS_LABEL },
{
label: PAGE_LOAD_TIME_LABEL,
field: TRANSACTION_DURATION,
id: TRANSACTION_DURATION,
columnType: OPERATION_COLUMN,
},
{
label: BACKEND_TIME_LABEL,
field: TRANSACTION_TIME_TO_FIRST_BYTE,
columnType: 'operation',
id: TRANSACTION_TIME_TO_FIRST_BYTE,
columnType: OPERATION_COLUMN,
},
{ label: FCP_LABEL, field: FCP_FIELD, columnType: 'operation' },
{ label: TBT_LABEL, field: TBT_FIELD, columnType: 'operation' },
{ label: LCP_LABEL, field: LCP_FIELD, columnType: 'operation' },
{ label: FID_LABEL, field: FID_FIELD, columnType: 'operation' },
{ label: CLS_LABEL, field: CLS_FIELD, columnType: 'operation' },
{ label: FCP_LABEL, field: FCP_FIELD, id: FCP_FIELD, columnType: OPERATION_COLUMN },
{ label: TBT_LABEL, field: TBT_FIELD, id: TBT_FIELD, columnType: OPERATION_COLUMN },
{ label: LCP_LABEL, field: LCP_FIELD, id: LCP_FIELD, columnType: OPERATION_COLUMN },
{ label: FID_LABEL, field: FID_FIELD, id: FID_FIELD, columnType: OPERATION_COLUMN },
{ label: CLS_LABEL, field: CLS_FIELD, id: CLS_FIELD, columnType: OPERATION_COLUMN },
],
},
],

View file

@ -80,15 +80,18 @@ export function getPerformanceDistLensConfig({ seriesId, indexPattern }: ConfigP
{
field: 'performance.metric',
custom: true,
defaultValue: TRANSACTION_DURATION,
options: [
{ label: PAGE_LOAD_TIME_LABEL, field: TRANSACTION_DURATION },
{ label: BACKEND_TIME_LABEL, field: TRANSACTION_TIME_TO_FIRST_BYTE },
{ label: FCP_LABEL, field: FCP_FIELD },
{ label: TBT_LABEL, field: TBT_FIELD },
{ label: LCP_LABEL, field: LCP_FIELD },
{ label: FID_LABEL, field: FID_FIELD },
{ label: CLS_LABEL, field: CLS_FIELD },
{ label: PAGE_LOAD_TIME_LABEL, id: TRANSACTION_DURATION, field: TRANSACTION_DURATION },
{
label: BACKEND_TIME_LABEL,
id: TRANSACTION_TIME_TO_FIRST_BYTE,
field: TRANSACTION_TIME_TO_FIRST_BYTE,
},
{ label: FCP_LABEL, id: FCP_FIELD, field: FCP_FIELD },
{ label: TBT_LABEL, id: TBT_FIELD, field: TBT_FIELD },
{ label: LCP_LABEL, id: LCP_FIELD, field: LCP_FIELD },
{ label: FID_LABEL, id: FID_FIELD, field: FID_FIELD },
{ label: CLS_LABEL, id: CLS_FIELD, field: CLS_FIELD },
],
},
],

View file

@ -67,7 +67,8 @@ export const useLensAttributes = ({
seriesType,
filters,
operationType,
reportDefinitions
reportDefinitions,
breakdown
);
if (breakdown) {

View file

@ -20,9 +20,11 @@ const CHART_TYPE_LABEL = i18n.translate('xpack.observability.expView.chartTypes.
export function SeriesChartTypesSelect({
seriesId,
seriesTypes,
defaultChartType,
}: {
seriesId: string;
seriesTypes?: SeriesType[];
defaultChartType: SeriesType;
}) {
const { series, setSeries, allSeries } = useUrlStorage(seriesId);
@ -42,8 +44,18 @@ export function SeriesChartTypesSelect({
onChange={onChange}
value={seriesType}
excludeChartTypes={['bar_percentage_stacked']}
includeChartTypes={
seriesTypes || [
'bar',
'bar_horizontal',
'line',
'area',
'bar_stacked',
'area_stacked',
'bar_horizontal_percentage_stacked',
]
}
label={CHART_TYPE_LABEL}
includeChartTypes={['bar', 'bar_horizontal', 'line', 'area', 'bar_stacked', 'area_stacked']}
/>
);
}

View file

@ -16,5 +16,11 @@ export function ReportBreakdowns({
dataViewSeries: DataSeries;
seriesId: string;
}) {
return <Breakdowns breakdowns={dataViewSeries.breakdowns ?? []} seriesId={seriesId} />;
return (
<Breakdowns
reportViewConfig={dataViewSeries}
breakdowns={dataViewSeries.breakdowns ?? []}
seriesId={seriesId}
/>
);
}

View file

@ -6,7 +6,7 @@
*/
import React from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui';
import styled from 'styled-components';
import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern';
import { useUrlStorage } from '../../hooks/use_url_storage';
@ -66,8 +66,9 @@ export function ReportDefinitionCol({
<EuiFlexItem>
<DatePickerCol seriesId={seriesId} />
</EuiFlexItem>
<EuiHorizontalRule margin="xs" />
{indexPattern &&
reportDefinitions.map(({ field, custom, options, defaultValue }) => (
reportDefinitions.map(({ field, custom, options }) => (
<EuiFlexItem key={field}>
{!custom ? (
<ReportDefinitionField
@ -77,12 +78,7 @@ export function ReportDefinitionCol({
onChange={onChange}
/>
) : (
<CustomReportField
field={field}
options={options}
defaultValue={defaultValue}
seriesId={seriesId}
/>
<CustomReportField field={field} options={options} seriesId={seriesId} />
)}
</EuiFlexItem>
))}
@ -95,7 +91,11 @@ export function ReportDefinitionCol({
</EuiFlexItem>
)}
<EuiFlexItem>
<SeriesChartTypesSelect seriesId={seriesId} defaultChartType={defaultSeriesType} />
<SeriesChartTypesSelect
seriesId={seriesId}
defaultChartType={defaultSeriesType}
seriesTypes={dataViewSeries.seriesTypes}
/>
</EuiFlexItem>
</FlexGroup>
);

View file

@ -17,7 +17,7 @@ interface Props {
options: ReportDefinition['options'];
}
export function CustomReportField({ field, seriesId, options: opts, defaultValue }: Props) {
export function CustomReportField({ field, seriesId, options: opts }: Props) {
const { series, setSeries } = useUrlStorage(seriesId);
const { reportDefinitions: rtd = {} } = series;
@ -35,11 +35,11 @@ export function CustomReportField({ field, seriesId, options: opts, defaultValue
fullWidth
compressed
prepend={'Metric'}
options={options.map(({ label, field: fd }) => ({
value: fd,
options={options.map(({ label, field: fd, id }) => ({
value: fd || id,
inputDisplay: label,
}))}
valueOfSelected={reportDefinitions?.[field]?.[0] || defaultValue || options?.[0].field}
valueOfSelected={reportDefinitions?.[field]?.[0] || options?.[0].field || options?.[0].id}
onChange={(value) => onChange(value)}
/>
);

View file

@ -27,6 +27,7 @@ export const ReportTypes: Record<AppDataType, Array<{ id: ReportViewTypeId; labe
ux: [
{ id: 'pld', label: 'Performance distribution' },
{ id: 'kpi', label: 'KPI over time' },
{ id: 'cwv', label: 'Core Web Vitals' },
],
apm: [
{ id: 'svl', label: 'Latency' },

View file

@ -20,7 +20,7 @@ export function ChartEditOptions({ series, seriesId, breakdowns }: Props) {
return (
<EuiFlexGroup wrap>
<EuiFlexItem>
<Breakdowns seriesId={seriesId} breakdowns={breakdowns} />
<Breakdowns seriesId={seriesId} breakdowns={breakdowns} reportViewConfig={series} />
</EuiFlexItem>
<EuiFlexItem>
<ChartOptions series={series} />

View file

@ -23,7 +23,13 @@ describe('Breakdowns', function () {
it('should render properly', async function () {
mockUrlStorage({});
render(<Breakdowns seriesId={'series-id'} breakdowns={dataViewSeries.breakdowns} />);
render(
<Breakdowns
seriesId={'series-id'}
breakdowns={dataViewSeries.breakdowns}
reportViewConfig={dataViewSeries}
/>
);
screen.getAllByText('Browser family');
});
@ -31,7 +37,13 @@ describe('Breakdowns', function () {
it('should call set series on change', function () {
const { setSeries } = mockUrlStorage({ breakdown: USER_AGENT_OS });
render(<Breakdowns seriesId={'series-id'} breakdowns={dataViewSeries.breakdowns} />);
render(
<Breakdowns
seriesId={'series-id'}
breakdowns={dataViewSeries.breakdowns}
reportViewConfig={dataViewSeries}
/>
);
screen.getAllByText('Operating system');

View file

@ -8,15 +8,17 @@
import React from 'react';
import { EuiSuperSelect } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FieldLabels } from '../../configurations/constants';
import { USE_BREAK_DOWN_COLUMN } from '../../configurations/constants';
import { useUrlStorage } from '../../hooks/use_url_storage';
import { DataSeries } from '../../types';
interface Props {
seriesId: string;
breakdowns: string[];
reportViewConfig: DataSeries;
}
export function Breakdowns({ seriesId, breakdowns = [] }: Props) {
export function Breakdowns({ reportViewConfig, seriesId, breakdowns = [] }: Props) {
const { setSeries, series } = useUrlStorage(seriesId);
const selectedBreakdown = series.breakdown;
@ -36,13 +38,21 @@ export function Breakdowns({ seriesId, breakdowns = [] }: Props) {
}
};
const items = breakdowns.map((breakdown) => ({ id: breakdown, label: FieldLabels[breakdown] }));
items.push({
id: NO_BREAKDOWN,
label: i18n.translate('xpack.observability.exp.breakDownFilter.noBreakdown', {
defaultMessage: 'No breakdown',
}),
});
const hasUseBreakdownColumn = reportViewConfig.xAxisColumn.sourceField === USE_BREAK_DOWN_COLUMN;
const items = breakdowns.map((breakdown) => ({
id: breakdown,
label: reportViewConfig.labels[breakdown],
}));
if (!hasUseBreakdownColumn) {
items.push({
id: NO_BREAKDOWN,
label: i18n.translate('xpack.observability.exp.breakDownFilter.noBreakdown', {
defaultMessage: 'No breakdown',
}),
});
}
const options = items.map(({ id, label }) => ({
inputDisplay: id === NO_BREAKDOWN ? label : <strong>{label}</strong>,
@ -50,13 +60,16 @@ export function Breakdowns({ seriesId, breakdowns = [] }: Props) {
dropdownDisplay: label,
}));
const valueOfSelected =
selectedBreakdown || (hasUseBreakdownColumn ? options[0].value : NO_BREAKDOWN);
return (
<div style={{ width: 200 }}>
<EuiSuperSelect
fullWidth
compressed
options={options}
valueOfSelected={selectedBreakdown ?? NO_BREAKDOWN}
valueOfSelected={valueOfSelected}
onChange={(value) => onOptionChange(value)}
data-test-subj={'seriesBreakdown'}
/>

View file

@ -12,6 +12,7 @@ import {
FieldBasedIndexPatternColumn,
SeriesType,
OperationType,
YConfig,
} from '../../../../../lens/public';
import { PersistableFilter } from '../../../../../lens/common';
@ -21,6 +22,7 @@ import { ExistsFilter } from '../../../../../../../src/plugins/data/common/es_qu
export const ReportViewTypes = {
pld: 'page-load-dist',
kpi: 'kpi-trends',
cwv: 'core-web-vitals',
upd: 'uptime-duration',
upp: 'uptime-pings',
svl: 'service-latency',
@ -37,16 +39,22 @@ export type ReportViewTypeId = keyof typeof ReportViewTypes;
export type ReportViewType = ValueOf<typeof ReportViewTypes>;
export interface ColumnFilter {
language: 'kuery';
query: string;
}
export interface ReportDefinition {
field: string;
required?: boolean;
custom?: boolean;
defaultValue?: string;
options?: Array<{
field: string;
id: string;
field?: string;
label: string;
description?: string;
columnType?: 'range' | 'operation';
columnType?: 'range' | 'operation' | 'FILTER_RECORDS';
columnFilters?: ColumnFilter[];
}>;
}
@ -66,6 +74,7 @@ export interface DataSeries {
hasOperationType: boolean;
palette?: PaletteOutput;
yTitle?: string;
yConfig?: YConfig[];
}
export type URLReportDefinition = Record<string, string[]>;