1import { GraphCtrl } from '../module';
2import { MetricsPanelCtrl } from 'app/angular/panel/metrics_panel_ctrl';
3import { PanelCtrl } from 'app/angular/panel/panel_ctrl';
4import config from 'app/core/config';
5
6import TimeSeries from 'app/core/time_series2';
7import $ from 'jquery';
8import { graphDirective, GraphElement } from '../graph';
9import { dateTime, EventBusSrv } from '@grafana/data';
10import { DashboardModel } from '../../../../features/dashboard/state';
11
12jest.mock('../event_manager', () => ({
13  EventManager: () => {
14    return {
15      on: () => {},
16      addFlotEvents: () => {},
17    };
18  },
19}));
20
21jest.mock('app/core/core', () => ({
22  coreModule: {
23    directive: () => {},
24  },
25  appEvents: {
26    subscribe: () => {},
27    on: () => {},
28  },
29}));
30
31const ctx = {} as any;
32let ctrl: any;
33const scope = {
34  ctrl: {},
35  range: {
36    from: dateTime([2015, 1, 1]),
37    to: dateTime([2015, 11, 20]),
38  },
39  $on: () => {},
40};
41let link;
42
43describe('grafanaGraph', () => {
44  const setupCtx = (beforeRender?: any) => {
45    config.bootData = {
46      user: {
47        lightTheme: false,
48      },
49    };
50    GraphCtrl.prototype = {
51      ...MetricsPanelCtrl.prototype,
52      ...PanelCtrl.prototype,
53      ...GraphCtrl.prototype,
54      height: 200,
55      panel: {
56        events: {
57          on: () => {},
58          emit: () => {},
59        },
60        legend: {},
61        grid: {},
62        yaxes: [
63          {
64            min: null,
65            max: null,
66            format: 'short',
67            logBase: 1,
68          },
69          {
70            min: null,
71            max: null,
72            format: 'short',
73            logBase: 1,
74          },
75        ],
76        thresholds: [],
77        xaxis: {},
78        seriesOverrides: [],
79        tooltip: {
80          shared: true,
81        },
82        fieldConfig: {
83          defaults: {},
84        },
85      },
86      renderingCompleted: jest.fn(),
87      hiddenSeries: {},
88      dashboard: {
89        getTimezone: () => 'browser',
90        events: new EventBusSrv(),
91      },
92      range: {
93        from: dateTime([2015, 1, 1, 10]),
94        to: dateTime([2015, 1, 1, 22]),
95      },
96      annotationsSrv: {
97        getAnnotations: () => Promise.resolve({}),
98      },
99    } as any;
100
101    ctx.data = [];
102    ctx.data.push(
103      new TimeSeries({
104        datapoints: [
105          [1, 1],
106          [2, 2],
107        ],
108        alias: 'series1',
109      })
110    );
111    ctx.data.push(
112      new TimeSeries({
113        datapoints: [
114          [10, 1],
115          [20, 2],
116        ],
117        alias: 'series2',
118      })
119    );
120
121    ctrl = new GraphCtrl(
122      {
123        $on: () => {},
124        $parent: {
125          panel: GraphCtrl.prototype.panel,
126          dashboard: GraphCtrl.prototype.dashboard,
127        },
128      },
129      {
130        get: () => {},
131      } as any
132    );
133
134    // @ts-ignore
135    $.plot = ctrl.plot = jest.fn();
136    scope.ctrl = ctrl;
137
138    link = graphDirective({} as any, {}, {} as any).link(scope, {
139      width: () => 500,
140      mouseleave: () => {},
141      bind: () => {},
142    } as any);
143    if (typeof beforeRender === 'function') {
144      beforeRender();
145    }
146    link.data = ctx.data;
147
148    //Emulate functions called by event listeners
149    link.buildFlotPairs(link.data);
150    link.renderPanel();
151    ctx.plotData = ctrl.plot.mock.calls[0][1];
152
153    ctx.plotOptions = ctrl.plot.mock.calls[0][2];
154  };
155
156  describe('simple lines options', () => {
157    beforeEach(() => {
158      setupCtx(() => {
159        ctrl.panel.lines = true;
160        ctrl.panel.fill = 5;
161        ctrl.panel.linewidth = 3;
162        ctrl.panel.steppedLine = true;
163      });
164    });
165
166    it('should configure plot with correct options', () => {
167      expect(ctx.plotOptions.series.lines.show).toBe(true);
168      expect(ctx.plotOptions.series.lines.fill).toBe(0.5);
169      expect(ctx.plotOptions.series.lines.lineWidth).toBe(3);
170      expect(ctx.plotOptions.series.lines.steps).toBe(true);
171    });
172  });
173
174  describe('sorting stacked series as legend. disabled', () => {
175    beforeEach(() => {
176      setupCtx(() => {
177        ctrl.panel.legend.sort = undefined;
178        ctrl.panel.stack = false;
179      });
180    });
181
182    it('should not modify order of time series', () => {
183      expect(ctx.plotData[0].alias).toBe('series1');
184      expect(ctx.plotData[1].alias).toBe('series2');
185    });
186  });
187
188  describe('sorting stacked series as legend. min descending order', () => {
189    beforeEach(() => {
190      setupCtx(() => {
191        const sortKey = 'min';
192        ctrl.panel.legend.sort = sortKey;
193        ctrl.panel.legend.sortDesc = true;
194        ctrl.panel.legend.alignAsTable = true;
195        ctrl.panel.legend[sortKey] = true;
196        ctrl.panel.stack = true;
197      });
198    });
199    it('highest value should be first', () => {
200      expect(ctx.plotData[0].alias).toBe('series2');
201      expect(ctx.plotData[1].alias).toBe('series1');
202    });
203  });
204
205  describe('sorting stacked series as legend. min ascending order', () => {
206    beforeEach(() => {
207      setupCtx(() => {
208        ctrl.panel.legend.sort = 'min';
209        ctrl.panel.legend.sortDesc = false;
210        ctrl.panel.stack = true;
211      });
212    });
213    it('lowest value should be first', () => {
214      expect(ctx.plotData[0].alias).toBe('series1');
215      expect(ctx.plotData[1].alias).toBe('series2');
216    });
217  });
218
219  describe('sorting stacked series as legend. stacking disabled', () => {
220    beforeEach(() => {
221      setupCtx(() => {
222        ctrl.panel.legend.sort = 'min';
223        ctrl.panel.legend.sortDesc = true;
224        ctrl.panel.stack = false;
225      });
226    });
227
228    it('highest value should be first', () => {
229      expect(ctx.plotData[0].alias).toBe('series1');
230      expect(ctx.plotData[1].alias).toBe('series2');
231    });
232  });
233
234  describe('sorting stacked series as legend. current descending order', () => {
235    beforeEach(() => {
236      setupCtx(() => {
237        const sortKey = 'current';
238        ctrl.panel.legend.sort = sortKey;
239        ctrl.panel.legend.sortDesc = true;
240        ctrl.panel.legend.alignAsTable = true;
241        ctrl.panel.legend[sortKey] = true;
242        ctrl.panel.stack = true;
243      });
244    });
245
246    it('highest last value should be first', () => {
247      expect(ctx.plotData[0].alias).toBe('series2');
248      expect(ctx.plotData[1].alias).toBe('series1');
249    });
250  });
251
252  describe('stacked series should not sort if legend is not as table or sort key column is not visible', () => {
253    beforeEach(() => {
254      setupCtx(() => {
255        const sortKey = 'min';
256        ctrl.panel.legend.sort = sortKey;
257        ctrl.panel.legend.sortDesc = true;
258        ctrl.panel.legend.alignAsTable = false;
259        ctrl.panel.legend[sortKey] = false;
260        ctrl.panel.stack = true;
261      });
262    });
263    it('highest value should be first', () => {
264      expect(ctx.plotData[0].alias).toBe('series1');
265      expect(ctx.plotData[1].alias).toBe('series2');
266    });
267  });
268
269  describe('when logBase is log 10', () => {
270    beforeEach(() => {
271      setupCtx(() => {
272        ctx.data[0] = new TimeSeries({
273          datapoints: [
274            [2000, 1],
275            [0.002, 2],
276            [0, 3],
277            [-1, 4],
278          ],
279          alias: 'seriesAutoscale',
280        });
281        ctx.data[0].yaxis = 1;
282        ctx.data[1] = new TimeSeries({
283          datapoints: [
284            [2000, 1],
285            [0.002, 2],
286            [0, 3],
287            [-1, 4],
288          ],
289          alias: 'seriesFixedscale',
290        });
291        ctx.data[1].yaxis = 2;
292        ctrl.panel.yaxes[0].logBase = 10;
293
294        ctrl.panel.yaxes[1].logBase = 10;
295        ctrl.panel.yaxes[1].min = '0.05';
296        ctrl.panel.yaxes[1].max = '1500';
297      });
298    });
299
300    it('should apply axis transform, autoscaling (if necessary) and ticks', () => {
301      const axisAutoscale = ctx.plotOptions.yaxes[0];
302      expect(axisAutoscale.transform(100)).toBe(2);
303      expect(axisAutoscale.inverseTransform(-3)).toBeCloseTo(0.001);
304      expect(axisAutoscale.min).toBeCloseTo(0.001);
305      expect(axisAutoscale.max).toBe(10000);
306      expect(axisAutoscale.ticks.length).toBeCloseTo(8);
307      expect(axisAutoscale.ticks[0]).toBeCloseTo(0.001);
308      if (axisAutoscale.ticks.length === 7) {
309        expect(axisAutoscale.ticks[axisAutoscale.ticks.length - 1]).toBeCloseTo(1000);
310      } else {
311        expect(axisAutoscale.ticks[axisAutoscale.ticks.length - 1]).toBe(10000);
312      }
313
314      const axisFixedscale = ctx.plotOptions.yaxes[1];
315      expect(axisFixedscale.min).toBe(0.05);
316      expect(axisFixedscale.max).toBe(1500);
317      expect(axisFixedscale.ticks.length).toBe(5);
318      expect(axisFixedscale.ticks[0]).toBe(0.1);
319      expect(axisFixedscale.ticks[4]).toBe(1000);
320    });
321  });
322
323  describe('when logBase is log 10 and data points contain only zeroes', () => {
324    beforeEach(() => {
325      setupCtx(() => {
326        ctrl.panel.yaxes[0].logBase = 10;
327        ctx.data[0] = new TimeSeries({
328          datapoints: [
329            [0, 1],
330            [0, 2],
331            [0, 3],
332            [0, 4],
333          ],
334          alias: 'seriesAutoscale',
335        });
336        ctx.data[0].yaxis = 1;
337      });
338    });
339
340    it('should not set min and max and should create some fake ticks', () => {
341      const axisAutoscale = ctx.plotOptions.yaxes[0];
342      expect(axisAutoscale.transform(100)).toBe(2);
343      expect(axisAutoscale.inverseTransform(-3)).toBeCloseTo(0.001);
344      expect(axisAutoscale.min).toBe(undefined);
345      expect(axisAutoscale.max).toBe(undefined);
346      expect(axisAutoscale.ticks.length).toBe(2);
347      expect(axisAutoscale.ticks[0]).toBe(1);
348      expect(axisAutoscale.ticks[1]).toBe(2);
349    });
350  });
351
352  // y-min set 0 is a special case for log scale,
353  // this approximates it by setting min to 0.1
354  describe('when logBase is log 10 and y-min is set to 0 and auto min is > 0.1', () => {
355    beforeEach(() => {
356      setupCtx(() => {
357        ctrl.panel.yaxes[0].logBase = 10;
358        ctrl.panel.yaxes[0].min = '0';
359        ctx.data[0] = new TimeSeries({
360          datapoints: [
361            [2000, 1],
362            [4, 2],
363            [500, 3],
364            [3000, 4],
365          ],
366          alias: 'seriesAutoscale',
367        });
368        ctx.data[0].yaxis = 1;
369      });
370    });
371    it('should set min to 0.1 and add a tick for 0.1', () => {
372      const axisAutoscale = ctx.plotOptions.yaxes[0];
373      expect(axisAutoscale.transform(100)).toBe(2);
374      expect(axisAutoscale.inverseTransform(-3)).toBeCloseTo(0.001);
375      expect(axisAutoscale.min).toBe(0.1);
376      expect(axisAutoscale.max).toBe(10000);
377      expect(axisAutoscale.ticks.length).toBe(6);
378      expect(axisAutoscale.ticks[0]).toBe(0.1);
379      expect(axisAutoscale.ticks[5]).toBe(10000);
380    });
381  });
382
383  describe('when logBase is log 2 and y-min is set to 0 and num of ticks exceeds max', () => {
384    beforeEach(() => {
385      setupCtx(() => {
386        const heightForApprox5Ticks = 125;
387        ctrl.height = heightForApprox5Ticks;
388        ctrl.panel.yaxes[0].logBase = 2;
389        ctrl.panel.yaxes[0].min = '0';
390        ctx.data[0] = new TimeSeries({
391          datapoints: [
392            [2000, 1],
393            [4, 2],
394            [500, 3],
395            [3000, 4],
396            [10000, 5],
397            [100000, 6],
398          ],
399          alias: 'seriesAutoscale',
400        });
401        ctx.data[0].yaxis = 1;
402      });
403    });
404
405    it('should regenerate ticks so that if fits on the y-axis', () => {
406      const axisAutoscale = ctx.plotOptions.yaxes[0];
407      expect(axisAutoscale.min).toBe(0.1);
408      expect(axisAutoscale.ticks.length).toBe(8);
409      expect(axisAutoscale.ticks[0]).toBe(0.1);
410      expect(axisAutoscale.ticks[7]).toBe(262144);
411      expect(axisAutoscale.max).toBe(262144);
412    });
413
414    it('should set axis max to be max tick value', () => {
415      expect(ctx.plotOptions.yaxes[0].max).toBe(262144);
416    });
417  });
418
419  describe('dashed lines options', () => {
420    beforeEach(() => {
421      setupCtx(() => {
422        ctrl.panel.lines = true;
423        ctrl.panel.linewidth = 2;
424        ctrl.panel.dashes = true;
425      });
426    });
427
428    it('should configure dashed plot with correct options', () => {
429      expect(ctx.plotOptions.series.lines.show).toBe(true);
430      expect(ctx.plotOptions.series.dashes.lineWidth).toBe(2);
431      expect(ctx.plotOptions.series.dashes.show).toBe(true);
432    });
433  });
434
435  describe('should use timeStep for barWidth', () => {
436    beforeEach(() => {
437      setupCtx(() => {
438        ctrl.panel.bars = true;
439        ctx.data[0] = new TimeSeries({
440          datapoints: [
441            [1, 10],
442            [2, 20],
443          ],
444          alias: 'series1',
445        });
446      });
447    });
448
449    it('should set barWidth', () => {
450      expect(ctx.plotOptions.series.bars.barWidth).toBe(1 / 1.5);
451    });
452  });
453
454  describe('series option overrides, fill & points', () => {
455    beforeEach(() => {
456      setupCtx(() => {
457        ctrl.panel.lines = true;
458        ctrl.panel.fill = 5;
459        ctx.data[0].zindex = 10;
460        ctx.data[1].alias = 'test';
461        ctx.data[1].lines = { fill: 0.001 };
462        ctx.data[1].points = { show: true };
463      });
464    });
465
466    it('should match second series and fill zero, and enable points', () => {
467      expect(ctx.plotOptions.series.lines.fill).toBe(0.5);
468      expect(ctx.plotData[1].lines.fill).toBe(0.001);
469      expect(ctx.plotData[1].points.show).toBe(true);
470    });
471  });
472
473  describe('should order series order according to zindex', () => {
474    beforeEach(() => {
475      setupCtx(() => {
476        ctx.data[1].zindex = 1;
477        ctx.data[0].zindex = 10;
478      });
479    });
480
481    it('should move zindex 2 last', () => {
482      expect(ctx.plotData[0].alias).toBe('series2');
483      expect(ctx.plotData[1].alias).toBe('series1');
484    });
485  });
486
487  describe('when series is hidden', () => {
488    beforeEach(() => {
489      setupCtx(() => {
490        ctrl.hiddenSeries = { series2: true };
491      });
492    });
493
494    it('should remove datapoints and disable stack', () => {
495      expect(ctx.plotData[0].alias).toBe('series1');
496      expect(ctx.plotData[1].data.length).toBe(0);
497      expect(ctx.plotData[1].stack).toBe(false);
498    });
499  });
500
501  describe('when stack and percent', () => {
502    beforeEach(() => {
503      setupCtx(() => {
504        ctrl.panel.percentage = true;
505        ctrl.panel.stack = true;
506      });
507    });
508
509    it('should show percentage', () => {
510      const axis = ctx.plotOptions.yaxes[0];
511      expect(axis.tickFormatter(100, axis)).toBe('100%');
512    });
513  });
514
515  describe('when panel too narrow to show x-axis dates in same granularity as wide panels', () => {
516    //Set width to 10px
517    describe('and the range is less than 24 hours', () => {
518      beforeEach(() => {
519        setupCtx(() => {
520          ctrl.range.from = dateTime([2015, 1, 1, 10]);
521          ctrl.range.to = dateTime([2015, 1, 1, 22]);
522        });
523      });
524
525      it('should format dates as hours minutes', () => {
526        const axis = ctx.plotOptions.xaxis;
527        expect(axis.timeformat).toBe('HH:mm');
528      });
529    });
530
531    describe('and the range is less than one year', () => {
532      beforeEach(() => {
533        setupCtx(() => {
534          ctrl.range.from = dateTime([2015, 1, 1]);
535          ctrl.range.to = dateTime([2015, 11, 20]);
536        });
537      });
538
539      it('should format dates as month days', () => {
540        const axis = ctx.plotOptions.xaxis;
541        expect(axis.timeformat).toBe('MM/DD');
542      });
543    });
544  });
545
546  describe('when graph is histogram, and enable stack', () => {
547    beforeEach(() => {
548      setupCtx(() => {
549        ctrl.panel.xaxis.mode = 'histogram';
550        ctrl.panel.stack = true;
551        ctrl.hiddenSeries = {};
552        ctx.data[0] = new TimeSeries({
553          datapoints: [
554            [100, 1],
555            [100, 2],
556            [200, 3],
557            [300, 4],
558          ],
559          alias: 'series1',
560        });
561        ctx.data[1] = new TimeSeries({
562          datapoints: [
563            [100, 1],
564            [100, 2],
565            [200, 3],
566            [300, 4],
567          ],
568          alias: 'series2',
569        });
570      });
571    });
572
573    it('should calculate correct histogram', () => {
574      expect(ctx.plotData[0].data[0][0]).toBe(100);
575      expect(ctx.plotData[0].data[0][1]).toBe(2);
576      expect(ctx.plotData[1].data[0][0]).toBe(100);
577      expect(ctx.plotData[1].data[0][1]).toBe(2);
578    });
579  });
580
581  describe('when graph is histogram, and some series are hidden', () => {
582    beforeEach(() => {
583      setupCtx(() => {
584        ctrl.panel.xaxis.mode = 'histogram';
585        ctrl.panel.stack = false;
586        ctrl.hiddenSeries = { series2: true };
587        ctx.data[0] = new TimeSeries({
588          datapoints: [
589            [100, 1],
590            [100, 2],
591            [200, 3],
592            [300, 4],
593          ],
594          alias: 'series1',
595        });
596        ctx.data[1] = new TimeSeries({
597          datapoints: [
598            [100, 1],
599            [100, 2],
600            [200, 3],
601            [300, 4],
602          ],
603          alias: 'series2',
604        });
605      });
606    });
607
608    it('should calculate correct histogram', () => {
609      expect(ctx.plotData[0].data[0][0]).toBe(100);
610      expect(ctx.plotData[0].data[0][1]).toBe(2);
611    });
612  });
613
614  describe('when graph is histogram, and xaxis min is set', () => {
615    beforeEach(() => {
616      setupCtx(() => {
617        ctrl.panel.xaxis.mode = 'histogram';
618        ctrl.panel.xaxis.min = 150;
619        ctrl.panel.stack = false;
620        ctrl.hiddenSeries = {};
621        ctx.data[0] = new TimeSeries({
622          datapoints: [
623            [100, 1],
624            [100, 2],
625            [200, 3],
626            [300, 4],
627          ],
628          alias: 'series1',
629        });
630      });
631    });
632
633    it('should not contain values lower than min', () => {
634      const nonZero = ctx.plotData[0].data.filter((t: number[]) => t[1] > 0);
635      expect(
636        Math.min.apply(
637          Math,
638          nonZero.map((t: number[]) => t[0])
639        )
640      ).toBe(200);
641      expect(
642        Math.max.apply(
643          Math,
644          nonZero.map((t: number[]) => t[0])
645        )
646      ).toBe(280);
647    });
648  });
649
650  describe('when graph is histogram, and xaxis min is zero', () => {
651    beforeEach(() => {
652      setupCtx(() => {
653        ctrl.panel.xaxis.mode = 'histogram';
654        ctrl.panel.xaxis.min = 0;
655        ctrl.panel.stack = false;
656        ctrl.hiddenSeries = {};
657        ctx.data[0] = new TimeSeries({
658          datapoints: [
659            [-100, 1],
660            [100, 2],
661            [200, 3],
662            [300, 4],
663          ],
664          alias: 'series1',
665        });
666      });
667    });
668
669    it('should not contain values lower than zero', () => {
670      const nonZero = ctx.plotData[0].data.filter((t: number[]) => t[1] > 0);
671      expect(
672        Math.min.apply(
673          Math,
674          nonZero.map((t: number[]) => t[0])
675        )
676      ).toBe(100);
677      expect(
678        Math.max.apply(
679          Math,
680          nonZero.map((t: number[]) => t[0])
681        )
682      ).toBe(280);
683    });
684  });
685
686  describe('when graph is histogram, and xaxis min is null', () => {
687    beforeEach(() => {
688      setupCtx(() => {
689        ctrl.panel.xaxis.mode = 'histogram';
690        ctrl.panel.xaxis.min = null;
691        ctrl.panel.stack = false;
692        ctrl.hiddenSeries = {};
693        ctx.data[0] = new TimeSeries({
694          datapoints: [
695            [-100, 1],
696            [100, 2],
697            [200, 3],
698            [300, 4],
699          ],
700          alias: 'series1',
701        });
702      });
703    });
704
705    it('xaxis min should not affect the histogram', () => {
706      const nonZero = ctx.plotData[0].data.filter((t: number[]) => t[1] > 0);
707      expect(
708        Math.min.apply(
709          Math,
710          nonZero.map((t: number[]) => t[0])
711        )
712      ).toBe(-100);
713      expect(
714        Math.max.apply(
715          Math,
716          nonZero.map((t: number[]) => t[0])
717        )
718      ).toBe(250);
719    });
720  });
721
722  describe('when graph is histogram, and xaxis min is undefined', () => {
723    beforeEach(() => {
724      setupCtx(() => {
725        ctrl.panel.xaxis.mode = 'histogram';
726        ctrl.panel.xaxis.min = undefined;
727        ctrl.panel.stack = false;
728        ctrl.hiddenSeries = {};
729        ctx.data[0] = new TimeSeries({
730          datapoints: [
731            [-100, 1],
732            [100, 2],
733            [200, 3],
734            [300, 4],
735          ],
736          alias: 'series1',
737        });
738      });
739    });
740
741    it('xaxis min should not affect the histogram', () => {
742      const nonZero = ctx.plotData[0].data.filter((t: number[]) => t[1] > 0);
743      expect(
744        Math.min.apply(
745          Math,
746          nonZero.map((t: number[]) => t[0])
747        )
748      ).toBe(-100);
749      expect(
750        Math.max.apply(
751          Math,
752          nonZero.map((t: number[]) => t[0])
753        )
754      ).toBe(250);
755    });
756  });
757
758  describe('when graph is histogram, and xaxis max is set', () => {
759    beforeEach(() => {
760      setupCtx(() => {
761        ctrl.panel.xaxis.mode = 'histogram';
762        ctrl.panel.xaxis.max = 250;
763        ctrl.panel.stack = false;
764        ctrl.hiddenSeries = {};
765        ctx.data[0] = new TimeSeries({
766          datapoints: [
767            [100, 1],
768            [100, 2],
769            [200, 3],
770            [300, 4],
771          ],
772          alias: 'series1',
773        });
774      });
775    });
776
777    it('should not contain values greater than max', () => {
778      const nonZero = ctx.plotData[0].data.filter((t: number[]) => t[1] > 0);
779      expect(
780        Math.min.apply(
781          Math,
782          nonZero.map((t: number[]) => t[0])
783        )
784      ).toBe(100);
785      expect(
786        Math.max.apply(
787          Math,
788          nonZero.map((t: number[]) => t[0])
789        )
790      ).toBe(200);
791    });
792  });
793
794  describe('when graph is histogram, and xaxis max is zero', () => {
795    beforeEach(() => {
796      setupCtx(() => {
797        ctrl.panel.xaxis.mode = 'histogram';
798        ctrl.panel.xaxis.max = 0;
799        ctrl.panel.stack = false;
800        ctrl.hiddenSeries = {};
801        ctx.data[0] = new TimeSeries({
802          datapoints: [
803            [-100, 1],
804            [100, 1],
805            [100, 2],
806            [200, 3],
807            [300, 4],
808          ],
809          alias: 'series1',
810        });
811      });
812    });
813
814    it('should not contain values greater than zero', () => {
815      const nonZero = ctx.plotData[0].data.filter((t: number[]) => t[1] > 0);
816      expect(
817        Math.min.apply(
818          Math,
819          nonZero.map((t: number[]) => t[0])
820        )
821      ).toBe(-100);
822      expect(
823        Math.max.apply(
824          Math,
825          nonZero.map((t: number[]) => t[0])
826        )
827      ).toBe(-100);
828    });
829  });
830
831  describe('when graph is histogram, and xaxis max is null', () => {
832    beforeEach(() => {
833      setupCtx(() => {
834        ctrl.panel.xaxis.mode = 'histogram';
835        ctrl.panel.xaxis.max = null;
836        ctrl.panel.stack = false;
837        ctrl.hiddenSeries = {};
838        ctx.data[0] = new TimeSeries({
839          datapoints: [
840            [-100, 1],
841            [100, 1],
842            [100, 2],
843            [200, 3],
844            [300, 4],
845          ],
846          alias: 'series1',
847        });
848      });
849    });
850
851    it('xaxis max should not affect the histogram', () => {
852      const nonZero = ctx.plotData[0].data.filter((t: number[]) => t[1] > 0);
853      expect(
854        Math.min.apply(
855          Math,
856          nonZero.map((t: number[]) => t[0])
857        )
858      ).toBe(-100);
859      expect(
860        Math.max.apply(
861          Math,
862          nonZero.map((t: number[]) => t[0])
863        )
864      ).toBe(250);
865    });
866  });
867
868  describe('when graph is histogram, and xaxis max is undefined', () => {
869    beforeEach(() => {
870      setupCtx(() => {
871        ctrl.panel.xaxis.mode = 'histogram';
872        ctrl.panel.xaxis.max = undefined;
873        ctrl.panel.stack = false;
874        ctrl.hiddenSeries = {};
875        ctx.data[0] = new TimeSeries({
876          datapoints: [
877            [-100, 1],
878            [100, 1],
879            [100, 2],
880            [200, 3],
881            [300, 4],
882          ],
883          alias: 'series1',
884        });
885      });
886    });
887
888    it('xaxis max should not should node affect the histogram', () => {
889      const nonZero = ctx.plotData[0].data.filter((t: number[]) => t[1] > 0);
890      expect(
891        Math.min.apply(
892          Math,
893          nonZero.map((t: number[]) => t[0])
894        )
895      ).toBe(-100);
896      expect(
897        Math.max.apply(
898          Math,
899          nonZero.map((t: number[]) => t[0])
900        )
901      ).toBe(250);
902    });
903  });
904
905  describe('when graph is histogram, and xaxis min and max are set', () => {
906    beforeEach(() => {
907      setupCtx(() => {
908        ctrl.panel.xaxis.mode = 'histogram';
909        ctrl.panel.xaxis.min = 150;
910        ctrl.panel.xaxis.max = 250;
911        ctrl.panel.stack = false;
912        ctrl.hiddenSeries = {};
913        ctx.data[0] = new TimeSeries({
914          datapoints: [
915            [100, 1],
916            [100, 2],
917            [200, 3],
918            [300, 4],
919          ],
920          alias: 'series1',
921        });
922      });
923    });
924
925    it('should not contain values lower than min and greater than max', () => {
926      const nonZero = ctx.plotData[0].data.filter((t: number[]) => t[1] > 0);
927      expect(
928        Math.min.apply(
929          Math,
930          nonZero.map((t: number[]) => t[0])
931        )
932      ).toBe(200);
933      expect(
934        Math.max.apply(
935          Math,
936          nonZero.map((t: number[]) => t[0])
937        )
938      ).toBe(200);
939    });
940  });
941
942  describe('when graph is histogram, and xaxis min and max are zero', () => {
943    beforeEach(() => {
944      setupCtx(() => {
945        ctrl.panel.xaxis.mode = 'histogram';
946        ctrl.panel.xaxis.min = 0;
947        ctrl.panel.xaxis.max = 0;
948        ctrl.panel.stack = false;
949        ctrl.hiddenSeries = {};
950        ctx.data[0] = new TimeSeries({
951          datapoints: [
952            [-100, 1],
953            [100, 1],
954            [100, 2],
955            [200, 3],
956            [300, 4],
957          ],
958          alias: 'series1',
959        });
960      });
961    });
962
963    it('xaxis max should be ignored otherwise the bucketSize is zero', () => {
964      const nonZero = ctx.plotData[0].data.filter((t: number[]) => t[1] > 0);
965      expect(
966        Math.min.apply(
967          Math,
968          nonZero.map((t: number[]) => t[0])
969        )
970      ).toBe(100);
971      expect(
972        Math.max.apply(
973          Math,
974          nonZero.map((t: number[]) => t[0])
975        )
976      ).toBe(280);
977    });
978  });
979
980  describe('when graph is histogram, and xaxis min and max are null', () => {
981    beforeEach(() => {
982      setupCtx(() => {
983        ctrl.panel.xaxis.mode = 'histogram';
984        ctrl.panel.xaxis.min = null;
985        ctrl.panel.xaxis.max = null;
986        ctrl.panel.stack = false;
987        ctrl.hiddenSeries = {};
988        ctx.data[0] = new TimeSeries({
989          datapoints: [
990            [100, 1],
991            [100, 2],
992            [200, 3],
993            [300, 4],
994          ],
995          alias: 'series1',
996        });
997      });
998    });
999
1000    it('xaxis min and max should not affect the histogram', () => {
1001      const nonZero = ctx.plotData[0].data.filter((t: number[]) => t[1] > 0);
1002      expect(
1003        Math.min.apply(
1004          Math,
1005          nonZero.map((t: number[]) => t[0])
1006        )
1007      ).toBe(100);
1008      expect(
1009        Math.max.apply(
1010          Math,
1011          nonZero.map((t: number[]) => t[0])
1012        )
1013      ).toBe(280);
1014    });
1015  });
1016
1017  describe('when graph is histogram, and xaxis min and max are undefined', () => {
1018    beforeEach(() => {
1019      setupCtx(() => {
1020        ctrl.panel.xaxis.mode = 'histogram';
1021        ctrl.panel.xaxis.min = undefined;
1022        ctrl.panel.xaxis.max = undefined;
1023        ctrl.panel.stack = false;
1024        ctrl.hiddenSeries = {};
1025        ctx.data[0] = new TimeSeries({
1026          datapoints: [
1027            [100, 1],
1028            [100, 2],
1029            [200, 3],
1030            [300, 4],
1031          ],
1032          alias: 'series1',
1033        });
1034      });
1035    });
1036
1037    it('xaxis min and max should not affect the histogram', () => {
1038      const nonZero = ctx.plotData[0].data.filter((t: number[]) => t[1] > 0);
1039      expect(
1040        Math.min.apply(
1041          Math,
1042          nonZero.map((t: number[]) => t[0])
1043        )
1044      ).toBe(100);
1045      expect(
1046        Math.max.apply(
1047          Math,
1048          nonZero.map((t: number[]) => t[0])
1049        )
1050      ).toBe(280);
1051    });
1052  });
1053
1054  describe('when graph is histogram, and xaxis min is greater than xaxis max', () => {
1055    beforeEach(() => {
1056      setupCtx(() => {
1057        ctrl.panel.xaxis.mode = 'histogram';
1058        ctrl.panel.xaxis.min = 150;
1059        ctrl.panel.xaxis.max = 100;
1060        ctrl.panel.stack = false;
1061        ctrl.hiddenSeries = {};
1062        ctx.data[0] = new TimeSeries({
1063          datapoints: [
1064            [100, 1],
1065            [100, 2],
1066            [200, 3],
1067            [300, 4],
1068          ],
1069          alias: 'series1',
1070        });
1071      });
1072    });
1073
1074    it('xaxis max should be ignored otherwise the bucketSize is negative', () => {
1075      const nonZero = ctx.plotData[0].data.filter((t: number[]) => t[1] > 0);
1076      expect(
1077        Math.min.apply(
1078          Math,
1079          nonZero.map((t: number[]) => t[0])
1080        )
1081      ).toBe(200);
1082      expect(
1083        Math.max.apply(
1084          Math,
1085          nonZero.map((t: number[]) => t[0])
1086        )
1087      ).toBe(280);
1088    });
1089  });
1090
1091  // aaa
1092  describe('when graph is histogram, and xaxis min is greater than the maximum value', () => {
1093    beforeEach(() => {
1094      setupCtx(() => {
1095        ctrl.panel.xaxis.mode = 'histogram';
1096        ctrl.panel.xaxis.min = 301;
1097        ctrl.panel.stack = false;
1098        ctrl.hiddenSeries = {};
1099        ctx.data[0] = new TimeSeries({
1100          datapoints: [
1101            [100, 1],
1102            [100, 2],
1103            [200, 3],
1104            [300, 4],
1105          ],
1106          alias: 'series1',
1107        });
1108      });
1109    });
1110
1111    it('xaxis min should be ignored otherwise the bucketSize is negative', () => {
1112      const nonZero = ctx.plotData[0].data.filter((t: number[]) => t[1] > 0);
1113      expect(
1114        Math.min.apply(
1115          Math,
1116          nonZero.map((t: number[]) => t[0])
1117        )
1118      ).toBe(100);
1119      expect(
1120        Math.max.apply(
1121          Math,
1122          nonZero.map((t: number[]) => t[0])
1123        )
1124      ).toBe(280);
1125    });
1126  });
1127
1128  describe('when graph is histogram, and xaxis min is equal to the maximum value', () => {
1129    beforeEach(() => {
1130      setupCtx(() => {
1131        ctrl.panel.xaxis.mode = 'histogram';
1132        ctrl.panel.xaxis.min = 300;
1133        ctrl.panel.stack = false;
1134        ctrl.hiddenSeries = {};
1135        ctx.data[0] = new TimeSeries({
1136          datapoints: [
1137            [100, 1],
1138            [100, 2],
1139            [200, 3],
1140            [300, 4],
1141          ],
1142          alias: 'series1',
1143        });
1144      });
1145    });
1146
1147    it('xaxis min should be ignored otherwise the bucketSize is zero', () => {
1148      const nonZero = ctx.plotData[0].data.filter((t: number[]) => t[1] > 0);
1149      expect(
1150        Math.min.apply(
1151          Math,
1152          nonZero.map((t: number[]) => t[0])
1153        )
1154      ).toBe(100);
1155      expect(
1156        Math.max.apply(
1157          Math,
1158          nonZero.map((t: number[]) => t[0])
1159        )
1160      ).toBe(280);
1161    });
1162  });
1163
1164  describe('when graph is histogram, and xaxis min is lower than the minimum value', () => {
1165    beforeEach(() => {
1166      setupCtx(() => {
1167        ctrl.panel.xaxis.mode = 'histogram';
1168        ctrl.panel.xaxis.min = 99;
1169        ctrl.panel.stack = false;
1170        ctrl.hiddenSeries = {};
1171        ctx.data[0] = new TimeSeries({
1172          datapoints: [
1173            [100, 1],
1174            [100, 2],
1175            [200, 3],
1176            [300, 4],
1177          ],
1178          alias: 'series1',
1179        });
1180      });
1181    });
1182
1183    it('xaxis min should not affect the histogram', () => {
1184      const nonZero = ctx.plotData[0].data.filter((t: number[]) => t[1] > 0);
1185      expect(
1186        Math.min.apply(
1187          Math,
1188          nonZero.map((t: number[]) => t[0])
1189        )
1190      ).toBe(100);
1191      expect(
1192        Math.max.apply(
1193          Math,
1194          nonZero.map((t: number[]) => t[0])
1195        )
1196      ).toBe(280);
1197    });
1198  });
1199
1200  describe('when graph is histogram, and xaxis max is equal to the minimum value', () => {
1201    beforeEach(() => {
1202      setupCtx(() => {
1203        ctrl.panel.xaxis.mode = 'histogram';
1204        ctrl.panel.xaxis.max = 100;
1205        ctrl.panel.stack = false;
1206        ctrl.hiddenSeries = {};
1207        ctx.data[0] = new TimeSeries({
1208          datapoints: [
1209            [100, 1],
1210            [100, 2],
1211            [200, 3],
1212            [300, 4],
1213          ],
1214          alias: 'series1',
1215        });
1216      });
1217    });
1218
1219    it('should calculate correct histogram', () => {
1220      const nonZero = ctx.plotData[0].data.filter((t: number[]) => t[1] > 0);
1221      expect(
1222        Math.min.apply(
1223          Math,
1224          nonZero.map((t: number[]) => t[0])
1225        )
1226      ).toBe(90);
1227      expect(
1228        Math.max.apply(
1229          Math,
1230          nonZero.map((t: number[]) => t[0])
1231        )
1232      ).toBe(90);
1233    });
1234  });
1235
1236  describe('when graph is histogram, and xaxis max is a lower than the minimum value', () => {
1237    beforeEach(() => {
1238      setupCtx(() => {
1239        ctrl.panel.xaxis.mode = 'histogram';
1240        ctrl.panel.xaxis.max = 99;
1241        ctrl.panel.stack = false;
1242        ctrl.hiddenSeries = {};
1243        ctx.data[0] = new TimeSeries({
1244          datapoints: [
1245            [100, 1],
1246            [100, 2],
1247            [200, 3],
1248            [300, 4],
1249          ],
1250          alias: 'series1',
1251        });
1252      });
1253    });
1254
1255    it('should calculate empty histogram', () => {
1256      const nonZero = ctx.plotData[0].data.filter((t: number[]) => t[1] > 0);
1257      expect(nonZero.length).toBe(0);
1258    });
1259  });
1260
1261  describe('when graph is histogram, and xaxis max is greater than the maximum value', () => {
1262    beforeEach(() => {
1263      setupCtx(() => {
1264        ctrl.panel.xaxis.mode = 'histogram';
1265        ctrl.panel.xaxis.max = 400;
1266        ctrl.panel.stack = false;
1267        ctrl.hiddenSeries = {};
1268        ctx.data[0] = new TimeSeries({
1269          datapoints: [
1270            [100, 1],
1271            [100, 2],
1272            [200, 3],
1273            [300, 4],
1274          ],
1275          alias: 'series1',
1276        });
1277      });
1278    });
1279
1280    it('should calculate correct histogram', () => {
1281      const nonZero = ctx.plotData[0].data.filter((t: number[]) => t[1] > 0);
1282      expect(
1283        Math.min.apply(
1284          Math,
1285          nonZero.map((t: number[]) => t[0])
1286        )
1287      ).toBe(100);
1288      expect(
1289        Math.max.apply(
1290          Math,
1291          nonZero.map((t: number[]) => t[0])
1292        )
1293      ).toBe(300);
1294    });
1295  });
1296
1297  describe('getContextMenuItemsSupplier', () => {
1298    describe('when called and user can edit the dashboard', () => {
1299      it('then the correct menu items should be returned', () => {
1300        const element = getGraphElement({ canEdit: true, canMakeEditable: false });
1301
1302        const result = element.getContextMenuItemsSupplier({ x: 1, y: 1 })();
1303
1304        expect(result.length).toEqual(1);
1305        expect(result[0].items.length).toEqual(1);
1306        expect(result[0].items[0].label).toEqual('Add annotation');
1307        expect(result[0].items[0].icon).toEqual('comment-alt');
1308        expect(result[0].items[0].onClick).toBeDefined();
1309      });
1310    });
1311
1312    describe('when called and user can make the dashboard editable', () => {
1313      it('then the correct menu items should be returned', () => {
1314        const element = getGraphElement({ canEdit: false, canMakeEditable: true });
1315
1316        const result = element.getContextMenuItemsSupplier({ x: 1, y: 1 })();
1317
1318        expect(result.length).toEqual(1);
1319        expect(result[0].items.length).toEqual(1);
1320        expect(result[0].items[0].label).toEqual('Add annotation');
1321        expect(result[0].items[0].icon).toEqual('comment-alt');
1322        expect(result[0].items[0].onClick).toBeDefined();
1323      });
1324    });
1325
1326    describe('when called and user can not edit the dashboard and can not make the dashboard editable', () => {
1327      it('then the correct menu items should be returned', () => {
1328        const element = getGraphElement({ canEdit: false, canMakeEditable: false });
1329
1330        const result = element.getContextMenuItemsSupplier({ x: 1, y: 1 })();
1331
1332        expect(result.length).toEqual(0);
1333      });
1334    });
1335  });
1336});
1337
1338function getGraphElement({ canEdit, canMakeEditable }: { canEdit?: boolean; canMakeEditable?: boolean } = {}) {
1339  const dashboard = new DashboardModel({});
1340  dashboard.events.on = jest.fn();
1341  dashboard.meta.canEdit = canEdit;
1342  dashboard.meta.canMakeEditable = canMakeEditable;
1343  const element = new GraphElement(
1344    {
1345      ctrl: {
1346        contextMenuCtrl: {},
1347        dashboard,
1348        events: { on: jest.fn() },
1349      },
1350    },
1351    { mouseleave: jest.fn(), bind: jest.fn() } as any,
1352    {} as any
1353  );
1354
1355  return element;
1356}
1357