Skip to content

Commit

Permalink
[Unified observability] Extract ObservabilityIndexPatterns from exp…
Browse files Browse the repository at this point in the history
…loratory view (#122132)
  • Loading branch information
Alejandro Fernández Gómez authored Jan 18, 2022
1 parent a8247ff commit 4d771c3
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import React, { useCallback, useEffect, useState } from 'react';
import { EuiLoadingSpinner } from '@elastic/eui';
import { CoreStart } from 'kibana/public';
import type { ExploratoryEmbeddableProps, ExploratoryEmbeddableComponentProps } from './embeddable';
import { ObservabilityIndexPatterns } from '../utils/observability_index_patterns';
import { ObservabilityDataViews } from '../../../../utils/observability_data_views';
import { ObservabilityPublicPluginsStart } from '../../../../plugin';
import type { IndexPatternState } from '../hooks/use_app_index_pattern';
import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common';
Expand Down Expand Up @@ -43,8 +43,8 @@ export function getExploratoryViewEmbeddable(

setLoading(true);
try {
const obsvIndexP = new ObservabilityIndexPatterns(plugins.data);
const indPattern = await obsvIndexP.getIndexPattern(
const obsvIndexP = new ObservabilityDataViews(plugins.data);
const indPattern = await obsvIndexP.getDataView(
dataType,
dataTypesIndexPatterns?.[dataType]
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import React from 'react';
import { screen } from '@testing-library/dom';
import { render, mockAppIndexPattern } from './rtl_helpers';
import { ExploratoryView } from './exploratory_view';
import * as obsvInd from './utils/observability_index_patterns';
import * as obsvDataViews from '../../../utils/observability_data_views/observability_data_views';
import * as pluginHook from '../../../hooks/use_plugin_context';
import { createStubIndexPattern } from '../../../../../../../src/plugins/data/common/stubs';

Expand Down Expand Up @@ -40,8 +40,8 @@ describe('ExploratoryView', () => {
},
});

jest.spyOn(obsvInd, 'ObservabilityIndexPatterns').mockReturnValue({
getIndexPattern: jest.fn().mockReturnValue(indexPattern),
jest.spyOn(obsvDataViews, 'ObservabilityDataViews').mockReturnValue({
getDataView: jest.fn().mockReturnValue(indexPattern),
} as any);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,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 { ObservabilityDataViews } from '../../../../utils/observability_data_views';
import { getDataHandler } from '../../../../data_handler';
import { useExploratoryView } from '../contexts/exploratory_view_config';
import { DataViewInsufficientAccessError } from '../../../../../../../../src/plugins/data_views/common';
Expand Down Expand Up @@ -83,8 +83,8 @@ export function IndexPatternContextProvider({ children }: ProviderProps) {
setHasAppData((prevState) => ({ ...prevState, [dataType]: hasDataT }));

if (hasDataT && indices) {
const obsvIndexP = new ObservabilityIndexPatterns(data);
const indPattern = await obsvIndexP.getIndexPattern(dataType, indices);
const obsvIndexP = new ObservabilityDataViews(data);
const indPattern = await obsvIndexP.getDataView(dataType, indices);

setIndexPatterns((prevState) => ({ ...prevState, [dataType]: indPattern }));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 './observability_data_views';
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
* 2.0.
*/

import { indexPatternList, ObservabilityIndexPatterns } from './observability_index_patterns';
import { mockCore, mockIndexPattern } from '../rtl_helpers';
import { SavedObjectNotFound } from '../../../../../../../../src/plugins/kibana_utils/public';
import { dataViewList, ObservabilityDataViews } from './observability_data_views';
import { mockCore, mockIndexPattern } from '../../components/shared/exploratory_view/rtl_helpers';
import { SavedObjectNotFound } from '../../../../../../src/plugins/kibana_utils/public';

const fieldFormats = {
'transaction.duration.us': {
Expand Down Expand Up @@ -70,13 +70,13 @@ const fieldFormats = {
describe('ObservabilityIndexPatterns', function () {
const { data } = mockCore();
data!.indexPatterns.get = jest.fn().mockReturnValue({ title: 'index-*' });
data!.indexPatterns.createAndSave = jest.fn().mockReturnValue({ id: indexPatternList.ux });
data!.indexPatterns.createAndSave = jest.fn().mockReturnValue({ id: dataViewList.ux });
data!.indexPatterns.updateSavedObject = jest.fn();

it('should return index pattern for app', async function () {
const obsv = new ObservabilityIndexPatterns(data!);
const obsv = new ObservabilityDataViews(data!);

const indexP = await obsv.getIndexPattern('ux', 'heartbeat-8*,synthetics-*');
const indexP = await obsv.getDataView('ux', 'heartbeat-8*,synthetics-*');

expect(indexP).toEqual({ id: 'rum_static_index_pattern_id' });

Expand All @@ -91,13 +91,13 @@ describe('ObservabilityIndexPatterns', function () {
throw new SavedObjectNotFound('index_pattern');
});

data!.indexPatterns.createAndSave = jest.fn().mockReturnValue({ id: indexPatternList.ux });
data!.indexPatterns.createAndSave = jest.fn().mockReturnValue({ id: dataViewList.ux });

const obsv = new ObservabilityIndexPatterns(data!);
const obsv = new ObservabilityDataViews(data!);

const indexP = await obsv.getIndexPattern('ux', 'trace-*,apm-*');
const indexP = await obsv.getDataView('ux', 'trace-*,apm-*');

expect(indexP).toEqual({ id: indexPatternList.ux });
expect(indexP).toEqual({ id: dataViewList.ux });

expect(data?.indexPatterns.createAndSave).toHaveBeenCalledWith({
fieldFormats,
Expand All @@ -110,15 +110,15 @@ describe('ObservabilityIndexPatterns', function () {
});

it('should return getFieldFormats', function () {
const obsv = new ObservabilityIndexPatterns(data!);
const obsv = new ObservabilityDataViews(data!);

expect(obsv.getFieldFormats('ux')).toEqual(fieldFormats);
});

it('should validate field formats', async function () {
mockIndexPattern.getFormatterForField = jest.fn().mockReturnValue({ params: () => {} });

const obsv = new ObservabilityIndexPatterns(data!);
const obsv = new ObservabilityDataViews(data!);

await obsv.validateFieldFormats('ux', mockIndexPattern);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@
*/

import type { FieldFormat as IFieldFormat } from 'src/plugins/field_formats/common';
import { SavedObjectNotFound } from '../../../../../../../../src/plugins/kibana_utils/public';
import { SavedObjectNotFound } from '../../../../../../src/plugins/kibana_utils/public';
import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public';
import type { DataView, DataViewSpec } from '../../../../../../src/plugins/data/common';
import { rumFieldFormats } from '../../components/shared/exploratory_view/configurations/rum/field_formats';
import { syntheticsFieldFormats } from '../../components/shared/exploratory_view/configurations/synthetics/field_formats';
import {
DataPublicPluginStart,
IndexPattern,
IndexPatternSpec,
} from '../../../../../../../../src/plugins/data/public';
import { rumFieldFormats } from '../configurations/rum/field_formats';
import { syntheticsFieldFormats } from '../configurations/synthetics/field_formats';
import { AppDataType, FieldFormat, FieldFormatParams } from '../types';
import { apmFieldFormats } from '../configurations/apm/field_formats';
import { getDataHandler } from '../../../../data_handler';
import { infraMetricsFieldFormats } from '../configurations/infra_metrics/field_formats';
AppDataType,
FieldFormat,
FieldFormatParams,
} from '../../components/shared/exploratory_view/types';
import { apmFieldFormats } from '../../components/shared/exploratory_view/configurations/apm/field_formats';
import { getDataHandler } from '../../data_handler';
import { infraMetricsFieldFormats } from '../../components/shared/exploratory_view/configurations/infra_metrics/field_formats';

const appFieldFormats: Record<AppDataType, FieldFormat[] | null> = {
infra_logs: null,
Expand All @@ -32,7 +33,7 @@ function getFieldFormatsForApp(app: AppDataType) {
return appFieldFormats[app];
}

export const indexPatternList: Record<AppDataType, string> = {
export const dataViewList: Record<AppDataType, string> = {
synthetics: 'synthetics_static_index_pattern_id',
apm: 'apm_static_index_pattern_id',
ux: 'rum_static_index_pattern_id',
Expand All @@ -54,11 +55,11 @@ const getAppIndicesWithPattern = (app: AppDataType, indices: string) => {
return `${appToPatternMap?.[app] ?? app},${indices}`;
};

const getAppIndexPatternId = (app: AppDataType, indices: string) => {
const getAppDataViewId = (app: AppDataType, indices: string) => {
// Replace characters / ? , " < > | * with _
const postfix = indices.replace(/[^A-Z0-9]+/gi, '_').toLowerCase();

return `${indexPatternList?.[app] ?? app}_${postfix}`;
return `${dataViewList?.[app] ?? app}_${postfix}`;
};

export function isParamsSame(param1: IFieldFormat['_params'], param2: FieldFormatParams) {
Expand All @@ -75,50 +76,50 @@ export function isParamsSame(param1: IFieldFormat['_params'], param2: FieldForma
return isSame;
}

export class ObservabilityIndexPatterns {
export class ObservabilityDataViews {
data?: DataPublicPluginStart;

constructor(data: DataPublicPluginStart) {
this.data = data;
}

async createIndexPattern(app: AppDataType, indices: string) {
async createDataView(app: AppDataType, indices: string) {
if (!this.data) {
throw new Error('data is not defined');
}

const appIndicesPattern = getAppIndicesWithPattern(app, indices);
return await this.data.indexPatterns.createAndSave({
return await this.data.dataViews.createAndSave({
title: appIndicesPattern,
id: getAppIndexPatternId(app, indices),
id: getAppDataViewId(app, indices),
timeFieldName: '@timestamp',
fieldFormats: this.getFieldFormats(app),
});
}
// we want to make sure field formats remain same
async validateFieldFormats(app: AppDataType, indexPattern: IndexPattern) {
async validateFieldFormats(app: AppDataType, dataView: DataView) {
const defaultFieldFormats = getFieldFormatsForApp(app);
if (defaultFieldFormats && defaultFieldFormats.length > 0) {
let isParamsDifferent = false;
defaultFieldFormats.forEach(({ field, format }) => {
const fieldByName = indexPattern.getFieldByName(field);
const fieldByName = dataView.getFieldByName(field);
if (fieldByName) {
const fieldFormat = indexPattern.getFormatterForField(fieldByName);
const fieldFormat = dataView.getFormatterForField(fieldByName);
const params = fieldFormat.params();
if (!isParamsSame(params, format.params) || format.id !== fieldFormat.type.id) {
indexPattern.setFieldFormat(field, format);
dataView.setFieldFormat(field, format);
isParamsDifferent = true;
}
}
});
if (isParamsDifferent) {
await this.data?.indexPatterns.updateSavedObject(indexPattern);
await this.data?.dataViews.updateSavedObject(dataView);
}
}
}

getFieldFormats(app: AppDataType) {
const fieldFormatMap: IndexPatternSpec['fieldFormats'] = {};
const fieldFormatMap: DataViewSpec['fieldFormats'] = {};

(appFieldFormats?.[app] ?? []).forEach(({ field, format }) => {
fieldFormatMap[field] = format;
Expand All @@ -140,7 +141,7 @@ export class ObservabilityIndexPatterns {
}
}

async getIndexPattern(app: AppDataType, indices?: string): Promise<IndexPattern | undefined> {
async getDataView(app: AppDataType, indices?: string): Promise<DataView | undefined> {
if (!this.data) {
throw new Error('data is not defined');
}
Expand All @@ -151,22 +152,22 @@ export class ObservabilityIndexPatterns {

if (appIndices) {
try {
const indexPatternId = getAppIndexPatternId(app, appIndices);
const indexPatternTitle = getAppIndicesWithPattern(app, appIndices);
// we will get index pattern by id
const indexPattern = await this.data?.indexPatterns.get(indexPatternId);
const dataViewId = getAppDataViewId(app, appIndices);
const dataViewTitle = getAppIndicesWithPattern(app, appIndices);
// we will get the data view by id
const dataView = await this.data?.indexPatterns.get(dataViewId);

// and make sure title matches, otherwise, we will need to create it
if (indexPattern.title !== indexPatternTitle) {
return await this.createIndexPattern(app, appIndices);
if (dataView.title !== dataViewTitle) {
return await this.createDataView(app, appIndices);
}

// this is intentional a non blocking call, so no await clause
this.validateFieldFormats(app, indexPattern);
return indexPattern;
this.validateFieldFormats(app, dataView);
return dataView;
} catch (e: unknown) {
if (e instanceof SavedObjectNotFound) {
return await this.createIndexPattern(app, appIndices);
return await this.createDataView(app, appIndices);
}
}
}
Expand Down

0 comments on commit 4d771c3

Please sign in to comment.