backendResultTransformer.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. import { DataQueryResponse, DataFrame, isDataFrame, FieldType, QueryResultMeta, DataQueryError } from '@grafana/data';
  2. import { getDerivedFields } from './getDerivedFields';
  3. import { makeTableFrames } from './makeTableFrames';
  4. import { formatQuery, getHighlighterExpressionsFromQuery } from './query_utils';
  5. import { dataFrameHasLokiError } from './responseUtils';
  6. import { DerivedFieldConfig, LokiQuery, LokiQueryType } from './types';
  7. function isMetricFrame(frame: DataFrame): boolean {
  8. return frame.fields.every((field) => field.type === FieldType.time || field.type === FieldType.number);
  9. }
  10. // returns a new frame, with meta shallow merged with it's original meta
  11. function setFrameMeta(frame: DataFrame, meta: QueryResultMeta): DataFrame {
  12. const { meta: oldMeta, ...rest } = frame;
  13. // meta maybe be undefined, we need to handle that
  14. const newMeta = { ...oldMeta, ...meta };
  15. return {
  16. ...rest,
  17. meta: newMeta,
  18. };
  19. }
  20. function processStreamFrame(
  21. frame: DataFrame,
  22. query: LokiQuery | undefined,
  23. derivedFieldConfigs: DerivedFieldConfig[]
  24. ): DataFrame {
  25. const custom: Record<string, string> = {
  26. ...frame.meta?.custom, // keep the original meta.custom
  27. // used by logs_model
  28. lokiQueryStatKey: 'Summary: total bytes processed',
  29. };
  30. if (dataFrameHasLokiError(frame)) {
  31. custom.error = 'Error when parsing some of the logs';
  32. }
  33. const meta: QueryResultMeta = {
  34. preferredVisualisationType: 'logs',
  35. limit: query?.maxLines,
  36. searchWords: query !== undefined ? getHighlighterExpressionsFromQuery(formatQuery(query.expr)) : undefined,
  37. custom,
  38. };
  39. const newFrame = setFrameMeta(frame, meta);
  40. const derivedFields = getDerivedFields(newFrame, derivedFieldConfigs);
  41. return {
  42. ...newFrame,
  43. fields: [...newFrame.fields, ...derivedFields],
  44. };
  45. }
  46. function processStreamsFrames(
  47. frames: DataFrame[],
  48. queryMap: Map<string, LokiQuery>,
  49. derivedFieldConfigs: DerivedFieldConfig[]
  50. ): DataFrame[] {
  51. return frames.map((frame) => {
  52. const query = frame.refId !== undefined ? queryMap.get(frame.refId) : undefined;
  53. return processStreamFrame(frame, query, derivedFieldConfigs);
  54. });
  55. }
  56. function processMetricInstantFrames(frames: DataFrame[]): DataFrame[] {
  57. return frames.length > 0 ? makeTableFrames(frames) : [];
  58. }
  59. function processMetricRangeFrames(frames: DataFrame[]): DataFrame[] {
  60. const meta: QueryResultMeta = { preferredVisualisationType: 'graph' };
  61. return frames.map((frame) => setFrameMeta(frame, meta));
  62. }
  63. // we split the frames into 3 groups, because we will handle
  64. // each group slightly differently
  65. function groupFrames(
  66. frames: DataFrame[],
  67. queryMap: Map<string, LokiQuery>
  68. ): {
  69. streamsFrames: DataFrame[];
  70. metricInstantFrames: DataFrame[];
  71. metricRangeFrames: DataFrame[];
  72. } {
  73. const streamsFrames: DataFrame[] = [];
  74. const metricInstantFrames: DataFrame[] = [];
  75. const metricRangeFrames: DataFrame[] = [];
  76. frames.forEach((frame) => {
  77. if (!isMetricFrame(frame)) {
  78. streamsFrames.push(frame);
  79. } else {
  80. const isInstantFrame = frame.refId != null && queryMap.get(frame.refId)?.queryType === LokiQueryType.Instant;
  81. if (isInstantFrame) {
  82. metricInstantFrames.push(frame);
  83. } else {
  84. metricRangeFrames.push(frame);
  85. }
  86. }
  87. });
  88. return { streamsFrames, metricInstantFrames, metricRangeFrames };
  89. }
  90. function improveError(error: DataQueryError | undefined, queryMap: Map<string, LokiQuery>): DataQueryError | undefined {
  91. // many things are optional in an error-object, we need an error-message to exist,
  92. // and we need to find the loki-query, based on the refId in the error-object.
  93. if (error === undefined) {
  94. return error;
  95. }
  96. const { refId, message } = error;
  97. if (refId === undefined || message === undefined) {
  98. return error;
  99. }
  100. const query = queryMap.get(refId);
  101. if (query === undefined) {
  102. return error;
  103. }
  104. if (message.includes('escape') && query.expr.includes('\\')) {
  105. return {
  106. ...error,
  107. message: `${message}. Make sure that all special characters are escaped with \\. For more information on escaping of special characters visit LogQL documentation at https://grafana.com/docs/loki/latest/logql/.`,
  108. };
  109. }
  110. return error;
  111. }
  112. export function transformBackendResult(
  113. response: DataQueryResponse,
  114. queries: LokiQuery[],
  115. derivedFieldConfigs: DerivedFieldConfig[]
  116. ): DataQueryResponse {
  117. const { data, error, ...rest } = response;
  118. // in the typescript type, data is an array of basically anything.
  119. // we do know that they have to be dataframes, so we make a quick check,
  120. // this way we can be sure, and also typescript is happy.
  121. const dataFrames = data.map((d) => {
  122. if (!isDataFrame(d)) {
  123. throw new Error('transformation only supports dataframe responses');
  124. }
  125. return d;
  126. });
  127. const queryMap = new Map(queries.map((query) => [query.refId, query]));
  128. const { streamsFrames, metricInstantFrames, metricRangeFrames } = groupFrames(dataFrames, queryMap);
  129. return {
  130. ...rest,
  131. error: improveError(error, queryMap),
  132. data: [
  133. ...processMetricRangeFrames(metricRangeFrames),
  134. ...processMetricInstantFrames(metricInstantFrames),
  135. ...processStreamsFrames(streamsFrames, queryMap, derivedFieldConfigs),
  136. ],
  137. };
  138. }