mirror of
https://github.com/grafana/grafana.git
synced 2024-12-28 18:01:40 -06:00
Elasticsearch: Decouple frontend dependencies from core (#82179)
* Elasticsearch: Decouple frontend dependencies from core * Remove not needed code change
This commit is contained in:
parent
b1dc505a2b
commit
48b4ca8228
@ -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": {
|
||||
|
@ -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
|
||||
|
@ -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<string, number>;
|
||||
@ -678,7 +677,7 @@ const flattenHits = (hits: Doc[]): { docs: Array<Record<string, any>>; 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,
|
||||
|
@ -1,5 +1,3 @@
|
||||
///<amd-dependency path="test/specs/helpers" name="helpers" />
|
||||
|
||||
import { toUtc, getLocale, setLocale, dateTime } from '@grafana/data';
|
||||
|
||||
import { IndexPattern } from './IndexPattern';
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { reducerTester } from 'test/core/redux/reducerTester';
|
||||
|
||||
import { PipelineVariable } from '../../../../../../types';
|
||||
import { reducerTester } from '../../../../../reducerTester';
|
||||
|
||||
import {
|
||||
addPipelineVariable,
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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';
|
||||
|
||||
|
@ -0,0 +1,109 @@
|
||||
import { AnyAction } from '@reduxjs/toolkit';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { Action } from 'redux';
|
||||
|
||||
import { StoreState } from 'app/types';
|
||||
|
||||
type GrafanaReducer<S = StoreState, A extends Action = AnyAction> = (state: S, action: A) => S;
|
||||
|
||||
export interface Given<State> {
|
||||
givenReducer: (
|
||||
reducer: GrafanaReducer<State, AnyAction>,
|
||||
state: State,
|
||||
showDebugOutput?: boolean,
|
||||
disableDeepFreeze?: boolean
|
||||
) => When<State>;
|
||||
}
|
||||
|
||||
export interface When<State> {
|
||||
whenActionIsDispatched: (action: AnyAction) => Then<State>;
|
||||
}
|
||||
|
||||
export interface Then<State> {
|
||||
thenStateShouldEqual: (state: State) => When<State>;
|
||||
thenStatePredicateShouldEqual: (predicate: (resultingState: State) => boolean) => When<State>;
|
||||
whenActionIsDispatched: (action: AnyAction) => Then<State>;
|
||||
}
|
||||
|
||||
const isNotException = (object: unknown, propertyName: string) =>
|
||||
typeof object === 'function'
|
||||
? propertyName !== 'caller' && propertyName !== 'callee' && propertyName !== 'arguments'
|
||||
: true;
|
||||
|
||||
export const deepFreeze = <T>(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<State> extends Given<State>, When<State>, Then<State> {}
|
||||
|
||||
export const reducerTester = <State>(): Given<State> => {
|
||||
let reducerUnderTest: GrafanaReducer<State, AnyAction>;
|
||||
let resultingState: State;
|
||||
let initialState: State;
|
||||
let showDebugOutput = false;
|
||||
|
||||
const givenReducer = (
|
||||
reducer: GrafanaReducer<State, AnyAction>,
|
||||
state: State,
|
||||
debug = false,
|
||||
disableDeepFreeze = false
|
||||
): When<State> => {
|
||||
reducerUnderTest = reducer;
|
||||
initialState = cloneDeep(state);
|
||||
if (!disableDeepFreeze && (typeof state === 'object' || typeof state === 'function')) {
|
||||
deepFreeze(initialState);
|
||||
}
|
||||
showDebugOutput = debug;
|
||||
|
||||
return instance;
|
||||
};
|
||||
|
||||
const whenActionIsDispatched = (action: AnyAction): Then<State> => {
|
||||
resultingState = reducerUnderTest(resultingState || initialState, action);
|
||||
|
||||
return instance;
|
||||
};
|
||||
|
||||
const thenStateShouldEqual = (state: State): When<State> => {
|
||||
if (showDebugOutput) {
|
||||
console.log(JSON.stringify(resultingState, null, 2));
|
||||
}
|
||||
expect(resultingState).toEqual(state);
|
||||
|
||||
return instance;
|
||||
};
|
||||
|
||||
const thenStatePredicateShouldEqual = (predicate: (resultingState: State) => boolean): When<State> => {
|
||||
if (showDebugOutput) {
|
||||
console.log(JSON.stringify(resultingState, null, 2));
|
||||
}
|
||||
expect(predicate(resultingState)).toBe(true);
|
||||
|
||||
return instance;
|
||||
};
|
||||
|
||||
const instance: ReducerTester<State> = {
|
||||
thenStateShouldEqual,
|
||||
thenStatePredicateShouldEqual,
|
||||
givenReducer,
|
||||
whenActionIsDispatched,
|
||||
};
|
||||
|
||||
return instance;
|
||||
};
|
@ -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', () => {
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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', () => {
|
||||
|
@ -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', () => {
|
||||
|
@ -0,0 +1,17 @@
|
||||
import { DataSourceSettings } from '@grafana/data';
|
||||
|
||||
import { ElasticsearchOptions } from '../../types';
|
||||
|
||||
export function createDefaultConfigOptions(): DataSourceSettings<ElasticsearchOptions> {
|
||||
return {
|
||||
jsonData: {
|
||||
timeField: '@time',
|
||||
interval: 'Hourly',
|
||||
timeInterval: '10s',
|
||||
maxConcurrentShardRequests: 300,
|
||||
logMessageField: 'test.message',
|
||||
logLevelField: 'test.level',
|
||||
},
|
||||
secureJsonFields: {},
|
||||
} as DataSourceSettings<ElasticsearchOptions>;
|
||||
}
|
@ -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<ElasticsearchOptions>
|
||||
): DataSourceSettings<ElasticsearchOptions> {
|
||||
return getMockDataSource<ElasticsearchOptions>({
|
||||
jsonData: {
|
||||
timeField: '@time',
|
||||
interval: 'Hourly',
|
||||
timeInterval: '10s',
|
||||
maxConcurrentShardRequests: 300,
|
||||
logMessageField: 'test.message',
|
||||
logLevelField: 'test.level',
|
||||
...options,
|
||||
},
|
||||
});
|
||||
}
|
@ -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<T>(data: T): FetchResponse<T> {
|
||||
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<ElasticsearchQuery>({
|
||||
const options: DataQueryRequest<ElasticsearchQuery> = {
|
||||
...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<ElasticsearchQuery>({
|
||||
const options: DataQueryRequest<ElasticsearchQuery> = {
|
||||
...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<ElasticsearchQuery>({
|
||||
const request: DataQueryRequest<ElasticsearchQuery> = {
|
||||
...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<ElasticsearchQuery>({
|
||||
const request: DataQueryRequest<ElasticsearchQuery> = {
|
||||
...DATAQUERY_BASE,
|
||||
targets: [
|
||||
{
|
||||
refId: 'A',
|
||||
bucketAggs: [{ type: 'date_histogram', id: '1' }],
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
expect(ds.getSupplementaryRequest(SupplementaryQueryType.LogsSample, request)).toBeDefined();
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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<string, unknown>,
|
||||
opts?: { delimiter?: string; maxDepth?: number; safe?: boolean }
|
||||
): Record<string, unknown> {
|
||||
opts = opts || {};
|
||||
|
||||
const delimiter = opts.delimiter || '.';
|
||||
let maxDepth = opts.maxDepth || 3;
|
||||
let currentDepth = 1;
|
||||
const output: Record<string, unknown> = {};
|
||||
|
||||
function step(object: Record<string, unknown>, 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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user