The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
grafana/public/app/plugins/datasource/elasticsearch/datasource.test.ts

1296 lines
39 KiB

import { map } from 'lodash';
import { Observable, of, throwError } from 'rxjs';
import {
ArrayVector,
CoreApp,
DataLink,
DataQueryRequest,
DataQueryResponse,
DataSourceInstanceSettings,
dateMath,
DateTime,
dateTime,
Field,
FieldType,
MutableDataFrame,
RawTimeRange,
SupplementaryQueryType,
TimeRange,
toUtc,
} from '@grafana/data';
import { BackendSrvRequest, FetchResponse, reportInteraction } from '@grafana/runtime';
import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { createFetchResponse } from '../../../../test/helpers/createFetchResponse';
import { ElasticDatasource, enhanceDataFrame } from './datasource';
import { createElasticDatasource } from './mocks';
import { Filters, ElasticsearchOptions, ElasticsearchQuery } from './types';
const ELASTICSEARCH_MOCK_URL = 'http://elasticsearch.local';
Chore: Remove angular dependency from backendSrv (#20999) * Chore: Remove angular dependency from backendSrv * Refactor: Naive soultion for logging out unauthorized users * Refactor: Restructures to different streams * Refactor: Restructures datasourceRequest * Refactor: Flipped back if statement * Refactor: Extracted getFromFetchStream * Refactor: Extracts toFailureStream operation * Refactor: Fixes issue when options.params contains arrays * Refactor: Fixes broken test (but we need a lot more) * Refactor: Adds explaining comments * Refactor: Adds latest RxJs version so cancellations work * Refactor: Cleans up the takeUntil code * Refactor: Adds tests for request function * Refactor: Separates into smaller functions * Refactor: Adds last error tests * Started to changed so we require getBackendSrv from the @grafana-runtime when applicable. * Using the getBackendSrv from @grafana/runtime. * Changed so we use the getBackendSrv from the @grafana-runtime when possible. * Fixed so Server Admin -> Orgs works again. * Removed unused dependency. * Fixed digest issues on the Server Admin -> Users page. * Fix: Fixes digest problems in Playlists * Fix: Fixes digest issues in VersionHistory * Tests: Fixes broken tests * Fix: Fixes digest issues in Alerting => Notification channels * Fixed digest issues on the Intive page. * Fixed so we run digest after password reset email sent. * Fixed digest issue when trying to sign up account. * Fixed so the Server Admin -> Edit Org works with backendSrv * Fixed so Server Admin -> Users works with backend srv. * Fixed digest issues in Server Admin -> Orgs * Fix: Fixes digest issues in DashList plugin * Fixed digest issues on Server Admin -> users. * Fix: Fixes digest issues with Snapshots * Fixed digest issue when deleting a user. * Fix: Fixes digest issues with dashLink * Chore: Changes RxJs version to 6.5.4 which includes the same cancellation fix * Fix: Fixes digest issue when toggling folder in manage dashboards * Fix: Fixes bug in executeInOrder * Fix: Fixes digest issue with CreateFolderCtrl and FolderDashboardsCtrl * Fix: Fixes tslint error in test * Refactor: Changes default behaviour for emitted messages as before migration * Fix: Fixes various digest issues when saving, starring or deleting dashboards * Fix: Fixes digest issues with FolderPickerCtrl * Fixed digest issue. * Fixed digest issues. * Fixed issues with angular digest. * Removed the this.digest pattern. Co-authored-by: Hugo Häggmark <hugo.haggmark@gmail.com> Co-authored-by: Marcus Andersson <systemvetaren@gmail.com>
6 years ago
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
Chore: Remove angular dependency from backendSrv (#20999) * Chore: Remove angular dependency from backendSrv * Refactor: Naive soultion for logging out unauthorized users * Refactor: Restructures to different streams * Refactor: Restructures datasourceRequest * Refactor: Flipped back if statement * Refactor: Extracted getFromFetchStream * Refactor: Extracts toFailureStream operation * Refactor: Fixes issue when options.params contains arrays * Refactor: Fixes broken test (but we need a lot more) * Refactor: Adds explaining comments * Refactor: Adds latest RxJs version so cancellations work * Refactor: Cleans up the takeUntil code * Refactor: Adds tests for request function * Refactor: Separates into smaller functions * Refactor: Adds last error tests * Started to changed so we require getBackendSrv from the @grafana-runtime when applicable. * Using the getBackendSrv from @grafana/runtime. * Changed so we use the getBackendSrv from the @grafana-runtime when possible. * Fixed so Server Admin -> Orgs works again. * Removed unused dependency. * Fixed digest issues on the Server Admin -> Users page. * Fix: Fixes digest problems in Playlists * Fix: Fixes digest issues in VersionHistory * Tests: Fixes broken tests * Fix: Fixes digest issues in Alerting => Notification channels * Fixed digest issues on the Intive page. * Fixed so we run digest after password reset email sent. * Fixed digest issue when trying to sign up account. * Fixed so the Server Admin -> Edit Org works with backendSrv * Fixed so Server Admin -> Users works with backend srv. * Fixed digest issues in Server Admin -> Orgs * Fix: Fixes digest issues in DashList plugin * Fixed digest issues on Server Admin -> users. * Fix: Fixes digest issues with Snapshots * Fixed digest issue when deleting a user. * Fix: Fixes digest issues with dashLink * Chore: Changes RxJs version to 6.5.4 which includes the same cancellation fix * Fix: Fixes digest issue when toggling folder in manage dashboards * Fix: Fixes bug in executeInOrder * Fix: Fixes digest issue with CreateFolderCtrl and FolderDashboardsCtrl * Fix: Fixes tslint error in test * Refactor: Changes default behaviour for emitted messages as before migration * Fix: Fixes various digest issues when saving, starring or deleting dashboards * Fix: Fixes digest issues with FolderPickerCtrl * Fixed digest issue. * Fixed digest issues. * Fixed issues with angular digest. * Removed the this.digest pattern. Co-authored-by: Hugo Häggmark <hugo.haggmark@gmail.com> Co-authored-by: Marcus Andersson <systemvetaren@gmail.com>
6 years ago
getBackendSrv: () => backendSrv,
reportInteraction: jest.fn(),
getDataSourceSrv: () => {
return {
getInstanceSettings: () => {
return { name: 'elastic25' };
},
};
},
Chore: Remove angular dependency from backendSrv (#20999) * Chore: Remove angular dependency from backendSrv * Refactor: Naive soultion for logging out unauthorized users * Refactor: Restructures to different streams * Refactor: Restructures datasourceRequest * Refactor: Flipped back if statement * Refactor: Extracted getFromFetchStream * Refactor: Extracts toFailureStream operation * Refactor: Fixes issue when options.params contains arrays * Refactor: Fixes broken test (but we need a lot more) * Refactor: Adds explaining comments * Refactor: Adds latest RxJs version so cancellations work * Refactor: Cleans up the takeUntil code * Refactor: Adds tests for request function * Refactor: Separates into smaller functions * Refactor: Adds last error tests * Started to changed so we require getBackendSrv from the @grafana-runtime when applicable. * Using the getBackendSrv from @grafana/runtime. * Changed so we use the getBackendSrv from the @grafana-runtime when possible. * Fixed so Server Admin -> Orgs works again. * Removed unused dependency. * Fixed digest issues on the Server Admin -> Users page. * Fix: Fixes digest problems in Playlists * Fix: Fixes digest issues in VersionHistory * Tests: Fixes broken tests * Fix: Fixes digest issues in Alerting => Notification channels * Fixed digest issues on the Intive page. * Fixed so we run digest after password reset email sent. * Fixed digest issue when trying to sign up account. * Fixed so the Server Admin -> Edit Org works with backendSrv * Fixed so Server Admin -> Users works with backend srv. * Fixed digest issues in Server Admin -> Orgs * Fix: Fixes digest issues in DashList plugin * Fixed digest issues on Server Admin -> users. * Fix: Fixes digest issues with Snapshots * Fixed digest issue when deleting a user. * Fix: Fixes digest issues with dashLink * Chore: Changes RxJs version to 6.5.4 which includes the same cancellation fix * Fix: Fixes digest issue when toggling folder in manage dashboards * Fix: Fixes bug in executeInOrder * Fix: Fixes digest issue with CreateFolderCtrl and FolderDashboardsCtrl * Fix: Fixes tslint error in test * Refactor: Changes default behaviour for emitted messages as before migration * Fix: Fixes various digest issues when saving, starring or deleting dashboards * Fix: Fixes digest issues with FolderPickerCtrl * Fixed digest issue. * Fixed digest issues. * Fixed issues with angular digest. * Removed the this.digest pattern. Co-authored-by: Hugo Häggmark <hugo.haggmark@gmail.com> Co-authored-by: Marcus Andersson <systemvetaren@gmail.com>
6 years ago
}));
const TIMESRV_START = [2022, 8, 21, 6, 10, 10];
const TIMESRV_END = [2022, 8, 24, 6, 10, 21];
const DATAQUERY_BASE = {
requestId: '1',
interval: '',
intervalMs: 0,
scopedVars: {
test: { text: '', value: '' },
},
timezone: '',
app: 'test',
startTime: 0,
};
jest.mock('app/features/dashboard/services/TimeSrv', () => ({
...jest.requireActual('app/features/dashboard/services/TimeSrv'),
getTimeSrv: () => ({
timeRange: () => createTimeRange(toUtc(TIMESRV_START), toUtc(TIMESRV_END)),
}),
}));
const createTimeRange = (from: DateTime, to: DateTime): TimeRange => ({
from,
to,
raw: {
from,
to,
},
});
interface TestContext {
data?: Data;
from?: string;
jsonData?: Partial<ElasticsearchOptions>;
database?: string;
fetchMockImplementation?: (options: BackendSrvRequest) => Observable<FetchResponse>;
templateSrvMock?: TemplateSrv;
}
interface Data {
[key: string]: undefined | string | string[] | number | Data | Data[];
}
Chore: Remove angular dependency from backendSrv (#20999) * Chore: Remove angular dependency from backendSrv * Refactor: Naive soultion for logging out unauthorized users * Refactor: Restructures to different streams * Refactor: Restructures datasourceRequest * Refactor: Flipped back if statement * Refactor: Extracted getFromFetchStream * Refactor: Extracts toFailureStream operation * Refactor: Fixes issue when options.params contains arrays * Refactor: Fixes broken test (but we need a lot more) * Refactor: Adds explaining comments * Refactor: Adds latest RxJs version so cancellations work * Refactor: Cleans up the takeUntil code * Refactor: Adds tests for request function * Refactor: Separates into smaller functions * Refactor: Adds last error tests * Started to changed so we require getBackendSrv from the @grafana-runtime when applicable. * Using the getBackendSrv from @grafana/runtime. * Changed so we use the getBackendSrv from the @grafana-runtime when possible. * Fixed so Server Admin -> Orgs works again. * Removed unused dependency. * Fixed digest issues on the Server Admin -> Users page. * Fix: Fixes digest problems in Playlists * Fix: Fixes digest issues in VersionHistory * Tests: Fixes broken tests * Fix: Fixes digest issues in Alerting => Notification channels * Fixed digest issues on the Intive page. * Fixed so we run digest after password reset email sent. * Fixed digest issue when trying to sign up account. * Fixed so the Server Admin -> Edit Org works with backendSrv * Fixed so Server Admin -> Users works with backend srv. * Fixed digest issues in Server Admin -> Orgs * Fix: Fixes digest issues in DashList plugin * Fixed digest issues on Server Admin -> users. * Fix: Fixes digest issues with Snapshots * Fixed digest issue when deleting a user. * Fix: Fixes digest issues with dashLink * Chore: Changes RxJs version to 6.5.4 which includes the same cancellation fix * Fix: Fixes digest issue when toggling folder in manage dashboards * Fix: Fixes bug in executeInOrder * Fix: Fixes digest issue with CreateFolderCtrl and FolderDashboardsCtrl * Fix: Fixes tslint error in test * Refactor: Changes default behaviour for emitted messages as before migration * Fix: Fixes various digest issues when saving, starring or deleting dashboards * Fix: Fixes digest issues with FolderPickerCtrl * Fixed digest issue. * Fixed digest issues. * Fixed issues with angular digest. * Removed the this.digest pattern. Co-authored-by: Hugo Häggmark <hugo.haggmark@gmail.com> Co-authored-by: Marcus Andersson <systemvetaren@gmail.com>
6 years ago
function getTestContext({
data = { responses: [] },
from = 'now-5m',
jsonData,
database = '[test-]YYYY.MM.DD',
fetchMockImplementation,
templateSrvMock,
}: TestContext = {}) {
const defaultMock = (options: BackendSrvRequest) => of(createFetchResponse(data));
const fetchMock = jest.spyOn(backendSrv, 'fetch');
fetchMock.mockImplementation(fetchMockImplementation ?? defaultMock);
const timeSrv = {
time: { from, to: 'now' },
timeRange: () => ({
from: dateMath.parse(timeSrv.time.from, false),
to: dateMath.parse(timeSrv.time.to, true),
}),
setTime: (time: RawTimeRange) => {
timeSrv.time = time;
},
} as TimeSrv;
const settings: Partial<DataSourceInstanceSettings<ElasticsearchOptions>> = { url: ELASTICSEARCH_MOCK_URL };
settings.jsonData = jsonData as ElasticsearchOptions;
const templateSrv =
templateSrvMock ??
({
replace: (text?: string) => {
if (text?.startsWith('$')) {
return `resolvedVariable`;
} else {
return text;
}
},
getAdhocFilters: () => [],
} as unknown as TemplateSrv);
const ds = createElasticDatasource(settings, templateSrv);
return { timeSrv, ds, fetchMock };
}
describe('ElasticDatasource', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('When calling getTagValues', () => {
it('should respect the currently selected time range', () => {
const data = {
responses: [
{
aggregations: {
'1': {
buckets: [
{
doc_count: 10,
key: 'val1',
},
{
doc_count: 20,
key: 'val2',
},
{
doc_count: 30,
key: 'val3',
},
],
},
},
},
],
};
const { ds, fetchMock } = getTestContext({
data,
jsonData: { interval: 'Daily', esVersion: '7.10.0', timeField: '@timestamp' },
});
ds.getTagValues({ key: 'test' });
expect(fetchMock).toHaveBeenCalledTimes(1);
const obj = JSON.parse(fetchMock.mock.calls[0][0].data.split('\n')[1]);
const { lte, gte } = obj.query.bool.filter[0].range['@timestamp'];
expect(gte).toBe('1663740610000'); // 2022-09-21T06:10:10Z
expect(lte).toBe('1663999821000'); // 2022-09-24T06:10:21Z
});
});
describe('When testing datasource with index pattern', () => {
it('should translate index pattern to current day', async () => {
const { ds, fetchMock } = getTestContext({ jsonData: { interval: 'Daily', esVersion: '7.10.0' } });
await ds.testDatasource();
const today = toUtc().format('YYYY.MM.DD');
const lastCall = fetchMock.mock.calls[fetchMock.mock.calls.length - 1];
expect(lastCall[0].url).toBe(`${ELASTICSEARCH_MOCK_URL}/test-${today}/_mapping`);
});
});
describe('When issuing metric query with interval pattern', () => {
async function runScenario() {
const range = { from: toUtc([2015, 4, 30, 10]), to: toUtc([2015, 5, 1, 10]), raw: { from: '', to: '' } };
const targets: ElasticsearchQuery[] = [
{
refId: 'test',
alias: '$varAlias',
bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '1' }],
metrics: [{ type: 'count', id: '1' }],
query: 'escape\\:test',
},
];
const query = { ...DATAQUERY_BASE, range, targets };
const data = {
responses: [
{
aggregations: {
'1': {
buckets: [
{
doc_count: 10,
key: 1000,
},
],
},
},
},
],
};
const { ds, fetchMock } = getTestContext({ jsonData: { interval: 'Daily', esVersion: '7.10.0' }, data });
let result: DataQueryResponse = { data: [] };
await expect(ds.query(query)).toEmitValuesWith((received) => {
expect(received.length).toBe(1);
expect(received[0]).toEqual({
data: [
{
name: 'resolvedVariable',
refId: 'test',
fields: [
{
name: 'Time',
type: FieldType.time,
config: {},
values: new ArrayVector([1000]),
},
{
name: 'Value',
type: FieldType.number,
config: {},
values: new ArrayVector([10]),
},
],
length: 1,
},
],
});
result = received[0];
});
expect(fetchMock).toHaveBeenCalledTimes(1);
const requestOptions = fetchMock.mock.calls[0][0];
const parts = requestOptions.data.split('\n');
const header = JSON.parse(parts[0]);
const body = JSON.parse(parts[1]);
return { result, body, header, query };
}
it('should translate index pattern to current day', async () => {
const { header } = await runScenario();
expect(header.index).toEqual(['test-2015.05.30', 'test-2015.05.31', 'test-2015.06.01']);
});
it('should not resolve the variable in the original alias field in the query', async () => {
const { query } = await runScenario();
expect(query.targets[0].alias).toEqual('$varAlias');
});
it('should resolve the alias variable for the alias/target in the result', async () => {
const { result } = await runScenario();
expect(result.data[0].name).toEqual('resolvedVariable');
});
it('should json escape lucene query', async () => {
const { body } = await runScenario();
expect(body.query.bool.filter[1].query_string.query).toBe('escape\\:test');
});
it('should report query interaction', async () => {
await runScenario();
expect(reportInteraction).toHaveBeenCalledWith(
'grafana_elasticsearch_query_executed',
expect.objectContaining({
alias: '$varAlias',
app: 'test',
has_data: true,
has_error: false,
line_limit: undefined,
query_type: 'metric',
simultaneously_sent_query_count: 1,
with_lucene_query: true,
})
);
});
});
describe('When issuing logs query with interval pattern', () => {
async function setupDataSource(jsonData?: Partial<ElasticsearchOptions>) {
jsonData = {
interval: 'Daily',
esVersion: '7.10.0',
timeField: '@timestamp',
...(jsonData || {}),
};
const { ds } = getTestContext({
jsonData,
data: logsResponse.data,
database: 'mock-index',
});
const query = {
range: createTimeRange(toUtc([2015, 4, 30, 10]), toUtc([2019, 7, 1, 10])),
targets: [
{
alias: '$varAlias',
refId: 'A',
bucketAggs: [
{
type: 'date_histogram',
settings: { interval: 'auto' },
id: '2',
},
],
metrics: [{ type: 'logs', id: '1' }],
query: 'escape\\:test',
timeField: '@timestamp',
},
],
} as unknown as DataQueryRequest<ElasticsearchQuery>;
const queryBuilderSpy = jest.spyOn(ds.queryBuilder, 'getLogsQuery');
let response: DataQueryResponse = { data: [] };
await expect(ds.query(query)).toEmitValuesWith((received) => {
expect(received.length).toBe(1);
response = received[0];
});
return { queryBuilderSpy, response };
}
it('should call getLogsQuery()', async () => {
const { queryBuilderSpy } = await setupDataSource();
expect(queryBuilderSpy).toHaveBeenCalled();
});
it('should enhance fields with links', async () => {
const { response } = await setupDataSource({
dataLinks: [
{
field: 'host',
url: 'http://localhost:3000/${__value.raw}',
urlDisplayLabel: 'Custom Label',
},
],
});
expect(response.data.length).toBe(1);
const links: DataLink[] = response.data[0].fields.find((field: Field) => field.name === 'host').config.links;
expect(links.length).toBe(1);
expect(links[0].url).toBe('http://localhost:3000/${__value.raw}');
expect(links[0].title).toBe('Custom Label');
});
it('should report query interaction', async () => {
await setupDataSource();
expect(reportInteraction).toHaveBeenCalledWith(
'grafana_elasticsearch_query_executed',
expect.objectContaining({
alias: '$varAlias',
app: undefined,
has_data: true,
has_error: false,
line_limit: undefined,
query_type: 'logs',
simultaneously_sent_query_count: 1,
with_lucene_query: true,
})
);
});
});
describe('When issuing document query', () => {
async function runScenario() {
const range = createTimeRange(dateTime([2015, 4, 30, 10]), dateTime([2015, 5, 1, 10]));
const targets: ElasticsearchQuery[] = [
{ refId: 'A', metrics: [{ type: 'raw_document', id: '1' }], query: 'test' },
];
const query = { ...DATAQUERY_BASE, range, targets };
const data = { responses: [] };
const { ds, fetchMock } = getTestContext({ jsonData: { esVersion: '7.10.0' }, data, database: 'test' });
await expect(ds.query(query)).toEmitValuesWith((received) => {
expect(received.length).toBe(1);
expect(received[0]).toEqual({ data: [] });
});
expect(fetchMock).toHaveBeenCalledTimes(1);
const requestOptions = fetchMock.mock.calls[0][0];
const parts = requestOptions.data.split('\n');
const header = JSON.parse(parts[0]);
const body = JSON.parse(parts[1]);
return { body, header };
}
it('should set search type to query_then_fetch', async () => {
const { header } = await runScenario();
expect(header.search_type).toEqual('query_then_fetch');
});
it('should set size', async () => {
const { body } = await runScenario();
expect(body.size).toBe(500);
});
it('should report query interaction', async () => {
await runScenario();
expect(reportInteraction).toHaveBeenCalledWith(
'grafana_elasticsearch_query_executed',
expect.objectContaining({
alias: undefined,
app: 'test',
has_data: false,
has_error: false,
line_limit: undefined,
query_type: 'raw_document',
simultaneously_sent_query_count: 1,
with_lucene_query: true,
})
);
});
});
describe('When getting an error on response', () => {
const query: DataQueryRequest<ElasticsearchQuery> = {
range: createTimeRange(toUtc([2020, 1, 1, 10]), toUtc([2020, 2, 1, 10])),
targets: [
{
refId: 'A',
alias: '$varAlias',
bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '1' }],
metrics: [{ type: 'count', id: '1' }],
query: 'escape\\:test',
},
],
} as DataQueryRequest<ElasticsearchQuery>;
it('should process it properly', async () => {
const { ds } = getTestContext({
jsonData: { interval: 'Daily', esVersion: '7.10.0' },
data: {
took: 1,
responses: [
{
error: {
reason: 'all shards failed',
},
status: 400,
},
],
},
});
const errObject = {
data: '{\n "reason": "all shards failed"\n}',
message: 'all shards failed',
config: {
url: 'http://localhost:3000/api/ds/query',
},
};
await expect(ds.query(query)).toEmitValuesWith((received) => {
expect(received.length).toBe(1);
expect(received[0]).toEqual(errObject);
});
});
it('should properly throw an error with just a message', async () => {
const response: FetchResponse = {
data: {
error: 'Bad Request',
message: 'Authentication to data source failed',
},
status: 400,
url: 'http://localhost:3000/api/ds/query',
config: { url: 'http://localhost:3000/api/ds/query' },
type: 'basic',
statusText: 'Bad Request',
redirected: false,
headers: {} as unknown as Headers,
ok: false,
};
const { ds } = getTestContext({
fetchMockImplementation: () => throwError(response),
from: undefined,
jsonData: { esVersion: '7.10.0' },
});
const errObject = {
error: 'Bad Request',
message: 'Elasticsearch error: Authentication to data source failed',
};
await expect(ds.query(query)).toEmitValuesWith((received) => {
expect(received.length).toBe(1);
expect(received[0]).toEqual(errObject);
});
});
it('should properly throw an unknown error', async () => {
const { ds } = getTestContext({
jsonData: { interval: 'Daily', esVersion: '7.10.0' },
data: {
took: 1,
responses: [
{
error: {},
status: 400,
},
],
},
});
const errObject = {
data: '{}',
message: 'Unknown elastic error response',
config: {
url: 'http://localhost:3000/api/ds/query',
},
};
await expect(ds.query(query)).toEmitValuesWith((received) => {
expect(received.length).toBe(1);
expect(received[0]).toEqual(errObject);
});
});
});
describe('When getting field mappings on indices with gaps', () => {
const basicResponse = {
metricbeat: {
mappings: {
metricsets: {
_all: {},
properties: {
'@timestamp': { type: 'date' },
beat: {
properties: {
hostname: { type: 'string' },
},
},
},
},
},
},
};
it('should not retry when ES is down', async () => {
const twoDaysBefore = toUtc().subtract(2, 'day').format('YYYY.MM.DD');
const { ds, timeSrv, fetchMock } = getTestContext({
from: 'now-2w',
jsonData: { interval: 'Daily', esVersion: '7.10.0' },
fetchMockImplementation: (options) => {
if (options.url === `${ELASTICSEARCH_MOCK_URL}/asd-${twoDaysBefore}/_mapping`) {
return of(createFetchResponse(basicResponse));
}
return throwError({ status: 500 });
},
});
const range = timeSrv.timeRange();
await expect(ds.getFields(undefined, range)).toEmitValuesWith((received) => {
expect(received.length).toBe(1);
expect(received[0]).toStrictEqual({ status: 500 });
expect(fetchMock).toBeCalledTimes(1);
});
});
it('should not retry more than 7 indices', async () => {
const { ds, timeSrv, fetchMock } = getTestContext({
from: 'now-2w',
jsonData: { interval: 'Daily', esVersion: '7.10.0' },
fetchMockImplementation: (options) => {
return throwError({ status: 404 });
},
});
const range = timeSrv.timeRange();
await expect(ds.getFields(undefined, range)).toEmitValuesWith((received) => {
expect(received.length).toBe(1);
expect(received[0]).toStrictEqual('Could not find an available index for this time range.');
expect(fetchMock).toBeCalledTimes(7);
});
});
});
describe('When getting fields from ES 7.0', () => {
const data = {
'genuine.es7._mapping.response': {
mappings: {
properties: {
'@timestamp_millis': {
type: 'date',
format: 'epoch_millis',
},
classification_terms: {
type: 'keyword',
},
domains: {
type: 'keyword',
},
ip_address: {
type: 'ip',
},
justification_blob: {
properties: {
criterion: {
type: 'text',
fields: {
keyword: {
type: 'keyword',
ignore_above: 256,
},
},
},
overall_vote_score: {
type: 'float',
},
shallow: {
properties: {
jsi: {
properties: {
sdb: {
properties: {
dsel2: {
properties: {
'bootlegged-gille': {
properties: {
botness: {
type: 'float',
},
general_algorithm_score: {
type: 'float',
},
},
},
'uncombed-boris': {
properties: {
botness: {
type: 'float',
},
general_algorithm_score: {
type: 'float',
},
},
},
},
},
},
},
},
},
},
},
},
},
overall_vote_score: {
type: 'float',
},
ua_terms_long: {
type: 'keyword',
},
ua_terms_short: {
type: 'keyword',
},
},
},
},
};
Elasticsearch: Add Top Metrics Aggregation and X-Pack support (#33041) * Elasticsearch: Add Top Metrics Aggregation * Adding support for non-timeseries visualizations * removing console.logs * restoring loadOptions type * Honor xpack setting * Adding test for elastic_response * adding test for query builder * Adding support of alerting * Fixing separator spelling * Fixing linting issues * attempting to reduce cyclomatic complexity * Adding elastic77 Docker block * Update public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.test.tsx Co-authored-by: Giordano Ricci <grdnricci@gmail.com> * refactoring MetricsEditor tests * Fixing typo * Change getFields type & move TopMetrics to a separate component * Fix SegmentAsync styles in TopMetrics Settings * Fix field types for TopMetrics * WIP * Refactoring client side to support multiple top metrics * Adding tests and finishing go implimentation * removing fmt lib from debugging * fixing tests * reducing the cyclomatic complexity * Update public/app/plugins/datasource/elasticsearch/elastic_response.ts Co-authored-by: Giordano Ricci <grdnricci@gmail.com> * Update public/app/plugins/datasource/elasticsearch/hooks/useFields.ts Co-authored-by: Giordano Ricci <grdnricci@gmail.com> * Checking for possible nil value * Fixing types * fix fake-data-gen param * fix useFields hook * Removing aggregateBy and size * Fixing go tests * Fixing TS tests * fixing tests * Fixes * Remove date from top_metrics fields * Restore previous formatting * Update pkg/tsdb/elasticsearch/client/models.go Co-authored-by: Dimitris Sotirakis <dimitrios.sotirakis@grafana.com> * Update pkg/tsdb/elasticsearch/client/models.go Co-authored-by: Dimitris Sotirakis <dimitrios.sotirakis@grafana.com> * Fix code review comments on processTopMetricValue * Remove underscore from variable names * Remove intermediate array definition * Refactor test to use testify Co-authored-by: Giordano Ricci <grdnricci@gmail.com> Co-authored-by: Elfo404 <me@giordanoricci.com> Co-authored-by: Dimitris Sotirakis <dimitrios.sotirakis@grafana.com>
4 years ago
const dateFields = ['@timestamp_millis'];
const numberFields = [
'justification_blob.overall_vote_score',
'justification_blob.shallow.jsi.sdb.dsel2.bootlegged-gille.botness',
'justification_blob.shallow.jsi.sdb.dsel2.bootlegged-gille.general_algorithm_score',
'justification_blob.shallow.jsi.sdb.dsel2.uncombed-boris.botness',
'justification_blob.shallow.jsi.sdb.dsel2.uncombed-boris.general_algorithm_score',
'overall_vote_score',
];
it('should return nested fields', async () => {
const { ds } = getTestContext({
data,
database: 'genuine.es7._mapping.response',
jsonData: { esVersion: '7.10.0' },
});
await expect(ds.getFields()).toEmitValuesWith((received) => {
expect(received.length).toBe(1);
const fieldObjects = received[0];
const fields = map(fieldObjects, 'text');
expect(fields).toEqual([
'@timestamp_millis',
'classification_terms',
'domains',
'ip_address',
'justification_blob.criterion.keyword',
'justification_blob.criterion',
'justification_blob.overall_vote_score',
'justification_blob.shallow.jsi.sdb.dsel2.bootlegged-gille.botness',
'justification_blob.shallow.jsi.sdb.dsel2.bootlegged-gille.general_algorithm_score',
'justification_blob.shallow.jsi.sdb.dsel2.uncombed-boris.botness',
'justification_blob.shallow.jsi.sdb.dsel2.uncombed-boris.general_algorithm_score',
'overall_vote_score',
'ua_terms_long',
'ua_terms_short',
]);
});
});
it('should return number fields', async () => {
const { ds } = getTestContext({
data,
database: 'genuine.es7._mapping.response',
jsonData: { esVersion: '7.10.0' },
});
Elasticsearch: Add Top Metrics Aggregation and X-Pack support (#33041) * Elasticsearch: Add Top Metrics Aggregation * Adding support for non-timeseries visualizations * removing console.logs * restoring loadOptions type * Honor xpack setting * Adding test for elastic_response * adding test for query builder * Adding support of alerting * Fixing separator spelling * Fixing linting issues * attempting to reduce cyclomatic complexity * Adding elastic77 Docker block * Update public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.test.tsx Co-authored-by: Giordano Ricci <grdnricci@gmail.com> * refactoring MetricsEditor tests * Fixing typo * Change getFields type & move TopMetrics to a separate component * Fix SegmentAsync styles in TopMetrics Settings * Fix field types for TopMetrics * WIP * Refactoring client side to support multiple top metrics * Adding tests and finishing go implimentation * removing fmt lib from debugging * fixing tests * reducing the cyclomatic complexity * Update public/app/plugins/datasource/elasticsearch/elastic_response.ts Co-authored-by: Giordano Ricci <grdnricci@gmail.com> * Update public/app/plugins/datasource/elasticsearch/hooks/useFields.ts Co-authored-by: Giordano Ricci <grdnricci@gmail.com> * Checking for possible nil value * Fixing types * fix fake-data-gen param * fix useFields hook * Removing aggregateBy and size * Fixing go tests * Fixing TS tests * fixing tests * Fixes * Remove date from top_metrics fields * Restore previous formatting * Update pkg/tsdb/elasticsearch/client/models.go Co-authored-by: Dimitris Sotirakis <dimitrios.sotirakis@grafana.com> * Update pkg/tsdb/elasticsearch/client/models.go Co-authored-by: Dimitris Sotirakis <dimitrios.sotirakis@grafana.com> * Fix code review comments on processTopMetricValue * Remove underscore from variable names * Remove intermediate array definition * Refactor test to use testify Co-authored-by: Giordano Ricci <grdnricci@gmail.com> Co-authored-by: Elfo404 <me@giordanoricci.com> Co-authored-by: Dimitris Sotirakis <dimitrios.sotirakis@grafana.com>
4 years ago
await expect(ds.getFields(['number'])).toEmitValuesWith((received) => {
expect(received.length).toBe(1);
const fieldObjects = received[0];
const fields = map(fieldObjects, 'text');
Elasticsearch: Add Top Metrics Aggregation and X-Pack support (#33041) * Elasticsearch: Add Top Metrics Aggregation * Adding support for non-timeseries visualizations * removing console.logs * restoring loadOptions type * Honor xpack setting * Adding test for elastic_response * adding test for query builder * Adding support of alerting * Fixing separator spelling * Fixing linting issues * attempting to reduce cyclomatic complexity * Adding elastic77 Docker block * Update public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.test.tsx Co-authored-by: Giordano Ricci <grdnricci@gmail.com> * refactoring MetricsEditor tests * Fixing typo * Change getFields type & move TopMetrics to a separate component * Fix SegmentAsync styles in TopMetrics Settings * Fix field types for TopMetrics * WIP * Refactoring client side to support multiple top metrics * Adding tests and finishing go implimentation * removing fmt lib from debugging * fixing tests * reducing the cyclomatic complexity * Update public/app/plugins/datasource/elasticsearch/elastic_response.ts Co-authored-by: Giordano Ricci <grdnricci@gmail.com> * Update public/app/plugins/datasource/elasticsearch/hooks/useFields.ts Co-authored-by: Giordano Ricci <grdnricci@gmail.com> * Checking for possible nil value * Fixing types * fix fake-data-gen param * fix useFields hook * Removing aggregateBy and size * Fixing go tests * Fixing TS tests * fixing tests * Fixes * Remove date from top_metrics fields * Restore previous formatting * Update pkg/tsdb/elasticsearch/client/models.go Co-authored-by: Dimitris Sotirakis <dimitrios.sotirakis@grafana.com> * Update pkg/tsdb/elasticsearch/client/models.go Co-authored-by: Dimitris Sotirakis <dimitrios.sotirakis@grafana.com> * Fix code review comments on processTopMetricValue * Remove underscore from variable names * Remove intermediate array definition * Refactor test to use testify Co-authored-by: Giordano Ricci <grdnricci@gmail.com> Co-authored-by: Elfo404 <me@giordanoricci.com> Co-authored-by: Dimitris Sotirakis <dimitrios.sotirakis@grafana.com>
4 years ago
expect(fields).toEqual(numberFields);
});
});
it('should return date fields', async () => {
const { ds } = getTestContext({
data,
database: 'genuine.es7._mapping.response',
jsonData: { esVersion: '7.10.0' },
});
Elasticsearch: Add Top Metrics Aggregation and X-Pack support (#33041) * Elasticsearch: Add Top Metrics Aggregation * Adding support for non-timeseries visualizations * removing console.logs * restoring loadOptions type * Honor xpack setting * Adding test for elastic_response * adding test for query builder * Adding support of alerting * Fixing separator spelling * Fixing linting issues * attempting to reduce cyclomatic complexity * Adding elastic77 Docker block * Update public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.test.tsx Co-authored-by: Giordano Ricci <grdnricci@gmail.com> * refactoring MetricsEditor tests * Fixing typo * Change getFields type & move TopMetrics to a separate component * Fix SegmentAsync styles in TopMetrics Settings * Fix field types for TopMetrics * WIP * Refactoring client side to support multiple top metrics * Adding tests and finishing go implimentation * removing fmt lib from debugging * fixing tests * reducing the cyclomatic complexity * Update public/app/plugins/datasource/elasticsearch/elastic_response.ts Co-authored-by: Giordano Ricci <grdnricci@gmail.com> * Update public/app/plugins/datasource/elasticsearch/hooks/useFields.ts Co-authored-by: Giordano Ricci <grdnricci@gmail.com> * Checking for possible nil value * Fixing types * fix fake-data-gen param * fix useFields hook * Removing aggregateBy and size * Fixing go tests * Fixing TS tests * fixing tests * Fixes * Remove date from top_metrics fields * Restore previous formatting * Update pkg/tsdb/elasticsearch/client/models.go Co-authored-by: Dimitris Sotirakis <dimitrios.sotirakis@grafana.com> * Update pkg/tsdb/elasticsearch/client/models.go Co-authored-by: Dimitris Sotirakis <dimitrios.sotirakis@grafana.com> * Fix code review comments on processTopMetricValue * Remove underscore from variable names * Remove intermediate array definition * Refactor test to use testify Co-authored-by: Giordano Ricci <grdnricci@gmail.com> Co-authored-by: Elfo404 <me@giordanoricci.com> Co-authored-by: Dimitris Sotirakis <dimitrios.sotirakis@grafana.com>
4 years ago
await expect(ds.getFields(['date'])).toEmitValuesWith((received) => {
expect(received.length).toBe(1);
const fieldObjects = received[0];
const fields = map(fieldObjects, 'text');
Elasticsearch: Add Top Metrics Aggregation and X-Pack support (#33041) * Elasticsearch: Add Top Metrics Aggregation * Adding support for non-timeseries visualizations * removing console.logs * restoring loadOptions type * Honor xpack setting * Adding test for elastic_response * adding test for query builder * Adding support of alerting * Fixing separator spelling * Fixing linting issues * attempting to reduce cyclomatic complexity * Adding elastic77 Docker block * Update public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.test.tsx Co-authored-by: Giordano Ricci <grdnricci@gmail.com> * refactoring MetricsEditor tests * Fixing typo * Change getFields type & move TopMetrics to a separate component * Fix SegmentAsync styles in TopMetrics Settings * Fix field types for TopMetrics * WIP * Refactoring client side to support multiple top metrics * Adding tests and finishing go implimentation * removing fmt lib from debugging * fixing tests * reducing the cyclomatic complexity * Update public/app/plugins/datasource/elasticsearch/elastic_response.ts Co-authored-by: Giordano Ricci <grdnricci@gmail.com> * Update public/app/plugins/datasource/elasticsearch/hooks/useFields.ts Co-authored-by: Giordano Ricci <grdnricci@gmail.com> * Checking for possible nil value * Fixing types * fix fake-data-gen param * fix useFields hook * Removing aggregateBy and size * Fixing go tests * Fixing TS tests * fixing tests * Fixes * Remove date from top_metrics fields * Restore previous formatting * Update pkg/tsdb/elasticsearch/client/models.go Co-authored-by: Dimitris Sotirakis <dimitrios.sotirakis@grafana.com> * Update pkg/tsdb/elasticsearch/client/models.go Co-authored-by: Dimitris Sotirakis <dimitrios.sotirakis@grafana.com> * Fix code review comments on processTopMetricValue * Remove underscore from variable names * Remove intermediate array definition * Refactor test to use testify Co-authored-by: Giordano Ricci <grdnricci@gmail.com> Co-authored-by: Elfo404 <me@giordanoricci.com> Co-authored-by: Dimitris Sotirakis <dimitrios.sotirakis@grafana.com>
4 years ago
expect(fields).toEqual(dateFields);
});
});
});
describe('When issuing aggregation query on es5.x', () => {
async function runScenario() {
const range = createTimeRange(dateTime([2015, 4, 30, 10]), dateTime([2015, 5, 1, 10]));
const targets: ElasticsearchQuery[] = [
{
refId: 'A',
bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '2' }],
metrics: [{ type: 'count', id: '1' }],
query: 'test',
},
];
const query = { ...DATAQUERY_BASE, range, targets };
const data = { responses: [] };
const { ds, fetchMock } = getTestContext({ jsonData: { esVersion: '7.10.0' }, data, database: 'test' });
await expect(ds.query(query)).toEmitValuesWith((received) => {
expect(received.length).toBe(1);
expect(received[0]).toEqual({ data: [] });
});
expect(fetchMock).toHaveBeenCalledTimes(1);
const requestOptions = fetchMock.mock.calls[0][0];
const parts = requestOptions.data.split('\n');
const header = JSON.parse(parts[0]);
const body = JSON.parse(parts[1]);
return { body, header };
}
it('should not set search type to count', async () => {
const { header } = await runScenario();
expect(header.search_type).not.toEqual('count');
});
it('should set size to 0', async () => {
const { body } = await runScenario();
expect(body.size).toBe(0);
});
});
describe('When issuing metricFind query on es5.x', () => {
async function runScenario() {
const data = {
responses: [
{
aggregations: {
'1': {
buckets: [
{ doc_count: 1, key: 'test' },
{
doc_count: 2,
key: 'test2',
key_as_string: 'test2_as_string',
},
],
},
},
},
],
};
const { ds, fetchMock } = getTestContext({ jsonData: { esVersion: '7.10.0' }, data, database: 'test' });
const results = await ds.metricFindQuery('{"find": "terms", "field": "test"}');
expect(fetchMock).toHaveBeenCalledTimes(1);
const requestOptions = fetchMock.mock.calls[0][0];
const parts = requestOptions.data.split('\n');
const header = JSON.parse(parts[0]);
const body = JSON.parse(parts[1]);
return { results, body, header };
}
it('should get results', async () => {
const { results } = await runScenario();
expect(results.length).toEqual(2);
});
it('should use key or key_as_string', async () => {
const { results } = await runScenario();
expect(results[0].text).toEqual('test');
expect(results[1].text).toEqual('test2_as_string');
});
it('should not set search type to count', async () => {
const { header } = await runScenario();
expect(header.search_type).not.toEqual('count');
});
it('should set size to 0', async () => {
const { body } = await runScenario();
expect(body.size).toBe(0);
});
it('should not set terms aggregation size to 0', async () => {
const { body } = await runScenario();
expect(body['aggs']['1']['terms'].size).not.toBe(0);
});
});
describe('query', () => {
it('should replace range as integer not string', async () => {
const { ds } = getTestContext({ jsonData: { interval: 'Daily', esVersion: '7.10.0', timeField: '@time' } });
const postMock = jest.fn((url: string, data) => of(createFetchResponse({ responses: [] })));
ds['post'] = postMock;
await expect(ds.query(createElasticQuery())).toEmitValuesWith((received) => {
expect(postMock).toHaveBeenCalledTimes(1);
const query = postMock.mock.calls[0][1];
expect(typeof JSON.parse(query.split('\n')[1]).query.bool.filter[0].range['@time'].gte).toBe('number');
});
});
});
it('should correctly interpolate variables in query', () => {
const { ds } = getTestContext();
const query: ElasticsearchQuery = {
refId: 'A',
bucketAggs: [{ type: 'filters', settings: { filters: [{ query: '$var', label: '' }] }, id: '1' }],
metrics: [{ type: 'count', id: '1' }],
query: '$var',
};
const interpolatedQuery = ds.interpolateVariablesInQueries([query], {})[0];
expect(interpolatedQuery.query).toBe('resolvedVariable');
expect((interpolatedQuery.bucketAggs![0] as Filters).settings!.filters![0].query).toBe('resolvedVariable');
});
it('should correctly add ad hoc filters when interpolating variables in query', () => {
const templateSrvMock = {
replace: (text?: string) => text,
getAdhocFilters: () => [{ key: 'bar', operator: '=', value: 'test' }],
} as unknown as TemplateSrv;
const { ds } = getTestContext({ templateSrvMock });
const query: ElasticsearchQuery = {
refId: 'A',
bucketAggs: [{ type: 'filters', settings: { filters: [{ query: '$var', label: '' }] }, id: '1' }],
metrics: [{ type: 'count', id: '1' }],
query: 'foo:"bar"',
};
const interpolatedQuery = ds.interpolateVariablesInQueries([query], {})[0];
expect(interpolatedQuery.query).toBe('foo:"bar" AND bar:"test"');
});
it('should correctly handle empty query strings in filters bucket aggregation', () => {
const { ds } = getTestContext();
const query: ElasticsearchQuery = {
refId: 'A',
bucketAggs: [{ type: 'filters', settings: { filters: [{ query: '', label: '' }] }, id: '1' }],
metrics: [{ type: 'count', id: '1' }],
query: '',
};
const interpolatedQuery = ds.interpolateVariablesInQueries([query], {})[0];
expect((interpolatedQuery.bucketAggs![0] as Filters).settings!.filters![0].query).toBe('*');
});
describe('getSupplementaryQuery', () => {
let ds: ElasticDatasource;
beforeEach(() => {
ds = getTestContext().ds;
});
it('does not return logs volume query for metric query', () => {
expect(
ds.getSupplementaryQuery(SupplementaryQueryType.LogsVolume, {
refId: 'A',
metrics: [{ type: 'count', id: '1' }],
bucketAggs: [{ type: 'filters', settings: { filters: [{ query: 'foo', label: '' }] }, id: '1' }],
query: 'foo="bar"',
})
).toEqual(undefined);
});
it('returns logs volume query for log query', () => {
expect(
ds.getSupplementaryQuery(SupplementaryQueryType.LogsVolume, {
refId: 'A',
metrics: [{ type: 'logs', id: '1' }],
query: 'foo="bar"',
})
).toEqual({
bucketAggs: [
{
field: '',
id: '3',
settings: {
interval: 'auto',
min_doc_count: '0',
trimEdges: '0',
},
type: 'date_histogram',
},
],
metrics: [
{
id: '1',
type: 'count',
},
],
query: 'foo="bar"',
refId: 'log-volume-A',
timeField: '',
});
});
});
});
describe('getMultiSearchUrl', () => {
describe('When esVersion >= 7.10.0', () => {
it('Should add correct params to URL if "includeFrozen" is enabled', () => {
const { ds } = getTestContext({ jsonData: { esVersion: '7.10.0', includeFrozen: true, xpack: true } });
expect(ds.getMultiSearchUrl()).toMatch(/ignore_throttled=false/);
});
it('Should NOT add ignore_throttled if "includeFrozen" is disabled', () => {
const { ds } = getTestContext({ jsonData: { esVersion: '7.10.0', includeFrozen: false, xpack: true } });
expect(ds.getMultiSearchUrl()).not.toMatch(/ignore_throttled=false/);
});
it('Should NOT add ignore_throttled if "xpack" is disabled', () => {
const { ds } = getTestContext({ jsonData: { esVersion: '7.10.0', includeFrozen: true, xpack: false } });
expect(ds.getMultiSearchUrl()).not.toMatch(/ignore_throttled=false/);
});
});
});
describe('enhanceDataFrame', () => {
it('adds links to dataframe', () => {
const df = new MutableDataFrame({
fields: [
{
name: 'urlField',
values: new ArrayVector([]),
},
{
name: 'traceField',
values: new ArrayVector([]),
},
],
});
enhanceDataFrame(df, [
{
field: 'urlField',
url: 'someUrl',
},
{
field: 'urlField',
url: 'someOtherUrl',
},
{
field: 'traceField',
url: 'query',
datasourceUid: 'ds1',
},
{
field: 'traceField',
url: 'otherQuery',
datasourceUid: 'ds2',
},
]);
expect(df.fields[0].config.links).toHaveLength(2);
expect(df.fields[0].config.links).toContainEqual({
title: '',
url: 'someUrl',
});
expect(df.fields[0].config.links).toContainEqual({
title: '',
url: 'someOtherUrl',
});
expect(df.fields[1].config.links).toHaveLength(2);
expect(df.fields[1].config.links).toContainEqual(
expect.objectContaining({
title: '',
url: '',
internal: expect.objectContaining({
query: { query: 'query' },
datasourceUid: 'ds1',
}),
})
);
expect(df.fields[1].config.links).toContainEqual(
expect.objectContaining({
title: '',
url: '',
internal: expect.objectContaining({
query: { query: 'otherQuery' },
datasourceUid: 'ds2',
}),
})
);
});
it('adds limit to dataframe', () => {
const df = new MutableDataFrame({
fields: [
{
name: 'someField',
values: new ArrayVector([]),
},
],
});
enhanceDataFrame(df, [], 10);
expect(df.meta?.limit).toBe(10);
});
});
describe('modifyQuery', () => {
let ds: ElasticDatasource;
beforeEach(() => {
ds = getTestContext().ds;
});
describe('with empty query', () => {
let query: ElasticsearchQuery;
beforeEach(() => {
query = { query: '', refId: 'A' };
});
it('should add the filter', () => {
expect(ds.modifyQuery(query, { type: 'ADD_FILTER', options: { key: 'foo', value: 'bar' } }).query).toBe(
'foo:"bar"'
);
});
it('should add the negative filter', () => {
expect(ds.modifyQuery(query, { type: 'ADD_FILTER_OUT', options: { key: 'foo', value: 'bar' } }).query).toBe(
'-foo:"bar"'
);
});
it('should do nothing on unknown type', () => {
expect(ds.modifyQuery(query, { type: 'unknown', options: { key: 'foo', value: 'bar' } }).query).toBe(query.query);
});
});
describe('with non-empty query', () => {
let query: ElasticsearchQuery;
beforeEach(() => {
query = { query: 'test:"value"', refId: 'A' };
});
it('should add the filter', () => {
expect(ds.modifyQuery(query, { type: 'ADD_FILTER', options: { key: 'foo', value: 'bar' } }).query).toBe(
'test:"value" AND foo:"bar"'
);
});
it('should add the negative filter', () => {
expect(ds.modifyQuery(query, { type: 'ADD_FILTER_OUT', options: { key: 'foo', value: 'bar' } }).query).toBe(
'test:"value" AND -foo:"bar"'
);
});
it('should do nothing on unknown type', () => {
expect(ds.modifyQuery(query, { type: 'unknown', options: { key: 'foo', value: 'bar' } }).query).toBe(query.query);
});
});
});
describe('addAdhocFilters', () => {
describe('with invalid filters', () => {
it('should filter out ad hoc filter without key', () => {
const templateSrvMock = {
getAdhocFilters: () => [{ key: '', operator: '=', value: 'a' }],
} as unknown as TemplateSrv;
const { ds } = getTestContext({ templateSrvMock });
const query = ds.addAdHocFilters('foo:"bar"');
expect(query).toBe('foo:"bar"');
});
it('should filter out ad hoc filter without value', () => {
const templateSrvMock = {
getAdhocFilters: () => [{ key: 'a', operator: '=', value: '' }],
} as unknown as TemplateSrv;
const { ds } = getTestContext({ templateSrvMock });
const query = ds.addAdHocFilters('foo:"bar"');
expect(query).toBe('foo:"bar"');
});
it('should filter out filter ad hoc filter with invalid operator', () => {
const templateSrvMock = {
getAdhocFilters: () => [{ key: 'a', operator: 'A', value: '' }],
} as unknown as TemplateSrv;
const { ds } = getTestContext({ templateSrvMock });
const query = ds.addAdHocFilters('foo:"bar"');
expect(query).toBe('foo:"bar"');
});
});
describe('with 1 ad hoc filter', () => {
const templateSrvMock = {
getAdhocFilters: () => [{ key: 'test', operator: '=', value: 'test1' }],
} as unknown as TemplateSrv;
const { ds } = getTestContext({ templateSrvMock });
it('should correctly add 1 ad hoc filter when query is not empty', () => {
const query = ds.addAdHocFilters('foo:"bar"');
expect(query).toBe('foo:"bar" AND test:"test1"');
});
it('should correctly add 1 ad hoc filter when query is empty', () => {
const query = ds.addAdHocFilters('');
expect(query).toBe('test:"test1"');
});
});
describe('with multiple ad hoc filters', () => {
const templateSrvMock = {
getAdhocFilters: () => [
{ key: 'bar', operator: '=', value: 'baz' },
{ key: 'job', operator: '!=', value: 'grafana' },
{ key: 'service', operator: '=~', value: 'service' },
{ key: 'count', operator: '>', value: '1' },
],
} as unknown as TemplateSrv;
const { ds } = getTestContext({ templateSrvMock });
it('should correctly add ad hoc filters when query is not empty', () => {
const query = ds.addAdHocFilters('foo:"bar" AND test:"test1"');
expect(query).toBe(
'foo:"bar" AND test:"test1" AND bar:"baz" AND -job:"grafana" AND service:/service/ AND count:>1'
);
});
it('should correctly add ad hoc filters when query is empty', () => {
const query = ds.addAdHocFilters('');
expect(query).toBe('bar:"baz" AND -job:"grafana" AND service:/service/ AND count:>1');
});
});
});
const createElasticQuery = (): DataQueryRequest<ElasticsearchQuery> => {
return {
requestId: '',
dashboardId: 0,
interval: '',
panelId: 0,
intervalMs: 1,
scopedVars: {},
timezone: '',
app: CoreApp.Dashboard,
startTime: 0,
range: {
from: dateTime([2015, 4, 30, 10]),
to: dateTime([2015, 5, 1, 10]),
raw: {
from: '',
to: '',
},
},
targets: [
{
refId: '',
bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '2' }],
metrics: [{ type: 'count', id: '' }],
query: 'test',
},
],
};
};
const logsResponse = {
data: {
responses: [
{
aggregations: {
'2': {
buckets: [
{
doc_count: 10,
key: 1000,
},
{
doc_count: 15,
key: 2000,
},
],
},
},
hits: {
hits: [
{
'@timestamp': ['2019-06-24T09:51:19.765Z'],
_id: 'fdsfs',
_type: '_doc',
_index: 'mock-index',
_source: {
'@timestamp': '2019-06-24T09:51:19.765Z',
host: 'djisaodjsoad',
message: 'hello, i am a message',
},
fields: {
'@timestamp': ['2019-06-24T09:51:19.765Z'],
},
},
{
'@timestamp': ['2019-06-24T09:52:19.765Z'],
_id: 'kdospaidopa',
_type: '_doc',
_index: 'mock-index',
_source: {
'@timestamp': '2019-06-24T09:52:19.765Z',
host: 'dsalkdakdop',
message: 'hello, i am also message',
},
fields: {
'@timestamp': ['2019-06-24T09:52:19.765Z'],
},
},
],
},
},
],
},
};