resultTransformer.test.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import { collectorTypes } from '@opentelemetry/exporter-collector';
  2. import {
  3. ArrayVector,
  4. FieldType,
  5. MutableDataFrame,
  6. PluginType,
  7. DataSourceInstanceSettings,
  8. dateTime,
  9. } from '@grafana/data';
  10. import {
  11. SearchResponse,
  12. createTableFrame,
  13. transformToOTLP,
  14. transformFromOTLP,
  15. transformTrace,
  16. createTableFrameFromSearch,
  17. } from './resultTransformer';
  18. import {
  19. badOTLPResponse,
  20. otlpDataFrameToResponse,
  21. otlpDataFrameFromResponse,
  22. otlpResponse,
  23. tempoSearchResponse,
  24. } from './testResponse';
  25. const defaultSettings: DataSourceInstanceSettings = {
  26. id: 0,
  27. uid: '0',
  28. type: 'tracing',
  29. name: 'tempo',
  30. access: 'proxy',
  31. meta: {
  32. id: 'tempo',
  33. name: 'tempo',
  34. type: PluginType.datasource,
  35. info: {} as any,
  36. module: '',
  37. baseUrl: '',
  38. },
  39. jsonData: {},
  40. };
  41. describe('transformTraceList()', () => {
  42. const lokiDataFrame = new MutableDataFrame({
  43. fields: [
  44. {
  45. name: 'ts',
  46. type: FieldType.time,
  47. values: ['2020-02-12T15:05:14.265Z', '2020-02-12T15:05:15.265Z', '2020-02-12T15:05:16.265Z'],
  48. },
  49. {
  50. name: 'line',
  51. type: FieldType.string,
  52. values: [
  53. 't=2020-02-12T15:04:51+0000 lvl=info msg="Starting Grafana" logger=server',
  54. 't=2020-02-12T15:04:52+0000 lvl=info msg="Starting Grafana" logger=server traceID=asdfa1234',
  55. 't=2020-02-12T15:04:53+0000 lvl=info msg="Starting Grafana" logger=server traceID=asdf88',
  56. ],
  57. },
  58. ],
  59. meta: {
  60. preferredVisualisationType: 'table',
  61. },
  62. });
  63. test('extracts traceIDs from log lines', () => {
  64. const frame = createTableFrame(lokiDataFrame, 't1', 'tempo', ['traceID=(\\w+)', 'traceID=(\\w\\w)']);
  65. expect(frame.fields[0].name).toBe('Time');
  66. expect(frame.fields[0].values.get(0)).toBe('2020-02-12T15:05:15.265Z');
  67. expect(frame.fields[1].name).toBe('traceID');
  68. expect(frame.fields[1].values.get(0)).toBe('asdfa1234');
  69. // Second match in new line
  70. expect(frame.fields[0].values.get(1)).toBe('2020-02-12T15:05:15.265Z');
  71. expect(frame.fields[1].values.get(1)).toBe('as');
  72. });
  73. });
  74. describe('transformToOTLP()', () => {
  75. test('transforms dataframe to OTLP format', () => {
  76. const otlp = transformToOTLP(otlpDataFrameToResponse);
  77. expect(otlp).toMatchObject(otlpResponse);
  78. });
  79. });
  80. describe('transformFromOTLP()', () => {
  81. test('transforms OTLP format to dataFrame', () => {
  82. const res = transformFromOTLP(
  83. otlpResponse.batches as unknown as collectorTypes.opentelemetryProto.trace.v1.ResourceSpans[],
  84. false
  85. );
  86. expect(res.data[0]).toMatchObject(otlpDataFrameFromResponse);
  87. });
  88. });
  89. describe('createTableFrameFromSearch()', () => {
  90. const mockTimeUnix = dateTime(1643357709095).valueOf();
  91. global.Date.now = jest.fn(() => mockTimeUnix);
  92. test('transforms search response to dataFrame', () => {
  93. const frame = createTableFrameFromSearch(tempoSearchResponse.traces as SearchResponse[], defaultSettings);
  94. expect(frame.fields[0].name).toBe('traceID');
  95. expect(frame.fields[0].values.get(0)).toBe('e641dcac1c3a0565');
  96. // TraceID must have unit = 'string' to prevent the ID from rendering as Infinity
  97. expect(frame.fields[0].config.unit).toBe('string');
  98. expect(frame.fields[1].name).toBe('traceName');
  99. expect(frame.fields[1].values.get(0)).toBe('c10d7ca4e3a00354 ');
  100. // expect time in ago format if startTime less than 1 hour
  101. expect(frame.fields[2].name).toBe('startTime');
  102. expect(frame.fields[2].values.get(0)).toBe('15 minutes ago');
  103. // expect time in format if startTime greater than 1 hour
  104. expect(frame.fields[2].values.get(1)).toBe('2022-01-27 22:56:06');
  105. expect(frame.fields[3].name).toBe('duration');
  106. expect(frame.fields[3].values.get(0)).toBe(65);
  107. });
  108. });
  109. describe('transformFromOTLP()', () => {
  110. // Mock the console error so that running the test suite doesnt throw the error
  111. const origError = console.error;
  112. const consoleErrorMock = jest.fn();
  113. afterEach(() => (console.error = origError));
  114. beforeEach(() => (console.error = consoleErrorMock));
  115. test('if passed bad data, will surface an error', () => {
  116. const res = transformFromOTLP(
  117. badOTLPResponse.batches as unknown as collectorTypes.opentelemetryProto.trace.v1.ResourceSpans[],
  118. false
  119. );
  120. expect(res.data[0]).toBeFalsy();
  121. expect(res.error?.message).toBeTruthy();
  122. // if it does have resources, no error will be thrown
  123. expect({
  124. ...res.data[0],
  125. resources: {
  126. attributes: [
  127. { key: 'service.name', value: { stringValue: 'db' } },
  128. { key: 'job', value: { stringValue: 'tns/db' } },
  129. { key: 'opencensus.exporterversion', value: { stringValue: 'Jaeger-Go-2.22.1' } },
  130. { key: 'host.name', value: { stringValue: '63d16772b4a2' } },
  131. { key: 'ip', value: { stringValue: '0.0.0.0' } },
  132. { key: 'client-uuid', value: { stringValue: '39fb01637a579639' } },
  133. ],
  134. },
  135. }).not.toBeFalsy();
  136. });
  137. });
  138. describe('transformTrace()', () => {
  139. // Mock the console error so that running the test suite doesnt throw the error
  140. const origError = console.error;
  141. const consoleErrorMock = jest.fn();
  142. afterEach(() => (console.error = origError));
  143. beforeEach(() => (console.error = consoleErrorMock));
  144. const badFrame = new MutableDataFrame({
  145. fields: [
  146. {
  147. name: 'serviceTags',
  148. values: new ArrayVector([undefined]),
  149. },
  150. ],
  151. });
  152. const goodFrame = new MutableDataFrame({
  153. fields: [
  154. {
  155. name: 'serviceTags',
  156. values: new ArrayVector(),
  157. },
  158. ],
  159. });
  160. test('if passed bad data, will surface an error', () => {
  161. const response = transformTrace({ data: [badFrame] }, false);
  162. expect(response.data[0]).toBeFalsy();
  163. expect(response.error?.message).toBeTruthy();
  164. });
  165. test('if passed good data, will parse successfully', () => {
  166. const response2 = transformTrace({ data: [goodFrame] }, false);
  167. expect(response2.data[0]).toBeTruthy();
  168. expect(response2.data[0]).toMatchObject(goodFrame);
  169. expect(response2.error).toBeFalsy();
  170. });
  171. });