|
|
|
@ -12,9 +12,9 @@ import { |
|
|
|
|
import { parseSelector } from 'app/plugins/datasource/prometheus/language_utils'; |
|
|
|
|
import PromqlSyntax from 'app/plugins/datasource/prometheus/promql'; |
|
|
|
|
|
|
|
|
|
const DEFAULT_KEYS = ['job', 'instance']; |
|
|
|
|
const DEFAULT_KEYS = ['job', 'namespace']; |
|
|
|
|
const EMPTY_SELECTOR = '{}'; |
|
|
|
|
const HISTORY_ITEM_COUNT = 5; |
|
|
|
|
const HISTORY_ITEM_COUNT = 10; |
|
|
|
|
const HISTORY_COUNT_CUTOFF = 1000 * 60 * 60 * 24; // 24h
|
|
|
|
|
|
|
|
|
|
const wrapLabel = (label: string) => ({ label }); |
|
|
|
@ -65,7 +65,7 @@ export default class LoggingLanguageProvider extends LanguageProvider { |
|
|
|
|
start = () => { |
|
|
|
|
if (!this.started) { |
|
|
|
|
this.started = true; |
|
|
|
|
return Promise.all([this.fetchLogLabels()]); |
|
|
|
|
return this.fetchLogLabels(); |
|
|
|
|
} |
|
|
|
|
return Promise.resolve([]); |
|
|
|
|
}; |
|
|
|
@ -118,35 +118,36 @@ export default class LoggingLanguageProvider extends LanguageProvider { |
|
|
|
|
|
|
|
|
|
getLabelCompletionItems({ text, wrapperClasses, labelKey, value }: TypeaheadInput): TypeaheadOutput { |
|
|
|
|
let context: string; |
|
|
|
|
let refresher: Promise<any> = null; |
|
|
|
|
const suggestions: CompletionItemGroup[] = []; |
|
|
|
|
const line = value.anchorBlock.getText(); |
|
|
|
|
const cursorOffset: number = value.anchorOffset; |
|
|
|
|
|
|
|
|
|
// Get normalized selector
|
|
|
|
|
let selector; |
|
|
|
|
// Use EMPTY_SELECTOR until series API is implemented for facetting
|
|
|
|
|
const selector = EMPTY_SELECTOR; |
|
|
|
|
let parsedSelector; |
|
|
|
|
try { |
|
|
|
|
parsedSelector = parseSelector(line, cursorOffset); |
|
|
|
|
selector = parsedSelector.selector; |
|
|
|
|
} catch { |
|
|
|
|
selector = EMPTY_SELECTOR; |
|
|
|
|
} |
|
|
|
|
const containsMetric = selector.indexOf('__name__=') > -1; |
|
|
|
|
} catch {} |
|
|
|
|
const existingKeys = parsedSelector ? parsedSelector.labelKeys : []; |
|
|
|
|
|
|
|
|
|
if ((text && text.match(/^!?=~?/)) || _.includes(wrapperClasses, 'attr-value')) { |
|
|
|
|
// Label values
|
|
|
|
|
if (labelKey && this.labelValues[selector] && this.labelValues[selector][labelKey]) { |
|
|
|
|
if (labelKey && this.labelValues[selector]) { |
|
|
|
|
const labelValues = this.labelValues[selector][labelKey]; |
|
|
|
|
context = 'context-label-values'; |
|
|
|
|
suggestions.push({ |
|
|
|
|
label: `Label values for "${labelKey}"`, |
|
|
|
|
items: labelValues.map(wrapLabel), |
|
|
|
|
}); |
|
|
|
|
if (labelValues) { |
|
|
|
|
context = 'context-label-values'; |
|
|
|
|
suggestions.push({ |
|
|
|
|
label: `Label values for "${labelKey}"`, |
|
|
|
|
items: labelValues.map(wrapLabel), |
|
|
|
|
}); |
|
|
|
|
} else { |
|
|
|
|
refresher = this.fetchLabelValues(labelKey); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
// Label keys
|
|
|
|
|
const labelKeys = this.labelKeys[selector] || (containsMetric ? null : DEFAULT_KEYS); |
|
|
|
|
const labelKeys = this.labelKeys[selector] || DEFAULT_KEYS; |
|
|
|
|
if (labelKeys) { |
|
|
|
|
const possibleKeys = _.difference(labelKeys, existingKeys); |
|
|
|
|
if (possibleKeys.length > 0) { |
|
|
|
@ -156,7 +157,7 @@ export default class LoggingLanguageProvider extends LanguageProvider { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return { context, suggestions }; |
|
|
|
|
return { context, refresher, suggestions }; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async fetchLogLabels() { |
|
|
|
@ -165,29 +166,18 @@ export default class LoggingLanguageProvider extends LanguageProvider { |
|
|
|
|
const res = await this.request(url); |
|
|
|
|
const body = await (res.data || res.json()); |
|
|
|
|
const labelKeys = body.data.slice().sort(); |
|
|
|
|
const labelKeysBySelector = { |
|
|
|
|
this.labelKeys = { |
|
|
|
|
...this.labelKeys, |
|
|
|
|
[EMPTY_SELECTOR]: labelKeys, |
|
|
|
|
}; |
|
|
|
|
const labelValuesByKey = {}; |
|
|
|
|
this.logLabelOptions = []; |
|
|
|
|
for (const key of labelKeys) { |
|
|
|
|
const valuesUrl = `/api/prom/label/${key}/values`; |
|
|
|
|
const res = await this.request(valuesUrl); |
|
|
|
|
const body = await (res.data || res.json()); |
|
|
|
|
const values = body.data.slice().sort(); |
|
|
|
|
labelValuesByKey[key] = values; |
|
|
|
|
this.logLabelOptions.push({ |
|
|
|
|
label: key, |
|
|
|
|
value: key, |
|
|
|
|
children: values.map(value => ({ label: value, value })), |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
this.labelValues = { [EMPTY_SELECTOR]: labelValuesByKey }; |
|
|
|
|
this.labelKeys = labelKeysBySelector; |
|
|
|
|
this.logLabelOptions = labelKeys.map(key => ({ label: key, value: key, isLeaf: false })); |
|
|
|
|
|
|
|
|
|
// Pre-load values for default labels
|
|
|
|
|
return labelKeys.filter(key => DEFAULT_KEYS.indexOf(key) > -1).map(key => this.fetchLabelValues(key)); |
|
|
|
|
} catch (e) { |
|
|
|
|
console.error(e); |
|
|
|
|
} |
|
|
|
|
return []; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async fetchLabelValues(key: string) { |
|
|
|
@ -195,14 +185,28 @@ export default class LoggingLanguageProvider extends LanguageProvider { |
|
|
|
|
try { |
|
|
|
|
const res = await this.request(url); |
|
|
|
|
const body = await (res.data || res.json()); |
|
|
|
|
const values = body.data.slice().sort(); |
|
|
|
|
|
|
|
|
|
// Add to label options
|
|
|
|
|
this.logLabelOptions = this.logLabelOptions.map(keyOption => { |
|
|
|
|
if (keyOption.value === key) { |
|
|
|
|
return { |
|
|
|
|
...keyOption, |
|
|
|
|
children: values.map(value => ({ label: value, value })), |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
return keyOption; |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// Add to key map
|
|
|
|
|
const exisingValues = this.labelValues[EMPTY_SELECTOR]; |
|
|
|
|
const values = { |
|
|
|
|
const nextValues = { |
|
|
|
|
...exisingValues, |
|
|
|
|
[key]: body.data, |
|
|
|
|
[key]: values, |
|
|
|
|
}; |
|
|
|
|
this.labelValues = { |
|
|
|
|
...this.labelValues, |
|
|
|
|
[EMPTY_SELECTOR]: values, |
|
|
|
|
[EMPTY_SELECTOR]: nextValues, |
|
|
|
|
}; |
|
|
|
|
} catch (e) { |
|
|
|
|
console.error(e); |
|
|
|
|