mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
@@ -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 }]) => ({
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
);
|
||||
};
|
||||
@@ -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 || {}),
|
||||
};
|
||||
|
||||
@@ -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}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user