import { ElasticsearchQuery } from './types'; import { DataQuery, LanguageProvider } from '@grafana/data'; import { ElasticDatasource } from './datasource'; import { PromQuery } from '../prometheus/types'; import Prism, { Token } from 'prismjs'; import grammar from '../prometheus/promql'; import { defaultBucketAgg } from './query_def'; function getNameLabelValue(promQuery: string, tokens: any): string { let nameLabelValue = ''; for (let prop in tokens) { if (typeof tokens[prop] === 'string') { nameLabelValue = tokens[prop] as string; break; } } return nameLabelValue; } function extractPrometheusLabels(promQuery: string): string[][] { const labels: string[][] = []; if (!promQuery || promQuery.length === 0) { return labels; } const tokens = Prism.tokenize(promQuery, grammar); const nameLabelValue = getNameLabelValue(promQuery, tokens); if (nameLabelValue && nameLabelValue.length > 0) { labels.push(['__name__', '=', '"' + nameLabelValue + '"']); } for (let prop in tokens) { if (tokens[prop] instanceof Token) { let token: Token = tokens[prop] as Token; if (token.type === 'context-labels') { let labelKey = ''; let labelValue = ''; let labelOperator = ''; let contentTokens: any[] = token.content as any[]; for (let currentToken in contentTokens) { if (typeof contentTokens[currentToken] === 'string') { let currentStr: string; currentStr = contentTokens[currentToken] as string; if (currentStr === '=' || currentStr === '!=' || currentStr === '=~' || currentStr === '!~') { labelOperator = currentStr; } } else if (contentTokens[currentToken] instanceof Token) { switch (contentTokens[currentToken].type) { case 'label-key': labelKey = contentTokens[currentToken].content as string; break; case 'label-value': labelValue = contentTokens[currentToken].content as string; labels.push([labelKey, labelOperator, labelValue]); break; } } } } } } return labels; } function getElasticsearchQuery(prometheusLabels: string[][]): string { let elasticsearchLuceneLabels = []; for (let keyOperatorValue of prometheusLabels) { switch (keyOperatorValue[1]) { case '=': { elasticsearchLuceneLabels.push(keyOperatorValue[0] + ':' + keyOperatorValue[2]); break; } case '!=': { elasticsearchLuceneLabels.push('NOT ' + keyOperatorValue[0] + ':' + keyOperatorValue[2]); break; } case '=~': { elasticsearchLuceneLabels.push( keyOperatorValue[0] + ':/' + keyOperatorValue[2].substring(1, keyOperatorValue[2].length - 1) + '/' ); break; } case '!~': { elasticsearchLuceneLabels.push( 'NOT ' + keyOperatorValue[0] + ':/' + keyOperatorValue[2].substring(1, keyOperatorValue[2].length - 1) + '/' ); break; } } } return elasticsearchLuceneLabels.join(' AND '); } export default class ElasticsearchLanguageProvider extends LanguageProvider { request: (url: string, params?: any) => Promise; start: () => Promise; datasource: ElasticDatasource; constructor(datasource: ElasticDatasource, initialValues?: any) { super(); this.datasource = datasource; Object.assign(this, initialValues); } /** * The current implementation only supports switching from Prometheus/Loki queries. * For them we transform the query to an ES Logs query since it's the behaviour most users expect. * For every other datasource we just copy the refId and let the query editor initialize a default query. * */ importQueries(queries: DataQuery[], datasourceType: string): ElasticsearchQuery[] { if (datasourceType === 'prometheus' || datasourceType === 'loki') { return queries.map((query) => { let prometheusQuery = query as PromQuery; const expr = getElasticsearchQuery(extractPrometheusLabels(prometheusQuery.expr)); return { metrics: [ { id: '1', type: 'logs', }, ], bucketAggs: [{ ...defaultBucketAgg('2'), field: this.datasource.timeField }], query: expr, refId: query.refId, }; }); } return queries.map((query) => { return { refId: query.refId, }; }); } }