diff --git a/.betterer.results b/.betterer.results index d69333d6934..97910a53208 100644 --- a/.betterer.results +++ b/.betterer.results @@ -3877,23 +3877,7 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "3"], [0, 0, 0, "Unexpected any. Specify a different type.", "4"], [0, 0, 0, "Unexpected any. Specify a different type.", "5"], - [0, 0, 0, "Unexpected any. Specify a different type.", "6"], - [0, 0, 0, "Unexpected any. Specify a different type.", "7"], - [0, 0, 0, "Unexpected any. Specify a different type.", "8"], - [0, 0, 0, "Unexpected any. Specify a different type.", "9"], - [0, 0, 0, "Unexpected any. Specify a different type.", "10"], - [0, 0, 0, "Unexpected any. Specify a different type.", "11"], - [0, 0, 0, "Unexpected any. Specify a different type.", "12"], - [0, 0, 0, "Unexpected any. Specify a different type.", "13"], - [0, 0, 0, "Unexpected any. Specify a different type.", "14"], - [0, 0, 0, "Unexpected any. Specify a different type.", "15"], - [0, 0, 0, "Unexpected any. Specify a different type.", "16"], - [0, 0, 0, "Unexpected any. Specify a different type.", "17"], - [0, 0, 0, "Unexpected any. Specify a different type.", "18"], - [0, 0, 0, "Unexpected any. Specify a different type.", "19"], - [0, 0, 0, "Unexpected any. Specify a different type.", "20"], - [0, 0, 0, "Unexpected any. Specify a different type.", "21"], - [0, 0, 0, "Unexpected any. Specify a different type.", "22"] + [0, 0, 0, "Unexpected any. Specify a different type.", "6"] ], "public/app/plugins/datasource/elasticsearch/hooks/useStatelessReducer.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] diff --git a/public/app/plugins/datasource/elasticsearch/datasource.ts b/public/app/plugins/datasource/elasticsearch/datasource.ts index c8cd535de34..933efdbcb9b 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.ts +++ b/public/app/plugins/datasource/elasticsearch/datasource.ts @@ -51,6 +51,7 @@ import { isPipelineAggregationWithMultipleBucketPaths, } from './components/QueryEditor/MetricAggregationsEditor/aggregations'; import { metricAggregationConfig } from './components/QueryEditor/MetricAggregationsEditor/utils'; +import { isMetricAggregationWithMeta } from './guards'; import { trackAnnotationQuery, trackQuery } from './tracking'; import { Logs, @@ -60,6 +61,8 @@ import { ElasticsearchQuery, TermsQuery, Interval, + ElasticsearchAnnotationQuery, + RangeMap, } from './types'; import { getScriptValue, isSupportedVersion, unsupportedVersionMessage } from './utils'; @@ -233,17 +236,7 @@ export class ElasticDatasource ); } - private prepareAnnotationRequest(options: { - annotation: { - target: ElasticsearchQuery; - timeField?: string; - timeEndField?: string; - titleField?: string; - query?: string; - index?: string; - }; - range: TimeRange; - }) { + private prepareAnnotationRequest(options: { annotation: ElasticsearchAnnotationQuery; range: TimeRange }) { const annotation = options.annotation; const timeField = annotation.timeField || '@timestamp'; const timeEndField = annotation.timeEndField || null; @@ -260,7 +253,7 @@ export class ElasticDatasource const queryString = annotation.query ?? annotation.target?.query ?? ''; const dateRanges = []; - const rangeStart: any = {}; + const rangeStart: RangeMap = {}; rangeStart[timeField] = { from: options.range.from.valueOf(), to: options.range.to.valueOf(), @@ -269,7 +262,7 @@ export class ElasticDatasource dateRanges.push({ range: rangeStart }); if (timeEndField) { - const rangeEnd: any = {}; + const rangeEnd: RangeMap = {}; rangeEnd[timeEndField] = { from: options.range.from.valueOf(), to: options.range.to.valueOf(), @@ -279,7 +272,9 @@ export class ElasticDatasource } const queryInterpolated = this.interpolateLuceneQuery(queryString); - const query: any = { + const query: { + bool: { filter: Array>>> }; + } = { bool: { filter: [ { @@ -299,12 +294,12 @@ export class ElasticDatasource }, }); } - const data: any = { + const data = { query, size: 10000, }; - const header: any = { + const header: Record = { search_type: 'query_then_fetch', ignore_unavailable: true, }; @@ -322,17 +317,8 @@ export class ElasticDatasource } private processHitsToAnnotationEvents( - annotation: { - target: ElasticsearchQuery; - timeField?: string; - titleField?: string; - timeEndField?: string; - query?: string; - tagsField?: string; - textField?: string; - index?: string; - }, - hits: Array<{ [key: string]: any }> + annotation: ElasticsearchAnnotationQuery, + hits: Array>> ) { const timeField = annotation.timeField || '@timestamp'; const timeEndField = annotation.timeEndField || null; @@ -340,7 +326,7 @@ export class ElasticDatasource const tagsField = annotation.tagsField || null; const list: AnnotationEvent[] = []; - const getFieldFromSource = (source: any, fieldName: any) => { + const getFieldFromSource = (source: any, fieldName: string | null) => { if (!fieldName) { return; } @@ -363,7 +349,7 @@ export class ElasticDatasource let time = getFieldFromSource(source, timeField); if (typeof hits[i].fields !== 'undefined') { const fields = hits[i].fields; - if (isString(fields[timeField]) || isNumber(fields[timeField])) { + if (typeof fields === 'object' && (isString(fields[timeField]) || isNumber(fields[timeField]))) { time = fields[timeField]; } } @@ -419,7 +405,7 @@ export class ElasticDatasource return lastValueFrom( this.getFields(['date']).pipe( mergeMap((dateFields) => { - const timeField: any = find(dateFields, { text: this.timeField }); + const timeField = find(dateFields, { text: this.timeField }); if (!timeField) { return of({ status: 'error', @@ -437,8 +423,8 @@ export class ElasticDatasource ); } - getQueryHeader(searchType: any, timeFrom?: DateTime, timeTo?: DateTime): string { - const queryHeader: any = { + getQueryHeader(searchType: string, timeFrom?: DateTime, timeTo?: DateTime): string { + const queryHeader = { search_type: searchType, ignore_unavailable: true, index: this.indexPattern.getIndexList(timeFrom, timeTo), @@ -680,8 +666,8 @@ export class ElasticDatasource }; // Store subfield names: [system, process, cpu, total] -> system.process.cpu.total - const fieldNameParts: any = []; - const fields: any = {}; + const fieldNameParts: string[] = []; + const fields: Record = {}; function getFieldsRecursively(obj: any) { for (const key in obj) { @@ -778,7 +764,7 @@ export class ElasticDatasource return ('_msearch?' + searchParams.toString()).replace(/\?$/, ''); } - metricFindQuery(query: string, options?: any): Promise { + metricFindQuery(query: string, options?: { range: TimeRange }): Promise { const range = options?.range; const parsedQuery = JSON.parse(query); if (query) { @@ -801,36 +787,48 @@ export class ElasticDatasource return lastValueFrom(this.getFields()); } - getTagValues(options: any) { + getTagValues(options: { key: string }) { const range = this.timeSrv.timeRange(); return lastValueFrom(this.getTerms({ field: options.key }, range)); } - targetContainsTemplate(target: any) { + targetContainsTemplate(target: ElasticsearchQuery) { if (this.templateSrv.containsTemplate(target.query) || this.templateSrv.containsTemplate(target.alias)) { return true; } - for (const bucketAgg of target.bucketAggs) { - if (this.templateSrv.containsTemplate(bucketAgg.field) || this.objectContainsTemplate(bucketAgg.settings)) { - return true; + if (target.bucketAggs) { + for (const bucketAgg of target.bucketAggs) { + if (isBucketAggregationWithField(bucketAgg) && this.templateSrv.containsTemplate(bucketAgg.field)) { + return true; + } + if (this.objectContainsTemplate(bucketAgg.settings)) { + return true; + } } } - for (const metric of target.metrics) { - if ( - this.templateSrv.containsTemplate(metric.field) || - this.objectContainsTemplate(metric.settings) || - this.objectContainsTemplate(metric.meta) - ) { - return true; + if (target.metrics) { + for (const metric of target.metrics) { + if (!isMetricAggregationWithField(metric)) { + continue; + } + if (metric.field && this.templateSrv.containsTemplate(metric.field)) { + return true; + } + if (metric.settings && this.objectContainsTemplate(metric.settings)) { + return true; + } + if (isMetricAggregationWithMeta(metric) && this.objectContainsTemplate(metric.meta)) { + return true; + } } } return false; } - private isPrimitive(obj: any) { + private isPrimitive(obj: unknown) { if (obj === null || obj === undefined) { return true; } diff --git a/public/app/plugins/datasource/elasticsearch/guards.test.ts b/public/app/plugins/datasource/elasticsearch/guards.test.ts new file mode 100644 index 00000000000..99176096a6e --- /dev/null +++ b/public/app/plugins/datasource/elasticsearch/guards.test.ts @@ -0,0 +1,23 @@ +import { Count, ExtendedStats } from './dataquery.gen'; +import { isMetricAggregationWithMeta } from './guards'; + +describe('Type guards', () => { + test('Identifies metrics with meta attribute', () => { + const metric: ExtendedStats = { + id: 'test', + type: 'extended_stats', + meta: { + test: 'test', + }, + }; + expect(isMetricAggregationWithMeta(metric)).toBe(true); + }); + + test('Identifies metrics without meta attribute', () => { + const metric: Count = { + id: 'test', + type: 'count', + }; + expect(isMetricAggregationWithMeta(metric)).toBe(false); + }); +}); diff --git a/public/app/plugins/datasource/elasticsearch/guards.ts b/public/app/plugins/datasource/elasticsearch/guards.ts new file mode 100644 index 00000000000..77faedc1a2a --- /dev/null +++ b/public/app/plugins/datasource/elasticsearch/guards.ts @@ -0,0 +1,8 @@ +import { ExtendedStats, MetricAggregation } from './dataquery.gen'; + +export function isMetricAggregationWithMeta(metric: MetricAggregation): metric is ExtendedStats { + if (!metric || typeof metric !== 'object') { + return false; + } + return 'meta' in metric; +} diff --git a/public/app/plugins/datasource/elasticsearch/tracking.ts b/public/app/plugins/datasource/elasticsearch/tracking.ts index 720a7eef24c..bc0456756e6 100644 --- a/public/app/plugins/datasource/elasticsearch/tracking.ts +++ b/public/app/plugins/datasource/elasticsearch/tracking.ts @@ -4,7 +4,7 @@ import { variableRegex } from 'app/features/variables/utils'; import { REF_ID_STARTER_LOG_VOLUME } from './datasource'; import pluginJson from './plugin.json'; -import { ElasticsearchQuery } from './types'; +import { ElasticsearchAnnotationQuery, ElasticsearchQuery } from './types'; type ElasticSearchOnDashboardLoadedTrackingEvent = { grafana_version?: string; @@ -141,16 +141,7 @@ export function trackQuery( } } -export function trackAnnotationQuery(annotation: { - target: ElasticsearchQuery; - timeField?: string; - timeEndField?: string; - query?: string; - tagsField?: string; - textField?: string; - index?: string; - [key: string]: unknown; -}): void { +export function trackAnnotationQuery(annotation: ElasticsearchAnnotationQuery): void { reportInteraction('grafana_elasticsearch_annotation_query_executed', { grafana_version: config.buildInfo.version, has_target_query: !!annotation.target?.query, diff --git a/public/app/plugins/datasource/elasticsearch/types.ts b/public/app/plugins/datasource/elasticsearch/types.ts index c7f7e68df62..12cf4a48646 100644 --- a/public/app/plugins/datasource/elasticsearch/types.ts +++ b/public/app/plugins/datasource/elasticsearch/types.ts @@ -14,6 +14,7 @@ import { MovingAverage as SchemaMovingAverage, BucketAggregation, Logs as SchemaLogs, + Elasticsearch, } from './dataquery.gen'; export * from './dataquery.gen'; @@ -121,3 +122,17 @@ export type DataLinkConfig = { urlDisplayLabel?: string; datasourceUid?: string; }; + +export interface ElasticsearchAnnotationQuery { + target: Elasticsearch; + timeField?: string; + titleField?: string; + timeEndField?: string; + query?: string; + tagsField?: string; + textField?: string; + // @deprecated index is deprecated and will be removed in the future + index?: string; +} + +export type RangeMap = Record;