grafana/public/app/plugins/datasource/elasticsearch/datasource.test.ts

2038 lines
63 KiB
TypeScript
Raw Normal View History

import { map } from 'lodash';
import { Observable, of, throwError } from 'rxjs';
import {
CoreApp,
DataLink,
DataQueryRequest,
DataQueryResponse,
DataSourceInstanceSettings,
DateTime,
dateTime,
Field,
FieldType,
MutableDataFrame,
SupplementaryQueryType,
TimeRange,
toUtc,
} from '@grafana/data';
import { BackendSrvRequest, FetchResponse, reportInteraction, config } from '@grafana/runtime';
import { enhanceDataFrame } from './LegacyQueryRunner';
import { ElasticDatasource } from './datasource';
import { createElasticDatasource } from './mocks';
import { Filters, ElasticsearchOptions, ElasticsearchQuery } from './types';
2018-07-04 16:43:36 +08:00
const ELASTICSEARCH_MOCK_URL = 'http://elasticsearch.local';
const originalConsoleError = console.error;
const backendSrv = {
fetch: jest.fn(),
};
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>
2020-01-21 17:08:07 +08:00
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>
2020-01-21 17:08:07 +08:00
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>
2020-01-21 17:08:07 +08:00
}));
const createTimeRange = (from: DateTime, to: DateTime): TimeRange => ({
from,
to,
raw: {
from,
to,
},
});
const TIME_START = [2022, 8, 21, 6, 10, 10];
const TIME_END = [2022, 8, 24, 6, 10, 21];
const DATAQUERY_BASE = {
requestId: '1',
interval: '',
intervalMs: 0,
scopedVars: {
test: { text: '', value: '' },
},
timezone: '',
app: 'test',
startTime: 0,
range: createTimeRange(toUtc(TIME_START), toUtc(TIME_END)),
};
function createFetchResponse<T>(data: T): FetchResponse<T> {
return {
data,
status: 200,
url: 'http://localhost:3000/api/ds/query',
config: { url: 'http://localhost:3000/api/ds/query' },
type: 'basic',
statusText: 'Ok',
redirected: false,
headers: {} as unknown as Headers,
ok: true,
};
}
interface TestContext {
data?: Data;
jsonData?: Partial<ElasticsearchOptions>;
database?: string;
fetchMockImplementation?: (options: BackendSrvRequest) => Observable<FetchResponse>;
}
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>
2020-01-21 17:08:07 +08:00
function getTestContext({ data = { responses: [] }, jsonData, fetchMockImplementation }: TestContext = {}) {
const defaultMock = (options: BackendSrvRequest) => of(createFetchResponse(data));
const fetchMock = jest.spyOn(backendSrv, 'fetch');
fetchMock.mockImplementation(fetchMockImplementation ?? defaultMock);
2018-07-03 16:12:07 +08:00
const settings: Partial<DataSourceInstanceSettings<ElasticsearchOptions>> = { url: ELASTICSEARCH_MOCK_URL };
settings.jsonData = jsonData as ElasticsearchOptions;
const ds = createElasticDatasource(settings);
const timeRange = createTimeRange(toUtc(TIME_START), toUtc(TIME_END));
return { ds, fetchMock, timeRange };
}
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, timeRange } = getTestContext({
data,
jsonData: { interval: 'Daily', timeField: '@timestamp' },
});
ds.getTagValues({ key: 'test', timeRange: timeRange, filters: [] });
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' } });
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`);
});
it('should call `/_mapping` with an empty index', async () => {
const { ds, fetchMock } = getTestContext({ jsonData: { index: '' } });
await ds.testDatasource();
const lastCall = fetchMock.mock.calls[fetchMock.mock.calls.length - 1];
expect(lastCall[0].url).toBe(`${ELASTICSEARCH_MOCK_URL}/_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,
},
],
},
},
2017-12-20 19:33:33 +08:00
},
],
2019-01-09 14:46:20 +08:00
};
const { ds, fetchMock } = getTestContext({ jsonData: { interval: 'Daily' }, 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: [1000],
},
{
name: 'Value',
type: FieldType.number,
config: {},
values: [10],
},
],
length: 1,
},
],
});
result = received[0];
});
2019-01-09 14:46:20 +08:00
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');
2019-01-09 14:46:20 +08:00
});
it('should json escape lucene query', async () => {
const { body } = await runScenario();
2018-07-03 16:12:07 +08:00
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',
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({ 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();
2018-07-03 16:12:07 +08:00
expect(header.search_type).toEqual('query_then_fetch');
});
it('should set size', async () => {
const { body } = await runScenario();
2018-07-03 16:12:07 +08:00
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' },
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),
});
const errObject = {
error: 'Bad Request',
message: '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' },
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, fetchMock, timeRange } = getTestContext({
jsonData: { interval: 'Daily' },
fetchMockImplementation: (options) => {
if (options.url === `${ELASTICSEARCH_MOCK_URL}/asd-${twoDaysBefore}/_mapping`) {
return of(createFetchResponse(basicResponse));
}
return throwError({ status: 500 });
},
});
await expect(ds.getFields(undefined, timeRange)).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, fetchMock } = getTestContext({
jsonData: { interval: 'Daily' },
fetchMockImplementation: (options) => {
return throwError({ status: 404 });
},
});
const timeRange = createTimeRange(dateTime().subtract(2, 'week'), dateTime());
await expect(ds.getFields(undefined, timeRange)).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>
2021-06-04 18:07:59 +08:00
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',
});
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',
});
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>
2021-06-04 18:07:59 +08:00
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>
2021-06-04 18:07:59 +08:00
expect(fields).toEqual(numberFields);
});
});
it('should return date fields', async () => {
const { ds } = getTestContext({
data,
database: 'genuine.es7._mapping.response',
});
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>
2021-06-04 18:07:59 +08:00
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>
2021-06-04 18:07:59 +08:00
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: [] };
2016-06-20 06:40:16 +08:00
const { ds, fetchMock } = getTestContext({ data, database: 'test' });
await expect(ds.query(query)).toEmitValuesWith((received) => {
expect(received.length).toBe(1);
expect(received[0]).toEqual({ data: [] });
});
2016-06-20 06:40:16 +08:00
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]);
2016-06-20 06:40:16 +08:00
return { body, header };
}
it('should not set search type to count', async () => {
const { header } = await runScenario();
2018-07-03 16:12:07 +08:00
expect(header.search_type).not.toEqual('count');
2016-06-20 06:40:16 +08:00
});
it('should set size to 0', async () => {
const { body } = await runScenario();
2018-07-03 16:12:07 +08:00
expect(body.size).toBe(0);
2016-06-20 06:40:16 +08:00
});
});
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',
2017-12-20 19:33:33 +08:00
},
],
2017-12-20 19:33:33 +08:00
},
},
2017-12-20 19:33:33 +08:00
},
],
};
2016-06-20 06:40:16 +08:00
const { ds, fetchMock } = getTestContext({ 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]);
2016-06-20 06:40:16 +08:00
return { results, body, header };
}
it('should get results', async () => {
const { results } = await runScenario();
2018-07-03 16:12:07 +08:00
expect(results.length).toEqual(2);
});
it('should use key or key_as_string', async () => {
const { results } = await runScenario();
2018-07-03 16:12:07 +08:00
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();
2018-07-03 16:12:07 +08:00
expect(header.search_type).not.toEqual('count');
2016-06-20 06:40:16 +08:00
});
it('should set size to 0', async () => {
const { body } = await runScenario();
2018-07-03 16:12:07 +08:00
expect(body.size).toBe(0);
2016-06-20 06:40:16 +08:00
});
it('should not set terms aggregation size to 0', async () => {
const { body } = await runScenario();
2018-07-03 16:12:07 +08:00
expect(body['aggs']['1']['terms'].size).not.toBe(0);
});
2016-06-20 06:40:16 +08:00
});
describe('query', () => {
it('should replace range as integer not string', async () => {
const { ds } = getTestContext({ jsonData: { interval: 'Daily', timeField: '@time' } });
const postMock = jest.fn((method: string, url: string, data, header: object) =>
of(createFetchResponse({ responses: [] }))
);
ds.legacyQueryRunner['request'] = postMock;
await expect(ds.query(createElasticQuery())).toEmitValuesWith((received) => {
expect(postMock).toHaveBeenCalledTimes(1);
const query = postMock.mock.calls[0][2];
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 adHocFilters = [{ key: 'bar', operator: '=', value: 'test' }];
const { ds } = getTestContext();
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], {}, adHocFilters)[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(
{ type: 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(
{ type: 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: '',
});
});
it('does not return logs samples for non time series queries', () => {
expect(
ds.getSupplementaryQuery(
{ type: SupplementaryQueryType.LogsSample, limit: 100 },
{
refId: 'A',
bucketAggs: [{ type: 'filters', id: '1' }],
query: '',
}
)
).toEqual(undefined);
});
it('returns logs samples for time series queries', () => {
expect(
ds.getSupplementaryQuery(
{ type: SupplementaryQueryType.LogsSample, limit: 100 },
{
refId: 'A',
query: '',
bucketAggs: [{ type: 'date_histogram', id: '1' }],
}
)
).toEqual({
refId: `log-sample-A`,
query: '',
metrics: [{ type: 'logs', id: '1', settings: { limit: '100' } }],
});
});
});
describe('getDataProvider', () => {
let ds: ElasticDatasource;
beforeEach(() => {
ds = getTestContext().ds;
});
it('does not create a logs sample provider for non time series query', () => {
const options: DataQueryRequest<ElasticsearchQuery> = {
...DATAQUERY_BASE,
targets: [
{
refId: 'A',
metrics: [{ type: 'logs', id: '1', settings: { limit: '100' } }],
},
],
};
Supplementary queries: allow plugin decoupling by allowing providers to return a request instance (#80281) * Supplementary queries: add support for providers returning a request instance * Formatting * DataSourceWithSupplementaryQueriesSupport: update getDataProvider signature * getLogLevelFromLabels: fix buggy implementation * getLogLevelFromKey: fix key type Why number?? * Revert "getLogLevelFromKey: fix key type" This reverts commit 14a95298a6f803cc3270e0421b2e04dd0d65f131. * getSupplementaryQueryProvider: remove observable support * Datasources: remove unnecessary check The switch is doing the same job * Supplementary queries: update unit test * datasource_srv: sync mock with real api * Formatting * Supplementary queries: pass targets from getSupplementaryQueryProvider * LogsVolumeQueryOptions: remove range and make extract level optional * logsModel: add missing range to test data * query: sync tests with changes * Formatting * DataSourceWithSupplementaryQueriesSupport: update interface with deprecated and new methods * DataSourceWithSupplementaryQueriesSupport: sync Loki and Elasticsearch * queryLogsVolume: extractLevel no longer customizable * Loki: update test * Supplementary queries: add support for the new method * hasSupplementaryQuerySupport: update signature * Formatting * Betterer * Query: update test * Supplementary queries: add test for the legacy API * Update public/app/features/explore/utils/supplementaryQueries.ts Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> --------- Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>
2024-01-30 18:36:20 +08:00
expect(ds.getSupplementaryRequest(SupplementaryQueryType.LogsSample, options)).not.toBeDefined();
});
it('does create a logs sample provider for time series query', () => {
const options: DataQueryRequest<ElasticsearchQuery> = {
...DATAQUERY_BASE,
targets: [
{
refId: 'A',
bucketAggs: [{ type: 'date_histogram', id: '1' }],
},
],
};
Supplementary queries: allow plugin decoupling by allowing providers to return a request instance (#80281) * Supplementary queries: add support for providers returning a request instance * Formatting * DataSourceWithSupplementaryQueriesSupport: update getDataProvider signature * getLogLevelFromLabels: fix buggy implementation * getLogLevelFromKey: fix key type Why number?? * Revert "getLogLevelFromKey: fix key type" This reverts commit 14a95298a6f803cc3270e0421b2e04dd0d65f131. * getSupplementaryQueryProvider: remove observable support * Datasources: remove unnecessary check The switch is doing the same job * Supplementary queries: update unit test * datasource_srv: sync mock with real api * Formatting * Supplementary queries: pass targets from getSupplementaryQueryProvider * LogsVolumeQueryOptions: remove range and make extract level optional * logsModel: add missing range to test data * query: sync tests with changes * Formatting * DataSourceWithSupplementaryQueriesSupport: update interface with deprecated and new methods * DataSourceWithSupplementaryQueriesSupport: sync Loki and Elasticsearch * queryLogsVolume: extractLevel no longer customizable * Loki: update test * Supplementary queries: add support for the new method * hasSupplementaryQuerySupport: update signature * Formatting * Betterer * Query: update test * Supplementary queries: add test for the legacy API * Update public/app/features/explore/utils/supplementaryQueries.ts Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> --------- Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>
2024-01-30 18:36:20 +08:00
expect(ds.getSupplementaryRequest(SupplementaryQueryType.LogsSample, options)).toBeDefined();
});
});
describe('getLogsSampleDataProvider', () => {
let ds: ElasticDatasource;
beforeEach(() => {
ds = getTestContext().ds;
});
it("doesn't return a logs sample provider given a non time series query", () => {
const request: DataQueryRequest<ElasticsearchQuery> = {
...DATAQUERY_BASE,
targets: [
{
refId: 'A',
metrics: [{ type: 'logs', id: '1', settings: { limit: '100' } }],
},
],
};
Supplementary queries: allow plugin decoupling by allowing providers to return a request instance (#80281) * Supplementary queries: add support for providers returning a request instance * Formatting * DataSourceWithSupplementaryQueriesSupport: update getDataProvider signature * getLogLevelFromLabels: fix buggy implementation * getLogLevelFromKey: fix key type Why number?? * Revert "getLogLevelFromKey: fix key type" This reverts commit 14a95298a6f803cc3270e0421b2e04dd0d65f131. * getSupplementaryQueryProvider: remove observable support * Datasources: remove unnecessary check The switch is doing the same job * Supplementary queries: update unit test * datasource_srv: sync mock with real api * Formatting * Supplementary queries: pass targets from getSupplementaryQueryProvider * LogsVolumeQueryOptions: remove range and make extract level optional * logsModel: add missing range to test data * query: sync tests with changes * Formatting * DataSourceWithSupplementaryQueriesSupport: update interface with deprecated and new methods * DataSourceWithSupplementaryQueriesSupport: sync Loki and Elasticsearch * queryLogsVolume: extractLevel no longer customizable * Loki: update test * Supplementary queries: add support for the new method * hasSupplementaryQuerySupport: update signature * Formatting * Betterer * Query: update test * Supplementary queries: add test for the legacy API * Update public/app/features/explore/utils/supplementaryQueries.ts Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> --------- Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>
2024-01-30 18:36:20 +08:00
expect(ds.getSupplementaryRequest(SupplementaryQueryType.LogsSample, request)).not.toBeDefined();
});
it('returns a logs sample provider given a time series query', () => {
const request: DataQueryRequest<ElasticsearchQuery> = {
...DATAQUERY_BASE,
targets: [
{
refId: 'A',
bucketAggs: [{ type: 'date_histogram', id: '1' }],
},
],
};
Supplementary queries: allow plugin decoupling by allowing providers to return a request instance (#80281) * Supplementary queries: add support for providers returning a request instance * Formatting * DataSourceWithSupplementaryQueriesSupport: update getDataProvider signature * getLogLevelFromLabels: fix buggy implementation * getLogLevelFromKey: fix key type Why number?? * Revert "getLogLevelFromKey: fix key type" This reverts commit 14a95298a6f803cc3270e0421b2e04dd0d65f131. * getSupplementaryQueryProvider: remove observable support * Datasources: remove unnecessary check The switch is doing the same job * Supplementary queries: update unit test * datasource_srv: sync mock with real api * Formatting * Supplementary queries: pass targets from getSupplementaryQueryProvider * LogsVolumeQueryOptions: remove range and make extract level optional * logsModel: add missing range to test data * query: sync tests with changes * Formatting * DataSourceWithSupplementaryQueriesSupport: update interface with deprecated and new methods * DataSourceWithSupplementaryQueriesSupport: sync Loki and Elasticsearch * queryLogsVolume: extractLevel no longer customizable * Loki: update test * Supplementary queries: add support for the new method * hasSupplementaryQuerySupport: update signature * Formatting * Betterer * Query: update test * Supplementary queries: add test for the legacy API * Update public/app/features/explore/utils/supplementaryQueries.ts Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> --------- Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>
2024-01-30 18:36:20 +08:00
expect(ds.getSupplementaryRequest(SupplementaryQueryType.LogsSample, request)).toBeDefined();
});
});
});
describe('getMultiSearchUrl', () => {
it('Should add correct params to URL if "includeFrozen" is enabled', () => {
const { ds } = getTestContext({ jsonData: { includeFrozen: true } });
expect(ds.getMultiSearchUrl()).toMatch(/ignore_throttled=false/);
});
it('Should NOT add ignore_throttled if "includeFrozen" is disabled', () => {
const { ds } = getTestContext({ jsonData: { includeFrozen: false } });
expect(ds.getMultiSearchUrl()).not.toMatch(/ignore_throttled=false/);
});
});
describe('enhanceDataFrame', () => {
it('adds links to dataframe', () => {
const df = new MutableDataFrame({
fields: [
{
name: 'urlField',
values: [],
},
{
name: 'traceField',
values: [],
},
],
});
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: [],
},
],
});
enhanceDataFrame(df, [], 10);
expect(df.meta?.limit).toBe(10);
});
});
describe('modifyQuery', () => {
let ds: ElasticDatasource;
Log Rows: Added popover menu with filter options when a log line is selected (#75306) * LogRow: detect text selection * LogRow: refactor menu as component * LogRow: add actions to menu * LogRow: hack menu position * Remove unsused imports * LogRowMessage: remove popover code * PopoverMenu: refactor * LogRows: implement PopoverMenu at log rows level * PopoverMenu: implement copy * PopoverMenu: receive row model * PopoverMenu: fix onClick capture issue * Explore: add new filter methods and props for line filters * PopoverMenu: use new filter props * Explore: separate toggleable and non toggleable filters * PopoverMenu: improve copy * ModifyQuery: extend line filter with value argument * PopoverMenu: close with escape * Remove unused import * Prettier * PopoverMenu: remove label filter options * LogRow: rename text selection handling prop * Update test * Remove unused import * Popover menu: add unit test * LogRows: update unit test * Log row: hide the log row menu if the user is selecting text * Log row: dont hide row menu if popover is not in scope * Log rows: rename state variable * Popover menu: allow menu to scroll * Log rows: fix classname prop * Log rows: close popover if mouse event comes from outside the log rows * Declare new class using object style * Fix style declaration * Logs Popover Menu: add string filtering functions (#76757) * Loki modifyQuery: add line does not contain query modification * Elastic modifyQuery: implement line filters * Modify query: change action name to not be loki specific * Prettier * Prettier * Elastic: escape filter values * Popover menu: create feature flag * Log Rows: integrate logsRowsPopoverMenu flag * Rename feature flag * Popover menu: track interactions * Prettier * logRowsPopoverMenu: update stage * Popover menu: add ds type to tracking data * Log rows: move feature flag check * Improve handle deselection
2023-11-16 17:48:10 +08:00
let query: ElasticsearchQuery;
beforeEach(() => {
ds = getTestContext().ds;
});
describe('with empty query', () => {
Log Rows: Added popover menu with filter options when a log line is selected (#75306) * LogRow: detect text selection * LogRow: refactor menu as component * LogRow: add actions to menu * LogRow: hack menu position * Remove unsused imports * LogRowMessage: remove popover code * PopoverMenu: refactor * LogRows: implement PopoverMenu at log rows level * PopoverMenu: implement copy * PopoverMenu: receive row model * PopoverMenu: fix onClick capture issue * Explore: add new filter methods and props for line filters * PopoverMenu: use new filter props * Explore: separate toggleable and non toggleable filters * PopoverMenu: improve copy * ModifyQuery: extend line filter with value argument * PopoverMenu: close with escape * Remove unused import * Prettier * PopoverMenu: remove label filter options * LogRow: rename text selection handling prop * Update test * Remove unused import * Popover menu: add unit test * LogRows: update unit test * Log row: hide the log row menu if the user is selecting text * Log row: dont hide row menu if popover is not in scope * Log rows: rename state variable * Popover menu: allow menu to scroll * Log rows: fix classname prop * Log rows: close popover if mouse event comes from outside the log rows * Declare new class using object style * Fix style declaration * Logs Popover Menu: add string filtering functions (#76757) * Loki modifyQuery: add line does not contain query modification * Elastic modifyQuery: implement line filters * Modify query: change action name to not be loki specific * Prettier * Prettier * Elastic: escape filter values * Popover menu: create feature flag * Log Rows: integrate logsRowsPopoverMenu flag * Rename feature flag * Popover menu: track interactions * Prettier * logRowsPopoverMenu: update stage * Popover menu: add ds type to tracking data * Log rows: move feature flag check * Improve handle deselection
2023-11-16 17:48:10 +08:00
describe('ADD_FILTER and ADD_FITER_OUT', () => {
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('ADD_STRING_FILTER and ADD_STRING_FILTER_OUT', () => {
beforeEach(() => {
query = { query: '', refId: 'A' };
});
it('should add the filter', () => {
Log Rows: Added popover menu with filter options when a log line is selected (#75306) * LogRow: detect text selection * LogRow: refactor menu as component * LogRow: add actions to menu * LogRow: hack menu position * Remove unsused imports * LogRowMessage: remove popover code * PopoverMenu: refactor * LogRows: implement PopoverMenu at log rows level * PopoverMenu: implement copy * PopoverMenu: receive row model * PopoverMenu: fix onClick capture issue * Explore: add new filter methods and props for line filters * PopoverMenu: use new filter props * Explore: separate toggleable and non toggleable filters * PopoverMenu: improve copy * ModifyQuery: extend line filter with value argument * PopoverMenu: close with escape * Remove unused import * Prettier * PopoverMenu: remove label filter options * LogRow: rename text selection handling prop * Update test * Remove unused import * Popover menu: add unit test * LogRows: update unit test * Log row: hide the log row menu if the user is selecting text * Log row: dont hide row menu if popover is not in scope * Log rows: rename state variable * Popover menu: allow menu to scroll * Log rows: fix classname prop * Log rows: close popover if mouse event comes from outside the log rows * Declare new class using object style * Fix style declaration * Logs Popover Menu: add string filtering functions (#76757) * Loki modifyQuery: add line does not contain query modification * Elastic modifyQuery: implement line filters * Modify query: change action name to not be loki specific * Prettier * Prettier * Elastic: escape filter values * Popover menu: create feature flag * Log Rows: integrate logsRowsPopoverMenu flag * Rename feature flag * Popover menu: track interactions * Prettier * logRowsPopoverMenu: update stage * Popover menu: add ds type to tracking data * Log rows: move feature flag check * Improve handle deselection
2023-11-16 17:48:10 +08:00
expect(ds.modifyQuery(query, { type: 'ADD_STRING_FILTER', options: { value: 'bar' } }).query).toBe('"bar"');
});
it('should add the negative filter', () => {
Log Rows: Added popover menu with filter options when a log line is selected (#75306) * LogRow: detect text selection * LogRow: refactor menu as component * LogRow: add actions to menu * LogRow: hack menu position * Remove unsused imports * LogRowMessage: remove popover code * PopoverMenu: refactor * LogRows: implement PopoverMenu at log rows level * PopoverMenu: implement copy * PopoverMenu: receive row model * PopoverMenu: fix onClick capture issue * Explore: add new filter methods and props for line filters * PopoverMenu: use new filter props * Explore: separate toggleable and non toggleable filters * PopoverMenu: improve copy * ModifyQuery: extend line filter with value argument * PopoverMenu: close with escape * Remove unused import * Prettier * PopoverMenu: remove label filter options * LogRow: rename text selection handling prop * Update test * Remove unused import * Popover menu: add unit test * LogRows: update unit test * Log row: hide the log row menu if the user is selecting text * Log row: dont hide row menu if popover is not in scope * Log rows: rename state variable * Popover menu: allow menu to scroll * Log rows: fix classname prop * Log rows: close popover if mouse event comes from outside the log rows * Declare new class using object style * Fix style declaration * Logs Popover Menu: add string filtering functions (#76757) * Loki modifyQuery: add line does not contain query modification * Elastic modifyQuery: implement line filters * Modify query: change action name to not be loki specific * Prettier * Prettier * Elastic: escape filter values * Popover menu: create feature flag * Log Rows: integrate logsRowsPopoverMenu flag * Rename feature flag * Popover menu: track interactions * Prettier * logRowsPopoverMenu: update stage * Popover menu: add ds type to tracking data * Log rows: move feature flag check * Improve handle deselection
2023-11-16 17:48:10 +08:00
expect(ds.modifyQuery(query, { type: 'ADD_STRING_FILTER_OUT', options: { value: 'bar' } }).query).toBe(
'NOT "bar"'
);
});
});
describe('with non-empty query', () => {
let query: ElasticsearchQuery;
beforeEach(() => {
query = { query: 'test:"value"', refId: 'A' };
});
it('should add the filter', () => {
Log Rows: Added popover menu with filter options when a log line is selected (#75306) * LogRow: detect text selection * LogRow: refactor menu as component * LogRow: add actions to menu * LogRow: hack menu position * Remove unsused imports * LogRowMessage: remove popover code * PopoverMenu: refactor * LogRows: implement PopoverMenu at log rows level * PopoverMenu: implement copy * PopoverMenu: receive row model * PopoverMenu: fix onClick capture issue * Explore: add new filter methods and props for line filters * PopoverMenu: use new filter props * Explore: separate toggleable and non toggleable filters * PopoverMenu: improve copy * ModifyQuery: extend line filter with value argument * PopoverMenu: close with escape * Remove unused import * Prettier * PopoverMenu: remove label filter options * LogRow: rename text selection handling prop * Update test * Remove unused import * Popover menu: add unit test * LogRows: update unit test * Log row: hide the log row menu if the user is selecting text * Log row: dont hide row menu if popover is not in scope * Log rows: rename state variable * Popover menu: allow menu to scroll * Log rows: fix classname prop * Log rows: close popover if mouse event comes from outside the log rows * Declare new class using object style * Fix style declaration * Logs Popover Menu: add string filtering functions (#76757) * Loki modifyQuery: add line does not contain query modification * Elastic modifyQuery: implement line filters * Modify query: change action name to not be loki specific * Prettier * Prettier * Elastic: escape filter values * Popover menu: create feature flag * Log Rows: integrate logsRowsPopoverMenu flag * Rename feature flag * Popover menu: track interactions * Prettier * logRowsPopoverMenu: update stage * Popover menu: add ds type to tracking data * Log rows: move feature flag check * Improve handle deselection
2023-11-16 17:48:10 +08:00
expect(ds.modifyQuery(query, { type: 'ADD_STRING_FILTER', options: { value: 'bar' } }).query).toBe(
'test:"value" AND "bar"'
);
});
it('should add the negative filter', () => {
Log Rows: Added popover menu with filter options when a log line is selected (#75306) * LogRow: detect text selection * LogRow: refactor menu as component * LogRow: add actions to menu * LogRow: hack menu position * Remove unsused imports * LogRowMessage: remove popover code * PopoverMenu: refactor * LogRows: implement PopoverMenu at log rows level * PopoverMenu: implement copy * PopoverMenu: receive row model * PopoverMenu: fix onClick capture issue * Explore: add new filter methods and props for line filters * PopoverMenu: use new filter props * Explore: separate toggleable and non toggleable filters * PopoverMenu: improve copy * ModifyQuery: extend line filter with value argument * PopoverMenu: close with escape * Remove unused import * Prettier * PopoverMenu: remove label filter options * LogRow: rename text selection handling prop * Update test * Remove unused import * Popover menu: add unit test * LogRows: update unit test * Log row: hide the log row menu if the user is selecting text * Log row: dont hide row menu if popover is not in scope * Log rows: rename state variable * Popover menu: allow menu to scroll * Log rows: fix classname prop * Log rows: close popover if mouse event comes from outside the log rows * Declare new class using object style * Fix style declaration * Logs Popover Menu: add string filtering functions (#76757) * Loki modifyQuery: add line does not contain query modification * Elastic modifyQuery: implement line filters * Modify query: change action name to not be loki specific * Prettier * Prettier * Elastic: escape filter values * Popover menu: create feature flag * Log Rows: integrate logsRowsPopoverMenu flag * Rename feature flag * Popover menu: track interactions * Prettier * logRowsPopoverMenu: update stage * Popover menu: add ds type to tracking data * Log rows: move feature flag check * Improve handle deselection
2023-11-16 17:48:10 +08:00
expect(ds.modifyQuery(query, { type: 'ADD_STRING_FILTER_OUT', options: { value: 'bar' } }).query).toBe(
'test:"value" NOT "bar"'
);
});
});
Logs: Show active state of "filter for value" buttons in Logs Details (#70328) * Datasource test: fix describe nesting * Parsing: export handleQuotes function * Modify query: add functions to detect the presence of a label and remove it * Loki: add support to toggle filters if already present * Datasource test: fix describe nesting * Loki: add support to toggle filter out if present * Remove label: handle escaped values * Datasource: add test case for escaped label values * Loki: remove = filter when applying != * Remove selector: add support for Selector node being far from Matcher * Modify query: add unit tests * Elasticsearch: create modifyQuery for elastic * Elastic modify query: implement functions * Elasticsearch: implement modifyQuery functions in datasource * Elasticsearch: update datasource test * Loki modify query: check for streamSelectorPositions length * Elasticsearch query has filter: escape filter value in regex * Remove unused type * Modify query: add functions to detect the presence of a label and remove it * Remove label: handle escaped values * Logs: create props to check for label filters in the query * Log Details Row: use label state props to show visual feedback * Make isCallbacks async * Explore: add placeholder for checking for filter in query * Datasource: define new API method * Inspect query: add base implementation * Remove isFilterOutLabelActive as it will not be needed * Check for "isActive" on every render Otherwise the active state will be out of sync * Elasticsearch: implement inspectQuery in the datasource * Logs: update test * Log details: update test * Datasources: update tests * Inspect query: rename to analize query to prevent confusion * Datasource types: mark method as alpha * Explore: add comment to log-specific functions * Remove duplicated code from bad rebase * Remove label filter: check node type * getMatchersWithFilter: rename argument * Fix bad rebase * Create DataSourceWithQueryManipulationSupport interface * Implement type guard for DataSourceWithQueryManipulationSupport * DataSourceWithQueryManipulationSupport: move to logs module * hasQueryManipulationSupport: change implementation `modifyQuery` comes from the prototype. * DataSourceWithQueryManipulationSupport: expand code comments * AnalyzeQueryOptions: move to logs module * DataSourceWithQueryManipulationSupport: add support for more return types * Fix merge error * Update packages/grafana-data/src/types/logs.ts Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com> * DatasourceAPI: deprecate modifyQuery * Explore: refactor isFilterLabelActive * DataSourceWithQueryModificationSupport: rename interface * Split interfaces into Analyze and Modify * Query analysis: better name for interface * Fix guard * Create feature flag for active state * Use new feature flag in Explore * DataSourceToggleableQueryFiltersSupport: create a specific interface for this feature * Rename feature flag * De-deprecate modifyQuery * DataSourceToggleableQueryFiltersSupport: Rethink types and methods * Explore: adjust modifyQuery and isFilterLabelActive to new methods * Loki: implement new interface and revert modifyQuery * DataSourceToggleableQueryFiltersSupport: better name for arguments * Elasticsearch: implement new interface and revert modifyQuery * Loki: better name for arguments * Explore: document current limitation on isFilterLabelActive * Explore: place toggleable filters under feature flag * Loki: add tests for the new methods * Loki: add legacy modifyQuery tests * Elasticsearch: add tests for the new methods * Elasticsearch: add legacy modifyQuery tests * Toggle filter action: improve type values * Logs types: update interface description * DataSourceWithToggleableQueryFiltersSupport: update interface name * Update feature flag description * Explore: add todo comment for isFilterLabelActive --------- Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>
2023-07-24 16:22:47 +08:00
});
Logs: Show active state of "filter for value" buttons in Logs Details (#70328) * Datasource test: fix describe nesting * Parsing: export handleQuotes function * Modify query: add functions to detect the presence of a label and remove it * Loki: add support to toggle filters if already present * Datasource test: fix describe nesting * Loki: add support to toggle filter out if present * Remove label: handle escaped values * Datasource: add test case for escaped label values * Loki: remove = filter when applying != * Remove selector: add support for Selector node being far from Matcher * Modify query: add unit tests * Elasticsearch: create modifyQuery for elastic * Elastic modify query: implement functions * Elasticsearch: implement modifyQuery functions in datasource * Elasticsearch: update datasource test * Loki modify query: check for streamSelectorPositions length * Elasticsearch query has filter: escape filter value in regex * Remove unused type * Modify query: add functions to detect the presence of a label and remove it * Remove label: handle escaped values * Logs: create props to check for label filters in the query * Log Details Row: use label state props to show visual feedback * Make isCallbacks async * Explore: add placeholder for checking for filter in query * Datasource: define new API method * Inspect query: add base implementation * Remove isFilterOutLabelActive as it will not be needed * Check for "isActive" on every render Otherwise the active state will be out of sync * Elasticsearch: implement inspectQuery in the datasource * Logs: update test * Log details: update test * Datasources: update tests * Inspect query: rename to analize query to prevent confusion * Datasource types: mark method as alpha * Explore: add comment to log-specific functions * Remove duplicated code from bad rebase * Remove label filter: check node type * getMatchersWithFilter: rename argument * Fix bad rebase * Create DataSourceWithQueryManipulationSupport interface * Implement type guard for DataSourceWithQueryManipulationSupport * DataSourceWithQueryManipulationSupport: move to logs module * hasQueryManipulationSupport: change implementation `modifyQuery` comes from the prototype. * DataSourceWithQueryManipulationSupport: expand code comments * AnalyzeQueryOptions: move to logs module * DataSourceWithQueryManipulationSupport: add support for more return types * Fix merge error * Update packages/grafana-data/src/types/logs.ts Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com> * DatasourceAPI: deprecate modifyQuery * Explore: refactor isFilterLabelActive * DataSourceWithQueryModificationSupport: rename interface * Split interfaces into Analyze and Modify * Query analysis: better name for interface * Fix guard * Create feature flag for active state * Use new feature flag in Explore * DataSourceToggleableQueryFiltersSupport: create a specific interface for this feature * Rename feature flag * De-deprecate modifyQuery * DataSourceToggleableQueryFiltersSupport: Rethink types and methods * Explore: adjust modifyQuery and isFilterLabelActive to new methods * Loki: implement new interface and revert modifyQuery * DataSourceToggleableQueryFiltersSupport: better name for arguments * Elasticsearch: implement new interface and revert modifyQuery * Loki: better name for arguments * Explore: document current limitation on isFilterLabelActive * Explore: place toggleable filters under feature flag * Loki: add tests for the new methods * Loki: add legacy modifyQuery tests * Elasticsearch: add tests for the new methods * Elasticsearch: add legacy modifyQuery tests * Toggle filter action: improve type values * Logs types: update interface description * DataSourceWithToggleableQueryFiltersSupport: update interface name * Update feature flag description * Explore: add todo comment for isFilterLabelActive --------- Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>
2023-07-24 16:22:47 +08:00
describe('toggleQueryFilter', () => {
let ds: ElasticDatasource;
beforeEach(() => {
ds = getTestContext().ds;
});
describe('with empty query', () => {
let query: ElasticsearchQuery;
beforeEach(() => {
Logs: Show active state of "filter for value" buttons in Logs Details (#70328) * Datasource test: fix describe nesting * Parsing: export handleQuotes function * Modify query: add functions to detect the presence of a label and remove it * Loki: add support to toggle filters if already present * Datasource test: fix describe nesting * Loki: add support to toggle filter out if present * Remove label: handle escaped values * Datasource: add test case for escaped label values * Loki: remove = filter when applying != * Remove selector: add support for Selector node being far from Matcher * Modify query: add unit tests * Elasticsearch: create modifyQuery for elastic * Elastic modify query: implement functions * Elasticsearch: implement modifyQuery functions in datasource * Elasticsearch: update datasource test * Loki modify query: check for streamSelectorPositions length * Elasticsearch query has filter: escape filter value in regex * Remove unused type * Modify query: add functions to detect the presence of a label and remove it * Remove label: handle escaped values * Logs: create props to check for label filters in the query * Log Details Row: use label state props to show visual feedback * Make isCallbacks async * Explore: add placeholder for checking for filter in query * Datasource: define new API method * Inspect query: add base implementation * Remove isFilterOutLabelActive as it will not be needed * Check for "isActive" on every render Otherwise the active state will be out of sync * Elasticsearch: implement inspectQuery in the datasource * Logs: update test * Log details: update test * Datasources: update tests * Inspect query: rename to analize query to prevent confusion * Datasource types: mark method as alpha * Explore: add comment to log-specific functions * Remove duplicated code from bad rebase * Remove label filter: check node type * getMatchersWithFilter: rename argument * Fix bad rebase * Create DataSourceWithQueryManipulationSupport interface * Implement type guard for DataSourceWithQueryManipulationSupport * DataSourceWithQueryManipulationSupport: move to logs module * hasQueryManipulationSupport: change implementation `modifyQuery` comes from the prototype. * DataSourceWithQueryManipulationSupport: expand code comments * AnalyzeQueryOptions: move to logs module * DataSourceWithQueryManipulationSupport: add support for more return types * Fix merge error * Update packages/grafana-data/src/types/logs.ts Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com> * DatasourceAPI: deprecate modifyQuery * Explore: refactor isFilterLabelActive * DataSourceWithQueryModificationSupport: rename interface * Split interfaces into Analyze and Modify * Query analysis: better name for interface * Fix guard * Create feature flag for active state * Use new feature flag in Explore * DataSourceToggleableQueryFiltersSupport: create a specific interface for this feature * Rename feature flag * De-deprecate modifyQuery * DataSourceToggleableQueryFiltersSupport: Rethink types and methods * Explore: adjust modifyQuery and isFilterLabelActive to new methods * Loki: implement new interface and revert modifyQuery * DataSourceToggleableQueryFiltersSupport: better name for arguments * Elasticsearch: implement new interface and revert modifyQuery * Loki: better name for arguments * Explore: document current limitation on isFilterLabelActive * Explore: place toggleable filters under feature flag * Loki: add tests for the new methods * Loki: add legacy modifyQuery tests * Elasticsearch: add tests for the new methods * Elasticsearch: add legacy modifyQuery tests * Toggle filter action: improve type values * Logs types: update interface description * DataSourceWithToggleableQueryFiltersSupport: update interface name * Update feature flag description * Explore: add todo comment for isFilterLabelActive --------- Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>
2023-07-24 16:22:47 +08:00
query = { query: '', refId: 'A' };
});
Logs: Show active state of "filter for value" buttons in Logs Details (#70328) * Datasource test: fix describe nesting * Parsing: export handleQuotes function * Modify query: add functions to detect the presence of a label and remove it * Loki: add support to toggle filters if already present * Datasource test: fix describe nesting * Loki: add support to toggle filter out if present * Remove label: handle escaped values * Datasource: add test case for escaped label values * Loki: remove = filter when applying != * Remove selector: add support for Selector node being far from Matcher * Modify query: add unit tests * Elasticsearch: create modifyQuery for elastic * Elastic modify query: implement functions * Elasticsearch: implement modifyQuery functions in datasource * Elasticsearch: update datasource test * Loki modify query: check for streamSelectorPositions length * Elasticsearch query has filter: escape filter value in regex * Remove unused type * Modify query: add functions to detect the presence of a label and remove it * Remove label: handle escaped values * Logs: create props to check for label filters in the query * Log Details Row: use label state props to show visual feedback * Make isCallbacks async * Explore: add placeholder for checking for filter in query * Datasource: define new API method * Inspect query: add base implementation * Remove isFilterOutLabelActive as it will not be needed * Check for "isActive" on every render Otherwise the active state will be out of sync * Elasticsearch: implement inspectQuery in the datasource * Logs: update test * Log details: update test * Datasources: update tests * Inspect query: rename to analize query to prevent confusion * Datasource types: mark method as alpha * Explore: add comment to log-specific functions * Remove duplicated code from bad rebase * Remove label filter: check node type * getMatchersWithFilter: rename argument * Fix bad rebase * Create DataSourceWithQueryManipulationSupport interface * Implement type guard for DataSourceWithQueryManipulationSupport * DataSourceWithQueryManipulationSupport: move to logs module * hasQueryManipulationSupport: change implementation `modifyQuery` comes from the prototype. * DataSourceWithQueryManipulationSupport: expand code comments * AnalyzeQueryOptions: move to logs module * DataSourceWithQueryManipulationSupport: add support for more return types * Fix merge error * Update packages/grafana-data/src/types/logs.ts Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com> * DatasourceAPI: deprecate modifyQuery * Explore: refactor isFilterLabelActive * DataSourceWithQueryModificationSupport: rename interface * Split interfaces into Analyze and Modify * Query analysis: better name for interface * Fix guard * Create feature flag for active state * Use new feature flag in Explore * DataSourceToggleableQueryFiltersSupport: create a specific interface for this feature * Rename feature flag * De-deprecate modifyQuery * DataSourceToggleableQueryFiltersSupport: Rethink types and methods * Explore: adjust modifyQuery and isFilterLabelActive to new methods * Loki: implement new interface and revert modifyQuery * DataSourceToggleableQueryFiltersSupport: better name for arguments * Elasticsearch: implement new interface and revert modifyQuery * Loki: better name for arguments * Explore: document current limitation on isFilterLabelActive * Explore: place toggleable filters under feature flag * Loki: add tests for the new methods * Loki: add legacy modifyQuery tests * Elasticsearch: add tests for the new methods * Elasticsearch: add legacy modifyQuery tests * Toggle filter action: improve type values * Logs types: update interface description * DataSourceWithToggleableQueryFiltersSupport: update interface name * Update feature flag description * Explore: add todo comment for isFilterLabelActive --------- Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>
2023-07-24 16:22:47 +08:00
it('should add the filter', () => {
expect(ds.toggleQueryFilter(query, { type: 'FILTER_FOR', options: { key: 'foo', value: 'bar' } }).query).toBe(
'foo:"bar"'
);
});
it('should toggle the filter', () => {
query.query = 'foo:"bar"';
expect(ds.toggleQueryFilter(query, { type: 'FILTER_FOR', options: { key: 'foo', value: 'bar' } }).query).toBe('');
});
Logs: Show active state of "filter for value" buttons in Logs Details (#70328) * Datasource test: fix describe nesting * Parsing: export handleQuotes function * Modify query: add functions to detect the presence of a label and remove it * Loki: add support to toggle filters if already present * Datasource test: fix describe nesting * Loki: add support to toggle filter out if present * Remove label: handle escaped values * Datasource: add test case for escaped label values * Loki: remove = filter when applying != * Remove selector: add support for Selector node being far from Matcher * Modify query: add unit tests * Elasticsearch: create modifyQuery for elastic * Elastic modify query: implement functions * Elasticsearch: implement modifyQuery functions in datasource * Elasticsearch: update datasource test * Loki modify query: check for streamSelectorPositions length * Elasticsearch query has filter: escape filter value in regex * Remove unused type * Modify query: add functions to detect the presence of a label and remove it * Remove label: handle escaped values * Logs: create props to check for label filters in the query * Log Details Row: use label state props to show visual feedback * Make isCallbacks async * Explore: add placeholder for checking for filter in query * Datasource: define new API method * Inspect query: add base implementation * Remove isFilterOutLabelActive as it will not be needed * Check for "isActive" on every render Otherwise the active state will be out of sync * Elasticsearch: implement inspectQuery in the datasource * Logs: update test * Log details: update test * Datasources: update tests * Inspect query: rename to analize query to prevent confusion * Datasource types: mark method as alpha * Explore: add comment to log-specific functions * Remove duplicated code from bad rebase * Remove label filter: check node type * getMatchersWithFilter: rename argument * Fix bad rebase * Create DataSourceWithQueryManipulationSupport interface * Implement type guard for DataSourceWithQueryManipulationSupport * DataSourceWithQueryManipulationSupport: move to logs module * hasQueryManipulationSupport: change implementation `modifyQuery` comes from the prototype. * DataSourceWithQueryManipulationSupport: expand code comments * AnalyzeQueryOptions: move to logs module * DataSourceWithQueryManipulationSupport: add support for more return types * Fix merge error * Update packages/grafana-data/src/types/logs.ts Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com> * DatasourceAPI: deprecate modifyQuery * Explore: refactor isFilterLabelActive * DataSourceWithQueryModificationSupport: rename interface * Split interfaces into Analyze and Modify * Query analysis: better name for interface * Fix guard * Create feature flag for active state * Use new feature flag in Explore * DataSourceToggleableQueryFiltersSupport: create a specific interface for this feature * Rename feature flag * De-deprecate modifyQuery * DataSourceToggleableQueryFiltersSupport: Rethink types and methods * Explore: adjust modifyQuery and isFilterLabelActive to new methods * Loki: implement new interface and revert modifyQuery * DataSourceToggleableQueryFiltersSupport: better name for arguments * Elasticsearch: implement new interface and revert modifyQuery * Loki: better name for arguments * Explore: document current limitation on isFilterLabelActive * Explore: place toggleable filters under feature flag * Loki: add tests for the new methods * Loki: add legacy modifyQuery tests * Elasticsearch: add tests for the new methods * Elasticsearch: add legacy modifyQuery tests * Toggle filter action: improve type values * Logs types: update interface description * DataSourceWithToggleableQueryFiltersSupport: update interface name * Update feature flag description * Explore: add todo comment for isFilterLabelActive --------- Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>
2023-07-24 16:22:47 +08:00
it('should add the negative filter', () => {
expect(ds.toggleQueryFilter(query, { type: 'FILTER_OUT', options: { key: 'foo', value: 'bar' } }).query).toBe(
'-foo:"bar"'
);
});
it('should remove a positive filter to add a negative filter', () => {
query.query = 'foo:"bar"';
expect(ds.toggleQueryFilter(query, { type: 'FILTER_OUT', options: { key: 'foo', value: 'bar' } }).query).toBe(
'-foo:"bar"'
);
});
});
describe('with non-empty query', () => {
let query: ElasticsearchQuery;
beforeEach(() => {
query = { query: 'test:"value"', refId: 'A' };
});
it('should add the filter', () => {
expect(ds.toggleQueryFilter(query, { type: 'FILTER_FOR', options: { key: 'foo', value: 'bar' } }).query).toBe(
'test:"value" AND foo:"bar"'
);
});
it('should add the negative filter', () => {
expect(ds.toggleQueryFilter(query, { type: 'FILTER_OUT', options: { key: 'foo', value: 'bar' } }).query).toBe(
'test:"value" AND -foo:"bar"'
);
});
});
});
describe('queryHasFilter()', () => {
let ds: ElasticDatasource;
beforeEach(() => {
ds = getTestContext().ds;
});
it('inspects queries for filter presence', () => {
const query = { refId: 'A', query: 'grafana:"awesome"' };
expect(
ds.queryHasFilter(query, {
key: 'grafana',
value: 'awesome',
})
).toBe(true);
});
});
Logs: Show active state of "filter for value" buttons in Logs Details (#70328) * Datasource test: fix describe nesting * Parsing: export handleQuotes function * Modify query: add functions to detect the presence of a label and remove it * Loki: add support to toggle filters if already present * Datasource test: fix describe nesting * Loki: add support to toggle filter out if present * Remove label: handle escaped values * Datasource: add test case for escaped label values * Loki: remove = filter when applying != * Remove selector: add support for Selector node being far from Matcher * Modify query: add unit tests * Elasticsearch: create modifyQuery for elastic * Elastic modify query: implement functions * Elasticsearch: implement modifyQuery functions in datasource * Elasticsearch: update datasource test * Loki modify query: check for streamSelectorPositions length * Elasticsearch query has filter: escape filter value in regex * Remove unused type * Modify query: add functions to detect the presence of a label and remove it * Remove label: handle escaped values * Logs: create props to check for label filters in the query * Log Details Row: use label state props to show visual feedback * Make isCallbacks async * Explore: add placeholder for checking for filter in query * Datasource: define new API method * Inspect query: add base implementation * Remove isFilterOutLabelActive as it will not be needed * Check for "isActive" on every render Otherwise the active state will be out of sync * Elasticsearch: implement inspectQuery in the datasource * Logs: update test * Log details: update test * Datasources: update tests * Inspect query: rename to analize query to prevent confusion * Datasource types: mark method as alpha * Explore: add comment to log-specific functions * Remove duplicated code from bad rebase * Remove label filter: check node type * getMatchersWithFilter: rename argument * Fix bad rebase * Create DataSourceWithQueryManipulationSupport interface * Implement type guard for DataSourceWithQueryManipulationSupport * DataSourceWithQueryManipulationSupport: move to logs module * hasQueryManipulationSupport: change implementation `modifyQuery` comes from the prototype. * DataSourceWithQueryManipulationSupport: expand code comments * AnalyzeQueryOptions: move to logs module * DataSourceWithQueryManipulationSupport: add support for more return types * Fix merge error * Update packages/grafana-data/src/types/logs.ts Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com> * DatasourceAPI: deprecate modifyQuery * Explore: refactor isFilterLabelActive * DataSourceWithQueryModificationSupport: rename interface * Split interfaces into Analyze and Modify * Query analysis: better name for interface * Fix guard * Create feature flag for active state * Use new feature flag in Explore * DataSourceToggleableQueryFiltersSupport: create a specific interface for this feature * Rename feature flag * De-deprecate modifyQuery * DataSourceToggleableQueryFiltersSupport: Rethink types and methods * Explore: adjust modifyQuery and isFilterLabelActive to new methods * Loki: implement new interface and revert modifyQuery * DataSourceToggleableQueryFiltersSupport: better name for arguments * Elasticsearch: implement new interface and revert modifyQuery * Loki: better name for arguments * Explore: document current limitation on isFilterLabelActive * Explore: place toggleable filters under feature flag * Loki: add tests for the new methods * Loki: add legacy modifyQuery tests * Elasticsearch: add tests for the new methods * Elasticsearch: add legacy modifyQuery tests * Toggle filter action: improve type values * Logs types: update interface description * DataSourceWithToggleableQueryFiltersSupport: update interface name * Update feature flag description * Explore: add todo comment for isFilterLabelActive --------- Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>
2023-07-24 16:22:47 +08:00
describe('addAdhocFilters', () => {
describe('with invalid filters', () => {
let ds: ElasticDatasource;
beforeEach(() => {
const context = getTestContext();
ds = context.ds;
});
it('should filter out ad hoc filter without key', () => {
const query = ds.addAdHocFilters('foo:"bar"', [{ key: '', operator: '=', value: 'a', condition: '' }]);
expect(query).toBe('foo:"bar"');
});
it('should filter out ad hoc filter without value', () => {
const query = ds.addAdHocFilters('foo:"bar"', [{ key: 'a', operator: '=', value: '', condition: '' }]);
expect(query).toBe('foo:"bar"');
});
it('should filter out filter ad hoc filter with invalid operator', () => {
const query = ds.addAdHocFilters('foo:"bar"', [{ key: 'a', operator: 'A', value: '', condition: '' }]);
expect(query).toBe('foo:"bar"');
});
});
describe('with 1 ad hoc filter', () => {
let ds: ElasticDatasource;
beforeEach(() => {
const { ds: datasource } = getTestContext();
ds = datasource;
});
it('should correctly add 1 ad hoc filter when query is not empty', () => {
const filters = [{ key: 'test', operator: '=', value: 'test1', condition: '' }];
const query = ds.addAdHocFilters('foo:"bar"', filters);
expect(query).toBe('foo:"bar" AND test:"test1"');
});
it('should correctly add 1 ad hoc filter when query is empty', () => {
const filters = [{ key: 'test', operator: '=', value: 'test1', condition: '' }];
expect(ds.addAdHocFilters('', filters)).toBe('test:"test1"');
expect(ds.addAdHocFilters(' ', filters)).toBe('test:"test1"');
expect(ds.addAdHocFilters(' ', filters)).toBe('test:"test1"');
});
it('should not fail if the filter value is a number', () => {
// @ts-expect-error
expect(ds.addAdHocFilters('', [{ key: 'key', operator: '=', value: 1, condition: '' }])).toBe('key:"1"');
});
it.each(['=', '!=', '=~', '!~', '>', '<', '', ''])(
`should properly build queries with '%s' filters`,
(operator: string) => {
const filters = [{ key: 'key', operator, value: 'value', condition: '' }];
const query = ds.addAdHocFilters('foo:"bar"', filters);
switch (operator) {
case '=':
expect(query).toBe('foo:"bar" AND key:"value"');
break;
case '!=':
expect(query).toBe('foo:"bar" AND -key:"value"');
break;
case '=~':
expect(query).toBe('foo:"bar" AND key:/value/');
break;
case '!~':
expect(query).toBe('foo:"bar" AND -key:/value/');
break;
case '>':
expect(query).toBe('foo:"bar" AND key:>value');
break;
case '<':
expect(query).toBe('foo:"bar" AND key:<value');
break;
}
}
);
it('should escape characters in filter keys', () => {
const filters = [{ key: 'field:name', operator: '=', value: 'field:value', condition: '' }];
const query = ds.addAdHocFilters('', filters);
expect(query).toBe('field\\:name:"field:value"');
});
it('should escape characters in filter values', () => {
const filters = [{ key: 'field:name', operator: '=', value: 'field "value"', condition: '' }];
const query = ds.addAdHocFilters('', filters);
expect(query).toBe('field\\:name:"field \\"value\\""');
});
});
describe('with multiple ad hoc filters', () => {
let ds: ElasticDatasource;
const filters = [
{ key: 'bar', operator: '=', value: 'baz', condition: '' },
{ key: 'job', operator: '!=', value: 'grafana', condition: '' },
{ key: 'service', operator: '=~', value: 'service', condition: '' },
{ key: 'count', operator: '>', value: '1', condition: '' },
];
beforeEach(() => {
const { ds: datasource } = getTestContext();
ds = datasource;
});
it('should correctly add ad hoc filters when query is not empty', () => {
const query = ds.addAdHocFilters('foo:"bar" AND test:"test1"', filters);
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('', filters);
expect(query).toBe('bar:"baz" AND -job:"grafana" AND service:/service/ AND count:>1');
});
});
});
const createElasticQuery = (): DataQueryRequest<ElasticsearchQuery> => {
return {
requestId: '',
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'],
},
},
],
},
},
],
},
};
describe('targetContainsTemplate', () => {
let ds: ElasticDatasource;
let target: ElasticsearchQuery;
beforeEach(() => {
const context = getTestContext();
ds = context.ds;
target = {
refId: 'test',
bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '1' }],
metrics: [{ type: 'count', id: '1' }],
query: 'escape\\:test',
};
});
it('returns false when there is no variable in the query', () => {
expect(ds.targetContainsTemplate(target)).toBe(false);
});
it('returns true when there are variables in the query alias', () => {
target.alias = '$variable';
expect(ds.targetContainsTemplate(target)).toBe(true);
});
it('returns true when there are variables in the query', () => {
target.query = '$variable:something';
expect(ds.targetContainsTemplate(target)).toBe(true);
});
it('returns true when there are variables in the bucket aggregation', () => {
target.bucketAggs = [{ type: 'date_histogram', field: '$field', id: '1' }];
expect(ds.targetContainsTemplate(target)).toBe(true);
target.bucketAggs = [{ type: 'date_histogram', field: '@timestamp', id: '1', settings: { interval: '$interval' } }];
expect(ds.targetContainsTemplate(target)).toBe(true);
});
it('returns true when there are variables in the metric aggregation', () => {
target.metrics = [{ type: 'moving_avg', id: '1', settings: { window: '$window' } }];
expect(ds.targetContainsTemplate(target)).toBe(true);
target.metrics = [{ type: 'moving_avg', id: '1', field: '$field' }];
expect(ds.targetContainsTemplate(target)).toBe(true);
target.metrics = [{ type: 'extended_stats', id: '1', meta: { something: '$something' } }];
expect(ds.targetContainsTemplate(target)).toBe(true);
});
it('returns true when there are variables in an array inside an object in metrics', () => {
target.metrics = [
{
field: 'counter',
id: '1',
settings: { percents: ['20', '40', '$qqq'] },
type: 'percentiles',
},
];
expect(ds.targetContainsTemplate(target)).toBe(true);
});
});
describe('ElasticDatasource using backend', () => {
beforeEach(() => {
console.error = jest.fn();
config.featureToggles.enableElasticsearchBackendQuerying = true;
});
afterEach(() => {
console.error = originalConsoleError;
config.featureToggles.enableElasticsearchBackendQuerying = false;
});
describe('annotationQuery', () => {
describe('results processing', () => {
it('should return simple annotations using defaults', async () => {
const { ds, timeRange } = getTestContext();
ds.postResourceRequest = jest.fn().mockResolvedValue({
responses: [
{
hits: {
hits: [
{ _source: { '@timestamp': 1, '@test_tags': 'foo', text: 'abc' } },
{ _source: { '@timestamp': 3, '@test_tags': 'bar', text: 'def' } },
],
},
},
],
});
const annotations = await ds.annotationQuery({
annotation: {},
dashboard: {
getVariables: () => [],
},
range: timeRange,
});
expect(annotations).toHaveLength(2);
expect(annotations[0].time).toBe(1);
expect(annotations[1].time).toBe(3);
});
it('should return annotation events using options', async () => {
const { ds, timeRange } = getTestContext();
ds.postResourceRequest = jest.fn().mockResolvedValue({
responses: [
{
hits: {
hits: [
{ _source: { '@test_time': 1, '@test_tags': 'foo', text: 'abc' } },
{ _source: { '@test_time': 3, '@test_tags': 'bar', text: 'def' } },
],
},
},
],
});
const annotations = await ds.annotationQuery({
annotation: {
timeField: '@test_time',
name: 'foo',
query: 'abc',
tagsField: '@test_tags',
textField: 'text',
},
dashboard: {
getVariables: () => [],
},
range: timeRange,
});
expect(annotations).toHaveLength(2);
expect(annotations[0].time).toBe(1);
expect(annotations[0].tags?.[0]).toBe('foo');
expect(annotations[0].text).toBe('abc');
expect(annotations[1].time).toBe(3);
expect(annotations[1].tags?.[0]).toBe('bar');
expect(annotations[1].text).toBe('def');
});
});
describe('request processing', () => {
it('should process annotation request using options', async () => {
const { ds } = getTestContext();
const postResourceRequestMock = jest.spyOn(ds, 'postResourceRequest').mockResolvedValue({
responses: [
{
hits: {
hits: [
{ _source: { '@test_time': 1, '@test_tags': 'foo', text: 'abc' } },
{ _source: { '@test_time': 3, '@test_tags': 'bar', text: 'def' } },
],
},
},
],
});
await ds.annotationQuery({
annotation: {
timeField: '@test_time',
timeEndField: '@time_end_field',
name: 'foo',
query: 'abc',
tagsField: '@test_tags',
textField: 'text',
},
dashboard: {
getVariables: () => [],
},
range: {
from: dateTime(1683291160012),
to: dateTime(1683291460012),
},
});
expect(postResourceRequestMock).toHaveBeenCalledWith(
'_msearch',
'{"search_type":"query_then_fetch","ignore_unavailable":true,"index":"[test-]YYYY.MM.DD"}\n{"query":{"bool":{"filter":[{"bool":{"should":[{"range":{"@test_time":{"from":1683291160012,"to":1683291460012,"format":"epoch_millis"}}},{"range":{"@time_end_field":{"from":1683291160012,"to":1683291460012,"format":"epoch_millis"}}}],"minimum_should_match":1}},{"query_string":{"query":"abc"}}]}},"size":10000}\n'
);
});
it('should process annotation request using defaults', async () => {
const { ds } = getTestContext();
const postResourceRequestMock = jest.spyOn(ds, 'postResourceRequest').mockResolvedValue({
responses: [
{
hits: {
hits: [
{ _source: { '@test_time': 1, '@test_tags': 'foo', text: 'abc' } },
{ _source: { '@test_time': 3, '@test_tags': 'bar', text: 'def' } },
],
},
},
],
});
await ds.annotationQuery({
annotation: {},
dashboard: {
getVariables: () => [],
},
range: {
from: dateTime(1683291160012),
to: dateTime(1683291460012),
},
});
expect(postResourceRequestMock).toHaveBeenCalledWith(
'_msearch',
'{"search_type":"query_then_fetch","ignore_unavailable":true,"index":"[test-]YYYY.MM.DD"}\n{"query":{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"from":1683291160012,"to":1683291460012,"format":"epoch_millis"}}}],"minimum_should_match":1}}]}},"size":10000}\n'
);
});
it('should process annotation request using dashboard adhoc variables', async () => {
const { ds } = getTestContext();
const postResourceRequestMock = jest.spyOn(ds, 'postResourceRequest').mockResolvedValue({
responses: [
{
hits: {
hits: [
{ _source: { '@test_time': 1, '@test_tags': 'foo', text: 'abc' } },
{ _source: { '@test_time': 3, '@test_tags': 'bar', text: 'def' } },
],
},
},
],
});
await ds.annotationQuery({
annotation: {
timeField: '@test_time',
timeEndField: '@time_end_field',
name: 'foo',
query: 'abc',
tagsField: '@test_tags',
textField: 'text',
datasource: {
type: 'elasticsearch',
uid: 'gdev-elasticsearch',
},
},
dashboard: {
getVariables: () => [
{
type: 'adhoc',
datasource: {
type: 'elasticsearch',
uid: 'gdev-elasticsearch',
},
filters: [
{
key: 'abc_key',
operator: '=',
value: 'abc_value',
},
],
},
],
},
range: {
from: dateTime(1683291160012),
to: dateTime(1683291460012),
},
});
expect(postResourceRequestMock).toHaveBeenCalledWith(
'_msearch',
'{"search_type":"query_then_fetch","ignore_unavailable":true,"index":"[test-]YYYY.MM.DD"}\n{"query":{"bool":{"filter":[{"bool":{"should":[{"range":{"@test_time":{"from":1683291160012,"to":1683291460012,"format":"epoch_millis"}}},{"range":{"@time_end_field":{"from":1683291160012,"to":1683291460012,"format":"epoch_millis"}}}],"minimum_should_match":1}},{"query_string":{"query":"abc AND abc_key:\\"abc_value\\""}}]}},"size":10000}\n'
);
});
});
});
describe('getDatabaseVersion', () => {
it('should correctly get db version', async () => {
const { ds } = getTestContext();
ds.getResource = jest.fn().mockResolvedValue({ version: { number: '8.0.0' } });
const version = await ds.getDatabaseVersion();
expect(version?.raw).toBe('8.0.0');
});
it('should correctly return null if invalid numeric version', async () => {
const { ds } = getTestContext();
ds.getResource = jest.fn().mockResolvedValue({ version: { number: 8 } });
const version = await ds.getDatabaseVersion();
expect(version).toBe(null);
});
it('should correctly return null if rejected request', async () => {
const { ds } = getTestContext();
ds.getResource = jest.fn().mockRejectedValue({});
const version = await ds.getDatabaseVersion();
expect(version).toBe(null);
});
});
describe('metricFindQuery', () => {
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 } = getTestContext();
const postResourceMock = jest.spyOn(ds, 'postResource');
postResourceMock.mockResolvedValue(data);
const results = await ds.metricFindQuery('{"find": "terms", "field": "test"}');
expect(ds.postResource).toHaveBeenCalledTimes(1);
const requestOptions = postResourceMock.mock.calls[0][1];
const parts = requestOptions.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('getFields', () => {
const getFieldsMockData = {
'[test-]YYYY.MM.DD': {
mappings: {
properties: {
'@timestamp_millis': {
type: 'date',
format: 'epoch_millis',
},
classification_terms: {
type: 'keyword',
},
ip_address: {
type: 'ip',
},
justification_blob: {
properties: {
criterion: {
type: 'text',
fields: {
keyword: {
type: 'keyword',
ignore_above: 256,
},
},
},
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',
},
},
},
},
};
it('should not retry when ES is down', async () => {
const twoDaysBefore = toUtc().subtract(2, 'day').format('YYYY.MM.DD');
const { ds, timeRange } = getTestContext({
jsonData: { interval: 'Daily' },
});
ds.getResource = jest.fn().mockImplementation((options) => {
if (options.url === `test-${twoDaysBefore}/_mapping`) {
return of({
data: {},
});
}
return throwError({ status: 500 });
});
await expect(ds.getFields(undefined, timeRange)).toEmitValuesWith((received) => {
expect(received.length).toBe(1);
expect(received[0]).toStrictEqual({ status: 500 });
expect(ds.getResource).toBeCalledTimes(1);
});
});
it('should not retry more than 7 indices', async () => {
const { ds } = getTestContext({
jsonData: { interval: 'Daily' },
});
ds.getResource = jest.fn().mockImplementation(() => {
return throwError({ status: 404 });
});
const timeRange = createTimeRange(dateTime().subtract(2, 'week'), dateTime());
await expect(ds.getFields(undefined, timeRange)).toEmitValuesWith((received) => {
expect(received.length).toBe(1);
expect(received[0]).toStrictEqual('Could not find an available index for this time range.');
expect(ds.getResource).toBeCalledTimes(7);
});
});
it('should return nested fields', async () => {
const { ds } = getTestContext({
jsonData: { interval: 'Daily' },
});
ds.getResource = jest.fn().mockResolvedValue(getFieldsMockData);
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',
'ip_address',
'justification_blob.criterion.keyword',
'justification_blob.criterion',
'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 number fields', async () => {
const { ds } = getTestContext({});
ds.getResource = jest.fn().mockResolvedValue(getFieldsMockData);
await expect(ds.getFields(['number'])).toEmitValuesWith((received) => {
expect(received.length).toBe(1);
const fieldObjects = received[0];
const fields = map(fieldObjects, 'text');
expect(fields).toEqual([
'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 date fields', async () => {
const { ds } = getTestContext({});
ds.getResource = jest.fn().mockResolvedValue(getFieldsMockData);
await expect(ds.getFields(['date'])).toEmitValuesWith((received) => {
expect(received.length).toBe(1);
const fieldObjects = received[0];
const fields = map(fieldObjects, 'text');
expect(fields).toEqual(['@timestamp_millis']);
});
});
});
});