[Exploratory view] Use index patterns for formatting (#96280)

This commit is contained in:
Shahzad 2021-04-08 07:16:23 +02:00 committed by GitHub
parent 93965343e5
commit 391e92ead3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
65 changed files with 470 additions and 123 deletions

1
.github/CODEOWNERS vendored
View file

@ -79,6 +79,7 @@
# Uptime
/x-pack/plugins/uptime @elastic/uptime
/x-pack/plugins/observability/public/components/shared/exploratory_view @elastic/uptime
/x-pack/test/functional_with_es_ssl/apps/uptime @elastic/uptime
/x-pack/test/functional/apps/uptime @elastic/uptime
/x-pack/test/api_integration/apis/uptime @elastic/uptime

View file

@ -9,6 +9,7 @@
import sinon from 'sinon';
import { CoreSetup } from 'src/core/public';
import { SerializedFieldFormat } from 'src/plugins/expressions/public';
import { IFieldType, FieldSpec } from '../../common/index_patterns';
import { IndexPattern, indexPatterns, KBN_FIELD_TYPES, fieldList } from '../';
import { getFieldFormatsRegistry } from '../test_utils';
@ -51,6 +52,7 @@ export class StubIndexPattern {
_reindexFields: Function;
stubSetFieldFormat: Function;
fields?: FieldSpec[];
setFieldFormat: (fieldName: string, format: SerializedFieldFormat) => void;
constructor(
pattern: string,
@ -74,6 +76,10 @@ export class StubIndexPattern {
this.metaFields = ['_id', '_type', '_source'];
this.fieldFormatMap = {};
this.setFieldFormat = (fieldName: string, format: SerializedFieldFormat) => {
this.fieldFormatMap[fieldName] = format;
};
this.getComputedFields = IndexPattern.prototype.getComputedFields.bind(this);
this.flattenHit = indexPatterns.flattenHitWrapper(
(this as unknown) as IndexPattern,

View file

@ -543,6 +543,7 @@ exports[`discover sidebar field details footer renders properly 1`] = `
"_source",
],
"popularizeField": [Function],
"setFieldFormat": [Function],
"stubSetFieldFormat": [Function],
"timeFieldName": "time",
"title": "logstash-*",

View file

@ -21,7 +21,7 @@ export type {
YAxisMode,
XYCurveType,
} from './xy_visualization/types';
export type { DataType } from './types';
export type { DataType, OperationMetadata } from './types';
export type {
PieVisualizationState,
PieLayerState,

View file

@ -18,7 +18,7 @@ const createStartContract = (): Start => {
}),
canUseEditor: jest.fn(() => true),
navigateToPrefilledEditor: jest.fn(),
getXyVisTypes: jest.fn().mockReturnValue(new Promise(() => visualizationTypes)),
getXyVisTypes: jest.fn().mockReturnValue(new Promise((resolve) => resolve(visualizationTypes))),
};
return startContract;
};

View file

@ -5,10 +5,10 @@
* 2.0.
*/
import { ConfigProps, DataSeries } from '../types';
import { FieldLabels } from './constants';
import { buildPhraseFilter } from './utils';
import { OperationType } from '../../../../../../lens/public';
import { ConfigProps, DataSeries } from '../../types';
import { FieldLabels } from '../constants';
import { buildPhraseFilter } from '../utils';
import { OperationType } from '../../../../../../../lens/public';
export function getServiceLatencyLensConfig({ seriesId, indexPattern }: ConfigProps): DataSeries {
return {
@ -20,7 +20,7 @@ export function getServiceLatencyLensConfig({ seriesId, indexPattern }: ConfigPr
sourceField: '@timestamp',
},
yAxisColumn: {
operationType: 'avg' as OperationType,
operationType: 'average' as OperationType,
sourceField: 'transaction.duration.us',
label: 'Latency',
},

View file

@ -5,10 +5,10 @@
* 2.0.
*/
import { ConfigProps, DataSeries } from '../types';
import { FieldLabels } from './constants';
import { buildPhraseFilter } from './utils';
import { OperationType } from '../../../../../../lens/public';
import { ConfigProps, DataSeries } from '../../types';
import { FieldLabels } from '../constants/constants';
import { buildPhraseFilter } from '../utils';
import { OperationType } from '../../../../../../../lens/public';
export function getServiceThroughputLensConfig({
seriesId,
@ -23,7 +23,7 @@ export function getServiceThroughputLensConfig({
sourceField: '@timestamp',
},
yAxisColumn: {
operationType: 'avg' as OperationType,
operationType: 'average' as OperationType,
sourceField: 'transaction.duration.us',
label: 'Throughput',
},

View file

@ -5,14 +5,8 @@
* 2.0.
*/
import { AppDataType, ReportViewTypeId } from '../types';
import {
CLS_FIELD,
FCP_FIELD,
FID_FIELD,
LCP_FIELD,
TBT_FIELD,
} from './data/elasticsearch_fieldnames';
import { AppDataType, ReportViewTypeId } from '../../types';
import { CLS_FIELD, FCP_FIELD, FID_FIELD, LCP_FIELD, TBT_FIELD } from './elasticsearch_fieldnames';
export const FieldLabels: Record<string, string> = {
'user_agent.name': 'Browser family',
@ -24,10 +18,10 @@ export const FieldLabels: Record<string, string> = {
'service.name': 'Service Name',
'service.environment': 'Environment',
[LCP_FIELD]: 'Largest contentful paint',
[FCP_FIELD]: 'First contentful paint',
[TBT_FIELD]: 'Total blocking time',
[FID_FIELD]: 'First input delay',
[LCP_FIELD]: 'Largest contentful paint (Seconds)',
[FCP_FIELD]: 'First contentful paint (Seconds)',
[TBT_FIELD]: 'Total blocking time (Seconds)',
[FID_FIELD]: 'First input delay (Seconds)',
[CLS_FIELD]: 'Cumulative layout shift',
'monitor.id': 'Monitor Id',
@ -38,6 +32,7 @@ export const FieldLabels: Record<string, string> = {
'monitor.name': 'Monitor name',
'monitor.type': 'Monitor Type',
'url.port': 'Port',
'url.full': 'Url',
tags: 'Tags',
// custom

View file

@ -0,0 +1,8 @@
/*
* 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 * from './constants';

View file

@ -6,16 +6,16 @@
*/
import { ReportViewTypes } from '../types';
import { getPerformanceDistLensConfig } from './performance_dist_config';
import { getMonitorDurationConfig } from './monitor_duration_config';
import { getServiceLatencyLensConfig } from './service_latency_config';
import { getMonitorPingsConfig } from './monitor_pings_config';
import { getServiceThroughputLensConfig } from './service_throughput_config';
import { getKPITrendsLensConfig } from './kpi_trends_config';
import { getCPUUsageLensConfig } from './cpu_usage_config';
import { getMemoryUsageLensConfig } from './memory_usage_config';
import { getNetworkActivityLensConfig } from './network_activity_config';
import { getLogsFrequencyLensConfig } from './logs_frequency_config';
import { getPerformanceDistLensConfig } from './rum/performance_dist_config';
import { getMonitorDurationConfig } from './synthetics/monitor_duration_config';
import { getServiceLatencyLensConfig } from './apm/service_latency_config';
import { getMonitorPingsConfig } from './synthetics/monitor_pings_config';
import { getServiceThroughputLensConfig } from './apm/service_throughput_config';
import { getKPITrendsLensConfig } from './rum/kpi_trends_config';
import { getCPUUsageLensConfig } from './metrics/cpu_usage_config';
import { getMemoryUsageLensConfig } from './metrics/memory_usage_config';
import { getNetworkActivityLensConfig } from './metrics/network_activity_config';
import { getLogsFrequencyLensConfig } from './logs/logs_frequency_config';
import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns';
interface Props {

View file

@ -8,9 +8,8 @@
import { LensAttributes } from './lens_attributes';
import { mockIndexPattern } from '../rtl_helpers';
import { getDefaultConfigs } from './default_configs';
import { sampleAttribute } from './data/sample_attribute';
import { LCP_FIELD, SERVICE_NAME } from './data/elasticsearch_fieldnames';
import { USER_AGENT_NAME } from './data/elasticsearch_fieldnames';
import { sampleAttribute } from './test_data/sample_attribute';
import { LCP_FIELD, SERVICE_NAME, USER_AGENT_NAME } from './constants/elasticsearch_fieldnames';
describe('Lens Attribute', () => {
const reportViewConfig = getDefaultConfigs({
@ -93,7 +92,7 @@ describe('Lens Attribute', () => {
expect(lnsAttr.getNumberColumn('transaction.duration.us')).toEqual({
dataType: 'number',
isBucketed: true,
label: 'Page load time',
label: 'Page load time (Seconds)',
operationType: 'range',
params: {
maxBars: 'auto',
@ -129,7 +128,7 @@ describe('Lens Attribute', () => {
expect(lnsAttr.getXAxis()).toEqual({
dataType: 'number',
isBucketed: true,
label: 'Page load time',
label: 'Page load time (Seconds)',
operationType: 'range',
params: {
maxBars: 'auto',
@ -154,7 +153,7 @@ describe('Lens Attribute', () => {
'x-axis-column': {
dataType: 'number',
isBucketed: true,
label: 'Page load time',
label: 'Page load time (Seconds)',
operationType: 'range',
params: {
maxBars: 'auto',
@ -318,7 +317,7 @@ describe('Lens Attribute', () => {
'x-axis-column': {
dataType: 'number',
isBucketed: true,
label: 'Page load time',
label: 'Page load time (Seconds)',
operationType: 'range',
params: {
maxBars: 'auto',
@ -363,7 +362,7 @@ describe('Lens Attribute', () => {
'x-axis-column': {
dataType: 'number',
isBucketed: true,
label: 'Page load time',
label: 'Page load time (Seconds)',
operationType: 'range',
params: {
maxBars: 'auto',

View file

@ -5,8 +5,8 @@
* 2.0.
*/
import { DataSeries } from '../types';
import { FieldLabels } from './constants';
import { DataSeries } from '../../types';
import { FieldLabels } from '../constants';
interface Props {
seriesId: string;

View file

@ -5,9 +5,9 @@
* 2.0.
*/
import { DataSeries } from '../types';
import { FieldLabels } from './constants';
import { OperationType } from '../../../../../../lens/public';
import { DataSeries } from '../../types';
import { FieldLabels } from '../constants';
import { OperationType } from '../../../../../../../lens/public';
interface Props {
seriesId: string;
@ -23,7 +23,7 @@ export function getCPUUsageLensConfig({ seriesId }: Props): DataSeries {
sourceField: '@timestamp',
},
yAxisColumn: {
operationType: 'avg' as OperationType,
operationType: 'average' as OperationType,
sourceField: 'system.cpu.user.pct',
label: 'CPU Usage %',
},

View file

@ -5,9 +5,9 @@
* 2.0.
*/
import { DataSeries } from '../types';
import { FieldLabels } from './constants';
import { OperationType } from '../../../../../../lens/public';
import { DataSeries } from '../../types';
import { FieldLabels } from '../constants';
import { OperationType } from '../../../../../../../lens/public';
interface Props {
seriesId: string;
@ -23,7 +23,7 @@ export function getMemoryUsageLensConfig({ seriesId }: Props): DataSeries {
sourceField: '@timestamp',
},
yAxisColumn: {
operationType: 'avg' as OperationType,
operationType: 'average' as OperationType,
sourceField: 'system.memory.used.pct',
label: 'Memory Usage %',
},

View file

@ -5,9 +5,9 @@
* 2.0.
*/
import { DataSeries } from '../types';
import { FieldLabels } from './constants';
import { OperationType } from '../../../../../../lens/public';
import { DataSeries } from '../../types';
import { FieldLabels } from '../constants';
import { OperationType } from '../../../../../../../lens/public';
interface Props {
seriesId: string;
@ -23,7 +23,7 @@ export function getNetworkActivityLensConfig({ seriesId }: Props): DataSeries {
sourceField: '@timestamp',
},
yAxisColumn: {
operationType: 'avg' as OperationType,
operationType: 'average' as OperationType,
sourceField: 'system.memory.used.pct',
},
hasMetricType: true,

View file

@ -0,0 +1,74 @@
/*
* 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 { FieldFormat } from '../../types';
import {
FCP_FIELD,
FID_FIELD,
LCP_FIELD,
TBT_FIELD,
TRANSACTION_DURATION,
} from '../constants/elasticsearch_fieldnames';
export const rumFieldFormats: FieldFormat[] = [
{
field: TRANSACTION_DURATION,
format: {
id: 'duration',
params: {
inputFormat: 'microseconds',
outputFormat: 'asSeconds',
showSuffix: true,
outputPrecision: 1,
},
},
},
{
field: FCP_FIELD,
format: {
id: 'duration',
params: {
inputFormat: 'milliseconds',
outputFormat: 'asSeconds',
showSuffix: true,
},
},
},
{
field: LCP_FIELD,
format: {
id: 'duration',
params: {
inputFormat: 'milliseconds',
outputFormat: 'asSeconds',
showSuffix: true,
},
},
},
{
field: TBT_FIELD,
format: {
id: 'duration',
params: {
inputFormat: 'milliseconds',
outputFormat: 'asSeconds',
showSuffix: true,
},
},
},
{
field: FID_FIELD,
format: {
id: 'duration',
params: {
inputFormat: 'milliseconds',
outputFormat: 'asSeconds',
showSuffix: true,
},
},
},
];

View file

@ -5,9 +5,9 @@
* 2.0.
*/
import { ConfigProps, DataSeries } from '../types';
import { FieldLabels } from './constants';
import { buildPhraseFilter } from './utils';
import { ConfigProps, DataSeries } from '../../types';
import { FieldLabels } from '../constants';
import { buildPhraseFilter } from '../utils';
import {
CLIENT_GEO_COUNTRY_NAME,
PROCESSOR_EVENT,
@ -18,7 +18,7 @@ import {
USER_AGENT_NAME,
USER_AGENT_OS,
USER_AGENT_VERSION,
} from './data/elasticsearch_fieldnames';
} from '../constants/elasticsearch_fieldnames';
export function getKPITrendsLensConfig({ seriesId, indexPattern }: ConfigProps): DataSeries {
return {

View file

@ -5,9 +5,9 @@
* 2.0.
*/
import { ConfigProps, DataSeries } from '../types';
import { FieldLabels } from './constants';
import { buildPhraseFilter } from './utils';
import { ConfigProps, DataSeries } from '../../types';
import { FieldLabels } from '../constants';
import { buildPhraseFilter } from '../utils';
import {
CLIENT_GEO_COUNTRY_NAME,
CLS_FIELD,
@ -24,7 +24,7 @@ import {
USER_AGENT_NAME,
USER_AGENT_OS,
USER_AGENT_VERSION,
} from './data/elasticsearch_fieldnames';
} from '../constants/elasticsearch_fieldnames';
export function getPerformanceDistLensConfig({ seriesId, indexPattern }: ConfigProps): DataSeries {
return {
@ -80,7 +80,7 @@ export function getPerformanceDistLensConfig({ seriesId, indexPattern }: ConfigP
labels: {
...FieldLabels,
[SERVICE_NAME]: 'Web Application',
[TRANSACTION_DURATION]: 'Page load time',
[TRANSACTION_DURATION]: 'Page load time (Seconds)',
},
};
}

View file

@ -0,0 +1,22 @@
/*
* 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 { FieldFormat } from '../../types';
export const syntheticsFieldFormats: FieldFormat[] = [
{
field: 'monitor.duration.us',
format: {
id: 'duration',
params: {
inputFormat: 'microseconds',
outputFormat: 'asMilliseconds',
outputPrecision: 0,
},
},
},
];

View file

@ -5,9 +5,9 @@
* 2.0.
*/
import { DataSeries } from '../types';
import { FieldLabels } from './constants';
import { OperationType } from '../../../../../../lens/public';
import { DataSeries } from '../../types';
import { FieldLabels } from '../constants/constants';
import { OperationType } from '../../../../../../../lens/public';
interface Props {
seriesId: string;
@ -23,7 +23,7 @@ export function getMonitorDurationConfig({ seriesId }: Props): DataSeries {
sourceField: '@timestamp',
},
yAxisColumn: {
operationType: 'avg' as OperationType,
operationType: 'average' as OperationType,
sourceField: 'monitor.duration.us',
label: 'Monitor duration (ms)',
},

View file

@ -5,8 +5,8 @@
* 2.0.
*/
import { DataSeries } from '../types';
import { FieldLabels } from './constants';
import { DataSeries } from '../../types';
import { FieldLabels } from '../constants';
interface Props {
seriesId: string;

View file

@ -21,7 +21,7 @@ export const sampleAttribute = {
columns: {
'x-axis-column': {
sourceField: 'transaction.duration.us',
label: 'Page load time',
label: 'Page load time (Seconds)',
dataType: 'number',
operationType: 'range',
isBucketed: true,

View file

@ -5,11 +5,11 @@
* 2.0.
*/
import rison, { RisonValue } from 'rison-node';
import type { AllSeries, AllShortSeries } from '../hooks/use_url_strorage';
import type { AllSeries, AllShortSeries } from '../hooks/use_url_storage';
import type { SeriesUrl } from '../types';
import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns';
import { esFilters } from '../../../../../../../../src/plugins/data/public';
import { URL_KEYS } from './url_constants';
import { URL_KEYS } from './constants/url_constants';
export function convertToShortUrl(series: SeriesUrl) {
const {

View file

@ -10,7 +10,7 @@ import { fireEvent, screen, waitFor } from '@testing-library/dom';
import { render, mockUrlStorage, mockCore } from './rtl_helpers';
import { ExploratoryView } from './exploratory_view';
import { getStubIndexPattern } from '../../../../../../../src/plugins/data/public/test_utils';
import * as obsvInd from '../../../utils/observability_index_patterns';
import * as obsvInd from './utils/observability_index_patterns';
describe('ExploratoryView', () => {
beforeEach(() => {

View file

@ -12,7 +12,7 @@ import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'
import { ObservabilityPublicPluginsStart } from '../../../plugin';
import { ExploratoryViewHeader } from './header/header';
import { SeriesEditor } from './series_editor/series_editor';
import { useUrlStorage } from './hooks/use_url_strorage';
import { useUrlStorage } from './hooks/use_url_storage';
import { useLensAttributes } from './hooks/use_lens_attributes';
import { EmptyView } from './components/empty_view';
import { useIndexPatternContext } from './hooks/use_default_index_pattern';

View file

@ -12,7 +12,7 @@ import { TypedLensByValueInput } from '../../../../../../lens/public';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
import { ObservabilityPublicPluginsStart } from '../../../../plugin';
import { DataViewLabels } from '../configurations/constants';
import { useUrlStorage } from '../hooks/use_url_strorage';
import { useUrlStorage } from '../hooks/use_url_storage';
interface Props {
seriesId: string;

View file

@ -10,7 +10,7 @@ import { IndexPattern } from '../../../../../../../../src/plugins/data/common';
import { AppDataType } from '../types';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
import { ObservabilityPublicPluginsStart } from '../../../../plugin';
import { ObservabilityIndexPatterns } from '../../../../utils/observability_index_patterns';
import { ObservabilityIndexPatterns } from '../utils/observability_index_patterns';
export interface IIndexPatternContext {
indexPattern: IndexPattern;

View file

@ -8,12 +8,9 @@ import { useFetcher } from '../../../..';
import { IKbnUrlStateStorage } from '../../../../../../../../src/plugins/kibana_utils/public';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
import { ObservabilityPublicPluginsStart } from '../../../../plugin';
import { AllShortSeries } from './use_url_strorage';
import { AllShortSeries } from './use_url_storage';
import { ReportToDataTypeMap } from '../configurations/constants';
import {
DataType,
ObservabilityIndexPatterns,
} from '../../../../utils/observability_index_patterns';
import { DataType, ObservabilityIndexPatterns } from '../utils/observability_index_patterns';
export const useInitExploratoryView = (storage: IKbnUrlStateStorage) => {
const {
@ -30,7 +27,7 @@ export const useInitExploratoryView = (storage: IKbnUrlStateStorage) => {
const firstSeries = allSeries[firstSeriesId];
const { data: indexPattern } = useFetcher(() => {
const { data: indexPattern, error } = useFetcher(() => {
const obsvIndexP = new ObservabilityIndexPatterns(data);
let reportType: DataType = 'apm';
if (firstSeries?.rt) {
@ -40,5 +37,9 @@ export const useInitExploratoryView = (storage: IKbnUrlStateStorage) => {
return obsvIndexP.getIndexPattern(reportType);
}, [firstSeries?.rt, data]);
if (error) {
throw error;
}
return indexPattern;
};

View file

@ -8,7 +8,7 @@
import { useMemo } from 'react';
import { TypedLensByValueInput } from '../../../../../../lens/public';
import { LensAttributes } from '../configurations/lens_attributes';
import { useUrlStorage } from './use_url_strorage';
import { useUrlStorage } from './use_url_storage';
import { getDefaultConfigs } from '../configurations/default_configs';
import { IndexPattern } from '../../../../../../../../src/plugins/data/common';

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { useUrlStorage } from './use_url_strorage';
import { useUrlStorage } from './use_url_storage';
import { UrlFilter } from '../types';
export interface UpdateFilter {

View file

@ -10,7 +10,7 @@ import { IKbnUrlStateStorage } from '../../../../../../../../src/plugins/kibana_
import type { AppDataType, ReportViewTypeId, SeriesUrl, UrlFilter } from '../types';
import { convertToShortUrl } from '../configurations/utils';
import { OperationType, SeriesType } from '../../../../../../lens/public';
import { URL_KEYS } from '../configurations/url_constants';
import { URL_KEYS } from '../configurations/constants/url_constants';
export const UrlStorageContext = createContext<IKbnUrlStateStorage | null>(null);

View file

@ -18,7 +18,7 @@ import {
createKbnUrlStateStorage,
withNotifyOnErrors,
} from '../../../../../../../src/plugins/kibana_utils/public/';
import { UrlStorageContextProvider } from './hooks/use_url_strorage';
import { UrlStorageContextProvider } from './hooks/use_url_storage';
import { useInitExploratoryView } from './hooks/use_init_exploratory_view';
import { WithHeaderLayout } from '../../app/layout/with_header';

View file

@ -23,20 +23,20 @@ import { ObservabilityPublicPluginsStart } from '../../../plugin';
import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common';
import { lensPluginMock } from '../../../../../lens/public/mocks';
import { IndexPatternContextProvider } from './hooks/use_default_index_pattern';
import { AllSeries, UrlStorageContextProvider } from './hooks/use_url_strorage';
import { AllSeries, UrlStorageContextProvider } from './hooks/use_url_storage';
import {
withNotifyOnErrors,
createKbnUrlStateStorage,
} from '../../../../../../../src/plugins/kibana_utils/public';
import * as fetcherHook from '../../../hooks/use_fetcher';
import * as useUrlHook from './hooks/use_url_strorage';
import * as useUrlHook from './hooks/use_url_storage';
import * as useSeriesFilterHook from './hooks/use_series_filters';
import * as useHasDataHook from '../../../hooks/use_has_data';
import * as useValuesListHook from '../../../hooks/use_values_list';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { getStubIndexPattern } from '../../../../../../../src/plugins/data/public/index_patterns/index_pattern.stub';
import indexPatternData from './configurations/data/test_index_pattern.json';
import indexPatternData from './configurations/test_data/test_index_pattern.json';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { setIndexPatterns } from '../../../../../../../src/plugins/data/public/services';

View file

@ -9,7 +9,7 @@ import React from 'react';
import { fireEvent, screen } from '@testing-library/react';
import { mockUrlStorage, render } from '../../rtl_helpers';
import { dataTypes, DataTypesCol } from './data_types_col';
import { NEW_SERIES_KEY } from '../../hooks/use_url_strorage';
import { NEW_SERIES_KEY } from '../../hooks/use_url_storage';
describe('DataTypesCol', function () {
it('should render properly', function () {

View file

@ -9,7 +9,7 @@ import React from 'react';
import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { AppDataType } from '../../types';
import { useIndexPatternContext } from '../../hooks/use_default_index_pattern';
import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_strorage';
import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_storage';
export const dataTypes: Array<{ id: AppDataType; label: string }> = [
{ id: 'synthetics', label: 'Synthetic Monitoring' },

View file

@ -10,9 +10,9 @@ import { fireEvent, screen } from '@testing-library/react';
import { render } from '../../../../../utils/test_helper';
import { getDefaultConfigs } from '../../configurations/default_configs';
import { mockIndexPattern, mockUrlStorage } from '../../rtl_helpers';
import { NEW_SERIES_KEY } from '../../hooks/use_url_strorage';
import { NEW_SERIES_KEY } from '../../hooks/use_url_storage';
import { ReportBreakdowns } from './report_breakdowns';
import { USER_AGENT_OS } from '../../configurations/data/elasticsearch_fieldnames';
import { USER_AGENT_OS } from '../../configurations/constants/elasticsearch_fieldnames';
describe('Series Builder ReportBreakdowns', function () {
const dataViewSeries = getDefaultConfigs({

View file

@ -7,7 +7,7 @@
import React from 'react';
import { Breakdowns } from '../../series_editor/columns/breakdowns';
import { NEW_SERIES_KEY } from '../../hooks/use_url_strorage';
import { NEW_SERIES_KEY } from '../../hooks/use_url_storage';
import { DataSeries } from '../../types';
export function ReportBreakdowns({ dataViewSeries }: { dataViewSeries: DataSeries }) {

View file

@ -9,9 +9,9 @@ import React from 'react';
import { fireEvent, screen } from '@testing-library/react';
import { getDefaultConfigs } from '../../configurations/default_configs';
import { mockIndexPattern, mockUrlStorage, mockUseValuesList, render } from '../../rtl_helpers';
import { NEW_SERIES_KEY } from '../../hooks/use_url_strorage';
import { NEW_SERIES_KEY } from '../../hooks/use_url_storage';
import { ReportDefinitionCol } from './report_definition_col';
import { SERVICE_NAME } from '../../configurations/data/elasticsearch_fieldnames';
import { SERVICE_NAME } from '../../configurations/constants/elasticsearch_fieldnames';
describe('Series Builder ReportDefinitionCol', function () {
const dataViewSeries = getDefaultConfigs({

View file

@ -8,7 +8,7 @@
import React from 'react';
import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { useIndexPatternContext } from '../../hooks/use_default_index_pattern';
import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_strorage';
import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_storage';
import { CustomReportField } from '../custom_report_field';
import FieldValueSuggestions from '../../../field_value_suggestions';
import { DataSeries } from '../../types';
@ -67,6 +67,7 @@ export function ReportDefinitionCol({ dataViewSeries }: { dataViewSeries: DataSe
{rtd?.[field] && (
<EuiFlexItem grow={false}>
<EuiBadge
className="globalFilterItem"
iconSide="right"
iconType="cross"
color="hollow"

View file

@ -11,7 +11,7 @@ import { render } from '../../../../../utils/test_helper';
import { ReportFilters } from './report_filters';
import { getDefaultConfigs } from '../../configurations/default_configs';
import { mockIndexPattern, mockUrlStorage } from '../../rtl_helpers';
import { NEW_SERIES_KEY } from '../../hooks/use_url_strorage';
import { NEW_SERIES_KEY } from '../../hooks/use_url_storage';
describe('Series Builder ReportFilters', function () {
const dataViewSeries = getDefaultConfigs({

View file

@ -7,7 +7,7 @@
import React from 'react';
import { SeriesFilter } from '../../series_editor/columns/series_filter';
import { NEW_SERIES_KEY } from '../../hooks/use_url_strorage';
import { NEW_SERIES_KEY } from '../../hooks/use_url_storage';
import { DataSeries } from '../../types';
export function ReportFilters({ dataViewSeries }: { dataViewSeries: DataSeries }) {

View file

@ -9,7 +9,7 @@ import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
import { ReportViewTypeId, SeriesUrl } from '../../types';
import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_strorage';
import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_storage';
interface Props {
reportTypes: Array<{ id: ReportViewTypeId; label: string }>;

View file

@ -7,7 +7,7 @@
import React from 'react';
import { EuiSuperSelect } from '@elastic/eui';
import { useUrlStorage } from '../hooks/use_url_strorage';
import { useUrlStorage } from '../hooks/use_url_storage';
import { ReportDefinition } from '../types';
interface Props {

View file

@ -16,7 +16,7 @@ import { ReportTypesCol } from './columns/report_types_col';
import { ReportDefinitionCol } from './columns/report_definition_col';
import { ReportFilters } from './columns/report_filters';
import { ReportBreakdowns } from './columns/report_breakdowns';
import { NEW_SERIES_KEY, useUrlStorage } from '../hooks/use_url_strorage';
import { NEW_SERIES_KEY, useUrlStorage } from '../hooks/use_url_storage';
import { useIndexPatternContext } from '../hooks/use_default_index_pattern';
import { getDefaultConfigs } from '../configurations/default_configs';

View file

@ -8,7 +8,7 @@
import { EuiSuperDatePicker } from '@elastic/eui';
import React, { useEffect } from 'react';
import { useHasData } from '../../../../hooks/use_has_data';
import { useUrlStorage } from '../hooks/use_url_strorage';
import { useUrlStorage } from '../hooks/use_url_storage';
import { useQuickTimeRanges } from '../../../../hooks/use_quick_time_ranges';
export interface TimePickerTime {

View file

@ -9,9 +9,9 @@ import React from 'react';
import { fireEvent, screen } from '@testing-library/react';
import { Breakdowns } from './breakdowns';
import { mockIndexPattern, mockUrlStorage, render } from '../../rtl_helpers';
import { NEW_SERIES_KEY } from '../../hooks/use_url_strorage';
import { NEW_SERIES_KEY } from '../../hooks/use_url_storage';
import { getDefaultConfigs } from '../../configurations/default_configs';
import { USER_AGENT_OS } from '../../configurations/data/elasticsearch_fieldnames';
import { USER_AGENT_OS } from '../../configurations/constants/elasticsearch_fieldnames';
describe('Breakdowns', function () {
const dataViewSeries = getDefaultConfigs({

View file

@ -9,7 +9,7 @@ import React from 'react';
import { EuiSuperSelect } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FieldLabels } from '../../configurations/constants';
import { useUrlStorage } from '../../hooks/use_url_strorage';
import { useUrlStorage } from '../../hooks/use_url_storage';
interface Props {
seriesId: string;

View file

@ -19,7 +19,7 @@ import styled from 'styled-components';
import { useKibana } from '../../../../../../../../../src/plugins/kibana_react/public';
import { ObservabilityPublicPluginsStart } from '../../../../../plugin';
import { useFetcher } from '../../../../..';
import { useUrlStorage } from '../../hooks/use_url_strorage';
import { useUrlStorage } from '../../hooks/use_url_storage';
import { SeriesType } from '../../../../../../../lens/public';
export function SeriesChartTypes({

View file

@ -9,7 +9,7 @@ import React from 'react';
import { fireEvent, screen } from '@testing-library/react';
import { FilterExpanded } from './filter_expanded';
import { mockUrlStorage, mockUseValuesList, render } from '../../rtl_helpers';
import { USER_AGENT_NAME } from '../../configurations/data/elasticsearch_fieldnames';
import { USER_AGENT_NAME } from '../../configurations/constants/elasticsearch_fieldnames';
describe('FilterExpanded', function () {
it('should render properly', async function () {

View file

@ -14,7 +14,7 @@ import {
EuiFilterGroup,
} from '@elastic/eui';
import { useIndexPatternContext } from '../../hooks/use_default_index_pattern';
import { useUrlStorage } from '../../hooks/use_url_strorage';
import { useUrlStorage } from '../../hooks/use_url_storage';
import { UrlFilter } from '../../types';
import { FilterValueButton } from './filter_value_btn';
import { useValuesList } from '../../../../../hooks/use_values_list';

View file

@ -12,7 +12,7 @@ import { mockUrlStorage, mockUseSeriesFilter, mockUseValuesList, render } from '
import {
USER_AGENT_NAME,
USER_AGENT_VERSION,
} from '../../configurations/data/elasticsearch_fieldnames';
} from '../../configurations/constants/elasticsearch_fieldnames';
describe('FilterValueButton', function () {
it('should render properly', async function () {

View file

@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n';
import React, { useMemo } from 'react';
import { EuiFilterButton, hexToRgb } from '@elastic/eui';
import { useIndexPatternContext } from '../../hooks/use_default_index_pattern';
import { useUrlStorage } from '../../hooks/use_url_strorage';
import { useUrlStorage } from '../../hooks/use_url_storage';
import { useSeriesFilters } from '../../hooks/use_series_filters';
import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common';
import FieldValueSuggestions from '../../../field_value_suggestions';

View file

@ -8,12 +8,12 @@
import React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiButton, EuiButtonGroup, EuiPopover } from '@elastic/eui';
import { useUrlStorage } from '../../hooks/use_url_strorage';
import { useUrlStorage } from '../../hooks/use_url_storage';
import { OperationType } from '../../../../../../../lens/public';
const toggleButtons = [
{
id: `avg`,
id: `average`,
label: i18n.translate('xpack.observability.expView.metricsSelect.average', {
defaultMessage: 'Average',
}),
@ -49,7 +49,7 @@ export function MetricSelection({
const [isOpen, setIsOpen] = useState(false);
const [toggleIdSelected, setToggleIdSelected] = useState(series?.metric ?? 'avg');
const [toggleIdSelected, setToggleIdSelected] = useState(series?.metric ?? 'average');
const onChange = (optionId: OperationType) => {
setToggleIdSelected(optionId);

View file

@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n';
import React from 'react';
import { EuiButtonIcon } from '@elastic/eui';
import { DataSeries } from '../../types';
import { useUrlStorage } from '../../hooks/use_url_strorage';
import { useUrlStorage } from '../../hooks/use_url_storage';
interface Props {
series: DataSeries;

View file

@ -17,9 +17,9 @@ import {
} from '@elastic/eui';
import { FilterExpanded } from './filter_expanded';
import { DataSeries } from '../../types';
import { FieldLabels } from '../../configurations/constants';
import { FieldLabels } from '../../configurations/constants/constants';
import { SelectedFilters } from '../selected_filters';
import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_strorage';
import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_storage';
interface Props {
seriesId: string;

View file

@ -10,8 +10,8 @@ import { screen, waitFor } from '@testing-library/react';
import { mockIndexPattern, mockUrlStorage, render } from '../rtl_helpers';
import { SelectedFilters } from './selected_filters';
import { getDefaultConfigs } from '../configurations/default_configs';
import { NEW_SERIES_KEY } from '../hooks/use_url_strorage';
import { USER_AGENT_NAME } from '../configurations/data/elasticsearch_fieldnames';
import { NEW_SERIES_KEY } from '../hooks/use_url_storage';
import { USER_AGENT_NAME } from '../configurations/constants/elasticsearch_fieldnames';
describe('SelectedFilters', function () {
const dataViewSeries = getDefaultConfigs({

View file

@ -7,7 +7,7 @@
import React, { Fragment } from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { NEW_SERIES_KEY, useUrlStorage } from '../hooks/use_url_strorage';
import { NEW_SERIES_KEY, useUrlStorage } from '../hooks/use_url_storage';
import { FilterLabel } from '../components/filter_label';
import { DataSeries, UrlFilter } from '../types';
import { useIndexPatternContext } from '../hooks/use_default_index_pattern';

View file

@ -13,7 +13,7 @@ import { ActionsCol } from './columns/actions_col';
import { Breakdowns } from './columns/breakdowns';
import { DataSeries } from '../types';
import { SeriesBuilder } from '../series_builder/series_builder';
import { NEW_SERIES_KEY, useUrlStorage } from '../hooks/use_url_strorage';
import { NEW_SERIES_KEY, useUrlStorage } from '../hooks/use_url_storage';
import { getDefaultConfigs } from '../configurations/default_configs';
import { DatePickerCol } from './columns/date_picker_col';
import { RemoveSeries } from './columns/remove_series';

View file

@ -87,3 +87,22 @@ export interface ConfigProps {
}
export type AppDataType = 'synthetics' | 'rum' | 'logs' | 'metrics' | 'apm';
type FormatType = 'duration' | 'number';
type InputFormat = 'microseconds' | 'milliseconds' | 'seconds';
type OutputFormat = 'asSeconds' | 'asMilliseconds' | 'humanize';
export interface FieldFormatParams {
inputFormat: InputFormat;
outputFormat: OutputFormat;
outputPrecision?: number;
showSuffix?: boolean;
}
export interface FieldFormat {
field: string;
format: {
id: FormatType;
params: FieldFormatParams;
};
}

View file

@ -0,0 +1,95 @@
/*
* 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 { indexPatternList, ObservabilityIndexPatterns } from './observability_index_patterns';
import { mockCore, mockIndexPattern } from '../rtl_helpers';
import { SavedObjectNotFound } from '../../../../../../../../src/plugins/kibana_utils/public';
const fieldFormats = {
'transaction.duration.us': {
id: 'duration',
params: {
inputFormat: 'microseconds',
outputFormat: 'asSeconds',
outputPrecision: 1,
showSuffix: true,
},
},
'transaction.experience.fid': {
id: 'duration',
params: { inputFormat: 'milliseconds', outputFormat: 'asSeconds', showSuffix: true },
},
'transaction.experience.tbt': {
id: 'duration',
params: { inputFormat: 'milliseconds', outputFormat: 'asSeconds', showSuffix: true },
},
'transaction.marks.agent.firstContentfulPaint': {
id: 'duration',
params: { inputFormat: 'milliseconds', outputFormat: 'asSeconds', showSuffix: true },
},
'transaction.marks.agent.largestContentfulPaint': {
id: 'duration',
params: { inputFormat: 'milliseconds', outputFormat: 'asSeconds', showSuffix: true },
},
};
describe('ObservabilityIndexPatterns', function () {
const { data } = mockCore();
data!.indexPatterns.get = jest.fn().mockReturnValue({ title: 'index-*' });
data!.indexPatterns.createAndSave = jest.fn().mockReturnValue({ id: indexPatternList.rum });
data!.indexPatterns.updateSavedObject = jest.fn();
it('should return index pattern for app', async function () {
const obsv = new ObservabilityIndexPatterns(data!);
const indexP = await obsv.getIndexPattern('rum');
expect(indexP).toEqual({ title: 'index-*' });
expect(data?.indexPatterns.get).toHaveBeenCalledWith(indexPatternList.rum);
expect(data?.indexPatterns.get).toHaveBeenCalledTimes(1);
});
it('should creates missing index pattern', async function () {
data!.indexPatterns.get = jest.fn().mockImplementation(() => {
throw new SavedObjectNotFound('index_pattern');
});
const obsv = new ObservabilityIndexPatterns(data!);
const indexP = await obsv.getIndexPattern('rum');
expect(indexP).toEqual({ id: indexPatternList.rum });
expect(data?.indexPatterns.createAndSave).toHaveBeenCalledWith({
fieldFormats,
id: 'rum_static_index_pattern_id',
timeFieldName: '@timestamp',
title: '(rum-data-view)*,apm-*',
});
expect(data?.indexPatterns.createAndSave).toHaveBeenCalledTimes(1);
});
it('should return getFieldFormats', function () {
const obsv = new ObservabilityIndexPatterns(data!);
expect(obsv.getFieldFormats('rum')).toEqual(fieldFormats);
});
it('should validate field formats', async function () {
mockIndexPattern.getFormatterForField = jest.fn().mockReturnValue({ params: () => {} });
const obsv = new ObservabilityIndexPatterns(data!);
await obsv.validateFieldFormats('rum', mockIndexPattern);
expect(data?.indexPatterns.updateSavedObject).toHaveBeenCalledTimes(1);
expect(data?.indexPatterns.updateSavedObject).toHaveBeenCalledWith(
expect.objectContaining({ fieldFormatMap: fieldFormats })
);
});
});

View file

@ -0,0 +1,124 @@
/*
* 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 { SavedObjectNotFound } from '../../../../../../../../src/plugins/kibana_utils/public';
import {
DataPublicPluginStart,
IndexPattern,
FieldFormat as IFieldFormat,
IndexPatternSpec,
} from '../../../../../../../../src/plugins/data/public';
import { rumFieldFormats } from '../configurations/rum/field_formats';
import { syntheticsFieldFormats } from '../configurations/synthetics/field_formats';
import { FieldFormat, FieldFormatParams } from '../types';
const appFieldFormats: Record<DataType, FieldFormat[] | null> = {
rum: rumFieldFormats,
apm: null,
logs: null,
metrics: null,
synthetics: syntheticsFieldFormats,
};
function getFieldFormatsForApp(app: DataType) {
return appFieldFormats[app];
}
export type DataType = 'synthetics' | 'apm' | 'logs' | 'metrics' | 'rum';
export const indexPatternList: Record<DataType, string> = {
synthetics: 'synthetics_static_index_pattern_id',
apm: 'apm_static_index_pattern_id',
rum: 'rum_static_index_pattern_id',
logs: 'logs_static_index_pattern_id',
metrics: 'metrics_static_index_pattern_id',
};
const appToPatternMap: Record<DataType, string> = {
synthetics: '(synthetics-data-view)*,heartbeat-*,synthetics-*',
apm: 'apm-*',
rum: '(rum-data-view)*,apm-*',
logs: 'logs-*,filebeat-*',
metrics: 'metrics-*,metricbeat-*',
};
export function isParamsSame(param1: IFieldFormat['_params'], param2: FieldFormatParams) {
return (
param1?.inputFormat === param2?.inputFormat &&
param1?.outputFormat === param2?.outputFormat &&
param1?.showSuffix === param2?.showSuffix &&
param2?.outputPrecision === param1?.outputPrecision
);
}
export class ObservabilityIndexPatterns {
data?: DataPublicPluginStart;
constructor(data: DataPublicPluginStart) {
this.data = data;
}
async createIndexPattern(app: DataType) {
if (!this.data) {
throw new Error('data is not defined');
}
const pattern = appToPatternMap[app];
return await this.data.indexPatterns.createAndSave({
title: pattern,
id: indexPatternList[app],
timeFieldName: '@timestamp',
fieldFormats: this.getFieldFormats(app),
});
}
// we want to make sure field formats remain same
async validateFieldFormats(app: DataType, indexPattern: IndexPattern) {
const defaultFieldFormats = getFieldFormatsForApp(app);
if (defaultFieldFormats && defaultFieldFormats.length > 0) {
let isParamsDifferent = false;
defaultFieldFormats.forEach(({ field, format }) => {
const fieldFormat = indexPattern.getFormatterForField(indexPattern.getFieldByName(field)!);
const params = fieldFormat.params();
if (!isParamsSame(params, format.params)) {
indexPattern.setFieldFormat(field, format);
isParamsDifferent = true;
}
});
if (isParamsDifferent) {
await this.data?.indexPatterns.updateSavedObject(indexPattern);
}
}
}
getFieldFormats(app: DataType) {
const fieldFormatMap: IndexPatternSpec['fieldFormats'] = {};
(appFieldFormats?.[app] ?? []).forEach(({ field, format }) => {
fieldFormatMap[field] = format;
});
return fieldFormatMap;
}
async getIndexPattern(app: DataType): Promise<IndexPattern | undefined> {
if (!this.data) {
throw new Error('data is not defined');
}
try {
const indexPattern = await this.data?.indexPatterns.get(indexPatternList[app]);
// this is intentional a non blocking call, so no await clause
this.validateFieldFormats(app, indexPattern);
return indexPattern;
} catch (e: unknown) {
if (e instanceof SavedObjectNotFound) {
return await this.createIndexPattern(app || 'apm');
}
}
}
}

View file

@ -76,6 +76,7 @@ export function FieldValueSelection({
<EuiButton
style={width ? { width } : {}}
size="s"
color="text"
iconType="arrowDown"
iconSide="right"
onClick={onButtonClick}