Files
grafana/public/app/plugins/datasource/loki/components/monaco-query-field/monaco-completion-provider/CompletionDataProvider.ts
Ivana Huckova 4fd1d92332 Loki: Remove relying on timeSrv.timeRange in LanguageProvider (#78450)
* Loki: Allow setting of timeRange when using languageProvider functions

* Loki: Use timerange where available for start

* Loki: Use timerange where available for fetchLabels

* Loki: Use timerange where available for fetchSeriesLabels

* Loki: Use timerange where available for fetchLabelValues

* Loki: Use timerange where available for getParserAndLabelKeys

* Loki: Update and add tests for fetchLabels

* Loki: Update and add tests for fetchSeriesLabels

* Loki: Update and add tests for fetchSeries

* Loki: Update and add tests for fetchLabelValues

* Loki: Update and add tests for fetchLabelValues

* Loki: Update and add tests for getParserAndLabelKeys

* Update public/app/plugins/datasource/loki/LanguageProvider.test.ts

Co-authored-by: Matias Chomicki <matyax@gmail.com>

* Update public/app/plugins/datasource/loki/LanguageProvider.test.ts

Co-authored-by: Matias Chomicki <matyax@gmail.com>

* Not needing to use languageProvider.getDefaultTime in Monaco

* Update comment

* Update getDefaultTimeRange to be ptivate

---------

Co-authored-by: Matias Chomicki <matyax@gmail.com>
2023-11-22 14:35:15 +01:00

99 lines
3.7 KiB
TypeScript

import { chain } from 'lodash';
import { HistoryItem, TimeRange } from '@grafana/data';
import { escapeLabelValueInExactSelector } from 'app/plugins/datasource/prometheus/language_utils';
import LanguageProvider from '../../../LanguageProvider';
import { ParserAndLabelKeysResult, LokiQuery } from '../../../types';
import { Label } from './situation';
interface HistoryRef {
current: Array<HistoryItem<LokiQuery>>;
}
export class CompletionDataProvider {
constructor(
private languageProvider: LanguageProvider,
private historyRef: HistoryRef = { current: [] },
private timeRange: TimeRange | undefined
) {
this.queryToLabelKeysCache = new Map();
}
private queryToLabelKeysCache: Map<string, ParserAndLabelKeysResult>;
private buildSelector(labels: Label[]): string {
const allLabelTexts = labels.map(
(label) => `${label.name}${label.op}"${escapeLabelValueInExactSelector(label.value)}"`
);
return `{${allLabelTexts.join(',')}}`;
}
getHistory() {
return chain(this.historyRef.current)
.map((history: HistoryItem<LokiQuery>) => history.query.expr)
.filter()
.uniq()
.value();
}
async getLabelNames(otherLabels: Label[] = []) {
if (otherLabels.length === 0) {
// if there is no filtering, we have to use a special endpoint
return this.languageProvider.getLabelKeys();
}
const data = await this.getSeriesLabels(otherLabels);
const possibleLabelNames = Object.keys(data); // all names from datasource
const usedLabelNames = new Set(otherLabels.map((l) => l.name)); // names used in the query
return possibleLabelNames.filter((label) => !usedLabelNames.has(label));
}
async getLabelValues(labelName: string, otherLabels: Label[]) {
if (otherLabels.length === 0) {
// if there is no filtering, we have to use a special endpoint
return await this.languageProvider.fetchLabelValues(labelName, { timeRange: this.timeRange });
}
const data = await this.getSeriesLabels(otherLabels);
return data[labelName] ?? [];
}
/**
* Runs a Loki query to extract label keys from the result.
* The result is cached for the query string.
*
* Since various "situations" in the monaco code editor trigger this function, it is prone to being called multiple times for the same query
* Here is a lightweight and simple cache to avoid calling the backend multiple times for the same query.
*
* @param logQuery
*/
async getParserAndLabelKeys(logQuery: string): Promise<ParserAndLabelKeysResult> {
const EXTRACTED_LABEL_KEYS_MAX_CACHE_SIZE = 2;
const cachedLabelKeys = this.queryToLabelKeysCache.has(logQuery) ? this.queryToLabelKeysCache.get(logQuery) : null;
if (cachedLabelKeys) {
// cache hit! Serve stale result from cache
return cachedLabelKeys;
} else {
// If cache is larger than max size, delete the first (oldest) index
if (this.queryToLabelKeysCache.size >= EXTRACTED_LABEL_KEYS_MAX_CACHE_SIZE) {
// Make room in the cache for the fresh result by deleting the "first" index
const keys = this.queryToLabelKeysCache.keys();
const firstKey = keys.next().value;
this.queryToLabelKeysCache.delete(firstKey);
}
// Fetch a fresh result from the backend
const labelKeys = await this.languageProvider.getParserAndLabelKeys(logQuery, { timeRange: this.timeRange });
// Add the result to the cache
this.queryToLabelKeysCache.set(logQuery, labelKeys);
return labelKeys;
}
}
async getSeriesLabels(labels: Label[]) {
return await this.languageProvider
.fetchSeriesLabels(this.buildSelector(labels), { timeRange: this.timeRange })
.then((data) => data ?? {});
}
}