From 777457559ca9b35cf5a9b64fc844d395cdf9a109 Mon Sep 17 00:00:00 2001 From: Vladimir Chernitsyn Date: Wed, 7 Feb 2024 16:37:48 +0100 Subject: [PATCH] fix(Yagr plugin): add aligner for uplot date (#410) --- package-lock.json | 7 ++-- package.json | 2 +- src/plugins/yagr/__tests__/utils.test.ts | 41 +++++++++++++++++++++++- src/plugins/yagr/renderer/utils.ts | 23 +++++++++++++ 4 files changed, 68 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 588dbf08..f8a86dca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@bem-react/classname": "^1.6.0", - "@gravity-ui/date-utils": "^1.4.1", + "@gravity-ui/date-utils": "^1.4.2", "@gravity-ui/yagr": "^4.2.3", "afterframe": "^1.0.2", "d3": "^7.8.5", @@ -2919,8 +2919,9 @@ } }, "node_modules/@gravity-ui/date-utils": { - "version": "1.4.1", - "license": "MIT", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@gravity-ui/date-utils/-/date-utils-1.4.2.tgz", + "integrity": "sha512-q966eCe6fJkVVYMixgDnpoWYahMvlJGxMymKipP9pkZPJKR9epmQ4RKdKFs+m8umosj8aIHrXH/aVMRveQKUCQ==", "dependencies": { "dayjs": "^1.11.7", "lodash": "^4.17.0" diff --git a/package.json b/package.json index 93955f02..6c323c13 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ ], "dependencies": { "@bem-react/classname": "^1.6.0", - "@gravity-ui/date-utils": "^1.4.1", + "@gravity-ui/date-utils": "^1.4.2", "@gravity-ui/yagr": "^4.2.3", "afterframe": "^1.0.2", "d3": "^7.8.5", diff --git a/src/plugins/yagr/__tests__/utils.test.ts b/src/plugins/yagr/__tests__/utils.test.ts index a2799d73..7c699c51 100644 --- a/src/plugins/yagr/__tests__/utils.test.ts +++ b/src/plugins/yagr/__tests__/utils.test.ts @@ -1,11 +1,27 @@ -import {shapeYagrConfig} from '../renderer/utils'; +import {shapeYagrConfig, getUplotTimezoneAligner} from '../renderer/utils'; import type {YagrWidgetData, MinimalValidConfig} from '../types'; +import type {YagrChartOptions} from '@gravity-ui/yagr'; const DATA: YagrWidgetData['data'] = { timeline: [1], graphs: [{data: [45]}], }; +jest.mock('@gravity-ui/date-utils', () => { + const originalModule = jest.requireActual('@gravity-ui/date-utils'); + return { + __esModule: true, + ...originalModule, + dateTime: ({input, timeZone}: {input: number; timeZone?: string}) => { + const browserMockedTimezone = 'Europe/Moscow'; + return originalModule.dateTime({ + input, + timeZone: timeZone || browserMockedTimezone, + }); + }, + }; +}); + describe('plugins/yagr/utils', () => { describe('shapeYagrConfig > check chart property', () => { test.each<[Partial, Partial]>([ @@ -26,4 +42,27 @@ describe('plugins/yagr/utils', () => { expect(config.chart).toEqual(expected); }); }); + + describe('GetUplotTimezoneAligner', () => { + test.each<[YagrChartOptions | undefined, string | undefined, number, number]>([ + // UTC + [{}, 'UTC', 1706659878000, 1706670678000], + // UTC + 1 + [{}, 'Europe/Belgrade', 1706659878000, 1706667078000], + // UTC - 1 + [{}, 'America/Scoresbysund', 1706659878000, 1706674278000], + // UTC + 4 + [{}, 'Asia/Muscat', 1706659878000, 1706656278000], + ])( + 'should return timestamp with added timezone diff', + (chart, timeZone, timestamp, expectedResult) => { + const uplotTimezoneAligener = getUplotTimezoneAligner(chart, timeZone); + + // timestamp is UTC Wed Jan 31 2024 00:11:18 + const result = uplotTimezoneAligener(timestamp); + + expect(result.getTime()).toEqual(expectedResult); + }, + ); + }); }); diff --git a/src/plugins/yagr/renderer/utils.ts b/src/plugins/yagr/renderer/utils.ts index 349092d5..5bcb5271 100644 --- a/src/plugins/yagr/renderer/utils.ts +++ b/src/plugins/yagr/renderer/utils.ts @@ -131,6 +131,24 @@ const getXAxisFormatter = }); }; +/** + * This function needs to align timezone that uplot is processing. + * Uplot uses simple new Date() when [processing ticks](https://github.com/leeoniya/uPlot/blob/master/src/opts.js#L177) on axis. + * It leads that timestamp will be converted to user browser timezone. + * In this function we artificially add shift diff between browser timezone and user timeozne to reset new Date() affects. + */ +export const getUplotTimezoneAligner = + (chart?: YagrChartOptions, timeZone?: string) => (ts: number) => { + const dt = ts / (chart?.timeMultiplier || 1); + const browserDate = dateTime({input: dt}); + const browserTimezone = browserDate.utcOffset(); + const timestampRealTimezone = dateTime({input: dt, timeZone}).utcOffset(); + + const uPlotOffset = (browserTimezone - timestampRealTimezone) * 60 * 1000; + + return new Date(browserDate.valueOf() + uPlotOffset); + }; + export const shapeYagrConfig = (args: ShapeYagrConfigArgs): MinimalValidConfig => { const {data, libraryConfig, theme} = args; const config: MinimalValidConfig = { @@ -176,6 +194,11 @@ export const shapeYagrConfig = (args: ShapeYagrConfigArgs): MinimalValidConfig = config.axes = config.axes || {}; const xAxis = config.axes[defaults.DEFAULT_X_SCALE]; + config.editUplotOptions = (opts) => ({ + ...opts, + tzDate: timeZone ? getUplotTimezoneAligner(config.chart, timeZone) : undefined, + }); + if (xAxis && !xAxis.values) { xAxis.values = getXAxisFormatter(config.chart.timeMultiplier, timeZone); }