[Exploratory view] Added timings breakdown (#143170)

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Shahzad 2022-10-18 19:07:43 +02:00 committed by GitHub
parent de7c17357c
commit 137b4bd316
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 614 additions and 207 deletions

View file

@ -6,6 +6,7 @@
*/
import type { DataView } from '@kbn/data-views-plugin/public';
import { Query } from '@kbn/es-query';
import { convertDataViewIntoLensIndexPattern } from '../../../../../data_views_service/loader';
import type { IndexPattern } from '../../../../../types';
import type { PersistedIndexPatternLayer } from '../../../types';
@ -31,6 +32,7 @@ export interface FormulaPublicApi {
column: {
formula: string;
label?: string;
filter?: Query;
format?: {
id: string;
params?: {
@ -58,7 +60,7 @@ export const createFormulaPublicApi = (): FormulaPublicApi => {
};
return {
insertOrReplaceFormulaColumn: (id, { formula, label, format }, layer, dataView) => {
insertOrReplaceFormulaColumn: (id, { formula, label, format, filter }, layer, dataView) => {
const indexPattern = getCachedLensIndexPattern(dataView);
return insertOrReplaceFormulaColumn(
@ -70,6 +72,7 @@ export const createFormulaPublicApi = (): FormulaPublicApi => {
dataType: 'number',
references: [],
isBucketed: false,
filter,
params: {
formula,
format,

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { DOCUMENT_FIELD_NAME } from '@kbn/lens-plugin/common';
export const LOG_RATE = DOCUMENT_FIELD_NAME;

View file

@ -0,0 +1,11 @@
/*
* 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 SYSTEM_CPU_PERCENTAGE_FIELD = 'system.cpu.total.norm.pct';
export const SYSTEM_MEMORY_PERCENTAGE_FIELD = 'system.memory.used.pct';
export const DOCKER_CPU_PERCENTAGE_FIELD = 'docker.cpu.total.pct';
export const K8S_POD_CPU_PERCENTAGE_FIELD = 'kubernetes.pod.cpu.usage.node.pct';

View file

@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export const MONITOR_DURATION_US = 'monitor.duration.us';
export const SYNTHETICS_CLS = 'browser.experience.cls';
export const SYNTHETICS_LCP = 'browser.experience.lcp.us';
export const SYNTHETICS_FCP = 'browser.experience.fcp.us';
export const SYNTHETICS_DOCUMENT_ONLOAD = 'browser.experience.load.us';
export const SYNTHETICS_DCL = 'browser.experience.dcl.us';
export const SYNTHETICS_STEP_NAME = 'synthetics.step.name.keyword';
export const SYNTHETICS_STEP_DURATION = 'synthetics.step.duration.us';
export const SYNTHETICS_DNS_TIMINGS = 'synthetics.payload.timings.dns';
export const SYNTHETICS_SSL_TIMINGS = 'synthetics.payload.timings.ssl';
export const SYNTHETICS_BLOCKED_TIMINGS = 'synthetics.payload.timings.blocked';
export const SYNTHETICS_CONNECT_TIMINGS = 'synthetics.payload.timings.connect';
export const SYNTHETICS_RECEIVE_TIMINGS = 'synthetics.payload.timings.receive';
export const SYNTHETICS_SEND_TIMINGS = 'synthetics.payload.timings.send';
export const SYNTHETICS_WAIT_TIMINGS = 'synthetics.payload.timings.wait';
export const SYNTHETICS_TOTAL_TIMINGS = 'synthetics.payload.timings.total';
export const NETWORK_TIMINGS_FIELDS = [
SYNTHETICS_DNS_TIMINGS,
SYNTHETICS_SSL_TIMINGS,
SYNTHETICS_BLOCKED_TIMINGS,
SYNTHETICS_CONNECT_TIMINGS,
SYNTHETICS_RECEIVE_TIMINGS,
SYNTHETICS_SEND_TIMINGS,
SYNTHETICS_WAIT_TIMINGS,
SYNTHETICS_TOTAL_TIMINGS,
];

View file

@ -52,3 +52,16 @@ export const casesPath = '/cases';
export const uptimeOverviewLocatorID = 'UPTIME_OVERVIEW_LOCATOR';
export const syntheticsMonitorDetailLocatorID = 'SYNTHETICS_MONITOR_DETAIL_LOCATOR';
export const syntheticsEditMonitorLocatorID = 'SYNTHETICS_EDIT_MONITOR_LOCATOR';
export {
NETWORK_TIMINGS_FIELDS,
SYNTHETICS_BLOCKED_TIMINGS,
SYNTHETICS_CONNECT_TIMINGS,
SYNTHETICS_DNS_TIMINGS,
SYNTHETICS_RECEIVE_TIMINGS,
SYNTHETICS_SEND_TIMINGS,
SYNTHETICS_SSL_TIMINGS,
SYNTHETICS_STEP_DURATION,
SYNTHETICS_TOTAL_TIMINGS,
SYNTHETICS_WAIT_TIMINGS,
} from './field_names/synthetics';

View file

@ -49,7 +49,7 @@ journey('Exploratory view', async ({ page, params }) => {
await page.click('[aria-label="Toggle series information"] >> text=Page views', TIMEOUT_60_SEC);
await page.click('[aria-label="Edit series"]', TIMEOUT_60_SEC);
await page.click('button:has-text("No breakdown")');
await page.click('button[role="option"]:has-text("Operating system")');
await page.click('button[role="option"]:has-text("Operating system")', TIMEOUT_60_SEC);
await page.click('button:has-text("Apply changes")');
await page.click('text=Chrome OS');

View file

@ -14,13 +14,13 @@ import { SeriesUrl } from '../types';
interface Props {
field: string;
label: string;
value: string | string[];
value: string | Array<string | number>;
seriesId: number;
series: SeriesUrl;
negate: boolean;
definitionFilter?: boolean;
dataView: DataView;
removeFilter: (field: string, value: string | string[], notVal: boolean) => void;
removeFilter: (field: string, value: string | Array<string | number>, notVal: boolean) => void;
}
export function FilterLabel({

View file

@ -6,6 +6,7 @@
*/
import { OperationType } from '@kbn/lens-plugin/public';
import { DOCUMENT_FIELD_NAME } from '@kbn/lens-plugin/common/constants';
import { i18n } from '@kbn/i18n';
import { ReportViewType } from '../../types';
import {
CLS_FIELD,
@ -61,13 +62,21 @@ import {
} from './labels';
import {
MONITOR_DURATION_US,
SYNTHETICS_BLOCKED_TIMINGS,
SYNTHETICS_CLS,
SYNTHETICS_CONNECT_TIMINGS,
SYNTHETICS_DCL,
SYNTHETICS_DNS_TIMINGS,
SYNTHETICS_DOCUMENT_ONLOAD,
SYNTHETICS_FCP,
SYNTHETICS_LCP,
SYNTHETICS_RECEIVE_TIMINGS,
SYNTHETICS_SEND_TIMINGS,
SYNTHETICS_SSL_TIMINGS,
SYNTHETICS_STEP_DURATION,
SYNTHETICS_STEP_NAME,
SYNTHETICS_TOTAL_TIMINGS,
SYNTHETICS_WAIT_TIMINGS,
} from './field_names/synthetics';
export const DEFAULT_TIME = { from: 'now-1h', to: 'now' };
@ -103,6 +112,30 @@ export const FieldLabels: Record<string, string> = {
[SYNTHETICS_DOCUMENT_ONLOAD]: PAGE_LOAD_TIME_LABEL,
[TRANSACTION_TIME_TO_FIRST_BYTE]: BACKEND_TIME_LABEL,
[TRANSACTION_DURATION]: PAGE_LOAD_TIME_LABEL,
[SYNTHETICS_CONNECT_TIMINGS]: i18n.translate('xpack.observability.expView.synthetics.connect', {
defaultMessage: 'Connect',
}),
[SYNTHETICS_DNS_TIMINGS]: i18n.translate('xpack.observability.expView.synthetics.dns', {
defaultMessage: 'DNS',
}),
[SYNTHETICS_WAIT_TIMINGS]: i18n.translate('xpack.observability.expView.synthetics.wait', {
defaultMessage: 'Wait',
}),
[SYNTHETICS_SSL_TIMINGS]: i18n.translate('xpack.observability.expView.synthetics.ssl', {
defaultMessage: 'SSL',
}),
[SYNTHETICS_BLOCKED_TIMINGS]: i18n.translate('xpack.observability.expView.synthetics.blocked', {
defaultMessage: 'Blocked',
}),
[SYNTHETICS_SEND_TIMINGS]: i18n.translate('xpack.observability.expView.synthetics.send', {
defaultMessage: 'Send',
}),
[SYNTHETICS_RECEIVE_TIMINGS]: i18n.translate('xpack.observability.expView.synthetics.receive', {
defaultMessage: 'Receive',
}),
[SYNTHETICS_TOTAL_TIMINGS]: i18n.translate('xpack.observability.expView.synthetics.total', {
defaultMessage: 'Total',
}),
'monitor.id': MONITOR_ID_LABEL,
'monitor.status': MONITOR_STATUS_LABEL,

View file

@ -13,3 +13,23 @@ export const SYNTHETICS_DOCUMENT_ONLOAD = 'browser.experience.load.us';
export const SYNTHETICS_DCL = 'browser.experience.dcl.us';
export const SYNTHETICS_STEP_NAME = 'synthetics.step.name.keyword';
export const SYNTHETICS_STEP_DURATION = 'synthetics.step.duration.us';
export const SYNTHETICS_DNS_TIMINGS = 'synthetics.payload.timings.dns';
export const SYNTHETICS_SSL_TIMINGS = 'synthetics.payload.timings.ssl';
export const SYNTHETICS_BLOCKED_TIMINGS = 'synthetics.payload.timings.blocked';
export const SYNTHETICS_CONNECT_TIMINGS = 'synthetics.payload.timings.connect';
export const SYNTHETICS_RECEIVE_TIMINGS = 'synthetics.payload.timings.receive';
export const SYNTHETICS_SEND_TIMINGS = 'synthetics.payload.timings.send';
export const SYNTHETICS_WAIT_TIMINGS = 'synthetics.payload.timings.wait';
export const SYNTHETICS_TOTAL_TIMINGS = 'synthetics.payload.timings.total';
export const NETWORK_TIMINGS_FIELDS = [
SYNTHETICS_DNS_TIMINGS,
SYNTHETICS_SSL_TIMINGS,
SYNTHETICS_BLOCKED_TIMINGS,
SYNTHETICS_CONNECT_TIMINGS,
SYNTHETICS_RECEIVE_TIMINGS,
SYNTHETICS_SEND_TIMINGS,
SYNTHETICS_WAIT_TIMINGS,
SYNTHETICS_TOTAL_TIMINGS,
];

View file

@ -86,6 +86,13 @@ export const CLS_LABEL = i18n.translate('xpack.observability.expView.fieldLabels
defaultMessage: 'Cumulative layout shift',
});
export const NETWORK_TIMINGS_LABEL = i18n.translate(
'xpack.observability.expView.fieldLabels.networkTimings',
{
defaultMessage: 'Network timings',
}
);
export const DCL_LABEL = i18n.translate('xpack.observability.expView.fieldLabels.dcl', {
defaultMessage: 'DOM content loaded',
});

View file

@ -131,7 +131,7 @@ describe('Lens Attribute', () => {
sourceField: '@timestamp',
},
...PERCENTILE_RANKS.reduce((acc: Record<string, any>, rank, index) => {
acc[`y-axis-column-${index === 0 ? 'layer' + index : index}`] = {
acc[`y-axis-column-${index === 0 ? 'layer' + index + '-0' : index}`] = {
customLabel: true,
dataType: 'number',
filter: {
@ -153,24 +153,26 @@ 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',
operationType: 'formula',
params: {
format: {
id: 'percent',
params: {
decimals: 0,
expect(lnsAttr.getMainYAxis(layerConfig, 'layer0', '')).toEqual([
{
customLabel: true,
dataType: 'number',
isBucketed: false,
label: 'Pages loaded',
operationType: 'formula',
params: {
format: {
id: 'percent',
params: {
decimals: 0,
},
},
formula: 'count() / overall_sum(count())',
isFormulaBroken: false,
},
formula: 'count() / overall_sum(count())',
isFormulaBroken: false,
references: ['y-axis-column-layer0X3'],
},
references: ['y-axis-column-layer0X3'],
});
]);
});
it('should return expected field type', function () {
@ -363,13 +365,13 @@ describe('Lens Attribute', () => {
gridlinesVisibilitySettings: { x: false, yLeft: true, yRight: true },
layers: [
{
accessors: ['y-axis-column-layer0'],
accessors: ['y-axis-column-layer0-0'],
layerId: 'layer0',
layerType: 'data',
palette: undefined,
seriesType: 'line',
xAccessor: 'x-axis-column-layer0',
yConfig: [{ color: 'green', forAccessor: 'y-axis-column-layer0', axisMode: 'left' }],
yConfig: [{ color: 'green', forAccessor: 'y-axis-column-layer0-0', axisMode: 'left' }],
},
{
accessors: [
@ -468,14 +470,14 @@ describe('Lens Attribute', () => {
expect(lnsAttr.visualization?.layers).toEqual([
{
accessors: ['y-axis-column-layer0'],
accessors: ['y-axis-column-layer0-0'],
layerId: 'layer0',
layerType: 'data',
palette: undefined,
seriesType: 'line',
splitAccessor: 'breakdown-column-layer0',
xAccessor: 'x-axis-column-layer0',
yConfig: [{ color: 'green', forAccessor: 'y-axis-column-layer0', axisMode: 'left' }],
yConfig: [{ color: 'green', forAccessor: 'y-axis-column-layer0-0', axisMode: 'left' }],
},
]);
@ -483,7 +485,7 @@ describe('Lens Attribute', () => {
columnOrder: [
'breakdown-column-layer0',
'x-axis-column-layer0',
'y-axis-column-layer0',
'y-axis-column-layer0-0',
'y-axis-column-layer0X0',
'y-axis-column-layer0X1',
'y-axis-column-layer0X2',
@ -534,7 +536,7 @@ describe('Lens Attribute', () => {
scale: 'interval',
sourceField: LCP_FIELD,
},
'y-axis-column-layer0': {
'y-axis-column-layer0-0': {
customLabel: true,
dataType: 'number',
filter: {

View file

@ -16,11 +16,15 @@ import {
DateHistogramIndexPatternColumn,
FieldBasedIndexPatternColumn,
FiltersIndexPatternColumn,
FormulaIndexPatternColumn,
FormulaPublicApi,
LastValueIndexPatternColumn,
MaxIndexPatternColumn,
MedianIndexPatternColumn,
MinIndexPatternColumn,
OperationMetadata,
OperationType,
PercentileIndexPatternColumn,
LastValueIndexPatternColumn,
PersistedIndexPatternLayer,
RangeIndexPatternColumn,
SeriesType,
@ -30,25 +34,21 @@ import {
XYCurveType,
XYState,
YAxisMode,
MinIndexPatternColumn,
MaxIndexPatternColumn,
FormulaPublicApi,
FormulaIndexPatternColumn,
} from '@kbn/lens-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/common';
import { PersistableFilter } from '@kbn/lens-plugin/common';
import { urlFiltersToKueryString } from '../utils/stringify_kueries';
import {
FILTER_RECORDS,
FORMULA_COLUMN,
PERCENTILE,
PERCENTILE_RANKS,
RECORDS_FIELD,
RECORDS_PERCENTAGE_FIELD,
REPORT_METRIC_FIELD,
ReportTypes,
TERMS_COLUMN,
USE_BREAK_DOWN_COLUMN,
PERCENTILE,
PERCENTILE_RANKS,
ReportTypes,
FORMULA_COLUMN,
} from './constants';
import {
ColumnFilter,
@ -84,14 +84,37 @@ export function getPercentileParam(operationType: string) {
export const parseCustomFieldName = (
seriesConfig: SeriesConfig,
selectedMetricField?: string
): Partial<MetricOption> & { fieldName: string; columnLabel?: string; columnField?: string } => {
):
| (Partial<MetricOption> & { fieldName: string; columnLabel?: string; columnField?: string })
| MetricOption[] => {
const metricOptions = seriesConfig.metricOptions ?? [];
if (selectedMetricField) {
if (metricOptions) {
const currField = metricOptions.find(
({ field, id }) => field === selectedMetricField || id === selectedMetricField
);
const currField = metricOptions.find((opt) => {
if ('items' in opt) {
return opt.id === selectedMetricField;
} else {
return opt.field === selectedMetricField || opt.id === selectedMetricField;
}
});
if (currField && 'items' in currField) {
const currFieldItem = currField.items.find(
(item) => item.id === selectedMetricField || item.field === selectedMetricField
);
if (currFieldItem) {
return {
...(currFieldItem ?? {}),
fieldName: selectedMetricField,
columnLabel: currFieldItem?.label,
columnField: currFieldItem?.field,
};
}
return currField.items;
}
return {
...(currField ?? {}),
@ -107,6 +130,8 @@ export const parseCustomFieldName = (
};
};
type MainYAxisColType = ReturnType<LensAttributes['getMainYAxis']>;
export interface LayerConfig {
filters?: UrlFilter[];
seriesConfig: SeriesConfig;
@ -216,7 +241,7 @@ export class LensAttributes {
params: {
orderBy: isFormulaColumn
? { type: 'custom' }
: { type: 'column', columnId: `y-axis-column-${layerId}` },
: { type: 'column', columnId: `y-axis-column-${layerId}-0` },
size: 10,
orderDirection: 'desc',
otherBucket: true,
@ -283,6 +308,7 @@ export class LensAttributes {
columnType,
columnFilter,
operationType,
shortLabel,
}: {
sourceField: string;
columnType?: string;
@ -290,6 +316,7 @@ export class LensAttributes {
operationType?: SupportedOperations | 'last_value';
label?: string;
seriesConfig: SeriesConfig;
shortLabel?: boolean;
}) {
if (columnType === 'operation' || operationType) {
if (
@ -302,6 +329,7 @@ export class LensAttributes {
label,
seriesConfig,
columnFilter,
shortLabel,
});
}
if (operationType === 'last_value') {
@ -336,13 +364,7 @@ export class LensAttributes {
return {
...buildNumberColumn(sourceField),
operationType,
label: i18n.translate('xpack.observability.expView.columns.operation.label', {
defaultMessage: '{operationType} of {sourceField}',
values: {
sourceField: label || seriesConfig.labels[sourceField],
operationType: capitalize(operationType),
},
}),
label: label || seriesConfig.labels[sourceField],
filter: columnFilter,
params: {
sortField: '@timestamp',
@ -357,12 +379,14 @@ export class LensAttributes {
seriesConfig,
operationType,
columnFilter,
shortLabel,
}: {
sourceField: string;
operationType: SupportedOperations;
label?: string;
seriesConfig: SeriesConfig;
columnFilter?: ColumnFilter;
shortLabel?: boolean;
}):
| MinIndexPatternColumn
| MaxIndexPatternColumn
@ -373,7 +397,7 @@ export class LensAttributes {
return {
...buildNumberColumn(sourceField),
label:
operationType === 'unique_count'
operationType === 'unique_count' || shortLabel
? label || seriesConfig.labels[sourceField]
: i18n.translate('xpack.observability.expView.columns.operation.label', {
defaultMessage: '{operationType} of {sourceField}',
@ -472,7 +496,7 @@ export class LensAttributes {
const { xAxisColumn } = layerConfig.seriesConfig;
if (!xAxisColumn.sourceField) {
return xAxisColumn as LastValueIndexPatternColumn;
return [xAxisColumn as LastValueIndexPatternColumn];
}
if (xAxisColumn?.sourceField === USE_BREAK_DOWN_COLUMN) {
@ -507,15 +531,21 @@ export class LensAttributes {
operationType,
colIndex,
layerId,
metricOption,
shortLabel,
}: {
sourceField: string;
metricOption?: MetricOption;
operationType?: SupportedOperations;
label?: string;
layerId: string;
layerConfig: LayerConfig;
colIndex?: number;
shortLabel?: boolean;
}) {
const { breakdown, seriesConfig } = layerConfig;
const fieldMetaInfo = this.getFieldMeta(sourceField, layerConfig, metricOption);
const {
formula,
fieldMeta,
@ -525,7 +555,7 @@ export class LensAttributes {
timeScale,
columnFilters,
showPercentileAnnotations,
} = this.getFieldMeta(sourceField, layerConfig);
} = fieldMetaInfo;
if (columnType === FORMULA_COLUMN) {
return getDistributionInPercentageColumn({
@ -578,6 +608,7 @@ export class LensAttributes {
operationType,
label: columnLabel || label,
seriesConfig: layerConfig.seriesConfig,
shortLabel,
});
}
if (operationType === 'unique_count' || fieldType === 'string') {
@ -604,8 +635,24 @@ export class LensAttributes {
return parseCustomFieldName(layerConfig.seriesConfig, sourceField);
}
getFieldMeta(sourceField: string, layerConfig: LayerConfig) {
getFieldMeta(sourceField: string, layerConfig: LayerConfig, metricOpt?: MetricOption) {
if (sourceField === REPORT_METRIC_FIELD) {
const metricOption = metricOpt
? {
...metricOpt,
columnLabel: metricOpt.label,
columnField: metricOpt.field,
fieldName: metricOpt.field!,
}
: parseCustomFieldName(layerConfig.seriesConfig, layerConfig.selectedMetricField);
if (Array.isArray(metricOption)) {
return {
fieldName: sourceField,
items: metricOption,
};
}
const {
palette,
fieldName,
@ -616,7 +663,7 @@ export class LensAttributes {
paramFilters,
showPercentileAnnotations,
formula,
} = parseCustomFieldName(layerConfig.seriesConfig, layerConfig.selectedMetricField);
} = metricOption;
const fieldMeta = layerConfig.indexPattern.getFieldByName(fieldName!);
return {
formula,
@ -644,29 +691,51 @@ export class LensAttributes {
layerConfig.seriesConfig.yAxisColumns[0];
if (sourceField === RECORDS_PERCENTAGE_FIELD) {
return getDistributionInPercentageColumn({
label,
layerId,
columnFilter,
dataView: layerConfig.indexPattern,
lensFormulaHelper: this.lensFormulaHelper!,
}).main;
return [
getDistributionInPercentageColumn({
label,
layerId,
columnFilter,
dataView: layerConfig.indexPattern,
lensFormulaHelper: this.lensFormulaHelper!,
}).main,
];
}
if (sourceField === RECORDS_FIELD || !sourceField) {
return this.getRecordsColumn(label, undefined, timeScale);
return [this.getRecordsColumn(label, undefined, timeScale)];
}
return this.getColumnBasedOnType({
sourceField,
label,
layerConfig,
colIndex: 0,
operationType: (breakdown === PERCENTILE
? PERCENTILE_RANKS[0]
: operationType) as SupportedOperations,
layerId,
});
const fieldMetaInfo = this.getFieldMeta(sourceField, layerConfig);
if ('items' in fieldMetaInfo) {
const { items } = fieldMetaInfo;
return items?.map((item, index) => {
return this.getColumnBasedOnType({
layerConfig,
layerId,
shortLabel: true,
label: item.label,
sourceField: REPORT_METRIC_FIELD,
metricOption: item,
operationType: operationType as SupportedOperations,
});
});
}
return [
this.getColumnBasedOnType({
sourceField,
label,
layerConfig,
colIndex: 0,
operationType: (breakdown === PERCENTILE
? PERCENTILE_RANKS[0]
: operationType) as SupportedOperations,
layerId,
}),
];
}
getChildYAxises(
@ -859,53 +928,21 @@ export class LensAttributes {
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, layerId, columnFilter);
const mainYAxises = this.getMainYAxis(layerConfig, layerId, columnFilter);
const { sourceField } = seriesConfig.xAxisColumn;
const label = timeShift ? `${mainYAxis.label}(${timeShift})` : mainYAxis.label;
const hasBreakdownColumn =
// do nothing since this will be used a x axis source
Boolean(breakdown && sourceField !== USE_BREAK_DOWN_COLUMN && breakdown !== PERCENTILE);
let filterQuery = columnFilter || mainYAxis.filter?.query;
if (columnFilter && mainYAxis.filter?.query) {
filterQuery = `${columnFilter} and ${mainYAxis.filter.query}`;
}
layers[layerId] = {
columnOrder: [
...(breakdown && sourceField !== USE_BREAK_DOWN_COLUMN && breakdown !== PERCENTILE
? [`breakdown-column-${layerId}`]
: []),
`x-axis-column-${layerId}`,
`y-axis-column-${layerId}`,
...Object.keys(this.getChildYAxises(layerConfig, layerId, columnFilter)),
],
columns: {
[`x-axis-column-${layerId}`]: this.getXAxis(layerConfig, layerId),
[`y-axis-column-${layerId}`]: {
...mainYAxis,
label,
filter: {
query: filterQuery ?? '',
language: 'kuery',
},
...(timeShift ? { timeShift } : {}),
},
...(breakdown && sourceField !== USE_BREAK_DOWN_COLUMN && breakdown !== PERCENTILE
? // do nothing since this will be used a x axis source
{
[`breakdown-column-${layerId}`]: this.getBreakdownColumn({
layerId,
sourceField: breakdown,
indexPattern: layerConfig.indexPattern,
labels: layerConfig.seriesConfig.labels,
layerConfig,
}),
}
: {}),
...this.getChildYAxises(layerConfig, layerId, columnFilter),
},
incompleteColumns: {},
};
layers[layerId] = this.getDataLayer({
layerId,
layerConfig,
mainYAxises,
columnFilter,
timeShift,
hasBreakdownColumn,
});
});
Object.entries(this.seriesReferenceLines).forEach(([id, { layerData }]) => {
@ -915,6 +952,80 @@ export class LensAttributes {
return layers;
}
getDataLayer({
hasBreakdownColumn,
layerId,
layerConfig,
columnFilter,
mainYAxises,
timeShift,
}: {
hasBreakdownColumn: boolean;
layerId: string;
timeShift: string | null;
layerConfig: LayerConfig;
columnFilter: string;
mainYAxises: MainYAxisColType;
}) {
const allYAxisColumns: Record<string, any> = {};
mainYAxises?.forEach((mainYAxis, index) => {
let filterQuery = columnFilter || mainYAxis.filter?.query;
if (columnFilter && mainYAxis.filter?.query) {
filterQuery = `${columnFilter} and ${mainYAxis.filter.query}`;
}
const label = timeShift ? `${mainYAxis.label}(${timeShift})` : mainYAxis.label;
allYAxisColumns[`y-axis-column-${layerId}-${index}`] = {
...mainYAxis,
label,
filter: {
query: filterQuery ?? '',
language: 'kuery',
},
...(timeShift ? { timeShift } : {}),
};
});
const { breakdown } = layerConfig;
const breakDownColumn = hasBreakdownColumn
? this.getBreakdownColumn({
layerId,
sourceField: breakdown!,
indexPattern: layerConfig.indexPattern,
labels: layerConfig.seriesConfig.labels,
layerConfig,
})
: null;
const xAxises = {
[`x-axis-column-${layerId}`]: this.getXAxis(layerConfig, layerId),
};
return {
columnOrder: [
...(hasBreakdownColumn ? [`breakdown-column-${layerId}`] : []),
...Object.keys(xAxises),
...Object.keys(allYAxisColumns),
...Object.keys(this.getChildYAxises(layerConfig, layerId, columnFilter)),
],
columns: {
...xAxises,
...allYAxisColumns,
...(hasBreakdownColumn
? {
[`breakdown-column-${layerId}`]: breakDownColumn!,
}
: {}),
...this.getChildYAxises(layerConfig, layerId, columnFilter),
},
incompleteColumns: {},
};
}
getXyState(): XYState {
return {
legend: { isVisible: true, showSingleSeries: true, position: 'right' },
@ -949,9 +1060,15 @@ export class LensAttributes {
}
}
const layerId = `layer${index}`;
const columnFilter = this.getLayerFilters(layerConfig, this.layerConfigs.length);
const mainYAxises = this.getMainYAxis(layerConfig, layerId, columnFilter) ?? [];
return {
accessors: [
`y-axis-column-layer${index}`,
...mainYAxises.map((key, yIndex) => `y-axis-column-${layerId}-${yIndex}`),
...Object.keys(this.getChildYAxises(layerConfig, `layer${index}`, undefined, true)),
],
layerId: `layer${index}`,
@ -960,7 +1077,7 @@ export class LensAttributes {
palette: palette ?? layerConfig.seriesConfig.palette,
yConfig: layerConfig.seriesConfig.yConfig || [
{
forAccessor: `y-axis-column-layer${index}`,
forAccessor: `y-axis-column-layer${index}-0`,
color: layerConfig.color,
/* if the fields format matches the field format of the first layer, use the default y axis (right)
* if not, use the secondary y axis (left) */

View file

@ -34,6 +34,10 @@ export const sampleMetricFormulaAttribute = {
'layer-0-column-1': {
customLabel: true,
dataType: 'number',
filter: {
language: 'kuery',
query: 'summary.up: *',
},
isBucketed: false,
label: 'Availability',
operationType: 'formula',
@ -54,7 +58,7 @@ export const sampleMetricFormulaAttribute = {
dataType: 'number',
filter: {
language: 'kuery',
query: 'summary.down > 0',
query: '(summary.up: *) AND (summary.down > 0)',
},
isBucketed: false,
label: 'Part of Availability',
@ -68,6 +72,10 @@ export const sampleMetricFormulaAttribute = {
'layer-0-column-1X1': {
customLabel: true,
dataType: 'number',
filter: {
language: 'kuery',
query: 'summary.up: *',
},
isBucketed: false,
label: 'Part of Availability',
operationType: 'count',

View file

@ -14,6 +14,7 @@ import {
import type { DataView } from '@kbn/data-views-plugin/common';
import { Query } from '@kbn/es-query';
import { FORMULA_COLUMN } from '../constants';
import { ColumnFilter, MetricOption } from '../../types';
import { SeriesConfig } from '../../../../..';
@ -43,63 +44,76 @@ export class SingleMetricLensAttributes extends LensAttributes {
this.columnId = 'layer-0-column-1';
this.globalFilter = this.getGlobalFilter(this.isMultiSeries);
this.layers = this.getSingleMetricLayer();
this.layers = this.getSingleMetricLayer()!;
}
getSingleMetricLayer() {
const { seriesConfig, selectedMetricField, operationType, indexPattern } = this.layerConfigs[0];
const {
columnFilter,
columnField,
columnLabel,
columnType,
formula,
metricStateOptions,
format,
} = parseCustomFieldName(seriesConfig, selectedMetricField);
const metricOption = parseCustomFieldName(seriesConfig, selectedMetricField);
this.metricStateOptions = metricStateOptions;
if (columnType === FORMULA_COLUMN && formula) {
return this.getFormulaLayer({ formula, label: columnLabel, dataView: indexPattern, format });
}
const getSourceField = () => {
if (selectedMetricField.startsWith('Records') || selectedMetricField.startsWith('records')) {
return 'Records';
}
return columnField || selectedMetricField;
};
const sourceField = getSourceField();
const isPercentileColumn = operationType?.includes('th');
if (isPercentileColumn) {
return this.getPercentileLayer({
sourceField,
operationType,
seriesConfig,
columnLabel,
if (!Array.isArray(metricOption)) {
const {
columnFilter,
});
}
columnField,
columnLabel,
columnType,
formula,
metricStateOptions,
format,
} = metricOption;
return {
layer0: {
columns: {
[this.columnId]: {
...buildNumberColumn(sourceField),
label: columnLabel ?? '',
operationType: sourceField === 'Records' ? 'count' : operationType || 'median',
filter: columnFilter,
this.metricStateOptions = metricStateOptions;
if (columnType === FORMULA_COLUMN && formula) {
return this.getFormulaLayer({
formula,
label: columnLabel,
dataView: indexPattern,
format,
filter: columnFilter,
});
}
const getSourceField = () => {
if (
selectedMetricField.startsWith('Records') ||
selectedMetricField.startsWith('records')
) {
return 'Records';
}
return columnField || selectedMetricField;
};
const sourceField = getSourceField();
const isPercentileColumn = operationType?.includes('th');
if (isPercentileColumn) {
return this.getPercentileLayer({
sourceField,
operationType,
seriesConfig,
columnLabel,
columnFilter,
});
}
return {
layer0: {
columns: {
[this.columnId]: {
...buildNumberColumn(sourceField),
label: columnLabel ?? '',
operationType: sourceField === 'Records' ? 'count' : operationType || 'median',
filter: columnFilter,
},
},
columnOrder: [this.columnId],
incompleteColumns: {},
},
columnOrder: [this.columnId],
incompleteColumns: {},
},
};
};
}
}
getFormulaLayer({
@ -107,10 +121,12 @@ export class SingleMetricLensAttributes extends LensAttributes {
label,
dataView,
format,
filter,
}: {
formula: string;
label?: string;
format?: string;
filter?: Query;
dataView: DataView;
}) {
const layer = this.lensFormulaHelper?.insertOrReplaceFormulaColumn(
@ -118,6 +134,7 @@ export class SingleMetricLensAttributes extends LensAttributes {
{
formula,
label,
filter,
format:
format === 'percent' || !format
? {

View file

@ -14,6 +14,14 @@ import {
SYNTHETICS_STEP_DURATION,
} from '../constants/field_names/synthetics';
export const MS_TO_HUMANIZE_PRECISE = {
inputFormat: 'milliseconds' as const,
outputFormat: 'humanizePrecise' as const,
outputPrecision: 1,
showSuffix: true,
useShortSuffix: true,
};
export const syntheticsFieldFormats: FieldFormat[] = [
{
field: 'monitor.duration.us',

View file

@ -24,9 +24,11 @@ import {
STEP_DURATION_LABEL,
UP_LABEL,
PAGE_LOAD_TIME_LABEL,
NETWORK_TIMINGS_LABEL,
} from '../constants/labels';
import {
MONITOR_DURATION_US,
NETWORK_TIMINGS_FIELDS,
SYNTHETICS_CLS,
SYNTHETICS_DCL,
SYNTHETICS_DOCUMENT_ONLOAD,
@ -66,7 +68,7 @@ export function getSyntheticsKPIConfig({ dataView }: ConfigProps): SeriesConfig
operationType: 'median',
},
],
hasOperationType: false,
hasOperationType: true,
filterFields: ['observer.geo.name', 'monitor.type', 'tags', 'url.full'],
breakdownFields: [
'observer.geo.name',
@ -163,16 +165,31 @@ export function getSyntheticsKPIConfig({ dataView }: ConfigProps): SeriesConfig
columnType: OPERATION_COLUMN,
columnFilters: getStepMetricColumnFilter(SYNTHETICS_CLS),
},
{
label: NETWORK_TIMINGS_LABEL,
id: 'network_timings',
columnType: OPERATION_COLUMN,
items: NETWORK_TIMINGS_FIELDS.map((field) => ({
label: FieldLabels[field] ?? field,
field,
id: field,
columnType: OPERATION_COLUMN,
columnFilters: getStepMetricColumnFilter(field, 'journey/network_info'),
})),
},
],
labels: { ...FieldLabels, [SUMMARY_UP]: UP_LABEL, [SUMMARY_DOWN]: DOWN_LABEL },
};
}
const getStepMetricColumnFilter = (field: string): ColumnFilter[] => {
const getStepMetricColumnFilter = (
field: string,
stepType: 'step/metrics' | 'step/end' | 'journey/network_info' = 'step/metrics'
): ColumnFilter[] => {
return [
{
language: 'kuery',
query: `synthetics.type: step/metrics and ${field}: *`,
query: `synthetics.type: ${stepType} and ${field}: * and ${field} > 0`,
},
];
};

View file

@ -0,0 +1,62 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { RuntimeField } from '@kbn/data-views-plugin/public';
import { MS_TO_HUMANIZE_PRECISE } from './field_formats';
import {
SYNTHETICS_DNS_TIMINGS,
SYNTHETICS_BLOCKED_TIMINGS,
SYNTHETICS_CONNECT_TIMINGS,
SYNTHETICS_TOTAL_TIMINGS,
SYNTHETICS_RECEIVE_TIMINGS,
SYNTHETICS_SEND_TIMINGS,
SYNTHETICS_WAIT_TIMINGS,
SYNTHETICS_SSL_TIMINGS,
} from '../constants/field_names/synthetics';
const LONG_FIELD = {
type: 'long' as const,
format: {
id: 'duration',
params: MS_TO_HUMANIZE_PRECISE,
},
};
export const syntheticsRuntimeFields: Array<{ name: string; field: RuntimeField }> = [
{
name: SYNTHETICS_DNS_TIMINGS,
field: LONG_FIELD,
},
{
name: SYNTHETICS_BLOCKED_TIMINGS,
field: LONG_FIELD,
},
{
name: SYNTHETICS_CONNECT_TIMINGS,
field: LONG_FIELD,
},
{
name: SYNTHETICS_TOTAL_TIMINGS,
field: LONG_FIELD,
},
{
name: SYNTHETICS_RECEIVE_TIMINGS,
field: LONG_FIELD,
},
{
name: SYNTHETICS_SEND_TIMINGS,
field: LONG_FIELD,
},
{
name: SYNTHETICS_WAIT_TIMINGS,
field: LONG_FIELD,
},
{
name: SYNTHETICS_SSL_TIMINGS,
field: LONG_FIELD,
},
];

View file

@ -6,7 +6,10 @@
*/
import { i18n } from '@kbn/i18n';
import { SYNTHETICS_STEP_NAME } from '../constants/field_names/synthetics';
import {
SYNTHETICS_STEP_DURATION,
SYNTHETICS_STEP_NAME,
} from '../constants/field_names/synthetics';
import { ConfigProps, SeriesConfig } from '../../types';
import { FieldLabels, FORMULA_COLUMN } from '../constants';
import { buildExistsFilter } from '../utils';
@ -29,7 +32,7 @@ export function getSyntheticsSingleMetricConfig({ dataView }: ConfigProps): Seri
{ field: 'url.full', filters: buildExistsFilter('summary.up', dataView) },
],
reportType: 'single-metric',
baseFilters: [...buildExistsFilter('summary.up', dataView)],
baseFilters: [],
metricOptions: [
{
id: 'monitor_availability',
@ -65,6 +68,7 @@ export function getSyntheticsSingleMetricConfig({ dataView }: ConfigProps): Seri
},
titlePosition: 'bottom',
},
columnFilter: { language: 'kuery', query: 'summary.up: *' },
},
{
id: 'monitor_duration',
@ -75,6 +79,17 @@ export function getSyntheticsSingleMetricConfig({ dataView }: ConfigProps): Seri
metricStateOptions: {
titlePosition: 'bottom',
},
columnFilter: { language: 'kuery', query: 'summary.up: *' },
},
{
id: 'step_duration',
field: SYNTHETICS_STEP_DURATION,
label: i18n.translate('xpack.observability.expView.stepDuration', {
defaultMessage: 'Total step duration',
}),
metricStateOptions: {
titlePosition: 'bottom',
},
},
{
id: 'monitor_errors',

View file

@ -26,7 +26,7 @@ export const testMobileKPIAttr = {
formBased: {
layers: {
layer0: {
columnOrder: ['x-axis-column-layer0', 'y-axis-column-layer0'],
columnOrder: ['x-axis-column-layer0', 'y-axis-column-layer0-0'],
columns: {
'x-axis-column-layer0': {
sourceField: '@timestamp',
@ -37,7 +37,7 @@ export const testMobileKPIAttr = {
params: { interval: 'auto' },
scale: 'interval',
},
'y-axis-column-layer0': {
'y-axis-column-layer0-0': {
isBucketed: false,
label: 'Median of System memory usage',
operationType: 'median',
@ -68,12 +68,12 @@ export const testMobileKPIAttr = {
preferredSeriesType: 'line',
layers: [
{
accessors: ['y-axis-column-layer0'],
accessors: ['y-axis-column-layer0-0'],
layerId: 'layer0',
layerType: 'data',
palette: undefined,
seriesType: 'line',
yConfig: [{ forAccessor: 'y-axis-column-layer0', color: 'green', axisMode: 'left' }],
yConfig: [{ forAccessor: 'y-axis-column-layer0-0', color: 'green', axisMode: 'left' }],
xAccessor: 'x-axis-column-layer0',
},
],

View file

@ -32,7 +32,7 @@ export const sampleAttribute = {
layer0: {
columnOrder: [
'x-axis-column-layer0',
'y-axis-column-layer0',
'y-axis-column-layer0-0',
'y-axis-column-layer0X0',
'y-axis-column-layer0X1',
'y-axis-column-layer0X2',
@ -58,7 +58,7 @@ export const sampleAttribute = {
scale: 'interval',
sourceField: 'transaction.duration.us',
},
'y-axis-column-layer0': {
'y-axis-column-layer0-0': {
customLabel: true,
dataType: 'number',
filter: {
@ -250,7 +250,7 @@ export const sampleAttribute = {
},
layers: [
{
accessors: ['y-axis-column-layer0'],
accessors: ['y-axis-column-layer0-0'],
layerId: 'layer0',
layerType: 'data',
palette: undefined,
@ -259,7 +259,7 @@ export const sampleAttribute = {
yConfig: [
{
color: 'green',
forAccessor: 'y-axis-column-layer0',
forAccessor: 'y-axis-column-layer0-0',
axisMode: 'left',
},
],

View file

@ -27,7 +27,7 @@ export const sampleAttributeCoreWebVital = {
layer0: {
columnOrder: [
'x-axis-column-layer0',
'y-axis-column-layer0',
'y-axis-column-layer0-0',
'y-axis-column-1',
'y-axis-column-2',
],
@ -40,7 +40,7 @@ export const sampleAttributeCoreWebVital = {
params: {
missingBucket: false,
orderBy: {
columnId: 'y-axis-column-layer0',
columnId: 'y-axis-column-layer0-0',
type: 'column',
},
orderDirection: 'desc',
@ -75,7 +75,7 @@ export const sampleAttributeCoreWebVital = {
scale: 'ratio',
sourceField: RECORDS_FIELD,
},
'y-axis-column-layer0': {
'y-axis-column-layer0-0': {
dataType: 'number',
filter: {
language: 'kuery',
@ -115,7 +115,7 @@ export const sampleAttributeCoreWebVital = {
},
layers: [
{
accessors: ['y-axis-column-layer0', 'y-axis-column-1', 'y-axis-column-2'],
accessors: ['y-axis-column-layer0-0', 'y-axis-column-1', 'y-axis-column-2'],
layerId: 'layer0',
layerType: 'data',
palette: undefined,

View file

@ -25,7 +25,7 @@ export const sampleAttributeKpi = {
formBased: {
layers: {
layer0: {
columnOrder: ['x-axis-column-layer0', 'y-axis-column-layer0'],
columnOrder: ['x-axis-column-layer0', 'y-axis-column-layer0-0'],
columns: {
'x-axis-column-layer0': {
dataType: 'date',
@ -38,7 +38,7 @@ export const sampleAttributeKpi = {
scale: 'interval',
sourceField: '@timestamp',
},
'y-axis-column-layer0': {
'y-axis-column-layer0-0': {
dataType: 'number',
filter: {
language: 'kuery',
@ -76,7 +76,7 @@ export const sampleAttributeKpi = {
},
layers: [
{
accessors: ['y-axis-column-layer0'],
accessors: ['y-axis-column-layer0-0'],
layerId: 'layer0',
layerType: 'data',
palette: undefined,
@ -85,7 +85,7 @@ export const sampleAttributeKpi = {
yConfig: [
{
color: 'green',
forAccessor: 'y-axis-column-layer0',
forAccessor: 'y-axis-column-layer0-0',
axisMode: 'left',
},
],

View file

@ -32,7 +32,7 @@ export const sampleAttributeWithReferenceLines = {
layer0: {
columnOrder: [
'x-axis-column-layer0',
'y-axis-column-layer0',
'y-axis-column-layer0-0',
'y-axis-column-layer0X0',
'y-axis-column-layer0X1',
'y-axis-column-layer0X2',
@ -58,7 +58,7 @@ export const sampleAttributeWithReferenceLines = {
scale: 'interval',
sourceField: 'transaction.duration.us',
},
'y-axis-column-layer0': {
'y-axis-column-layer0-0': {
customLabel: true,
dataType: 'number',
filter: {
@ -250,7 +250,7 @@ export const sampleAttributeWithReferenceLines = {
},
layers: [
{
accessors: ['y-axis-column-layer0'],
accessors: ['y-axis-column-layer0-0'],
layerId: 'layer0',
layerType: 'data',
palette: undefined,
@ -260,7 +260,7 @@ export const sampleAttributeWithReferenceLines = {
{
axisMode: 'left',
color: 'green',
forAccessor: 'y-axis-column-layer0',
forAccessor: 'y-axis-column-layer0-0',
},
],
},

View file

@ -61,7 +61,11 @@ export function getQueryFilter(field: string, value: string[], dataView?: DataVi
return [];
}
export function buildPhrasesFilter(field: string, value: string[], dataView?: DataView) {
export function buildPhrasesFilter(
field: string,
value: Array<string | number>,
dataView?: DataView
) {
const fieldMeta = dataView?.fields.find((fieldT) => fieldT.name === field);
if (fieldMeta && dataView) {
if (value.length === 1) {

View file

@ -11,7 +11,7 @@ import { SeriesUrl, UrlFilter } from '../types';
export interface UpdateFilter {
field: string;
value: string | string[];
value: string | Array<string | number>;
negate?: boolean;
wildcards?: string[];
isWildcard?: boolean;
@ -30,8 +30,8 @@ export const useSeriesFilters = ({ seriesId, series }: { seriesId: number; serie
notWildcards,
}: {
field: string;
values: string[];
notValues: string[];
values: Array<string | number>;
notValues: Array<string | number>;
wildcards?: string[];
notWildcards?: string[];
}) => {

View file

@ -18,7 +18,7 @@ import { NestedFilterOpen } from './filter_expanded';
interface Props {
value: string;
field: string;
allSelectedValues?: string[];
allSelectedValues?: Array<string | number>;
negate: boolean;
nestedField?: string;
seriesId: number;

View file

@ -20,9 +20,11 @@ import { Breakdowns } from './breakdown/breakdowns';
import { LabelsBreakdown } from './breakdown/label_breakdown';
function getColumnType(seriesConfig: SeriesConfig, selectedMetricField?: string) {
const { columnType } = parseCustomFieldName(seriesConfig, selectedMetricField);
const metricOption = parseCustomFieldName(seriesConfig, selectedMetricField);
return columnType;
if (!Array.isArray(metricOption)) {
return metricOption?.columnType;
}
}
interface Props {

View file

@ -91,7 +91,10 @@ export interface SeriesConfig {
}
>;
textDefinitionFields?: string[];
metricOptions?: MetricOption[];
metricOptions?: Array<
| MetricOption
| { id: string; field?: string; label: string; items: MetricOption[]; columnType?: string }
>;
labels: Record<string, string>;
hasOperationType: boolean;
palette?: PaletteOutput;
@ -124,8 +127,8 @@ export interface SeriesUrl {
export interface UrlFilter {
field: string;
values?: string[];
notValues?: string[];
values?: Array<string | number>;
notValues?: Array<string | number>;
wildcards?: string[];
notWildcards?: string[];
}

View file

@ -20,7 +20,7 @@ const buildOrCondition = (values: string[]) => {
return `(${values.join(' or ')})`;
};
function addSlashes(str: string) {
function addSlashes(str: string | number) {
return (str + '').replace(/[\\"']/g, '\\$&').replace(/\u0000/g, '\\0');
}

View file

@ -20,7 +20,7 @@ export function buildFilterLabel({
negate,
}: {
label: string;
value: string | string[];
value: string | Array<string | number>;
negate: boolean;
field: string;
dataView: DataView;
@ -46,10 +46,14 @@ export function buildFilterLabel({
export interface FilterValueLabelProps {
field: string;
label: string;
value: string | string[];
value: string | Array<string | number>;
negate: boolean;
removeFilter: (field: string, value: string | string[], notVal: boolean) => void;
invertFilter: (val: { field: string; value: string | string[]; negate: boolean }) => void;
removeFilter: (field: string, value: string | Array<string | number>, notVal: boolean) => void;
invertFilter: (val: {
field: string;
value: string | Array<string | number>;
negate: boolean;
}) => void;
dataView: DataView;
allowExclusion?: boolean;
}

View file

@ -12,6 +12,8 @@ import type {
DataView,
DataViewSpec,
} from '@kbn/data-views-plugin/public';
import { RuntimeField } from '@kbn/data-views-plugin/public';
import { syntheticsRuntimeFields } from '../../components/shared/exploratory_view/configurations/synthetics/runtime_fields';
import { rumFieldFormats } from '../../components/shared/exploratory_view/configurations/rum/field_formats';
import { syntheticsFieldFormats } from '../../components/shared/exploratory_view/configurations/synthetics/field_formats';
import {
@ -32,8 +34,17 @@ const appFieldFormats: Record<AppDataType, FieldFormat[] | null> = {
mobile: apmFieldFormats,
};
const appRuntimeFields: Record<AppDataType, Array<{ name: string; field: RuntimeField }> | null> = {
infra_logs: null,
infra_metrics: null,
ux: null,
apm: null,
synthetics: syntheticsRuntimeFields,
mobile: null,
};
function getFieldFormatsForApp(app: AppDataType) {
return appFieldFormats[app];
return { runtimeFields: appRuntimeFields[app], formats: appFieldFormats[app] };
}
export const dataViewList: Record<AppDataType, string> = {
@ -101,7 +112,7 @@ export class ObservabilityDataViews {
}
// we want to make sure field formats remain same
async validateFieldFormats(app: AppDataType, dataView: DataView) {
const defaultFieldFormats = getFieldFormatsForApp(app);
const { formats: defaultFieldFormats, runtimeFields } = getFieldFormatsForApp(app);
if (defaultFieldFormats && defaultFieldFormats.length > 0) {
let isParamsDifferent = false;
defaultFieldFormats.forEach(({ field, format }) => {
@ -115,7 +126,12 @@ export class ObservabilityDataViews {
}
}
});
if (isParamsDifferent) {
if (runtimeFields !== null) {
runtimeFields.forEach(({ name, field }) => {
dataView.addRuntimeField(name, field);
});
}
if (isParamsDifferent || runtimeFields !== null) {
await this.dataViews?.updateSavedObject(dataView);
}
}