PanelQueryRunner.test.ts 5.6 KB

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