From 48b4ca82283dce0f159c5a3da26528cb578c73fb Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Fri, 9 Feb 2024 13:11:08 +0100 Subject: [PATCH] Elasticsearch: Decouple frontend dependencies from core (#82179) * Elasticsearch: Decouple frontend dependencies from core * Remove not needed code change --- .eslintrc | 4 +- .../elasticsearch/ElasticResponse.test.ts | 4 +- .../elasticsearch/ElasticResponse.ts | 5 +- .../elasticsearch/IndexPattern.test.ts | 2 - .../DateHistogramSettingsEditor.test.tsx | 9 +- .../DateHistogramSettingsEditor.tsx | 2 +- .../TermsSettingsEditor.test.tsx | 3 +- .../SettingsEditor/useDescription.ts | 3 +- .../state/reducer.test.ts | 9 +- .../state/reducer.test.ts | 3 +- .../SettingsEditor/SettingField.tsx | 2 +- .../state/reducer.test.ts | 7 +- .../components/QueryEditor/state.test.ts | 3 +- .../elasticsearch/components/reducerTester.ts | 109 ++++++++++++++++++ .../configuration/ConfigEditor.test.tsx | 2 +- .../configuration/ConfigEditor.tsx | 2 +- .../elasticsearch/configuration/DataLink.tsx | 2 +- .../configuration/ElasticDetails.test.tsx | 2 +- .../configuration/LogsConfig.test.tsx | 2 +- .../configuration/__mocks__/configOptions.ts | 17 +++ .../elasticsearch/configuration/mocks.ts | 20 ---- .../elasticsearch/datasource.test.ts | 59 ++++++---- .../datasource/elasticsearch/datasource.ts | 5 +- .../datasource/elasticsearch/tracking.ts | 2 +- .../datasource/elasticsearch/utils.test.ts | 38 +++++- .../plugins/datasource/elasticsearch/utils.ts | 50 ++++++++ 26 files changed, 282 insertions(+), 84 deletions(-) create mode 100644 public/app/plugins/datasource/elasticsearch/components/reducerTester.ts create mode 100644 public/app/plugins/datasource/elasticsearch/configuration/__mocks__/configOptions.ts delete mode 100644 public/app/plugins/datasource/elasticsearch/configuration/mocks.ts diff --git a/.eslintrc b/.eslintrc index 0ba27fd484e..e8cfd522609 100644 --- a/.eslintrc +++ b/.eslintrc @@ -113,7 +113,9 @@ "public/app/plugins/datasource/tempo/*.{ts,tsx}", "public/app/plugins/datasource/tempo/**/*.{ts,tsx}", "public/app/plugins/datasource/loki/*.{ts,tsx}", - "public/app/plugins/datasource/loki/**/*.{ts,tsx}" + "public/app/plugins/datasource/loki/**/*.{ts,tsx}", + "public/app/plugins/datasource/elasticsearch/*.{ts,tsx}", + "public/app/plugins/datasource/elasticsearch/**/*.{ts,tsx}" ], "settings": { "import/resolver": { diff --git a/public/app/plugins/datasource/elasticsearch/ElasticResponse.test.ts b/public/app/plugins/datasource/elasticsearch/ElasticResponse.test.ts index e72e6b87a8c..0f8a9aa059d 100644 --- a/public/app/plugins/datasource/elasticsearch/ElasticResponse.test.ts +++ b/public/app/plugins/datasource/elasticsearch/ElasticResponse.test.ts @@ -1,9 +1,9 @@ import { DataFrame, DataFrameView, Field, FieldCache, FieldType, KeyValue, MutableDataFrame } from '@grafana/data'; -import flatten from 'app/core/utils/flatten'; import { ElasticResponse } from './ElasticResponse'; import { highlightTags } from './queryDef'; import { ElasticsearchQuery } from './types'; +import { flattenObject } from './utils'; function getTimeField(frame: DataFrame): Field { const field = frame.fields[0]; @@ -1445,7 +1445,7 @@ describe('ElasticResponse', () => { expect(r._id).toEqual(response.responses[0].hits.hits[i]._id); expect(r._type).toEqual(response.responses[0].hits.hits[i]._type); expect(r._index).toEqual(response.responses[0].hits.hits[i]._index); - expect(r._source).toEqual(flatten(response.responses[0].hits.hits[i]._source)); + expect(r._source).toEqual(flattenObject(response.responses[0].hits.hits[i]._source)); } // Make a map from the histogram results diff --git a/public/app/plugins/datasource/elasticsearch/ElasticResponse.ts b/public/app/plugins/datasource/elasticsearch/ElasticResponse.ts index 46994b11ba6..07ef4a2645a 100644 --- a/public/app/plugins/datasource/elasticsearch/ElasticResponse.ts +++ b/public/app/plugins/datasource/elasticsearch/ElasticResponse.ts @@ -10,13 +10,12 @@ import { } from '@grafana/data'; import { convertFieldType } from '@grafana/data/src/transformations/transformers/convertFieldType'; import TableModel from 'app/core/TableModel'; -import flatten from 'app/core/utils/flatten'; import { isMetricAggregationWithField } from './components/QueryEditor/MetricAggregationsEditor/aggregations'; import { metricAggregationConfig } from './components/QueryEditor/MetricAggregationsEditor/utils'; import * as queryDef from './queryDef'; import { ElasticsearchAggregation, ElasticsearchQuery, TopMetrics, ExtendedStatMetaType } from './types'; -import { describeMetric, getScriptValue } from './utils'; +import { describeMetric, flattenObject, getScriptValue } from './utils'; const HIGHLIGHT_TAGS_EXP = `${queryDef.highlightTags.pre}([^@]+)${queryDef.highlightTags.post}`; type TopMetricMetric = Record; @@ -678,7 +677,7 @@ const flattenHits = (hits: Doc[]): { docs: Array>; propNames let propNames: string[] = []; for (const hit of hits) { - const flattened = hit._source ? flatten(hit._source) : {}; + const flattened = hit._source ? flattenObject(hit._source) : {}; const doc = { _id: hit._id, _type: hit._type, diff --git a/public/app/plugins/datasource/elasticsearch/IndexPattern.test.ts b/public/app/plugins/datasource/elasticsearch/IndexPattern.test.ts index 38ecf5c07fa..30ee68f7be3 100644 --- a/public/app/plugins/datasource/elasticsearch/IndexPattern.test.ts +++ b/public/app/plugins/datasource/elasticsearch/IndexPattern.test.ts @@ -1,5 +1,3 @@ -/// - import { toUtc, getLocale, setLocale, dateTime } from '@grafana/data'; import { IndexPattern } from './IndexPattern'; diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.test.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.test.tsx index 9389f779bf7..52b45832fbf 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.test.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.test.tsx @@ -1,10 +1,9 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; -import { selectOptionInTest } from 'test/helpers/selectOptionInTest'; - -import { DateHistogram } from 'app/plugins/datasource/elasticsearch/types'; +import { select } from 'react-select-event'; import { useDispatch } from '../../../../hooks/useStatelessReducer'; +import { DateHistogram } from '../../../../types'; import { DateHistogramSettingsEditor } from './DateHistogramSettingsEditor'; @@ -63,7 +62,7 @@ describe('DateHistogramSettingsEditor', () => { expect(await screen.findByText('Calendar interval')).toBeInTheDocument(); expect(await screen.findByText('1w')).toBeInTheDocument(); - await selectOptionInTest(screen.getByLabelText('Calendar interval'), '10s'); + await select(screen.getByLabelText('Calendar interval'), '10s', { container: document.body }); expect(dispatch).toHaveBeenCalledTimes(1); }); @@ -79,7 +78,7 @@ describe('DateHistogramSettingsEditor', () => { expect(await screen.findByText('Fixed interval')).toBeInTheDocument(); expect(await screen.findByText('1m')).toBeInTheDocument(); - await selectOptionInTest(screen.getByLabelText('Fixed interval'), '1q'); + await select(screen.getByLabelText('Fixed interval'), '1q', { container: document.body }); expect(dispatch).toHaveBeenCalledTimes(1); }); diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.tsx index 7fd36121783..52a3081ac19 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.tsx @@ -4,8 +4,8 @@ import { GroupBase, OptionsOrGroups } from 'react-select'; import { InternalTimeZones, SelectableValue } from '@grafana/data'; import { InlineField, Input, Select, TimeZonePicker } from '@grafana/ui'; -import { calendarIntervals } from 'app/plugins/datasource/elasticsearch/QueryBuilder'; +import { calendarIntervals } from '../../../../QueryBuilder'; import { useDispatch } from '../../../../hooks/useStatelessReducer'; import { DateHistogram } from '../../../../types'; import { useCreatableSelectPersistedBehaviour } from '../../../hooks/useCreatableSelectPersistedBehaviour'; diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/TermsSettingsEditor.test.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/TermsSettingsEditor.test.tsx index 87dedea1f6d..97442a66f35 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/TermsSettingsEditor.test.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/TermsSettingsEditor.test.tsx @@ -2,10 +2,9 @@ import { screen } from '@testing-library/react'; import React from 'react'; import selectEvent from 'react-select-event'; -import { describeMetric } from 'app/plugins/datasource/elasticsearch/utils'; - import { renderWithESProvider } from '../../../../test-helpers/render'; import { ElasticsearchQuery, Terms, Average, Derivative, TopMetrics } from '../../../../types'; +import { describeMetric } from '../../../../utils'; import { TermsSettingsEditor } from './TermsSettingsEditor'; diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/useDescription.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/useDescription.ts index 5def15faa55..282df98b144 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/useDescription.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/useDescription.ts @@ -1,5 +1,4 @@ -import { defaultGeoHashPrecisionString } from 'app/plugins/datasource/elasticsearch/queryDef'; - +import { defaultGeoHashPrecisionString } from '../../../../queryDef'; import { BucketAggregation } from '../../../../types'; import { describeMetric, convertOrderByToMetricId } from '../../../../utils'; import { useQuery } from '../../ElasticsearchQueryContext'; diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.test.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.test.ts index b39166c1093..bdb9616b91d 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.test.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.test.ts @@ -1,9 +1,6 @@ -import { reducerTester } from 'test/core/redux/reducerTester'; - -import { defaultBucketAgg } from 'app/plugins/datasource/elasticsearch/queryDef'; -import { ElasticsearchQuery } from 'app/plugins/datasource/elasticsearch/types'; - -import { BucketAggregation, DateHistogram } from '../../../../types'; +import { defaultBucketAgg } from '../../../../queryDef'; +import { BucketAggregation, DateHistogram, ElasticsearchQuery } from '../../../../types'; +import { reducerTester } from '../../../reducerTester'; import { changeMetricType } from '../../MetricAggregationsEditor/state/actions'; import { initQuery } from '../../state'; import { bucketAggregationConfig } from '../utils'; diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/BucketScriptSettingsEditor/state/reducer.test.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/BucketScriptSettingsEditor/state/reducer.test.ts index f21388ffc7f..b9ecf0749e7 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/BucketScriptSettingsEditor/state/reducer.test.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/BucketScriptSettingsEditor/state/reducer.test.ts @@ -1,6 +1,5 @@ -import { reducerTester } from 'test/core/redux/reducerTester'; - import { PipelineVariable } from '../../../../../../types'; +import { reducerTester } from '../../../../../reducerTester'; import { addPipelineVariable, diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/SettingField.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/SettingField.tsx index 8d58b2960d1..5a0d48924c1 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/SettingField.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/SettingField.tsx @@ -2,10 +2,10 @@ import { uniqueId } from 'lodash'; import React, { ComponentProps, useState } from 'react'; import { InlineField, Input } from '@grafana/ui'; -import { getScriptValue } from 'app/plugins/datasource/elasticsearch/utils'; import { useDispatch } from '../../../../hooks/useStatelessReducer'; import { MetricAggregationWithInlineScript, MetricAggregationWithSettings } from '../../../../types'; +import { getScriptValue } from '../../../../utils'; import { SettingKeyOf } from '../../../types'; import { changeMetricSetting } from '../state/actions'; diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.test.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.test.ts index 246642b7b5e..0719b2024c8 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.test.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.test.ts @@ -1,9 +1,6 @@ -import { reducerTester } from 'test/core/redux/reducerTester'; - -import { ElasticsearchQuery } from 'app/plugins/datasource/elasticsearch/types'; - import { defaultMetricAgg } from '../../../../queryDef'; -import { Derivative, ExtendedStats, MetricAggregation } from '../../../../types'; +import { Derivative, ElasticsearchQuery, ExtendedStats, MetricAggregation } from '../../../../types'; +import { reducerTester } from '../../../reducerTester'; import { initQuery } from '../../state'; import { metricAggregationConfig } from '../utils'; diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/state.test.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/state.test.ts index bb7ea5a161f..987551baf51 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/state.test.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/state.test.ts @@ -1,6 +1,5 @@ -import { reducerTester } from 'test/core/redux/reducerTester'; - import { ElasticsearchQuery } from '../../types'; +import { reducerTester } from '../reducerTester'; import { aliasPatternReducer, changeAliasPattern, changeQuery, initQuery, queryReducer } from './state'; diff --git a/public/app/plugins/datasource/elasticsearch/components/reducerTester.ts b/public/app/plugins/datasource/elasticsearch/components/reducerTester.ts new file mode 100644 index 00000000000..38c466afa2b --- /dev/null +++ b/public/app/plugins/datasource/elasticsearch/components/reducerTester.ts @@ -0,0 +1,109 @@ +import { AnyAction } from '@reduxjs/toolkit'; +import { cloneDeep } from 'lodash'; +import { Action } from 'redux'; + +import { StoreState } from 'app/types'; + +type GrafanaReducer = (state: S, action: A) => S; + +export interface Given { + givenReducer: ( + reducer: GrafanaReducer, + state: State, + showDebugOutput?: boolean, + disableDeepFreeze?: boolean + ) => When; +} + +export interface When { + whenActionIsDispatched: (action: AnyAction) => Then; +} + +export interface Then { + thenStateShouldEqual: (state: State) => When; + thenStatePredicateShouldEqual: (predicate: (resultingState: State) => boolean) => When; + whenActionIsDispatched: (action: AnyAction) => Then; +} + +const isNotException = (object: unknown, propertyName: string) => + typeof object === 'function' + ? propertyName !== 'caller' && propertyName !== 'callee' && propertyName !== 'arguments' + : true; + +export const deepFreeze = (obj: T): T => { + if (typeof obj === 'object') { + for (const key in obj) { + const prop = obj[key]; + + if ( + prop && + Object.hasOwn(obj, key) && + isNotException(obj, key) && + (typeof prop === 'object' || typeof prop === 'function') && + !Object.isFrozen(prop) + ) { + deepFreeze(prop); + } + } + } + + return Object.freeze(obj); +}; + +interface ReducerTester extends Given, When, Then {} + +export const reducerTester = (): Given => { + let reducerUnderTest: GrafanaReducer; + let resultingState: State; + let initialState: State; + let showDebugOutput = false; + + const givenReducer = ( + reducer: GrafanaReducer, + state: State, + debug = false, + disableDeepFreeze = false + ): When => { + reducerUnderTest = reducer; + initialState = cloneDeep(state); + if (!disableDeepFreeze && (typeof state === 'object' || typeof state === 'function')) { + deepFreeze(initialState); + } + showDebugOutput = debug; + + return instance; + }; + + const whenActionIsDispatched = (action: AnyAction): Then => { + resultingState = reducerUnderTest(resultingState || initialState, action); + + return instance; + }; + + const thenStateShouldEqual = (state: State): When => { + if (showDebugOutput) { + console.log(JSON.stringify(resultingState, null, 2)); + } + expect(resultingState).toEqual(state); + + return instance; + }; + + const thenStatePredicateShouldEqual = (predicate: (resultingState: State) => boolean): When => { + if (showDebugOutput) { + console.log(JSON.stringify(resultingState, null, 2)); + } + expect(predicate(resultingState)).toBe(true); + + return instance; + }; + + const instance: ReducerTester = { + thenStateShouldEqual, + thenStatePredicateShouldEqual, + givenReducer, + whenActionIsDispatched, + }; + + return instance; +}; diff --git a/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.test.tsx b/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.test.tsx index 850cdb470df..8438017aa40 100644 --- a/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.test.tsx +++ b/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.test.tsx @@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; import { ConfigEditor } from './ConfigEditor'; -import { createDefaultConfigOptions } from './mocks'; +import { createDefaultConfigOptions } from './__mocks__/configOptions'; describe('ConfigEditor', () => { it('should render without error', () => { diff --git a/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.tsx b/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.tsx index 296a4e24b28..1dc4aa84780 100644 --- a/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.tsx +++ b/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.tsx @@ -11,8 +11,8 @@ import { convertLegacyAuthProps, DataSourceDescription, } from '@grafana/experimental'; +import { config } from '@grafana/runtime'; import { Alert, SecureSocksProxySettings, Divider, Stack } from '@grafana/ui'; -import { config } from 'app/core/config'; import { ElasticsearchOptions } from '../types'; diff --git a/public/app/plugins/datasource/elasticsearch/configuration/DataLink.tsx b/public/app/plugins/datasource/elasticsearch/configuration/DataLink.tsx index 87835825539..da2c231ad13 100644 --- a/public/app/plugins/datasource/elasticsearch/configuration/DataLink.tsx +++ b/public/app/plugins/datasource/elasticsearch/configuration/DataLink.tsx @@ -3,6 +3,7 @@ import React, { Dispatch, SetStateAction, useEffect, useState } from 'react'; import { usePrevious } from 'react-use'; import { DataSourceInstanceSettings, VariableSuggestion } from '@grafana/data'; +import { DataSourcePicker } from '@grafana/runtime'; import { Button, DataLinkInput, @@ -13,7 +14,6 @@ import { Input, useStyles2, } from '@grafana/ui'; -import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker'; import { DataLinkConfig } from '../types'; diff --git a/public/app/plugins/datasource/elasticsearch/configuration/ElasticDetails.test.tsx b/public/app/plugins/datasource/elasticsearch/configuration/ElasticDetails.test.tsx index 42c69cd9cd9..0b7af77201d 100644 --- a/public/app/plugins/datasource/elasticsearch/configuration/ElasticDetails.test.tsx +++ b/public/app/plugins/datasource/elasticsearch/configuration/ElasticDetails.test.tsx @@ -3,7 +3,7 @@ import React from 'react'; import selectEvent from 'react-select-event'; import { ElasticDetails } from './ElasticDetails'; -import { createDefaultConfigOptions } from './mocks'; +import { createDefaultConfigOptions } from './__mocks__/configOptions'; describe('ElasticDetails', () => { describe('Max concurrent Shard Requests', () => { diff --git a/public/app/plugins/datasource/elasticsearch/configuration/LogsConfig.test.tsx b/public/app/plugins/datasource/elasticsearch/configuration/LogsConfig.test.tsx index 861ed7cdfc1..97d3d130398 100644 --- a/public/app/plugins/datasource/elasticsearch/configuration/LogsConfig.test.tsx +++ b/public/app/plugins/datasource/elasticsearch/configuration/LogsConfig.test.tsx @@ -2,7 +2,7 @@ import { render, screen, fireEvent } from '@testing-library/react'; import React from 'react'; import { LogsConfig } from './LogsConfig'; -import { createDefaultConfigOptions } from './mocks'; +import { createDefaultConfigOptions } from './__mocks__/configOptions'; describe('ElasticDetails', () => { it('should pass correct data to onChange', () => { diff --git a/public/app/plugins/datasource/elasticsearch/configuration/__mocks__/configOptions.ts b/public/app/plugins/datasource/elasticsearch/configuration/__mocks__/configOptions.ts new file mode 100644 index 00000000000..8f7e7963ceb --- /dev/null +++ b/public/app/plugins/datasource/elasticsearch/configuration/__mocks__/configOptions.ts @@ -0,0 +1,17 @@ +import { DataSourceSettings } from '@grafana/data'; + +import { ElasticsearchOptions } from '../../types'; + +export function createDefaultConfigOptions(): DataSourceSettings { + return { + jsonData: { + timeField: '@time', + interval: 'Hourly', + timeInterval: '10s', + maxConcurrentShardRequests: 300, + logMessageField: 'test.message', + logLevelField: 'test.level', + }, + secureJsonFields: {}, + } as DataSourceSettings; +} diff --git a/public/app/plugins/datasource/elasticsearch/configuration/mocks.ts b/public/app/plugins/datasource/elasticsearch/configuration/mocks.ts deleted file mode 100644 index c716a4d03ab..00000000000 --- a/public/app/plugins/datasource/elasticsearch/configuration/mocks.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { DataSourceSettings } from '@grafana/data'; -import { getMockDataSource } from 'app/features/datasources/__mocks__'; - -import { ElasticsearchOptions } from '../types'; - -export function createDefaultConfigOptions( - options?: Partial -): DataSourceSettings { - return getMockDataSource({ - jsonData: { - timeField: '@time', - interval: 'Hourly', - timeInterval: '10s', - maxConcurrentShardRequests: 300, - logMessageField: 'test.message', - logLevelField: 'test.level', - ...options, - }, - }); -} diff --git a/public/app/plugins/datasource/elasticsearch/datasource.test.ts b/public/app/plugins/datasource/elasticsearch/datasource.test.ts index 0a7d8a57469..7fd3446dcfb 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.test.ts +++ b/public/app/plugins/datasource/elasticsearch/datasource.test.ts @@ -1,6 +1,5 @@ import { map } from 'lodash'; import { Observable, of, throwError } from 'rxjs'; -import { getQueryOptions } from 'test/helpers/getQueryOptions'; import { CoreApp, @@ -18,9 +17,6 @@ import { toUtc, } from '@grafana/data'; import { BackendSrvRequest, FetchResponse, reportInteraction, config } from '@grafana/runtime'; -import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ - -import { createFetchResponse } from '../../../../test/helpers/createFetchResponse'; import { enhanceDataFrame } from './LegacyQueryRunner'; import { ElasticDatasource } from './datasource'; @@ -30,7 +26,9 @@ import { Filters, ElasticsearchOptions, ElasticsearchQuery } from './types'; const ELASTICSEARCH_MOCK_URL = 'http://elasticsearch.local'; const originalConsoleError = console.error; - +const backendSrv = { + fetch: jest.fn(), +}; jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), getBackendSrv: () => backendSrv, @@ -44,6 +42,15 @@ jest.mock('@grafana/runtime', () => ({ }, })); +const createTimeRange = (from: DateTime, to: DateTime): TimeRange => ({ + from, + to, + raw: { + from, + to, + }, +}); + const TIME_START = [2022, 8, 21, 6, 10, 10]; const TIME_END = [2022, 8, 24, 6, 10, 21]; const DATAQUERY_BASE = { @@ -56,16 +63,22 @@ const DATAQUERY_BASE = { timezone: '', app: 'test', startTime: 0, + range: createTimeRange(toUtc(TIME_START), toUtc(TIME_END)), }; -const createTimeRange = (from: DateTime, to: DateTime): TimeRange => ({ - from, - to, - raw: { - from, - to, - }, -}); +function createFetchResponse(data: T): FetchResponse { + return { + data, + status: 200, + url: 'http://localhost:3000/api/ds/query', + config: { url: 'http://localhost:3000/api/ds/query' }, + type: 'basic', + statusText: 'Ok', + redirected: false, + headers: {} as unknown as Headers, + ok: true, + }; +} interface TestContext { data?: Data; @@ -977,27 +990,29 @@ describe('ElasticDatasource', () => { }); it('does not create a logs sample provider for non time series query', () => { - const options = getQueryOptions({ + const options: DataQueryRequest = { + ...DATAQUERY_BASE, targets: [ { refId: 'A', metrics: [{ type: 'logs', id: '1', settings: { limit: '100' } }], }, ], - }); + }; expect(ds.getSupplementaryRequest(SupplementaryQueryType.LogsSample, options)).not.toBeDefined(); }); it('does create a logs sample provider for time series query', () => { - const options = getQueryOptions({ + const options: DataQueryRequest = { + ...DATAQUERY_BASE, targets: [ { refId: 'A', bucketAggs: [{ type: 'date_histogram', id: '1' }], }, ], - }); + }; expect(ds.getSupplementaryRequest(SupplementaryQueryType.LogsSample, options)).toBeDefined(); }); @@ -1010,27 +1025,29 @@ describe('ElasticDatasource', () => { }); it("doesn't return a logs sample provider given a non time series query", () => { - const request = getQueryOptions({ + const request: DataQueryRequest = { + ...DATAQUERY_BASE, targets: [ { refId: 'A', metrics: [{ type: 'logs', id: '1', settings: { limit: '100' } }], }, ], - }); + }; expect(ds.getSupplementaryRequest(SupplementaryQueryType.LogsSample, request)).not.toBeDefined(); }); it('returns a logs sample provider given a time series query', () => { - const request = getQueryOptions({ + const request: DataQueryRequest = { + ...DATAQUERY_BASE, targets: [ { refId: 'A', bucketAggs: [{ type: 'date_histogram', id: '1' }], }, ], - }); + }; expect(ds.getSupplementaryRequest(SupplementaryQueryType.LogsSample, request)).toBeDefined(); }); diff --git a/public/app/plugins/datasource/elasticsearch/datasource.ts b/public/app/plugins/datasource/elasticsearch/datasource.ts index 52169626b2a..7fab51613c9 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.ts +++ b/public/app/plugins/datasource/elasticsearch/datasource.ts @@ -38,6 +38,7 @@ import { AdHocVariableFilter, DataSourceWithQueryModificationSupport, AdHocVariableModel, + TypedVariableModel, } from '@grafana/data'; import { DataSourceWithBackend, @@ -47,7 +48,6 @@ import { TemplateSrv, getTemplateSrv, } from '@grafana/runtime'; -import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; import { IndexPattern, intervalMap } from './IndexPattern'; import LanguageProvider from './LanguageProvider'; @@ -264,7 +264,8 @@ export class ElasticDatasource private prepareAnnotationRequest(options: { annotation: ElasticsearchAnnotationQuery; - dashboard: DashboardModel; + // Should be DashboardModel but cannot import that here from the main app. This is a temporary solution as we need to move from deprecated annotations. + dashboard: { getVariables: () => TypedVariableModel[] }; range: TimeRange; }) { const annotation = options.annotation; diff --git a/public/app/plugins/datasource/elasticsearch/tracking.ts b/public/app/plugins/datasource/elasticsearch/tracking.ts index a4d25158ec7..01e3c106dfc 100644 --- a/public/app/plugins/datasource/elasticsearch/tracking.ts +++ b/public/app/plugins/datasource/elasticsearch/tracking.ts @@ -1,10 +1,10 @@ import { CoreApp, DashboardLoadedEvent, DataQueryRequest, DataQueryResponse } from '@grafana/data'; import { config, reportInteraction } from '@grafana/runtime'; -import { variableRegex } from 'app/features/variables/utils'; import { REF_ID_STARTER_LOG_VOLUME } from './datasource'; import pluginJson from './plugin.json'; import { ElasticsearchAnnotationQuery, ElasticsearchQuery } from './types'; +import { variableRegex } from './utils'; type ElasticSearchOnDashboardLoadedTrackingEvent = { grafana_version?: string; diff --git a/public/app/plugins/datasource/elasticsearch/utils.test.ts b/public/app/plugins/datasource/elasticsearch/utils.test.ts index dcf10900c64..731efa9aefe 100644 --- a/public/app/plugins/datasource/elasticsearch/utils.test.ts +++ b/public/app/plugins/datasource/elasticsearch/utils.test.ts @@ -1,5 +1,5 @@ import { ElasticsearchQuery } from './types'; -import { isTimeSeriesQuery, removeEmpty } from './utils'; +import { flattenObject, isTimeSeriesQuery, removeEmpty } from './utils'; describe('removeEmpty', () => { it('Should remove all empty', () => { @@ -79,3 +79,39 @@ describe('isTimeSeriesQuery', () => { expect(isTimeSeriesQuery(query)).toBe(true); }); }); + +describe('flattenObject', () => { + it('flattens objects of arbitrary depth', () => { + const nestedObject = { + a: { + b: { + c: 1, + d: { + e: 2, + f: 3, + }, + }, + g: 4, + }, + h: 5, + }; + + expect(flattenObject(nestedObject)).toEqual({ + 'a.b.c': 1, + 'a.b.d.e': 2, + 'a.b.d.f': 3, + 'a.g': 4, + h: 5, + }); + }); + + it('does not alter other objects', () => { + const nestedObject = { + a: 'uno', + b: 'dos', + c: 3, + }; + + expect(flattenObject(nestedObject)).toEqual(nestedObject); + }); +}); diff --git a/public/app/plugins/datasource/elasticsearch/utils.ts b/public/app/plugins/datasource/elasticsearch/utils.ts index 63e3ac25feb..1ca679734cf 100644 --- a/public/app/plugins/datasource/elasticsearch/utils.ts +++ b/public/app/plugins/datasource/elasticsearch/utils.ts @@ -106,3 +106,53 @@ export const unsupportedVersionMessage = export const isTimeSeriesQuery = (query: ElasticsearchQuery): boolean => { return query?.bucketAggs?.slice(-1)[0]?.type === 'date_histogram'; }; + +/* + * This regex matches 3 types of variable reference with an optional format specifier + * There are 6 capture groups that replace will return + * \$(\w+) $var1 + * \[\[(\w+?)(?::(\w+))?\]\] [[var2]] or [[var2:fmt2]] + * \${(\w+)(?:\.([^:^\}]+))?(?::([^\}]+))?} ${var3} or ${var3.fieldPath} or ${var3:fmt3} (or ${var3.fieldPath:fmt3} but that is not a separate capture group) + */ +export const variableRegex = /\$(\w+)|\[\[(\w+?)(?::(\w+))?\]\]|\${(\w+)(?:\.([^:^\}]+))?(?::([^\}]+))?}/g; + +// Copyright (c) 2014, Hugh Kennedy +// Based on code from https://github.com/hughsk/flat/blob/master/index.js +// +export function flattenObject( + target: Record, + opts?: { delimiter?: string; maxDepth?: number; safe?: boolean } +): Record { + opts = opts || {}; + + const delimiter = opts.delimiter || '.'; + let maxDepth = opts.maxDepth || 3; + let currentDepth = 1; + const output: Record = {}; + + function step(object: Record, prev: string | null) { + Object.keys(object).forEach((key) => { + const value = object[key]; + const isarray = opts?.safe && Array.isArray(value); + const type = Object.prototype.toString.call(value); + const isobject = type === '[object Object]'; + + const newKey = prev ? prev + delimiter + key : key; + + if (!opts?.maxDepth) { + maxDepth = currentDepth + 1; + } + + if (!isarray && isobject && value && Object.keys(value).length && currentDepth < maxDepth) { + ++currentDepth; + return step({ ...value }, newKey); + } + + output[newKey] = value; + }); + } + + step(target, null); + + return output; +}