Skip to content

Commit

Permalink
Improve date handling
Browse files Browse the repository at this point in the history
Previous to this change the plugin assumed that the date should be
displayed in the timezone of the browser no matter what was the setting.

Also it wouldn't deal with the timerange that was specified (sic).

This is fixed in this change now, we leverage `serverDelay` to send the
metrics back in time by providing a positive delay and forward in time
(in case the selected timezone is ahead of time) by providing a negative
delay.
  • Loading branch information
ekacnet committed May 7, 2024
1 parent d42f23e commit bbb02b4
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 21 deletions.
64 changes: 43 additions & 21 deletions src/components/SimplePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,15 @@ import * as d3 from 'd3';

import { config } from '@grafana/runtime';
import { convertDataToCubism } from '../cubism_utils';
import { log_debug } from '../misc_utils';
import { calculateSecondOffset } from '../date_utils';

if (config.theme2.isDark) {
require('../sass/cubism_dark.scss');
} else {
require('../sass/cubism_light.scss');
}

function log_debug(message?: any, ...optionalParams: any[]): void {
// Set it to true to enable debugging
let debug = false;
if (debug) {
return console.log(message, optionalParams);
}
}

interface Props extends PanelProps<SimpleOptions> {}

type CSS = string;
Expand Down Expand Up @@ -71,6 +65,7 @@ const getStyles = (showText: boolean): (() => Styles) => {
};
};


export const D3Graph: React.FC<{
height: number;
width: number;
Expand All @@ -86,20 +81,28 @@ export const D3Graph: React.FC<{
if (!wrapperDiv || data.series.length === 0) {
return;
}
// Initialize most of the variables and constants
let now = Date.now();
let begin = new Date();
const request = data.request!;

const start = +request.range.from;
const end = +request.range.to;
const span = end - start;

const anHour = 60 * 60 * 1000;
const aDay = 24 * anHour;
const aWeek = 7 * aDay;
const aMonth = 4 * aWeek;
let firstSeries = data.series[0].fields[0].values;
let earliest = firstSeries[0];
let latest = firstSeries[firstSeries.length - 1];
let span = latest - earliest;
let size = wrapperDiv.clientWidth;

// the width of the div with the panel
let size = wrapperDiv.clientWidth;

let step = Math.floor(span / size);

let cubismTimestamps: number[] = [];

for (let ts = earliest; ts <= latest; ts = ts + step) {
for (let ts = start; ts <= end; ts = ts + step) {
cubismTimestamps.push(ts);
}
log_debug('Step is:', step);
Expand All @@ -114,26 +117,39 @@ export const D3Graph: React.FC<{
}
});

let prev = +now
now = Date.now();
log_debug(`Took ${now - prev} to convert the series`);

wrapperDiv.innerHTML = '';
wrapperDiv.className = stylesGetter.wrapper;

if (cubismData.length === 0) {
wrapperDiv.innerHTML = 'The series contained no data, check your query';
return;
}

// setup Div layout and set classes
const delta = calculateSecondOffset(begin, +(end), request.timezone, request.range.from.utcOffset());
const outerDiv = d3.create('div');
outerDiv.node()!.className = stylesGetter.d3outer;
// size seems to be more the nubmer of pix
// steps seems to be how often things things change in microseconds ?
// TODO rename that to canvas
const innnerDiv = d3.create('div');
const axisDiv = d3.create('div');
innnerDiv.node()!.className = stylesGetter.d3inner;

// setup the context
// size is the nubmer of pixel
// steps seems to be the number of microseconds between change
// it also control the range (ie. given the number of pixel and that a
// pixel reprensent 1e3 milliseconds, the range is 1e3 * size seconds)
// pixel represent 1e3 milliseconds, the range is 1e3 * size seconds)
context.size(size).step(step);
// @ts-ignore
// negative delta means that we go in the past ...
context.serverDelay(delta*1000);
// @ts-ignore
context.stop();

const innnerDiv = d3.create('div');
const axisDiv = d3.create('div');
innnerDiv.node()!.className = stylesGetter.d3inner;

// create axis: try to find divs with .axis class
axisDiv
Expand Down Expand Up @@ -168,6 +184,9 @@ export const D3Graph: React.FC<{
}
context.axis().ticks(scale, count).orient(dataValue).render(d3.select(this));
});
prev = now
now = Date.now();
log_debug(`Took ${now - prev} ms to do the axis`);

// create the horizon
const h = innnerDiv
Expand All @@ -184,6 +203,7 @@ export const D3Graph: React.FC<{
.attr('background-color', theme.colors.primary.main)
.attr('id', 'rule');
context.rule().render(ruleDiv);

// extent is the vertical range for the values for a given horinzon
if (options.automaticExtents || options.extentMin === undefined || options.extentMax === undefined) {
context.horizon().render(h);
Expand All @@ -210,10 +230,12 @@ export const D3Graph: React.FC<{
msgDivContainer.append('div').text(msg);
wrapperDiv.append(msgDivContainer.node()!);
}
prev = now
now = Date.now();
log_debug(`Took ${now - prev} ms to finish`);
},
[theme.colors.primary.main, context, data, options, stylesGetter]
);

return <div ref={renderD3} />;
};

Expand Down
28 changes: 28 additions & 0 deletions src/date_utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { log_debug } from 'misc_utils';


export const calculateSecondOffset = (now: Date, end: number, tz: string, grafanaTZ2UTC: number): number => {
// A positive delta will send the start and the end of the graph back in time.
// we calculate the number of second of delta between the start of the function and the end of
// the range this will be a positive number because we want to go back in time (potentially)
let delta = (+now - end)/1000;

// Now let's take timezone into account if the timezone is not the one of the browser
// This could be positive or negative (obviously).
// To do so we get the number of minutes between UTC and the browser (ie. -420 if you are in
// UTC-7) and the number of minutes between UTC and the timezone defined in Grafana (ie. UTC
// + 1 or Pacific/Marquesas UTC-9:30) and we substract this to the first number.
// If the delta is positive it means that there is less minutes than the browser to UTC and we
// need to go back in time and add an additional positive delta, if the number is negative it
// means that we need to go forward in time because there is less minutes.
if (tz !== "browser") {
// the number of minutes !!! of delta from the TZ selected in grafana to UTC (ie. 60 if it's
// you are in UTC+1 or -420 if you are in UTC-7
// if tz == "browser" this information doesn't exists ...
let browser2UTC = -now.getTimezoneOffset();
let tzDelta = browser2UTC - grafanaTZ2UTC;
log_debug(`Browser to UTC delta: ${browser2UTC}, utcOffset ${grafanaTZ2UTC} delta from the browser to the TZ (${tz}) in grafana: ${tzDelta}`);
delta += (tzDelta) * 60;
}
return delta;
}
8 changes: 8 additions & 0 deletions src/misc_utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export function log_debug(message?: any, ...optionalParams: any[]): void {
// Set it to true to enable debugging
let debug = false;
if (debug) {
return console.log(message, optionalParams);
}
}

36 changes: 36 additions & 0 deletions src/tests/date_utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// convertDataToCubism
import {
calculateSecondOffset,
} from '../date_utils';

describe('getUTCDelta', () => {
it('should return whatever is the browser time offset', () => {
let now = new Date();
let end = now.valueOf();
expect(calculateSecondOffset(now, end, "UTC", 0)).toBe( 0 - now.getTimezoneOffset()*60);
});
it('should return 3600 for 1 hour delay ', () => {
let now = new Date();
// let's pretend that the end of the range was one hour ago (ie 10 AM when it's 11 AM)
let end = now.valueOf() - 3600*1000;
expect(calculateSecondOffset(now, end, "UTC", 0)).toBe(3600 - now.getTimezoneOffset()*50);
})
it('should return 3600 for 1 hour delay and using the browser TZ', () => {
let now = new Date();
// let's pretend that the end of the range was one hour ago (ie 10 AM when it's 11 AM)
let end = now.valueOf() - 3600*1000;
expect(calculateSecondOffset(now, end, "browser", 0)).toBe(3600);
});
it('should return -3600 for 1 hour delay and using the UTC+2 timezone', () => {
let now = new Date();
// let's pretend that the end of the range was one hour ago (ie 10 AM when it's 11 AM)
let end = now.valueOf() - 3600*1000;
//
expect(calculateSecondOffset(now, end, "Europe/Paris", 120)).toBe(-3600);
});
it('should return 7200 if grafana ask for something in UTC+2', () => {
let now = new Date();
let end = now.valueOf();
expect(calculateSecondOffset(now, end, "Africa/Cairo", 120)).toBe(-7200 - now.getTimezoneOffset()*60)
});
});

0 comments on commit bbb02b4

Please sign in to comment.