language_utils.ts 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. export const RATE_RANGES = ['1m', '5m', '10m', '30m', '1h'];
  2. export function processLabels(labels, withName = false) {
  3. const values = {};
  4. labels.forEach(l => {
  5. const { __name__, ...rest } = l;
  6. if (withName) {
  7. values['__name__'] = values['__name__'] || [];
  8. if (values['__name__'].indexOf(__name__) === -1) {
  9. values['__name__'].push(__name__);
  10. }
  11. }
  12. Object.keys(rest).forEach(key => {
  13. if (!values[key]) {
  14. values[key] = [];
  15. }
  16. if (values[key].indexOf(rest[key]) === -1) {
  17. values[key].push(rest[key]);
  18. }
  19. });
  20. });
  21. return { values, keys: Object.keys(values) };
  22. }
  23. // const cleanSelectorRegexp = /\{(\w+="[^"\n]*?")(,\w+="[^"\n]*?")*\}/;
  24. export const selectorRegexp = /\{[^}]*?\}/;
  25. export const labelRegexp = /\b(\w+)(!?=~?)("[^"\n]*?")/g;
  26. export function parseSelector(query: string, cursorOffset = 1): { labelKeys: any[]; selector: string } {
  27. if (!query.match(selectorRegexp)) {
  28. // Special matcher for metrics
  29. if (query.match(/^[A-Za-z:][\w:]*$/)) {
  30. return {
  31. selector: `{__name__="${query}"}`,
  32. labelKeys: ['__name__'],
  33. };
  34. }
  35. throw new Error('Query must contain a selector: ' + query);
  36. }
  37. // Check if inside a selector
  38. const prefix = query.slice(0, cursorOffset);
  39. const prefixOpen = prefix.lastIndexOf('{');
  40. const prefixClose = prefix.lastIndexOf('}');
  41. if (prefixOpen === -1) {
  42. throw new Error('Not inside selector, missing open brace: ' + prefix);
  43. }
  44. if (prefixClose > -1 && prefixClose > prefixOpen) {
  45. throw new Error('Not inside selector, previous selector already closed: ' + prefix);
  46. }
  47. const suffix = query.slice(cursorOffset);
  48. const suffixCloseIndex = suffix.indexOf('}');
  49. const suffixClose = suffixCloseIndex + cursorOffset;
  50. const suffixOpenIndex = suffix.indexOf('{');
  51. const suffixOpen = suffixOpenIndex + cursorOffset;
  52. if (suffixClose === -1) {
  53. throw new Error('Not inside selector, missing closing brace in suffix: ' + suffix);
  54. }
  55. if (suffixOpenIndex > -1 && suffixOpen < suffixClose) {
  56. throw new Error('Not inside selector, next selector opens before this one closed: ' + suffix);
  57. }
  58. // Extract clean labels to form clean selector, incomplete labels are dropped
  59. const selector = query.slice(prefixOpen, suffixClose);
  60. const labels = {};
  61. selector.replace(labelRegexp, (_, key, operator, value) => {
  62. labels[key] = { value, operator };
  63. return '';
  64. });
  65. // Add metric if there is one before the selector
  66. const metricPrefix = query.slice(0, prefixOpen);
  67. const metricMatch = metricPrefix.match(/[A-Za-z:][\w:]*$/);
  68. if (metricMatch) {
  69. labels['__name__'] = { value: `"${metricMatch[0]}"`, operator: '=' };
  70. }
  71. // Build sorted selector
  72. const labelKeys = Object.keys(labels).sort();
  73. const cleanSelector = labelKeys.map(key => `${key}${labels[key].operator}${labels[key].value}`).join(',');
  74. const selectorString = ['{', cleanSelector, '}'].join('');
  75. return { labelKeys, selector: selectorString };
  76. }
  77. export function expandRecordingRules(query: string, mapping: { [name: string]: string }): string {
  78. const ruleNames = Object.keys(mapping);
  79. const rulesRegex = new RegExp(`(\\s|^)(${ruleNames.join('|')})(\\s|$|\\(|\\[|\\{)`, 'ig');
  80. return query.replace(rulesRegex, (match, pre, name, post) => `${pre}${mapping[name]}${post}`);
  81. }