PanelQueryRunner.test.ts 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import { PanelQueryRunner } from './PanelQueryRunner';
  2. import {
  3. PanelData,
  4. DataQueryRequest,
  5. DataStreamObserver,
  6. DataStreamState,
  7. LoadingState,
  8. ScopedVars,
  9. } from '@grafana/ui/src/types';
  10. import { dateTime } from '@grafana/ui/src/utils/moment_wrapper';
  11. jest.mock('app/core/services/backend_srv');
  12. interface ScenarioContext {
  13. setup: (fn: () => void) => void;
  14. maxDataPoints?: number | null;
  15. widthPixels: number;
  16. dsInterval?: string;
  17. minInterval?: string;
  18. events?: PanelData[];
  19. res?: PanelData;
  20. queryCalledWith?: DataQueryRequest;
  21. observer: DataStreamObserver;
  22. runner: PanelQueryRunner;
  23. scopedVars: ScopedVars;
  24. }
  25. type ScenarioFn = (ctx: ScenarioContext) => void;
  26. function describeQueryRunnerScenario(description: string, scenarioFn: ScenarioFn) {
  27. describe(description, () => {
  28. let setupFn = () => {};
  29. const ctx: ScenarioContext = {
  30. widthPixels: 200,
  31. scopedVars: {
  32. server: { text: 'Server1', value: 'server-1' },
  33. },
  34. runner: new PanelQueryRunner(),
  35. observer: (args: any) => {},
  36. setup: (fn: () => void) => {
  37. setupFn = fn;
  38. },
  39. };
  40. const response: any = {
  41. data: [{ target: 'hello', datapoints: [] }],
  42. };
  43. beforeEach(async () => {
  44. setupFn();
  45. const datasource: any = {
  46. name: 'TestDB',
  47. interval: ctx.dsInterval,
  48. query: (options: DataQueryRequest, observer: DataStreamObserver) => {
  49. ctx.queryCalledWith = options;
  50. ctx.observer = observer;
  51. return Promise.resolve(response);
  52. },
  53. testDatasource: jest.fn(),
  54. };
  55. const args: any = {
  56. datasource,
  57. scopedVars: ctx.scopedVars,
  58. minInterval: ctx.minInterval,
  59. widthPixels: ctx.widthPixels,
  60. maxDataPoints: ctx.maxDataPoints,
  61. timeRange: {
  62. from: dateTime().subtract(1, 'days'),
  63. to: dateTime(),
  64. raw: { from: '1h', to: 'now' },
  65. },
  66. panelId: 0,
  67. queries: [{ refId: 'A', test: 1 }],
  68. };
  69. ctx.runner = new PanelQueryRunner();
  70. ctx.runner.subscribe({
  71. next: (data: PanelData) => {
  72. ctx.events.push(data);
  73. },
  74. });
  75. ctx.events = [];
  76. ctx.res = await ctx.runner.run(args);
  77. });
  78. scenarioFn(ctx);
  79. });
  80. }
  81. describe('PanelQueryRunner', () => {
  82. describeQueryRunnerScenario('simple scenario', ctx => {
  83. it('should set requestId on request', async () => {
  84. expect(ctx.queryCalledWith.requestId).toBe('Q100');
  85. });
  86. it('should set datasource name on request', async () => {
  87. expect(ctx.queryCalledWith.targets[0].datasource).toBe('TestDB');
  88. });
  89. it('should pass scopedVars to datasource with interval props', async () => {
  90. expect(ctx.queryCalledWith.scopedVars.server.text).toBe('Server1');
  91. expect(ctx.queryCalledWith.scopedVars.__interval.text).toBe('5m');
  92. expect(ctx.queryCalledWith.scopedVars.__interval_ms.text).toBe('300000');
  93. });
  94. });
  95. describeQueryRunnerScenario('with no maxDataPoints or minInterval', ctx => {
  96. ctx.setup(() => {
  97. ctx.maxDataPoints = null;
  98. ctx.widthPixels = 200;
  99. });
  100. it('should return data', async () => {
  101. expect(ctx.res.error).toBeUndefined();
  102. expect(ctx.res.series.length).toBe(1);
  103. });
  104. it('should use widthPixels as maxDataPoints', async () => {
  105. expect(ctx.queryCalledWith.maxDataPoints).toBe(200);
  106. });
  107. it('should calculate interval based on width', async () => {
  108. expect(ctx.queryCalledWith.interval).toBe('5m');
  109. });
  110. it('fast query should only publish 1 data events', async () => {
  111. expect(ctx.events.length).toBe(1);
  112. });
  113. });
  114. describeQueryRunnerScenario('with no panel min interval but datasource min interval', ctx => {
  115. ctx.setup(() => {
  116. ctx.widthPixels = 20000;
  117. ctx.dsInterval = '15s';
  118. });
  119. it('should limit interval to data source min interval', async () => {
  120. expect(ctx.queryCalledWith.interval).toBe('15s');
  121. });
  122. });
  123. describeQueryRunnerScenario('with panel min interval and data source min interval', ctx => {
  124. ctx.setup(() => {
  125. ctx.widthPixels = 20000;
  126. ctx.dsInterval = '15s';
  127. ctx.minInterval = '30s';
  128. });
  129. it('should limit interval to panel min interval', async () => {
  130. expect(ctx.queryCalledWith.interval).toBe('30s');
  131. });
  132. });
  133. describeQueryRunnerScenario('with maxDataPoints', ctx => {
  134. ctx.setup(() => {
  135. ctx.maxDataPoints = 10;
  136. });
  137. it('should pass maxDataPoints if specified', async () => {
  138. expect(ctx.queryCalledWith.maxDataPoints).toBe(10);
  139. });
  140. });
  141. describeQueryRunnerScenario('when datasource is streaming data', ctx => {
  142. let streamState: DataStreamState;
  143. let isUnsubbed = false;
  144. beforeEach(() => {
  145. streamState = {
  146. state: LoadingState.Streaming,
  147. key: 'test-stream-1',
  148. series: [
  149. {
  150. rows: [],
  151. fields: [],
  152. name: 'I am a magic stream',
  153. },
  154. ],
  155. request: {
  156. requestId: ctx.queryCalledWith.requestId,
  157. } as any,
  158. unsubscribe: () => {
  159. isUnsubbed = true;
  160. },
  161. };
  162. ctx.observer(streamState);
  163. });
  164. it('should push another update to subscriber', async () => {
  165. expect(ctx.events.length).toBe(2);
  166. });
  167. it('should set state to streaming', async () => {
  168. expect(ctx.events[1].state).toBe(LoadingState.Streaming);
  169. });
  170. it('should not unsubscribe', async () => {
  171. expect(isUnsubbed).toBe(false);
  172. });
  173. it('destroy should unsubscribe streams', async () => {
  174. ctx.runner.destroy();
  175. expect(isUnsubbed).toBe(true);
  176. });
  177. });
  178. });