query_hints.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import _ from 'lodash';
  2. import { QueryHint } from '@grafana/ui/src/types';
  3. /**
  4. * Number of time series results needed before starting to suggest sum aggregation hints
  5. */
  6. export const SUM_HINT_THRESHOLD_COUNT = 20;
  7. export function getQueryHints(query: string, series?: any[], datasource?: any): QueryHint[] {
  8. const hints = [];
  9. // ..._bucket metric needs a histogram_quantile()
  10. const histogramMetric = query.trim().match(/^\w+_bucket$/);
  11. if (histogramMetric) {
  12. const label = 'Time series has buckets, you probably wanted a histogram.';
  13. hints.push({
  14. type: 'HISTOGRAM_QUANTILE',
  15. label,
  16. fix: {
  17. label: 'Fix by adding histogram_quantile().',
  18. action: {
  19. type: 'ADD_HISTOGRAM_QUANTILE',
  20. query,
  21. },
  22. },
  23. });
  24. }
  25. // Check for monotonicity on series (table results are being ignored here)
  26. if (series && series.length > 0) {
  27. series.forEach(s => {
  28. const datapoints: number[][] = s.datapoints;
  29. if (query.indexOf('rate(') === -1 && datapoints.length > 1) {
  30. let increasing = false;
  31. const nonNullData = datapoints.filter(dp => dp[0] !== null);
  32. const monotonic = nonNullData.every((dp, index) => {
  33. if (index === 0) {
  34. return true;
  35. }
  36. increasing = increasing || dp[0] > nonNullData[index - 1][0];
  37. // monotonic?
  38. return dp[0] >= nonNullData[index - 1][0];
  39. });
  40. if (increasing && monotonic) {
  41. const simpleMetric = query.trim().match(/^\w+$/);
  42. let label = 'Time series is monotonically increasing.';
  43. let fix;
  44. if (simpleMetric) {
  45. fix = {
  46. label: 'Fix by adding rate().',
  47. action: {
  48. type: 'ADD_RATE',
  49. query,
  50. },
  51. };
  52. } else {
  53. label = `${label} Try applying a rate() function.`;
  54. }
  55. hints.push({
  56. type: 'APPLY_RATE',
  57. label,
  58. fix,
  59. });
  60. }
  61. }
  62. });
  63. }
  64. // Check for recording rules expansion
  65. if (datasource && datasource.ruleMappings) {
  66. const mapping = datasource.ruleMappings;
  67. const mappingForQuery = Object.keys(mapping).reduce((acc, ruleName) => {
  68. if (query.search(ruleName) > -1) {
  69. return {
  70. ...acc,
  71. [ruleName]: mapping[ruleName],
  72. };
  73. }
  74. return acc;
  75. }, {});
  76. if (_.size(mappingForQuery) > 0) {
  77. const label = 'Query contains recording rules.';
  78. hints.push({
  79. type: 'EXPAND_RULES',
  80. label,
  81. fix: {
  82. label: 'Expand rules',
  83. action: {
  84. type: 'EXPAND_RULES',
  85. query,
  86. mapping: mappingForQuery,
  87. },
  88. },
  89. });
  90. }
  91. }
  92. if (series && series.length >= SUM_HINT_THRESHOLD_COUNT) {
  93. const simpleMetric = query.trim().match(/^\w+$/);
  94. if (simpleMetric) {
  95. hints.push({
  96. type: 'ADD_SUM',
  97. label: 'Many time series results returned.',
  98. fix: {
  99. label: 'Consider aggregating with sum().',
  100. action: {
  101. type: 'ADD_SUM',
  102. query: query,
  103. preventSubmit: true,
  104. },
  105. },
  106. });
  107. }
  108. }
  109. return hints.length > 0 ? hints : null;
  110. }