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