Elasticsearch: Removed reference to obsolete esVersion value (#65415)

* elastic: removed reference to obsolete esVersion value

* removed unused code

* cleaned up tests
pull/65127/merge
Gábor Farkas 2 years ago committed by GitHub
parent de1637afe5
commit 0cff917f2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      devenv/datasources.yaml
  2. 3
      devenv/datasources_docker.yaml
  3. 5
      docs/sources/administration/provisioning/index.md
  4. 13
      docs/sources/datasources/elasticsearch/_index.md
  5. 1
      docs/sources/developers/http_api/data_source.md
  6. 4
      pkg/tsdb/elasticsearch/client/client.go
  7. 5
      pkg/tsdb/elasticsearch/client/client_test.go
  8. 42
      pkg/tsdb/elasticsearch/elasticsearch.go
  9. 118
      pkg/tsdb/elasticsearch/elasticsearch_test.go
  10. 2
      pkg/tsdb/elasticsearch/querydata_test.go
  11. 1
      public/app/plugins/datasource/elasticsearch/components/QueryEditor/index.test.tsx
  12. 3
      public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.test.tsx
  13. 29
      public/app/plugins/datasource/elasticsearch/configuration/ElasticDetails.test.tsx
  14. 46
      public/app/plugins/datasource/elasticsearch/configuration/ElasticDetails.tsx
  15. 1
      public/app/plugins/datasource/elasticsearch/configuration/mocks.ts
  16. 8
      public/app/plugins/datasource/elasticsearch/configuration/utils.ts
  17. 35
      public/app/plugins/datasource/elasticsearch/datasource.test.ts
  18. 4
      public/app/plugins/datasource/elasticsearch/datasource.ts
  19. 1
      public/app/plugins/datasource/elasticsearch/mocks.ts
  20. 3
      public/app/plugins/datasource/elasticsearch/types.ts
  21. 24
      public/app/plugins/datasource/elasticsearch/utils.test.ts
  22. 28
      public/app/plugins/datasource/elasticsearch/utils.ts

@ -132,7 +132,6 @@ datasources:
timeField: "@timestamp" timeField: "@timestamp"
logLevelField: level logLevelField: level
logMessageField: line logMessageField: line
esVersion: 8.1.4
- name: gdev-elasticsearch-filebeat - name: gdev-elasticsearch-filebeat
type: elasticsearch type: elasticsearch
@ -142,7 +141,6 @@ datasources:
jsonData: jsonData:
interval: Daily interval: Daily
timeField: "@timestamp" timeField: "@timestamp"
esVersion: 8.1.4
timeInterval: "10s" timeInterval: "10s"
logMessageField: message logMessageField: message
logLevelField: fields.level logLevelField: fields.level
@ -155,7 +153,6 @@ datasources:
jsonData: jsonData:
interval: Daily interval: Daily
timeField: "@timestamp" timeField: "@timestamp"
esVersion: 8.1.4
timeInterval: "10s" timeInterval: "10s"
- name: gdev-mysql - name: gdev-mysql

@ -76,7 +76,6 @@ datasources:
jsonData: jsonData:
interval: Daily interval: Daily
timeField: "@timestamp" timeField: "@timestamp"
esVersion: 8.1.4
- name: gdev-elasticsearch-filebeat - name: gdev-elasticsearch-filebeat
type: elasticsearch type: elasticsearch
@ -86,7 +85,6 @@ datasources:
jsonData: jsonData:
interval: Daily interval: Daily
timeField: "@timestamp" timeField: "@timestamp"
esVersion: 8.1.4
timeInterval: "10s" timeInterval: "10s"
logMessageField: message logMessageField: message
logLevelField: fields.level logLevelField: fields.level
@ -99,7 +97,6 @@ datasources:
jsonData: jsonData:
interval: Daily interval: Daily
timeField: "@timestamp" timeField: "@timestamp"
esVersion: 8.1.4
timeInterval: "10s" timeInterval: "10s"
- name: gdev-mysql - name: gdev-mysql

@ -170,7 +170,7 @@ Common settings in the [built-in core data sources]({{< relref "../../datasource
> **Note:** Data sources tagged with _HTTP\*_ communicate using the HTTP protocol, which includes all core data source plugins except MySQL, PostgreSQL, and MSSQL. > **Note:** Data sources tagged with _HTTP\*_ communicate using the HTTP protocol, which includes all core data source plugins except MySQL, PostgreSQL, and MSSQL.
| Name | Type | Data source | Description | | Name | Type | Data source | Description |
| -------------------------- | ------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | -------------------------- | ------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| tlsAuth | boolean | _HTTP\*_, MySQL | Enable TLS authentication using client cert configured in secure json data | | tlsAuth | boolean | _HTTP\*_, MySQL | Enable TLS authentication using client cert configured in secure json data |
| tlsAuthWithCACert | boolean | _HTTP\*_, MySQL, PostgreSQL | Enable TLS authentication using CA cert | | tlsAuthWithCACert | boolean | _HTTP\*_, MySQL, PostgreSQL | Enable TLS authentication using CA cert |
| tlsSkipVerify | boolean | _HTTP\*_, MySQL, PostgreSQL, MSSQL | Controls whether a client verifies the server's certificate chain and host name. | | tlsSkipVerify | boolean | _HTTP\*_, MySQL, PostgreSQL, MSSQL | Controls whether a client verifies the server's certificate chain and host name. |
@ -184,12 +184,11 @@ Common settings in the [built-in core data sources]({{< relref "../../datasource
| customQueryParameters | string | Prometheus | Query parameters to add, as a URL-encoded string. | | customQueryParameters | string | Prometheus | Query parameters to add, as a URL-encoded string. |
| manageAlerts | boolean | Prometheus and Loki | Manage alerts via Alerting UI | | manageAlerts | boolean | Prometheus and Loki | Manage alerts via Alerting UI |
| alertmanagerUid | string | Prometheus and Loki | UID of Alert Manager that manages Alert for this data source. | | alertmanagerUid | string | Prometheus and Loki | UID of Alert Manager that manages Alert for this data source. |
| esVersion | string | Elasticsearch | Elasticsearch version (e.g. `7.0.0`, `7.6.1`) |
| timeField | string | Elasticsearch | Which field that should be used as timestamp | | timeField | string | Elasticsearch | Which field that should be used as timestamp |
| interval | string | Elasticsearch | Index date time format. nil(No Pattern), 'Hourly', 'Daily', 'Weekly', 'Monthly' or 'Yearly' | | interval | string | Elasticsearch | Index date time format. nil(No Pattern), 'Hourly', 'Daily', 'Weekly', 'Monthly' or 'Yearly' |
| logMessageField | string | Elasticsearch | Which field should be used as the log message | | logMessageField | string | Elasticsearch | Which field should be used as the log message |
| logLevelField | string | Elasticsearch | Which field should be used to indicate the priority of the log message | | logLevelField | string | Elasticsearch | Which field should be used to indicate the priority of the log message |
| maxConcurrentShardRequests | number | Elasticsearch | Maximum number of concurrent shard requests that each sub-search request executes per node. Defaults to 5 if esVersion is greater than or equals 7.0.0. When the esVersion is less than 7.0.0 and greater than or equals 5.6.0, then the default value is 256. Option is ignored when esVersion is less than 5.6.0. | | maxConcurrentShardRequests | number | Elasticsearch | Maximum number of concurrent shard requests that each sub-search request executes per node |
| sigV4Auth | boolean | Elasticsearch and Prometheus | Enable usage of SigV4 | | sigV4Auth | boolean | Elasticsearch and Prometheus | Enable usage of SigV4 |
| sigV4AuthType | string | Elasticsearch and Prometheus | SigV4 auth provider. default/credentials/keys | | sigV4AuthType | string | Elasticsearch and Prometheus | SigV4 auth provider. default/credentials/keys |
| sigV4ExternalId | string | Elasticsearch and Prometheus | Optional SigV4 External ID | | sigV4ExternalId | string | Elasticsearch and Prometheus | Optional SigV4 External ID |

@ -59,18 +59,6 @@ You must also configure settings specific to the Elasticsearch data source.
Use the index settings to specify a default for the `time field` and your Elasticsearch index's name. Use the index settings to specify a default for the `time field` and your Elasticsearch index's name.
You can use a time pattern, such as `YYYY.MM.DD`, or a wildcard for the index name. You can use a time pattern, such as `YYYY.MM.DD`, or a wildcard for the index name.
### Elasticsearch version
Select the version of your Elasticsearch data source from the version selection dropdown.
Different versions provide different query compositions and functionalities in the [query editor]({{< relref "./query-editor/" >}}).
Available Elasticsearch versions are `2.x`, `5.x`, `5.6+`, `6.0+`, `7.0+`, `7.7+`, and `7.10+`.
Grafana assumes you're running the lowest possible version for a specified range.
This ensures that new features or breaking changes in a future Elasticsearch release don't affect your configuration.
For example, if you run Elasticsearch `7.6.1` and select `7.0+`, and a new feature is made available for Elasticsearch `7.5.0` or newer releases, then a `7.5+` option will be available.
However, your configuration won't be affected until you explicitly select the new `7.5+` option in your settings.
### Configure Min time interval ### Configure Min time interval
The **Min time interval** setting defines a lower limit for the auto group-by time interval. The **Min time interval** setting defines a lower limit for the auto group-by time interval.
@ -181,7 +169,6 @@ datasources:
jsonData: jsonData:
interval: Daily interval: Daily
timeField: '@timestamp' timeField: '@timestamp'
esVersion: '7.0.0'
logMessageField: message logMessageField: message
logLevelField: fields.level logLevelField: fields.level
dataLinks: dataLinks:

@ -62,7 +62,6 @@ Content-Type: application/json
"basicAuth": false, "basicAuth": false,
"isDefault": false, "isDefault": false,
"jsonData": { "jsonData": {
"esVersion": 5,
"logLevelField": "", "logLevelField": "",
"logMessageField": "", "logMessageField": "",
"maxConcurrentShardRequests": 256, "maxConcurrentShardRequests": 256,

@ -12,7 +12,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/Masterminds/semver"
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
@ -23,7 +22,6 @@ type DatasourceInfo struct {
HTTPClient *http.Client HTTPClient *http.Client
URL string URL string
Database string Database string
ESVersion *semver.Version
ConfiguredFields ConfiguredFields ConfiguredFields ConfiguredFields
Interval string Interval string
TimeInterval string TimeInterval string
@ -60,7 +58,7 @@ var NewClient = func(ctx context.Context, ds *DatasourceInfo, timeRange backend.
} }
logger := log.New(loggerName).FromContext(ctx) logger := log.New(loggerName).FromContext(ctx)
logger.Debug("Creating new client", "version", ds.ESVersion, "configuredFields", fmt.Sprintf("%#v", ds.ConfiguredFields), "indices", strings.Join(indices, ", ")) logger.Debug("Creating new client", "configuredFields", fmt.Sprintf("%#v", ds.ConfiguredFields), "indices", strings.Join(indices, ", "))
return &baseClientImpl{ return &baseClientImpl{
logger: logger, logger: logger,

@ -9,7 +9,6 @@ import (
"testing" "testing"
"time" "time"
"github.com/Masterminds/semver"
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -43,9 +42,6 @@ func TestClient_ExecuteMultisearch(t *testing.T) {
rw.WriteHeader(200) rw.WriteHeader(200)
})) }))
version, err := semver.NewVersion("8.0.0")
require.NoError(t, err)
configuredFields := ConfiguredFields{ configuredFields := ConfiguredFields{
TimeField: "testtime", TimeField: "testtime",
LogMessageField: "line", LogMessageField: "line",
@ -56,7 +52,6 @@ func TestClient_ExecuteMultisearch(t *testing.T) {
URL: ts.URL, URL: ts.URL,
HTTPClient: ts.Client(), HTTPClient: ts.Client(),
Database: "[metrics-]YYYY.MM.DD", Database: "[metrics-]YYYY.MM.DD",
ESVersion: version,
ConfiguredFields: configuredFields, ConfiguredFields: configuredFields,
Interval: "Daily", Interval: "Daily",
MaxConcurrentShardRequests: 6, MaxConcurrentShardRequests: 6,

@ -7,7 +7,6 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"github.com/Masterminds/semver"
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/datasource" "github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt" "github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
@ -44,12 +43,6 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest)
// separate function to allow testing the whole transformation and query flow // separate function to allow testing the whole transformation and query flow
func queryData(ctx context.Context, queries []backend.DataQuery, dsInfo *es.DatasourceInfo) (*backend.QueryDataResponse, error) { func queryData(ctx context.Context, queries []backend.DataQuery, dsInfo *es.DatasourceInfo) (*backend.QueryDataResponse, error) {
// Support for version after their end-of-life (currently <7.10.0) was removed
lastSupportedVersion, _ := semver.NewVersion("7.10.0")
if dsInfo.ESVersion.LessThan(lastSupportedVersion) {
return &backend.QueryDataResponse{}, fmt.Errorf("support for elasticsearch versions after their end-of-life (currently versions < 7.10) was removed")
}
if len(queries) == 0 { if len(queries) == 0 {
return &backend.QueryDataResponse{}, fmt.Errorf("query contains no queries") return &backend.QueryDataResponse{}, fmt.Errorf("query contains no queries")
} }
@ -84,10 +77,7 @@ func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.Inst
return nil, err return nil, err
} }
version, err := coerceVersion(jsonData["esVersion"]) // we used to have a field named `esVersion`, please do not use this name in the future.
if err != nil {
return nil, fmt.Errorf("elasticsearch version is required, err=%v", err)
}
timeField, ok := jsonData["timeField"].(string) timeField, ok := jsonData["timeField"].(string)
if !ok { if !ok {
@ -154,7 +144,6 @@ func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.Inst
HTTPClient: httpCli, HTTPClient: httpCli,
Database: settings.Database, Database: settings.Database,
MaxConcurrentShardRequests: int64(maxConcurrentShardRequests), MaxConcurrentShardRequests: int64(maxConcurrentShardRequests),
ESVersion: version,
ConfiguredFields: configuredFields, ConfiguredFields: configuredFields,
Interval: interval, Interval: interval,
TimeInterval: timeInterval, TimeInterval: timeInterval,
@ -175,32 +164,3 @@ func (s *Service) getDSInfo(pluginCtx backend.PluginContext) (*es.DatasourceInfo
return &instance, nil return &instance, nil
} }
func coerceVersion(v interface{}) (*semver.Version, error) {
versionString, ok := v.(string)
if ok {
return semver.NewVersion(versionString)
}
versionNumber, ok := v.(float64)
if !ok {
return nil, fmt.Errorf("elasticsearch version %v, cannot be cast to int", v)
}
// Legacy version numbers (before Grafana 8)
// valid values were 2,5,56,60,70
switch int64(versionNumber) {
case 2:
return semver.NewVersion("2.0.0")
case 5:
return semver.NewVersion("5.0.0")
case 56:
return semver.NewVersion("5.6.0")
case 60:
return semver.NewVersion("6.0.0")
case 70:
return semver.NewVersion("7.0.0")
default:
return nil, fmt.Errorf("elasticsearch version=%d is not supported", int64(versionNumber))
}
}

@ -11,50 +11,15 @@ import (
) )
type datasourceInfo struct { type datasourceInfo struct {
ESVersion interface{} `json:"esVersion"`
TimeField interface{} `json:"timeField"` TimeField interface{} `json:"timeField"`
MaxConcurrentShardRequests int64 `json:"maxConcurrentShardRequests"` MaxConcurrentShardRequests int64 `json:"maxConcurrentShardRequests"`
Interval string `json:"interval"` Interval string `json:"interval"`
TimeInterval string `json:"timeInterval"` TimeInterval string `json:"timeInterval"`
} }
func TestCoerceVersion(t *testing.T) {
t.Run("version is string", func(t *testing.T) {
ver := "7.0.0"
smvr, err := coerceVersion(ver)
require.NoError(t, err)
require.NotNil(t, smvr)
require.Equal(t, "7.0.0", smvr.String())
})
t.Run("version is int", func(t *testing.T) {
testCases := []struct {
intVersion float64
stringVersion string
}{
{intVersion: 2, stringVersion: "2.0.0"},
{intVersion: 5, stringVersion: "5.0.0"},
{intVersion: 56, stringVersion: "5.6.0"},
{intVersion: 60, stringVersion: "6.0.0"},
{intVersion: 70, stringVersion: "7.0.0"},
}
for _, tc := range testCases {
smvr, err := coerceVersion(tc.intVersion)
require.NoError(t, err)
require.Equal(t, tc.stringVersion, smvr.String())
}
smvr, err := coerceVersion(12345)
require.Error(t, err)
require.Nil(t, smvr)
})
}
func TestNewInstanceSettings(t *testing.T) { func TestNewInstanceSettings(t *testing.T) {
t.Run("fields exist", func(t *testing.T) { t.Run("fields exist", func(t *testing.T) {
dsInfo := datasourceInfo{ dsInfo := datasourceInfo{
ESVersion: "7.0.0",
TimeField: "@timestamp", TimeField: "@timestamp",
MaxConcurrentShardRequests: 5, MaxConcurrentShardRequests: 5,
} }
@ -69,91 +34,9 @@ func TestNewInstanceSettings(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
}) })
t.Run("esVersion", func(t *testing.T) {
t.Run("correct version", func(t *testing.T) {
dsInfo := datasourceInfo{
ESVersion: 5,
TimeField: "@timestamp",
MaxConcurrentShardRequests: 5,
Interval: "Daily",
TimeInterval: "TimeInterval",
}
settingsJSON, err := json.Marshal(dsInfo)
require.NoError(t, err)
dsSettings := backend.DataSourceInstanceSettings{
JSONData: json.RawMessage(settingsJSON),
}
_, err = newInstanceSettings(httpclient.NewProvider())(dsSettings)
require.NoError(t, err)
})
t.Run("faulty version int", func(t *testing.T) {
dsInfo := datasourceInfo{
ESVersion: 1234,
TimeField: "@timestamp",
MaxConcurrentShardRequests: 5,
Interval: "Daily",
TimeInterval: "TimeInterval",
}
settingsJSON, err := json.Marshal(dsInfo)
require.NoError(t, err)
dsSettings := backend.DataSourceInstanceSettings{
JSONData: json.RawMessage(settingsJSON),
}
_, err = newInstanceSettings(httpclient.NewProvider())(dsSettings)
require.EqualError(t, err, "elasticsearch version is required, err=elasticsearch version=1234 is not supported")
})
t.Run("faulty version string", func(t *testing.T) {
dsInfo := datasourceInfo{
ESVersion: "NOT_VALID",
TimeField: "@timestamp",
MaxConcurrentShardRequests: 5,
Interval: "Daily",
TimeInterval: "TimeInterval",
}
settingsJSON, err := json.Marshal(dsInfo)
require.NoError(t, err)
dsSettings := backend.DataSourceInstanceSettings{
JSONData: json.RawMessage(settingsJSON),
}
_, err = newInstanceSettings(httpclient.NewProvider())(dsSettings)
require.EqualError(t, err, "elasticsearch version is required, err=Invalid Semantic Version")
})
t.Run("no version", func(t *testing.T) {
dsInfo := datasourceInfo{
TimeField: "@timestamp",
MaxConcurrentShardRequests: 5,
Interval: "Daily",
TimeInterval: "TimeInterval",
}
settingsJSON, err := json.Marshal(dsInfo)
require.NoError(t, err)
dsSettings := backend.DataSourceInstanceSettings{
JSONData: json.RawMessage(settingsJSON),
}
_, err = newInstanceSettings(httpclient.NewProvider())(dsSettings)
require.EqualError(t, err, "elasticsearch version is required, err=elasticsearch version <nil>, cannot be cast to int")
})
})
t.Run("timeField", func(t *testing.T) { t.Run("timeField", func(t *testing.T) {
t.Run("is nil", func(t *testing.T) { t.Run("is nil", func(t *testing.T) {
dsInfo := datasourceInfo{ dsInfo := datasourceInfo{
ESVersion: 2,
MaxConcurrentShardRequests: 5, MaxConcurrentShardRequests: 5,
Interval: "Daily", Interval: "Daily",
TimeInterval: "TimeInterval", TimeInterval: "TimeInterval",
@ -172,7 +55,6 @@ func TestNewInstanceSettings(t *testing.T) {
t.Run("is empty", func(t *testing.T) { t.Run("is empty", func(t *testing.T) {
dsInfo := datasourceInfo{ dsInfo := datasourceInfo{
ESVersion: 2,
MaxConcurrentShardRequests: 5, MaxConcurrentShardRequests: 5,
Interval: "Daily", Interval: "Daily",
TimeField: "", TimeField: "",

@ -9,7 +9,6 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/Masterminds/semver"
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
es "github.com/grafana/grafana/pkg/tsdb/elasticsearch/client" es "github.com/grafana/grafana/pkg/tsdb/elasticsearch/client"
@ -49,7 +48,6 @@ func newFlowTestDsInfo(body []byte, statusCode int, requestCallback func(req *ht
} }
return &es.DatasourceInfo{ return &es.DatasourceInfo{
ESVersion: semver.MustParse("8.5.0"),
Interval: "Daily", Interval: "Daily",
Database: "[testdb-]YYYY.MM.DD", Database: "[testdb-]YYYY.MM.DD",
ConfiguredFields: configuredFields, ConfiguredFields: configuredFields,

@ -8,7 +8,6 @@ import { QueryEditor } from '.';
const noop = () => void 0; const noop = () => void 0;
const datasourceMock = { const datasourceMock = {
esVersion: '7.10.0',
getDatabaseVersion: () => Promise.resolve(null), getDatabaseVersion: () => Promise.resolve(null),
} as ElasticDatasource; } as ElasticDatasource;

@ -28,8 +28,6 @@ describe('ConfigEditor', () => {
const mockOnOptionsChange = jest.fn(); const mockOnOptionsChange = jest.fn();
const options = createDefaultConfigOptions(); const options = createDefaultConfigOptions();
// @ts-ignore // @ts-ignore
delete options.jsonData.esVersion;
// @ts-ignore
delete options.jsonData.timeField; delete options.jsonData.timeField;
delete options.jsonData.maxConcurrentShardRequests; delete options.jsonData.maxConcurrentShardRequests;
@ -38,7 +36,6 @@ describe('ConfigEditor', () => {
expect(mockOnOptionsChange).toHaveBeenCalledWith( expect(mockOnOptionsChange).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
jsonData: expect.objectContaining({ jsonData: expect.objectContaining({
esVersion: '8.0.0',
timeField: '@timestamp', timeField: '@timestamp',
maxConcurrentShardRequests: 5, maxConcurrentShardRequests: 5,
}), }),

@ -8,7 +8,7 @@ import { createDefaultConfigOptions } from './mocks';
describe('ElasticDetails', () => { describe('ElasticDetails', () => {
describe('Max concurrent Shard Requests', () => { describe('Max concurrent Shard Requests', () => {
it('should render "Max concurrent Shard Requests" ', () => { it('should render "Max concurrent Shard Requests" ', () => {
render(<ElasticDetails onChange={() => {}} value={createDefaultConfigOptions({ esVersion: '8.2.0' })} />); render(<ElasticDetails onChange={() => {}} value={createDefaultConfigOptions()} />);
expect(screen.getByLabelText('Max concurrent Shard Requests')).toBeInTheDocument(); expect(screen.getByLabelText('Max concurrent Shard Requests')).toBeInTheDocument();
}); });
}); });
@ -44,31 +44,4 @@ describe('ElasticDetails', () => {
}) })
); );
}); });
describe('version change', () => {
const tc = { version: '7.10+', maxConcurrentShardRequests: 6, expectedMaxConcurrentShardRequests: 6 };
const onChangeMock = jest.fn();
it(`sets maxConcurrentShardRequests=${tc.expectedMaxConcurrentShardRequests} if version=${tc.version},`, async () => {
render(
<ElasticDetails
onChange={onChangeMock}
value={createDefaultConfigOptions({
maxConcurrentShardRequests: tc.maxConcurrentShardRequests,
esVersion: '7.0.0',
})}
/>
);
const selectEl = screen.getByLabelText('ElasticSearch version');
await selectEvent.select(selectEl, tc.version, { container: document.body });
expect(onChangeMock).toHaveBeenCalledWith(
expect.objectContaining({
jsonData: expect.objectContaining({ maxConcurrentShardRequests: tc.expectedMaxConcurrentShardRequests }),
})
);
});
});
}); });

@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import { valid } from 'semver';
import { DataSourceSettings, SelectableValue, isTruthy } from '@grafana/data'; import { DataSourceSettings, SelectableValue } from '@grafana/data';
import { FieldSet, InlineField, Input, Select, InlineSwitch } from '@grafana/ui'; import { FieldSet, InlineField, Input, Select, InlineSwitch } from '@grafana/ui';
import { ElasticsearchOptions, Interval } from '../types'; import { ElasticsearchOptions, Interval } from '../types';
@ -15,24 +14,11 @@ const indexPatternTypes: Array<SelectableValue<'none' | Interval>> = [
{ label: 'Yearly', value: 'Yearly', example: '[logstash-]YYYY' }, { label: 'Yearly', value: 'Yearly', example: '[logstash-]YYYY' },
]; ];
const esVersions: SelectableValue[] = [
{ label: '7.10+', value: '7.10.0' },
{ label: '8.x', value: '8.0.0' },
];
type Props = { type Props = {
value: DataSourceSettings<ElasticsearchOptions>; value: DataSourceSettings<ElasticsearchOptions>;
onChange: (value: DataSourceSettings<ElasticsearchOptions>) => void; onChange: (value: DataSourceSettings<ElasticsearchOptions>) => void;
}; };
export const ElasticDetails = ({ value, onChange }: Props) => { export const ElasticDetails = ({ value, onChange }: Props) => {
const currentVersion = esVersions.find((version) => version.value === value.jsonData.esVersion);
const customOption =
!currentVersion && valid(value.jsonData.esVersion)
? {
label: value.jsonData.esVersion,
value: value.jsonData.esVersion,
}
: undefined;
return ( return (
<> <>
<FieldSet label="Elasticsearch details"> <FieldSet label="Elasticsearch details">
@ -70,28 +56,6 @@ export const ElasticDetails = ({ value, onChange }: Props) => {
/> />
</InlineField> </InlineField>
<InlineField label="ElasticSearch version" labelWidth={26}>
<Select
inputId="es_config_version"
options={[customOption, ...esVersions].filter(isTruthy)}
onChange={(option) => {
const maxConcurrentShardRequests = getMaxConcurrenShardRequestOrDefault(
value.jsonData.maxConcurrentShardRequests
);
onChange({
...value,
jsonData: {
...value.jsonData,
esVersion: option.value!,
maxConcurrentShardRequests,
},
});
}}
value={currentVersion || customOption}
width={24}
/>
</InlineField>
<InlineField label="Max concurrent Shard Requests" labelWidth={26}> <InlineField label="Max concurrent Shard Requests" labelWidth={26}>
<Input <Input
id="es_config_shardRequests" id="es_config_shardRequests"
@ -208,14 +172,6 @@ const intervalHandler =
} }
}; };
function getMaxConcurrenShardRequestOrDefault(maxConcurrentShardRequests: number | undefined): number {
if (maxConcurrentShardRequests === 256) {
return 5;
}
return maxConcurrentShardRequests || defaultMaxConcurrentShardRequests();
}
export function defaultMaxConcurrentShardRequests() { export function defaultMaxConcurrentShardRequests() {
return 5; return 5;
} }

@ -9,7 +9,6 @@ export function createDefaultConfigOptions(
return getMockDataSource<ElasticsearchOptions>({ return getMockDataSource<ElasticsearchOptions>({
jsonData: { jsonData: {
timeField: '@time', timeField: '@time',
esVersion: '7.0.0',
interval: 'Hourly', interval: 'Hourly',
timeInterval: '10s', timeInterval: '10s',
maxConcurrentShardRequests: 300, maxConcurrentShardRequests: 300,

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

@ -166,7 +166,7 @@ describe('ElasticDatasource', () => {
}; };
const { ds, fetchMock } = getTestContext({ const { ds, fetchMock } = getTestContext({
data, data,
jsonData: { interval: 'Daily', esVersion: '7.10.0', timeField: '@timestamp' }, jsonData: { interval: 'Daily', timeField: '@timestamp' },
}); });
ds.getTagValues({ key: 'test' }); ds.getTagValues({ key: 'test' });
@ -182,7 +182,7 @@ describe('ElasticDatasource', () => {
describe('When testing datasource with index pattern', () => { describe('When testing datasource with index pattern', () => {
it('should translate index pattern to current day', async () => { it('should translate index pattern to current day', async () => {
const { ds, fetchMock } = getTestContext({ jsonData: { interval: 'Daily', esVersion: '7.10.0' } }); const { ds, fetchMock } = getTestContext({ jsonData: { interval: 'Daily' } });
await ds.testDatasource(); await ds.testDatasource();
@ -221,7 +221,7 @@ describe('ElasticDatasource', () => {
}, },
], ],
}; };
const { ds, fetchMock } = getTestContext({ jsonData: { interval: 'Daily', esVersion: '7.10.0' }, data }); const { ds, fetchMock } = getTestContext({ jsonData: { interval: 'Daily' }, data });
let result: DataQueryResponse = { data: [] }; let result: DataQueryResponse = { data: [] };
await expect(ds.query(query)).toEmitValuesWith((received) => { await expect(ds.query(query)).toEmitValuesWith((received) => {
@ -303,7 +303,6 @@ describe('ElasticDatasource', () => {
async function setupDataSource(jsonData?: Partial<ElasticsearchOptions>) { async function setupDataSource(jsonData?: Partial<ElasticsearchOptions>) {
jsonData = { jsonData = {
interval: 'Daily', interval: 'Daily',
esVersion: '7.10.0',
timeField: '@timestamp', timeField: '@timestamp',
...(jsonData || {}), ...(jsonData || {}),
}; };
@ -394,7 +393,7 @@ describe('ElasticDatasource', () => {
const query = { ...DATAQUERY_BASE, range, targets }; const query = { ...DATAQUERY_BASE, range, targets };
const data = { responses: [] }; const data = { responses: [] };
const { ds, fetchMock } = getTestContext({ jsonData: { esVersion: '7.10.0' }, data, database: 'test' }); const { ds, fetchMock } = getTestContext({ data, database: 'test' });
await expect(ds.query(query)).toEmitValuesWith((received) => { await expect(ds.query(query)).toEmitValuesWith((received) => {
expect(received.length).toBe(1); expect(received.length).toBe(1);
@ -453,7 +452,7 @@ describe('ElasticDatasource', () => {
it('should process it properly', async () => { it('should process it properly', async () => {
const { ds } = getTestContext({ const { ds } = getTestContext({
jsonData: { interval: 'Daily', esVersion: '7.10.0' }, jsonData: { interval: 'Daily' },
data: { data: {
took: 1, took: 1,
responses: [ responses: [
@ -500,7 +499,6 @@ describe('ElasticDatasource', () => {
const { ds } = getTestContext({ const { ds } = getTestContext({
fetchMockImplementation: () => throwError(response), fetchMockImplementation: () => throwError(response),
from: undefined, from: undefined,
jsonData: { esVersion: '7.10.0' },
}); });
const errObject = { const errObject = {
@ -516,7 +514,7 @@ describe('ElasticDatasource', () => {
it('should properly throw an unknown error', async () => { it('should properly throw an unknown error', async () => {
const { ds } = getTestContext({ const { ds } = getTestContext({
jsonData: { interval: 'Daily', esVersion: '7.10.0' }, jsonData: { interval: 'Daily' },
data: { data: {
took: 1, took: 1,
responses: [ responses: [
@ -567,7 +565,7 @@ describe('ElasticDatasource', () => {
const { ds, timeSrv, fetchMock } = getTestContext({ const { ds, timeSrv, fetchMock } = getTestContext({
from: 'now-2w', from: 'now-2w',
jsonData: { interval: 'Daily', esVersion: '7.10.0' }, jsonData: { interval: 'Daily' },
fetchMockImplementation: (options) => { fetchMockImplementation: (options) => {
if (options.url === `${ELASTICSEARCH_MOCK_URL}/asd-${twoDaysBefore}/_mapping`) { if (options.url === `${ELASTICSEARCH_MOCK_URL}/asd-${twoDaysBefore}/_mapping`) {
return of(createFetchResponse(basicResponse)); return of(createFetchResponse(basicResponse));
@ -588,7 +586,7 @@ describe('ElasticDatasource', () => {
it('should not retry more than 7 indices', async () => { it('should not retry more than 7 indices', async () => {
const { ds, timeSrv, fetchMock } = getTestContext({ const { ds, timeSrv, fetchMock } = getTestContext({
from: 'now-2w', from: 'now-2w',
jsonData: { interval: 'Daily', esVersion: '7.10.0' }, jsonData: { interval: 'Daily' },
fetchMockImplementation: (options) => { fetchMockImplementation: (options) => {
return throwError({ status: 404 }); return throwError({ status: 404 });
}, },
@ -701,7 +699,6 @@ describe('ElasticDatasource', () => {
const { ds } = getTestContext({ const { ds } = getTestContext({
data, data,
database: 'genuine.es7._mapping.response', database: 'genuine.es7._mapping.response',
jsonData: { esVersion: '7.10.0' },
}); });
await expect(ds.getFields()).toEmitValuesWith((received) => { await expect(ds.getFields()).toEmitValuesWith((received) => {
@ -732,7 +729,6 @@ describe('ElasticDatasource', () => {
const { ds } = getTestContext({ const { ds } = getTestContext({
data, data,
database: 'genuine.es7._mapping.response', database: 'genuine.es7._mapping.response',
jsonData: { esVersion: '7.10.0' },
}); });
await expect(ds.getFields(['number'])).toEmitValuesWith((received) => { await expect(ds.getFields(['number'])).toEmitValuesWith((received) => {
@ -748,7 +744,6 @@ describe('ElasticDatasource', () => {
const { ds } = getTestContext({ const { ds } = getTestContext({
data, data,
database: 'genuine.es7._mapping.response', database: 'genuine.es7._mapping.response',
jsonData: { esVersion: '7.10.0' },
}); });
await expect(ds.getFields(['date'])).toEmitValuesWith((received) => { await expect(ds.getFields(['date'])).toEmitValuesWith((received) => {
@ -775,7 +770,7 @@ describe('ElasticDatasource', () => {
const query = { ...DATAQUERY_BASE, range, targets }; const query = { ...DATAQUERY_BASE, range, targets };
const data = { responses: [] }; const data = { responses: [] };
const { ds, fetchMock } = getTestContext({ jsonData: { esVersion: '7.10.0' }, data, database: 'test' }); const { ds, fetchMock } = getTestContext({ data, database: 'test' });
await expect(ds.query(query)).toEmitValuesWith((received) => { await expect(ds.query(query)).toEmitValuesWith((received) => {
expect(received.length).toBe(1); expect(received.length).toBe(1);
@ -823,7 +818,7 @@ describe('ElasticDatasource', () => {
], ],
}; };
const { ds, fetchMock } = getTestContext({ jsonData: { esVersion: '7.10.0' }, data, database: 'test' }); const { ds, fetchMock } = getTestContext({ data, database: 'test' });
const results = await ds.metricFindQuery('{"find": "terms", "field": "test"}'); const results = await ds.metricFindQuery('{"find": "terms", "field": "test"}');
@ -865,7 +860,7 @@ describe('ElasticDatasource', () => {
describe('query', () => { describe('query', () => {
it('should replace range as integer not string', async () => { it('should replace range as integer not string', async () => {
const { ds } = getTestContext({ jsonData: { interval: 'Daily', esVersion: '7.10.0', timeField: '@time' } }); const { ds } = getTestContext({ jsonData: { interval: 'Daily', timeField: '@time' } });
const postMock = jest.fn((url: string, data) => of(createFetchResponse({ responses: [] }))); const postMock = jest.fn((url: string, data) => of(createFetchResponse({ responses: [] })));
ds['post'] = postMock; ds['post'] = postMock;
@ -977,25 +972,23 @@ describe('ElasticDatasource', () => {
}); });
describe('getMultiSearchUrl', () => { describe('getMultiSearchUrl', () => {
describe('When esVersion >= 7.10.0', () => {
it('Should add correct params to URL if "includeFrozen" is enabled', () => { it('Should add correct params to URL if "includeFrozen" is enabled', () => {
const { ds } = getTestContext({ jsonData: { esVersion: '7.10.0', includeFrozen: true, xpack: true } }); const { ds } = getTestContext({ jsonData: { includeFrozen: true, xpack: true } });
expect(ds.getMultiSearchUrl()).toMatch(/ignore_throttled=false/); expect(ds.getMultiSearchUrl()).toMatch(/ignore_throttled=false/);
}); });
it('Should NOT add ignore_throttled if "includeFrozen" is disabled', () => { it('Should NOT add ignore_throttled if "includeFrozen" is disabled', () => {
const { ds } = getTestContext({ jsonData: { esVersion: '7.10.0', includeFrozen: false, xpack: true } }); const { ds } = getTestContext({ jsonData: { includeFrozen: false, xpack: true } });
expect(ds.getMultiSearchUrl()).not.toMatch(/ignore_throttled=false/); expect(ds.getMultiSearchUrl()).not.toMatch(/ignore_throttled=false/);
}); });
it('Should NOT add ignore_throttled if "xpack" is disabled', () => { it('Should NOT add ignore_throttled if "xpack" is disabled', () => {
const { ds } = getTestContext({ jsonData: { esVersion: '7.10.0', includeFrozen: true, xpack: false } }); const { ds } = getTestContext({ jsonData: { includeFrozen: true, xpack: false } });
expect(ds.getMultiSearchUrl()).not.toMatch(/ignore_throttled=false/); expect(ds.getMultiSearchUrl()).not.toMatch(/ignore_throttled=false/);
}); });
});
}); });
describe('enhanceDataFrame', () => { describe('enhanceDataFrame', () => {

@ -50,7 +50,7 @@ import { metricAggregationConfig } from './components/QueryEditor/MetricAggregat
import { defaultBucketAgg, hasMetricOfType } from './queryDef'; import { defaultBucketAgg, hasMetricOfType } from './queryDef';
import { trackQuery } from './tracking'; import { trackQuery } from './tracking';
import { Logs, BucketAggregation, DataLinkConfig, ElasticsearchOptions, ElasticsearchQuery, TermsQuery } from './types'; import { Logs, BucketAggregation, DataLinkConfig, ElasticsearchOptions, ElasticsearchQuery, TermsQuery } from './types';
import { coerceESVersion, getScriptValue, isSupportedVersion, unsupportedVersionMessage } from './utils'; import { getScriptValue, isSupportedVersion, unsupportedVersionMessage } from './utils';
export const REF_ID_STARTER_LOG_VOLUME = 'log-volume-'; export const REF_ID_STARTER_LOG_VOLUME = 'log-volume-';
// Those are metadata fields as defined in https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-fields.html#_identity_metadata_fields. // Those are metadata fields as defined in https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-fields.html#_identity_metadata_fields.
@ -80,7 +80,6 @@ export class ElasticDatasource
name: string; name: string;
index: string; index: string;
timeField: string; timeField: string;
esVersion: string;
xpack: boolean; xpack: boolean;
interval: string; interval: string;
maxConcurrentShardRequests?: number; maxConcurrentShardRequests?: number;
@ -109,7 +108,6 @@ export class ElasticDatasource
const settingsData = instanceSettings.jsonData || ({} as ElasticsearchOptions); const settingsData = instanceSettings.jsonData || ({} as ElasticsearchOptions);
this.timeField = settingsData.timeField; this.timeField = settingsData.timeField;
this.esVersion = coerceESVersion(settingsData.esVersion);
this.xpack = Boolean(settingsData.xpack); this.xpack = Boolean(settingsData.xpack);
this.indexPattern = new IndexPattern(this.index, settingsData.interval); this.indexPattern = new IndexPattern(this.index, settingsData.interval);
this.interval = settingsData.timeInterval; this.interval = settingsData.timeInterval;

@ -41,7 +41,6 @@ export function createElasticDatasource(
url: '', url: '',
jsonData: { jsonData: {
timeField: '', timeField: '',
esVersion: '',
timeInterval: '', timeInterval: '',
...jsonData, ...jsonData,
}, },

@ -39,7 +39,8 @@ export type Interval = 'Hourly' | 'Daily' | 'Weekly' | 'Monthly' | 'Yearly';
export interface ElasticsearchOptions extends DataSourceJsonData { export interface ElasticsearchOptions extends DataSourceJsonData {
timeField: string; timeField: string;
esVersion: string; // we used to have a field named `esVersion` in the past,
// please do not use that name in the future.
xpack?: boolean; xpack?: boolean;
interval?: Interval; interval?: Interval;
timeInterval: string; timeInterval: string;

@ -1,4 +1,4 @@
import { removeEmpty, coerceESVersion } from './utils'; import { removeEmpty } from './utils';
describe('removeEmpty', () => { describe('removeEmpty', () => {
it('Should remove all empty', () => { it('Should remove all empty', () => {
@ -33,26 +33,4 @@ describe('removeEmpty', () => {
expect(removeEmpty(original)).toStrictEqual(expectedResult); expect(removeEmpty(original)).toStrictEqual(expectedResult);
}); });
it('should correctly coerce the version info', () => {
// valid string
expect(coerceESVersion('8.1.3')).toBe('8.1.3');
// invalid string
expect(coerceESVersion('haha')).toBe('8.0.0');
// known number
expect(coerceESVersion(2)).toBe('2.0.0');
expect(coerceESVersion(5)).toBe('5.0.0');
expect(coerceESVersion(56)).toBe('5.6.0');
expect(coerceESVersion(60)).toBe('6.0.0');
expect(coerceESVersion(70)).toBe('7.0.0');
expect(coerceESVersion(8)).toBe('8.0.0');
// unknown number
expect(coerceESVersion(42)).toBe('8.0.0');
// undefined
expect(coerceESVersion(undefined)).toBe('8.0.0');
});
}); });

@ -1,4 +1,4 @@
import { valid, gte, SemVer } from 'semver'; import { gte, SemVer } from 'semver';
import { isMetricAggregationWithField } from './components/QueryEditor/MetricAggregationsEditor/aggregations'; import { isMetricAggregationWithField } from './components/QueryEditor/MetricAggregationsEditor/aggregations';
import { metricAggregationConfig } from './components/QueryEditor/MetricAggregationsEditor/utils'; import { metricAggregationConfig } from './components/QueryEditor/MetricAggregationsEditor/utils';
@ -91,32 +91,6 @@ export const convertOrderByToMetricId = (orderBy: string): string | undefined =>
export const getScriptValue = (metric: MetricAggregationWithInlineScript) => export const getScriptValue = (metric: MetricAggregationWithInlineScript) =>
(typeof metric.settings?.script === 'object' ? metric.settings?.script?.inline : metric.settings?.script) || ''; (typeof metric.settings?.script === 'object' ? metric.settings?.script?.inline : metric.settings?.script) || '';
/**
* Coerces the version 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 | undefined): string => {
if (typeof version === 'string') {
return valid(version) || '8.0.0';
}
switch (version) {
case 2:
return '2.0.0';
case 5:
return '5.0.0';
case 56:
return '5.6.0';
case 60:
return '6.0.0';
case 70:
return '7.0.0';
default:
return '8.0.0';
}
};
export const isSupportedVersion = (version: SemVer): boolean => { export const isSupportedVersion = (version: SemVer): boolean => {
if (gte(version, '7.10.0')) { if (gte(version, '7.10.0')) {
return true; return true;

Loading…
Cancel
Save