1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-03-18 00:19:49 +01:00

fix: Data usage graphs don't work in UTC-n time zones (#9530)

Fixes a number of issues that would surface in UTC-n (where n > 1)
timezones. I've not found a way to check this with tests (and it looks
like [we weren't able to last time
either](https://github.com/Unleash/unleash/pull/9110/files#r1919746328)),
so all the testing's been done manually by adjusting my system time and
zone. (From what I understand, you can't generate a Date with a specific
TZ offset in JS: it's only utc or local time)

Resolved:
- [x] Selecting "Jan" in the dropdown results in the selection being
"December" (off by one in the selector)

- [x] Selecting a month view only gives you one data point (and it's
probably empty). Wrong date parsing on the way out resulted in sending
`{ from: "2025-02-28", to: "2025-02-28"}` instead of `{ from:
"2025-03-01", to: "2025-03-31"}`

- [x] The dates we create when making "daysRec" need to be adjusted.
They showed the wrong month, so the dates were off.

- [x] Make sure the labels are correct when hovering over. Again: we
used the wrong month for generating these.

- [x] The available months are wrong. Incorrect month parsing again.

- [x] The request summary month is wrong. You guessed it: incorrect
month parsing
This commit is contained in:
Thomas Heartman 2025-03-13 15:44:10 +01:00 committed by GitHub
parent 0aae3bac0a
commit dadda7b648
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 42 additions and 27 deletions

View File

@ -5,6 +5,7 @@ import { useRef, useState, type FC } from 'react';
import { format } from 'date-fns';
import type { ChartDataSelection } from './chart-data-selection';
import { selectablePeriods } from './selectable-periods';
import { parseMonthString } from './dates';
const dropdownWidth = '15rem';
const dropdownInlinePadding = (theme: Theme) => theme.spacing(3);
@ -148,10 +149,13 @@ export const PeriodSelector: FC<Props> = ({ selectedPeriod, setPeriod }) => {
selectedPeriod.grouping === 'daily'
? selectedPeriod.month === format(new Date(), 'yyyy-MM')
? 'Current month'
: new Date(selectedPeriod.month).toLocaleDateString('en-US', {
month: 'long',
year: 'numeric',
})
: parseMonthString(selectedPeriod.month).toLocaleDateString(
'en-US',
{
month: 'long',
year: 'numeric',
},
)
: `Last ${selectedPeriod.monthsBack} months`;
return (
@ -184,7 +188,7 @@ export const PeriodSelector: FC<Props> = ({ selectedPeriod, setPeriod }) => {
<p>Last 12 months</p>
</MonthSelectorHeaderGroup>
<MonthGrid>
{selectablePeriods.map((period, index) => (
{selectablePeriods.map((period) => (
<li key={period.label}>
<GridButton
selected={

View File

@ -4,6 +4,7 @@ import { subMonths } from 'date-fns';
import { useLocationSettings } from 'hooks/useLocationSettings';
import type { FC } from 'react';
import type { ChartDataSelection } from './chart-data-selection';
import { parseMonthString } from './dates';
type Props = {
period: ChartDataSelection;
@ -62,7 +63,7 @@ const incomingRequestsText = (period: ChartDataSelection): string => {
return `Average requests from ${formatMonth(fromMonth)} to ${formatMonth(toMonth)}`;
}
return `Incoming requests in ${formatMonth(new Date(period.month))}`;
return `Incoming requests in ${formatMonth(parseMonthString(period.month))}`;
};
export const RequestSummary: FC<Props> = ({

View File

@ -1,4 +1,5 @@
import { endOfMonth, format, startOfMonth, subMonths } from 'date-fns';
import { parseMonthString } from './dates';
export type ChartDataSelection =
| {
@ -16,7 +17,7 @@ export const toDateRange = (
): { from: string; to: string } => {
const fmt = (date: Date) => format(date, 'yyyy-MM-dd');
if (selection.grouping === 'daily') {
const month = new Date(selection.month);
const month = parseMonthString(selection.month);
const from = fmt(month);
const to = fmt(endOfMonth(month));
return { from, to };

View File

@ -10,7 +10,7 @@ import {
differenceInCalendarDays,
differenceInCalendarMonths,
} from 'date-fns';
import { formatDay, formatMonth } from './dates';
import { formatDay, formatMonth, parseDateString } from './dates';
import type { ChartDataSelection } from './chart-data-selection';
export type ChartDatasetType = ChartDataset<'bar'>;
@ -80,8 +80,8 @@ const getLabelsAndRecords = (
>,
) => {
if (traffic.grouping === 'monthly') {
const from = new Date(traffic.dateRange.from);
const to = new Date(traffic.dateRange.to);
const from = parseDateString(traffic.dateRange.from);
const to = parseDateString(traffic.dateRange.to);
const numMonths = Math.abs(differenceInCalendarMonths(to, from)) + 1;
const monthsRec: { [month: string]: number } = {};
for (let i = 0; i < numMonths; i++) {
@ -95,8 +95,8 @@ const getLabelsAndRecords = (
);
return { newRecord: () => ({ ...monthsRec }), labels };
} else {
const from = new Date(traffic.dateRange.from);
const to = new Date(traffic.dateRange.to);
const from = parseDateString(traffic.dateRange.from);
const to = parseDateString(traffic.dateRange.to);
const numDays = Math.abs(differenceInCalendarDays(to, from)) + 1;
const daysRec: { [day: string]: number } = {};
for (let i = 0; i < numDays; i++) {

View File

@ -1,4 +1,4 @@
import { format, getDaysInMonth } from 'date-fns';
import { format, getDaysInMonth, parse } from 'date-fns';
export const currentDate = new Date();
@ -11,3 +11,13 @@ export const daysInCurrentMonth = getDaysInMonth(currentDate);
export const formatMonth = (date: Date) => format(date, 'yyyy-MM');
export const formatDay = (date: Date) => format(date, 'yyyy-MM-dd');
export const parseMonthString = (month: string): Date => {
// parses a month into a Date starting on the first day of the month, regardless of the current time zone (e.g. works in Norway and Brazil)
return parse(month, 'yyyy-MM', new Date());
};
export const parseDateString = (month: string): Date => {
// parses a date string into a Date, regardless of the current time zone (e.g. works in Norway and Brazil)
return parse(month, 'yyyy-MM-dd', new Date());
};

View File

@ -4,6 +4,7 @@ import { periodsRecord, selectablePeriods } from '../selectable-periods';
import { createBarChartOptions } from '../bar-chart-options';
import useTheme from '@mui/material/styles/useTheme';
import { useLocationSettings } from 'hooks/useLocationSettings';
import { parseMonthString } from '../dates';
export const useChartDataSelection = (includedTraffic?: number) => {
const theme = useTheme();
@ -35,11 +36,11 @@ export const useChartDataSelection = (includedTraffic?: number) => {
},
);
} else {
const timestamp = Date.parse(tooltipItems[0].label);
if (Number.isNaN(timestamp)) {
const month = parseMonthString(tooltipItems[0].label);
if (Number.isNaN(month.getTime())) {
return 'Current month to date';
}
return new Date(timestamp).toLocaleDateString(
return month.toLocaleDateString(
locationSettings?.locale ?? 'en-US',
{
month: 'long',

View File

@ -3,8 +3,8 @@ import { useTrafficSearch } from 'hooks/api/getters/useInstanceTrafficMetrics/us
import { currentDate } from '../dates';
import { useMemo } from 'react';
import {
toTrafficUsageChartData as newToChartData,
toConnectionChartData,
toTrafficUsageChartData,
} from '../chart-functions';
import {
calculateEstimatedMonthlyCost,
@ -36,7 +36,7 @@ export const useTrafficStats = (
}
const traffic = result.data;
const chartData = newToChartData(traffic, filter);
const chartData = toTrafficUsageChartData(traffic, filter);
const usageTotal = calculateTotalUsage(traffic);
const overageCost = calculateOverageCost(
usageTotal,

View File

@ -1,4 +1,4 @@
import { getDaysInMonth } from 'date-fns';
import { getDaysInMonth, startOfMonth, subMonths } from 'date-fns';
import { currentDate, formatMonth } from './dates';
import { TRAFFIC_MEASUREMENT_START_DATE } from 'utils/traffic-calculations';
@ -38,21 +38,18 @@ export const toSelectablePeriod = (
export const generateSelectablePeriodsFromDate = (now: Date) => {
const selectablePeriods = [toSelectablePeriod(now, 'Current month')];
const startOfCurrentMonth = startOfMonth(now);
for (
let subtractMonthCount = 1;
subtractMonthCount < 12;
subtractMonthCount++
) {
// this complicated calc avoids DST issues
const utcYear = now.getUTCFullYear();
const utcMonth = now.getUTCMonth();
const targetMonth = utcMonth - subtractMonthCount;
const targetDate = new Date(Date.UTC(utcYear, targetMonth, 1, 0, 0, 0));
const targetMonth = subMonths(startOfCurrentMonth, subtractMonthCount);
selectablePeriods.push(
toSelectablePeriod(
targetDate,
targetMonth,
undefined,
targetDate >= TRAFFIC_MEASUREMENT_START_DATE,
targetMonth >= TRAFFIC_MEASUREMENT_START_DATE,
),
);
}

View File

@ -4,10 +4,11 @@ import type {
} from 'openapi';
import { getDaysInMonth } from 'date-fns';
import { format } from 'date-fns';
import { parseDateString } from 'component/admin/network/NetworkTrafficUsage/dates';
export const DEFAULT_TRAFFIC_DATA_UNIT_COST = 5;
export const DEFAULT_TRAFFIC_DATA_UNIT_SIZE = 1_000_000;
export const TRAFFIC_MEASUREMENT_START_DATE = new Date('2024-05-01');
export const TRAFFIC_MEASUREMENT_START_DATE = parseDateString('2024-05-01');
export const METERED_TRAFFIC_ENDPOINTS = [
'/api/admin',