mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Exploratory view] Added timings breakdown (#143170)
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
de7c17357c
commit
137b4bd316
31 changed files with 614 additions and 207 deletions
|
@ -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,
|
||||
|
|
|
@ -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;
|
|
@ -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';
|
|
@ -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,
|
||||
];
|
|
@ -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';
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
];
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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) */
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
? {
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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`,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
];
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
],
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
],
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
],
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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[];
|
||||
}) => {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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[];
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue