backendResultTransformer.test.ts 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. import { cloneDeep } from 'lodash';
  2. import { ArrayVector, DataFrame, DataQueryResponse, Field, FieldType } from '@grafana/data';
  3. import { transformBackendResult } from './backendResultTransformer';
  4. // needed because the derived-fields functionality calls it
  5. jest.mock('@grafana/runtime', () => ({
  6. // @ts-ignore
  7. ...jest.requireActual('@grafana/runtime'),
  8. getDataSourceSrv: () => {
  9. return {
  10. getInstanceSettings: () => {
  11. return { name: 'Loki1' };
  12. },
  13. };
  14. },
  15. }));
  16. const LOKI_EXPR = '{level="info"} |= "thing1"';
  17. const inputFrame: DataFrame = {
  18. refId: 'A',
  19. meta: {
  20. executedQueryString: LOKI_EXPR,
  21. custom: {
  22. frameType: 'LabeledTimeValues',
  23. },
  24. },
  25. fields: [
  26. {
  27. name: 'time',
  28. type: FieldType.time,
  29. config: {},
  30. values: new ArrayVector([1645030244810, 1645030247027]),
  31. },
  32. {
  33. name: 'value',
  34. type: FieldType.string,
  35. config: {},
  36. values: new ArrayVector(['line1', 'line2']),
  37. },
  38. {
  39. name: 'labels',
  40. type: FieldType.string,
  41. config: {},
  42. values: new ArrayVector([
  43. { level: 'info', code: '41🌙' },
  44. { level: 'error', code: '41🌙' },
  45. ]),
  46. },
  47. {
  48. name: 'tsNs',
  49. type: FieldType.string,
  50. config: {},
  51. values: new ArrayVector(['1645030244810757120', '1645030247027735040']),
  52. },
  53. {
  54. name: 'id',
  55. type: FieldType.string,
  56. config: {},
  57. values: new ArrayVector(['id1', 'id2']),
  58. },
  59. ],
  60. length: 5,
  61. };
  62. describe('loki backendResultTransformer', () => {
  63. it('processes a logs-dataframe correctly', () => {
  64. const response: DataQueryResponse = { data: [cloneDeep(inputFrame)] };
  65. const expectedFrame = cloneDeep(inputFrame);
  66. expectedFrame.meta = {
  67. ...expectedFrame.meta,
  68. preferredVisualisationType: 'logs',
  69. searchWords: ['thing1'],
  70. custom: {
  71. ...expectedFrame.meta?.custom,
  72. lokiQueryStatKey: 'Summary: total bytes processed',
  73. },
  74. };
  75. const expected: DataQueryResponse = { data: [expectedFrame] };
  76. const result = transformBackendResult(
  77. response,
  78. [
  79. {
  80. refId: 'A',
  81. expr: LOKI_EXPR,
  82. },
  83. ],
  84. []
  85. );
  86. expect(result).toEqual(expected);
  87. });
  88. it('applies maxLines correctly', () => {
  89. const response: DataQueryResponse = { data: [cloneDeep(inputFrame)] };
  90. const frame1: DataFrame = transformBackendResult(
  91. response,
  92. [
  93. {
  94. refId: 'A',
  95. expr: LOKI_EXPR,
  96. },
  97. ],
  98. []
  99. ).data[0];
  100. expect(frame1.meta?.limit).toBeUndefined();
  101. const frame2 = transformBackendResult(
  102. response,
  103. [
  104. {
  105. refId: 'A',
  106. expr: LOKI_EXPR,
  107. maxLines: 42,
  108. },
  109. ],
  110. []
  111. ).data[0];
  112. expect(frame2.meta?.limit).toBe(42);
  113. });
  114. it('processed derived fields correctly', () => {
  115. const input: DataFrame = {
  116. length: 1,
  117. fields: [
  118. {
  119. name: 'time',
  120. config: {},
  121. values: new ArrayVector([1]),
  122. type: FieldType.time,
  123. },
  124. {
  125. name: 'line',
  126. config: {},
  127. values: new ArrayVector(['line1']),
  128. type: FieldType.string,
  129. },
  130. ],
  131. };
  132. const response: DataQueryResponse = { data: [input] };
  133. const result = transformBackendResult(
  134. response,
  135. [{ refId: 'A', expr: '' }],
  136. [
  137. {
  138. matcherRegex: 'trace=(w+)',
  139. name: 'derived1',
  140. url: 'example.com',
  141. },
  142. ]
  143. );
  144. expect(
  145. result.data[0].fields.filter((field: Field) => field.name === 'derived1' && field.type === 'string').length
  146. ).toBe(1);
  147. });
  148. it('handle loki parsing errors', () => {
  149. const clonedFrame = cloneDeep(inputFrame);
  150. clonedFrame.fields[2] = {
  151. name: 'labels',
  152. type: FieldType.string,
  153. config: {},
  154. values: new ArrayVector([
  155. { level: 'info', code: '41🌙', __error__: 'LogfmtParserErr' },
  156. { level: 'error', code: '41🌙' },
  157. ]),
  158. };
  159. const response: DataQueryResponse = { data: [clonedFrame] };
  160. const result = transformBackendResult(
  161. response,
  162. [
  163. {
  164. refId: 'A',
  165. expr: LOKI_EXPR,
  166. },
  167. ],
  168. []
  169. );
  170. expect(result.data[0]?.meta?.custom?.error).toBe('Error when parsing some of the logs');
  171. });
  172. it('improve loki escaping error message when query contains escape', () => {
  173. const response: DataQueryResponse = {
  174. data: [],
  175. error: {
  176. refId: 'A',
  177. message: 'parse error at line 1, col 2: invalid char escape',
  178. },
  179. };
  180. const result = transformBackendResult(
  181. response,
  182. [
  183. {
  184. refId: 'A',
  185. expr: '{place="g\\arden"}',
  186. },
  187. ],
  188. []
  189. );
  190. expect(result.error?.message).toBe(
  191. `parse error at line 1, col 2: invalid char escape. 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/.`
  192. );
  193. });
  194. it('do not change loki escaping error message when query does not contain escape', () => {
  195. const response: DataQueryResponse = {
  196. data: [],
  197. error: {
  198. refId: 'A',
  199. message: 'parse error at line 1, col 2: invalid char escape',
  200. },
  201. };
  202. const result = transformBackendResult(
  203. response,
  204. [
  205. {
  206. refId: 'A',
  207. expr: '{place="garden"}',
  208. },
  209. ],
  210. []
  211. );
  212. expect(result.error?.message).toBe('parse error at line 1, col 2: invalid char escape');
  213. });
  214. });