Elasticsearch: use semver strings to identify ES version (#33646)

* Elasticsearch: use proper semver strings to identify ES version

* Update BE & tests

* refactor BE tests

* refactor isValidOption check

* update test

* Update pkg/tsdb/elasticsearch/client/client.go

Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com>

* Update pkg/tsdb/elasticsearch/client/search_request_test.go

Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com>

* Remove leftover FIXME comment

* Add new test cases for new version format

* Docs: add documentation about version dropdown

* Update docs/sources/datasources/elasticsearch.md

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>

* Update docs/sources/datasources/elasticsearch.md

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>

* Update docs/sources/datasources/elasticsearch.md

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>

* Update provisioning documentation

Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com>
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
This commit is contained in:
Giordano Ricci
2021-05-11 09:44:00 +01:00
committed by GitHub
parent 1a504ce673
commit e98a8bd11b
26 changed files with 363 additions and 242 deletions

View File

@@ -21,6 +21,7 @@ import {
MetricAggregationType,
} from './aggregations';
import { useFields } from '../../../hooks/useFields';
import { satisfies } from 'semver';
const toOption = (metric: MetricAggregation) => ({
label: metricAggregationConfig[metric.type].label,
@@ -39,7 +40,7 @@ const isBasicAggregation = (metric: MetricAggregation) => !metricAggregationConf
const getTypeOptions = (
previousMetrics: MetricAggregation[],
esVersion: number
esVersion: string
): Array<SelectableValue<MetricAggregationType>> => {
// we'll include Pipeline Aggregations only if at least one previous metric is a "Basic" one
const includePipelineAggregations = previousMetrics.some(isBasicAggregation);
@@ -47,10 +48,7 @@ const getTypeOptions = (
return (
Object.entries(metricAggregationConfig)
// Only showing metrics type supported by the configured version of ES
.filter(([_, { minVersion = 0, maxVersion = esVersion }]) => {
// TODO: Double check this
return esVersion >= minVersion && esVersion <= maxVersion;
})
.filter(([_, { versionRange = '*' }]) => satisfies(esVersion, versionRange))
// Filtering out Pipeline Aggregations if there's no basic metric selected before
.filter(([_, config]) => includePipelineAggregations || !config.isPipelineAgg)
.map(([key, { label }]) => ({

View File

@@ -112,7 +112,7 @@ export const metricAggregationConfig: MetricsConfiguration = {
label: 'Moving Average',
requiresField: true,
isPipelineAgg: true,
minVersion: 2,
versionRange: '>=2.0.0',
supportsMissing: false,
supportsMultipleBucketPaths: false,
hasSettings: true,
@@ -135,14 +135,14 @@ export const metricAggregationConfig: MetricsConfiguration = {
supportsMissing: false,
hasMeta: false,
hasSettings: true,
minVersion: 70,
versionRange: '>=7.0.0',
defaults: {},
},
derivative: {
label: 'Derivative',
requiresField: true,
isPipelineAgg: true,
minVersion: 2,
versionRange: '>=2.0.0',
supportsMissing: false,
supportsMultipleBucketPaths: false,
hasSettings: true,
@@ -154,7 +154,7 @@ export const metricAggregationConfig: MetricsConfiguration = {
label: 'Serial Difference',
requiresField: true,
isPipelineAgg: true,
minVersion: 2,
versionRange: '>=2.0.0',
supportsMissing: false,
supportsMultipleBucketPaths: false,
hasSettings: true,
@@ -170,7 +170,7 @@ export const metricAggregationConfig: MetricsConfiguration = {
label: 'Cumulative Sum',
requiresField: true,
isPipelineAgg: true,
minVersion: 2,
versionRange: '>=2.0.0',
supportsMissing: false,
supportsMultipleBucketPaths: false,
hasSettings: true,
@@ -184,7 +184,7 @@ export const metricAggregationConfig: MetricsConfiguration = {
isPipelineAgg: true,
supportsMissing: false,
supportsMultipleBucketPaths: true,
minVersion: 2,
versionRange: '>=2.0.0',
hasSettings: true,
supportsInlineScript: false,
hasMeta: false,

View File

@@ -31,7 +31,7 @@ describe('ConfigEditor', () => {
mount(
<ConfigEditor
onOptionsChange={(options) => {
expect(options.jsonData.esVersion).toBe(5);
expect(options.jsonData.esVersion).toBe('5.0.0');
expect(options.jsonData.timeField).toBe('@timestamp');
expect(options.jsonData.maxConcurrentShardRequests).toBe(256);
}}
@@ -41,17 +41,10 @@ describe('ConfigEditor', () => {
});
it('should not apply default if values are set', () => {
expect.assertions(3);
const onChange = jest.fn();
mount(
<ConfigEditor
onOptionsChange={(options) => {
expect(options.jsonData.esVersion).toBe(70);
expect(options.jsonData.timeField).toBe('@time');
expect(options.jsonData.maxConcurrentShardRequests).toBe(300);
}}
options={createDefaultConfigOptions()}
/>
);
mount(<ConfigEditor onOptionsChange={onChange} options={createDefaultConfigOptions()} />);
expect(onChange).toHaveBeenCalledTimes(0);
});
});

View File

@@ -2,30 +2,23 @@ import React, { useEffect } from 'react';
import { Alert, DataSourceHttpSettings } from '@grafana/ui';
import { DataSourcePluginOptionsEditorProps } from '@grafana/data';
import { ElasticsearchOptions } from '../types';
import { defaultMaxConcurrentShardRequests, ElasticDetails } from './ElasticDetails';
import { ElasticDetails } from './ElasticDetails';
import { LogsConfig } from './LogsConfig';
import { DataLinks } from './DataLinks';
import { config } from 'app/core/config';
import { coerceOptions, isValidOptions } from './utils';
export type Props = DataSourcePluginOptionsEditorProps<ElasticsearchOptions>;
export const ConfigEditor = (props: Props) => {
const { options, onOptionsChange } = props;
// Apply some defaults on initial render
export const ConfigEditor = (props: Props) => {
const { options: originalOptions, onOptionsChange } = props;
const options = coerceOptions(originalOptions);
useEffect(() => {
const esVersion = options.jsonData.esVersion || 5;
onOptionsChange({
...options,
jsonData: {
...options.jsonData,
timeField: options.jsonData.timeField || '@timestamp',
esVersion,
maxConcurrentShardRequests:
options.jsonData.maxConcurrentShardRequests || defaultMaxConcurrentShardRequests(esVersion),
logMessageField: options.jsonData.logMessageField || '',
logLevelField: options.jsonData.logLevelField || '',
},
});
if (!isValidOptions(originalOptions)) {
onOptionsChange(coerceOptions(originalOptions));
}
// We can't enforce the eslint rule here because we only want to run this once.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -39,9 +32,9 @@ export const ConfigEditor = (props: Props) => {
)}
<DataSourceHttpSettings
defaultUrl={'http://localhost:9200'}
defaultUrl="http://localhost:9200"
dataSourceConfig={options}
showAccessOptions={true}
showAccessOptions
onChange={onOptionsChange}
sigV4AuthToggleEnabled={config.sigV4AuthEnabled}
/>

View File

@@ -18,7 +18,7 @@ describe('ElasticDetails', () => {
it('should not render "Max concurrent Shard Requests" if version is low', () => {
const options = createDefaultConfigOptions();
options.jsonData.esVersion = 5;
options.jsonData.esVersion = '5.0.0';
const wrapper = mount(<ElasticDetails onChange={() => {}} value={options} />);
expect(wrapper.find('input[aria-label="Max concurrent Shard Requests input"]').length).toBe(0);
});
@@ -48,16 +48,16 @@ describe('ElasticDetails', () => {
describe('version change', () => {
const testCases = [
{ version: 50, expectedMaxConcurrentShardRequests: 256 },
{ version: 50, maxConcurrentShardRequests: 50, expectedMaxConcurrentShardRequests: 50 },
{ version: 56, expectedMaxConcurrentShardRequests: 256 },
{ version: 56, maxConcurrentShardRequests: 256, expectedMaxConcurrentShardRequests: 256 },
{ version: 56, maxConcurrentShardRequests: 5, expectedMaxConcurrentShardRequests: 256 },
{ version: 56, maxConcurrentShardRequests: 200, expectedMaxConcurrentShardRequests: 200 },
{ version: 70, expectedMaxConcurrentShardRequests: 5 },
{ version: 70, maxConcurrentShardRequests: 256, expectedMaxConcurrentShardRequests: 5 },
{ version: 70, maxConcurrentShardRequests: 5, expectedMaxConcurrentShardRequests: 5 },
{ version: 70, maxConcurrentShardRequests: 6, expectedMaxConcurrentShardRequests: 6 },
{ version: '5.0.0', expectedMaxConcurrentShardRequests: 256 },
{ version: '5.0.0', maxConcurrentShardRequests: 50, expectedMaxConcurrentShardRequests: 50 },
{ version: '5.6.0', expectedMaxConcurrentShardRequests: 256 },
{ version: '5.6.0', maxConcurrentShardRequests: 256, expectedMaxConcurrentShardRequests: 256 },
{ version: '5.6.0', maxConcurrentShardRequests: 5, expectedMaxConcurrentShardRequests: 256 },
{ version: '5.6.0', maxConcurrentShardRequests: 200, expectedMaxConcurrentShardRequests: 200 },
{ version: '7.0.0', expectedMaxConcurrentShardRequests: 5 },
{ version: '7.0.0', maxConcurrentShardRequests: 256, expectedMaxConcurrentShardRequests: 5 },
{ version: '7.0.0', maxConcurrentShardRequests: 5, expectedMaxConcurrentShardRequests: 5 },
{ version: '7.0.0', maxConcurrentShardRequests: 6, expectedMaxConcurrentShardRequests: 6 },
];
const onChangeMock = jest.fn();

View File

@@ -3,6 +3,7 @@ import { EventsWithValidation, regexValidation, LegacyForms } from '@grafana/ui'
const { Select, Input, FormField } = LegacyForms;
import { ElasticsearchOptions, Interval } from '../types';
import { DataSourceSettings, SelectableValue } from '@grafana/data';
import { gte, lt } from 'semver';
const indexPatternTypes = [
{ label: 'No pattern', value: 'none' },
@@ -14,20 +15,18 @@ const indexPatternTypes = [
];
const esVersions = [
{ label: '2.x', value: 2 },
{ label: '5.x', value: 5 },
{ label: '5.6+', value: 56 },
{ label: '6.0+', value: 60 },
{ label: '7.0+', value: 70 },
{ label: '2.x', value: '2.0.0' },
{ label: '5.x', value: '5.0.0' },
{ label: '5.6+', value: '5.6.0' },
{ label: '6.0+', value: '6.0.0' },
{ label: '7.0+', value: '7.0.0' },
];
type Props = {
value: DataSourceSettings<ElasticsearchOptions>;
onChange: (value: DataSourceSettings<ElasticsearchOptions>) => void;
};
export const ElasticDetails = (props: Props) => {
const { value, onChange } = props;
export const ElasticDetails = ({ value, onChange }: Props) => {
return (
<>
<h3 className="page-heading">Elasticsearch details</h3>
@@ -101,7 +100,7 @@ export const ElasticDetails = (props: Props) => {
}
/>
</div>
{value.jsonData.esVersion >= 56 && (
{gte(value.jsonData.esVersion, '5.6.0') && (
<div className="gf-form max-width-30">
<FormField
aria-label={'Max concurrent Shard Requests input'}
@@ -207,18 +206,18 @@ const intervalHandler = (value: Props['value'], onChange: Props['onChange']) =>
}
};
function getMaxConcurrenShardRequestOrDefault(maxConcurrentShardRequests: number | undefined, version: number): number {
if (maxConcurrentShardRequests === 5 && version < 70) {
function getMaxConcurrenShardRequestOrDefault(maxConcurrentShardRequests: number | undefined, version: string): number {
if (maxConcurrentShardRequests === 5 && lt(version, '7.0.0')) {
return 256;
}
if (maxConcurrentShardRequests === 256 && version >= 70) {
if (maxConcurrentShardRequests === 256 && gte(version, '7.0.0')) {
return 5;
}
return maxConcurrentShardRequests || defaultMaxConcurrentShardRequests(version);
}
export function defaultMaxConcurrentShardRequests(version: number) {
return version >= 70 ? 5 : 256;
export function defaultMaxConcurrentShardRequests(version: string) {
return gte(version, '7.0.0') ? 5 : 256;
}

View File

@@ -5,7 +5,7 @@ import { createDatasourceSettings } from '../../../../features/datasources/mocks
export function createDefaultConfigOptions(): DataSourceSettings<ElasticsearchOptions> {
return createDatasourceSettings<ElasticsearchOptions>({
timeField: '@time',
esVersion: 70,
esVersion: '7.0.0',
interval: 'Hourly',
timeInterval: '10s',
maxConcurrentShardRequests: 300,

View File

@@ -0,0 +1,38 @@
import { DataSourceSettings } from '@grafana/data';
import { valid } from 'semver';
import { ElasticsearchOptions } from '../types';
import { coerceESVersion } from '../utils';
import { defaultMaxConcurrentShardRequests } from './ElasticDetails';
export const coerceOptions = (
options: DataSourceSettings<ElasticsearchOptions, {}>
): DataSourceSettings<ElasticsearchOptions, {}> => {
const esVersion = coerceESVersion(options.jsonData.esVersion);
return {
...options,
jsonData: {
...options.jsonData,
timeField: options.jsonData.timeField || '@timestamp',
esVersion,
maxConcurrentShardRequests:
options.jsonData.maxConcurrentShardRequests || defaultMaxConcurrentShardRequests(esVersion),
logMessageField: options.jsonData.logMessageField || '',
logLevelField: options.jsonData.logLevelField || '',
},
};
};
export const isValidOptions = (options: DataSourceSettings<ElasticsearchOptions, {}>): boolean => {
return (
// esVersion should be a valid semver string
!!valid(options.jsonData.esVersion) &&
// timeField should not be empty or nullish
!!options.jsonData.timeField &&
// maxConcurrentShardRequests should be a number AND greater than 0
!!options.jsonData.maxConcurrentShardRequests &&
// message & level fields should be defined
options.jsonData.logMessageField !== undefined &&
options.jsonData.logLevelField !== undefined
);
};

View File

@@ -203,7 +203,7 @@ describe('ElasticDatasource', function (this: any) {
async function setupDataSource(jsonData?: Partial<ElasticsearchOptions>) {
jsonData = {
interval: 'Daily',
esVersion: 2,
esVersion: '2.0.0',
timeField: '@timestamp',
...(jsonData || {}),
};

View File

@@ -39,7 +39,8 @@ import {
} from './components/QueryEditor/BucketAggregationsEditor/aggregations';
import { generate, Observable, of, throwError } from 'rxjs';
import { catchError, first, map, mergeMap, skipWhile, throwIfEmpty } from 'rxjs/operators';
import { getScriptValue } from './utils';
import { coerceESVersion, getScriptValue } from './utils';
import { gte, lt, satisfies } from 'semver';
// Those are metadata fields as defined in https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-fields.html#_identity_metadata_fields.
// custom fields can start with underscores, therefore is not safe to exclude anything that starts with one.
@@ -62,7 +63,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
name: string;
index: string;
timeField: string;
esVersion: number;
esVersion: string;
interval: string;
maxConcurrentShardRequests?: number;
queryBuilder: ElasticQueryBuilder;
@@ -85,7 +86,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
const settingsData = instanceSettings.jsonData || ({} as ElasticsearchOptions);
this.timeField = settingsData.timeField;
this.esVersion = settingsData.esVersion;
this.esVersion = coerceESVersion(settingsData.esVersion);
this.indexPattern = new IndexPattern(this.index, settingsData.interval);
this.interval = settingsData.timeInterval;
this.maxConcurrentShardRequests = settingsData.maxConcurrentShardRequests;
@@ -256,7 +257,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
};
// fields field not supported on ES 5.x
if (this.esVersion < 5) {
if (lt(this.esVersion, '5.0.0')) {
data['fields'] = [timeField, '_source'];
}
@@ -420,7 +421,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
index: this.indexPattern.getIndexList(timeFrom, timeTo),
};
if (this.esVersion >= 56 && this.esVersion < 70) {
if (satisfies(this.esVersion, '>=5.6.0 <7.0.0')) {
queryHeader['max_concurrent_shard_requests'] = this.maxConcurrentShardRequests;
}
@@ -484,7 +485,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
* search_after feature.
*/
showContextToggle(): boolean {
return this.esVersion > 5;
return gte(this.esVersion, '5.0.0');
}
getLogRowContext = async (row: LogRowModel, options?: RowContextOptions): Promise<{ data: DataFrame[] }> => {
@@ -588,7 +589,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
const esQuery = JSON.stringify(queryObj);
const searchType = queryObj.size === 0 && this.esVersion < 5 ? 'count' : 'query_then_fetch';
const searchType = queryObj.size === 0 && lt(this.esVersion, '5.0.0') ? 'count' : 'query_then_fetch';
const header = this.getQueryHeader(searchType, options.range.from, options.range.to);
payload += header + '\n';
@@ -637,7 +638,6 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
// FIXME: This doesn't seem to return actual MetricFindValues, we should either change the return type
// or fix the implementation.
getFields(type?: string, range?: TimeRange): Observable<MetricFindValue[]> {
const configuredEsVersion = this.esVersion;
return this.get('/_mapping', range).pipe(
map((result) => {
const typeMap: any = {
@@ -706,7 +706,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
if (index && index.mappings) {
const mappings = index.mappings;
if (configuredEsVersion < 70) {
if (lt(this.esVersion, '7.0.0')) {
for (const typeName in mappings) {
const properties = mappings[typeName].properties;
getFieldsRecursively(properties);
@@ -727,7 +727,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
}
getTerms(queryDef: any, range = getDefaultTimeRange()): Observable<MetricFindValue[]> {
const searchType = this.esVersion >= 5 ? 'query_then_fetch' : 'count';
const searchType = gte(this.esVersion, '5.0.0') ? 'query_then_fetch' : 'count';
const header = this.getQueryHeader(searchType, range.from, range.to);
let esQuery = JSON.stringify(this.queryBuilder.getTermsQuery(queryDef));
@@ -755,7 +755,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
}
getMultiSearchUrl() {
if (this.esVersion >= 70 && this.maxConcurrentShardRequests) {
if (gte(this.esVersion, '7.0.0') && this.maxConcurrentShardRequests) {
return `_msearch?max_concurrent_shard_requests=${this.maxConcurrentShardRequests}`;
}

View File

@@ -18,7 +18,7 @@ const dataSource = new ElasticDatasource(
database: '[asd-]YYYY.MM.DD',
jsonData: {
interval: 'Daily',
esVersion: 2,
esVersion: '2.0.0',
timeField: '@time',
},
} as DataSourceInstanceSettings<ElasticsearchOptions>,

View File

@@ -1,3 +1,4 @@
import { gte, lt } from 'semver';
import {
Filters,
Histogram,
@@ -19,9 +20,9 @@ import { convertOrderByToMetricId, getScriptValue } from './utils';
export class ElasticQueryBuilder {
timeField: string;
esVersion: number;
esVersion: string;
constructor(options: { timeField: string; esVersion: number }) {
constructor(options: { timeField: string; esVersion: string }) {
this.timeField = options.timeField;
this.esVersion = options.esVersion;
}
@@ -50,7 +51,7 @@ export class ElasticQueryBuilder {
if (aggDef.settings.orderBy !== void 0) {
queryNode.terms.order = {};
if (aggDef.settings.orderBy === '_term' && this.esVersion >= 60) {
if (aggDef.settings.orderBy === '_term' && gte(this.esVersion, '6.0.0')) {
queryNode.terms.order['_key'] = aggDef.settings.order;
} else {
queryNode.terms.order[aggDef.settings.orderBy] = aggDef.settings.order;
@@ -147,7 +148,7 @@ export class ElasticQueryBuilder {
];
// fields field not supported on ES 5.x
if (this.esVersion < 5) {
if (lt(this.esVersion, '5.0.0')) {
query.fields = ['*', '_source'];
}
@@ -443,7 +444,7 @@ export class ElasticQueryBuilder {
switch (orderBy) {
case 'key':
case 'term':
const keyname = this.esVersion >= 60 ? '_key' : '_term';
const keyname = gte(this.esVersion, '6.0.0') ? '_key' : '_term';
query.aggs['1'].terms.order[keyname] = order;
break;
case 'doc_count':

View File

@@ -1,12 +1,13 @@
import { gte, lt } from 'semver';
import { ElasticQueryBuilder } from '../query_builder';
import { ElasticsearchQuery } from '../types';
describe('ElasticQueryBuilder', () => {
const builder = new ElasticQueryBuilder({ timeField: '@timestamp', esVersion: 2 });
const builder5x = new ElasticQueryBuilder({ timeField: '@timestamp', esVersion: 5 });
const builder56 = new ElasticQueryBuilder({ timeField: '@timestamp', esVersion: 56 });
const builder6x = new ElasticQueryBuilder({ timeField: '@timestamp', esVersion: 60 });
const builder7x = new ElasticQueryBuilder({ timeField: '@timestamp', esVersion: 70 });
const builder = new ElasticQueryBuilder({ timeField: '@timestamp', esVersion: '2.0.0' });
const builder5x = new ElasticQueryBuilder({ timeField: '@timestamp', esVersion: '5.0.0' });
const builder56 = new ElasticQueryBuilder({ timeField: '@timestamp', esVersion: '5.6.0' });
const builder6x = new ElasticQueryBuilder({ timeField: '@timestamp', esVersion: '6.0.0' });
const builder7x = new ElasticQueryBuilder({ timeField: '@timestamp', esVersion: '7.0.0' });
const allBuilders = [builder, builder5x, builder56, builder6x, builder7x];
@@ -91,7 +92,7 @@ describe('ElasticQueryBuilder', () => {
const query = builder.build(target, 100, '1000');
const firstLevel = query.aggs['2'];
if (builder.esVersion >= 60) {
if (gte(builder.esVersion, '6.0.0')) {
expect(firstLevel.terms.order._key).toBe('asc');
} else {
expect(firstLevel.terms.order._term).toBe('asc');
@@ -642,7 +643,7 @@ describe('ElasticQueryBuilder', () => {
}
function checkSort(order: any, expected: string) {
if (builder.esVersion < 60) {
if (lt(builder.esVersion, '6.0.0')) {
expect(order._term).toBe(expected);
expect(order._key).toBeUndefined();
} else {

View File

@@ -12,7 +12,7 @@ export type Interval = 'Hourly' | 'Daily' | 'Weekly' | 'Monthly' | 'Yearly';
export interface ElasticsearchOptions extends DataSourceJsonData {
timeField: string;
esVersion: number;
esVersion: string;
interval?: Interval;
timeInterval: string;
maxConcurrentShardRequests?: number;
@@ -27,8 +27,11 @@ interface MetricConfiguration<T extends MetricAggregationType> {
supportsInlineScript: boolean;
supportsMissing: boolean;
isPipelineAgg: boolean;
minVersion?: number;
maxVersion?: number;
/**
* A valid semver range for which the metric is known to be available.
* If omitted defaults to '*'.
*/
versionRange?: string;
supportsMultipleBucketPaths: boolean;
isSingleMetric?: boolean;
hasSettings: boolean;

View File

@@ -4,6 +4,7 @@ import {
MetricAggregationWithInlineScript,
} from './components/QueryEditor/MetricAggregationsEditor/aggregations';
import { metricAggregationConfig } from './components/QueryEditor/MetricAggregationsEditor/utils';
import { valid } from 'semver';
export const describeMetric = (metric: MetricAggregation) => {
if (!isMetricAggregationWithField(metric)) {
@@ -91,3 +92,28 @@ export const convertOrderByToMetricId = (orderBy: string): string | undefined =>
*/
export const getScriptValue = (metric: MetricAggregationWithInlineScript) =>
(typeof metric.settings?.script === 'object' ? metric.settings?.script?.inline : metric.settings?.script) || '';
/**
* Coerces the a version string/number to a valid semver string.
* It takes care of also converting from the legacy format (numeric) to the new one.
* @param version
*/
export const coerceESVersion = (version: string | number): string => {
if (typeof version === 'string') {
return valid(version) || '5.0.0';
}
switch (version) {
case 2:
return '2.0.0';
case 56:
return '5.6.0';
case 60:
return '6.0.0';
case 70:
return '7.0.0';
case 5:
default:
return '5.0.0';
}
};