mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-04 01:18:20 +02: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:
parent
0aae3bac0a
commit
dadda7b648
@ -5,6 +5,7 @@ import { useRef, useState, type FC } from 'react';
|
|||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import type { ChartDataSelection } from './chart-data-selection';
|
import type { ChartDataSelection } from './chart-data-selection';
|
||||||
import { selectablePeriods } from './selectable-periods';
|
import { selectablePeriods } from './selectable-periods';
|
||||||
|
import { parseMonthString } from './dates';
|
||||||
|
|
||||||
const dropdownWidth = '15rem';
|
const dropdownWidth = '15rem';
|
||||||
const dropdownInlinePadding = (theme: Theme) => theme.spacing(3);
|
const dropdownInlinePadding = (theme: Theme) => theme.spacing(3);
|
||||||
@ -148,10 +149,13 @@ export const PeriodSelector: FC<Props> = ({ selectedPeriod, setPeriod }) => {
|
|||||||
selectedPeriod.grouping === 'daily'
|
selectedPeriod.grouping === 'daily'
|
||||||
? selectedPeriod.month === format(new Date(), 'yyyy-MM')
|
? selectedPeriod.month === format(new Date(), 'yyyy-MM')
|
||||||
? 'Current month'
|
? 'Current month'
|
||||||
: new Date(selectedPeriod.month).toLocaleDateString('en-US', {
|
: parseMonthString(selectedPeriod.month).toLocaleDateString(
|
||||||
month: 'long',
|
'en-US',
|
||||||
year: 'numeric',
|
{
|
||||||
})
|
month: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
},
|
||||||
|
)
|
||||||
: `Last ${selectedPeriod.monthsBack} months`;
|
: `Last ${selectedPeriod.monthsBack} months`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -184,7 +188,7 @@ export const PeriodSelector: FC<Props> = ({ selectedPeriod, setPeriod }) => {
|
|||||||
<p>Last 12 months</p>
|
<p>Last 12 months</p>
|
||||||
</MonthSelectorHeaderGroup>
|
</MonthSelectorHeaderGroup>
|
||||||
<MonthGrid>
|
<MonthGrid>
|
||||||
{selectablePeriods.map((period, index) => (
|
{selectablePeriods.map((period) => (
|
||||||
<li key={period.label}>
|
<li key={period.label}>
|
||||||
<GridButton
|
<GridButton
|
||||||
selected={
|
selected={
|
||||||
|
@ -4,6 +4,7 @@ import { subMonths } from 'date-fns';
|
|||||||
import { useLocationSettings } from 'hooks/useLocationSettings';
|
import { useLocationSettings } from 'hooks/useLocationSettings';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import type { ChartDataSelection } from './chart-data-selection';
|
import type { ChartDataSelection } from './chart-data-selection';
|
||||||
|
import { parseMonthString } from './dates';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
period: ChartDataSelection;
|
period: ChartDataSelection;
|
||||||
@ -62,7 +63,7 @@ const incomingRequestsText = (period: ChartDataSelection): string => {
|
|||||||
return `Average requests from ${formatMonth(fromMonth)} to ${formatMonth(toMonth)}`;
|
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> = ({
|
export const RequestSummary: FC<Props> = ({
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { endOfMonth, format, startOfMonth, subMonths } from 'date-fns';
|
import { endOfMonth, format, startOfMonth, subMonths } from 'date-fns';
|
||||||
|
import { parseMonthString } from './dates';
|
||||||
|
|
||||||
export type ChartDataSelection =
|
export type ChartDataSelection =
|
||||||
| {
|
| {
|
||||||
@ -16,7 +17,7 @@ export const toDateRange = (
|
|||||||
): { from: string; to: string } => {
|
): { from: string; to: string } => {
|
||||||
const fmt = (date: Date) => format(date, 'yyyy-MM-dd');
|
const fmt = (date: Date) => format(date, 'yyyy-MM-dd');
|
||||||
if (selection.grouping === 'daily') {
|
if (selection.grouping === 'daily') {
|
||||||
const month = new Date(selection.month);
|
const month = parseMonthString(selection.month);
|
||||||
const from = fmt(month);
|
const from = fmt(month);
|
||||||
const to = fmt(endOfMonth(month));
|
const to = fmt(endOfMonth(month));
|
||||||
return { from, to };
|
return { from, to };
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
differenceInCalendarDays,
|
differenceInCalendarDays,
|
||||||
differenceInCalendarMonths,
|
differenceInCalendarMonths,
|
||||||
} from 'date-fns';
|
} from 'date-fns';
|
||||||
import { formatDay, formatMonth } from './dates';
|
import { formatDay, formatMonth, parseDateString } from './dates';
|
||||||
import type { ChartDataSelection } from './chart-data-selection';
|
import type { ChartDataSelection } from './chart-data-selection';
|
||||||
export type ChartDatasetType = ChartDataset<'bar'>;
|
export type ChartDatasetType = ChartDataset<'bar'>;
|
||||||
|
|
||||||
@ -80,8 +80,8 @@ const getLabelsAndRecords = (
|
|||||||
>,
|
>,
|
||||||
) => {
|
) => {
|
||||||
if (traffic.grouping === 'monthly') {
|
if (traffic.grouping === 'monthly') {
|
||||||
const from = new Date(traffic.dateRange.from);
|
const from = parseDateString(traffic.dateRange.from);
|
||||||
const to = new Date(traffic.dateRange.to);
|
const to = parseDateString(traffic.dateRange.to);
|
||||||
const numMonths = Math.abs(differenceInCalendarMonths(to, from)) + 1;
|
const numMonths = Math.abs(differenceInCalendarMonths(to, from)) + 1;
|
||||||
const monthsRec: { [month: string]: number } = {};
|
const monthsRec: { [month: string]: number } = {};
|
||||||
for (let i = 0; i < numMonths; i++) {
|
for (let i = 0; i < numMonths; i++) {
|
||||||
@ -95,8 +95,8 @@ const getLabelsAndRecords = (
|
|||||||
);
|
);
|
||||||
return { newRecord: () => ({ ...monthsRec }), labels };
|
return { newRecord: () => ({ ...monthsRec }), labels };
|
||||||
} else {
|
} else {
|
||||||
const from = new Date(traffic.dateRange.from);
|
const from = parseDateString(traffic.dateRange.from);
|
||||||
const to = new Date(traffic.dateRange.to);
|
const to = parseDateString(traffic.dateRange.to);
|
||||||
const numDays = Math.abs(differenceInCalendarDays(to, from)) + 1;
|
const numDays = Math.abs(differenceInCalendarDays(to, from)) + 1;
|
||||||
const daysRec: { [day: string]: number } = {};
|
const daysRec: { [day: string]: number } = {};
|
||||||
for (let i = 0; i < numDays; i++) {
|
for (let i = 0; i < numDays; i++) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { format, getDaysInMonth } from 'date-fns';
|
import { format, getDaysInMonth, parse } from 'date-fns';
|
||||||
|
|
||||||
export const currentDate = new Date();
|
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 formatMonth = (date: Date) => format(date, 'yyyy-MM');
|
||||||
export const formatDay = (date: Date) => format(date, 'yyyy-MM-dd');
|
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());
|
||||||
|
};
|
||||||
|
@ -4,6 +4,7 @@ import { periodsRecord, selectablePeriods } from '../selectable-periods';
|
|||||||
import { createBarChartOptions } from '../bar-chart-options';
|
import { createBarChartOptions } from '../bar-chart-options';
|
||||||
import useTheme from '@mui/material/styles/useTheme';
|
import useTheme from '@mui/material/styles/useTheme';
|
||||||
import { useLocationSettings } from 'hooks/useLocationSettings';
|
import { useLocationSettings } from 'hooks/useLocationSettings';
|
||||||
|
import { parseMonthString } from '../dates';
|
||||||
|
|
||||||
export const useChartDataSelection = (includedTraffic?: number) => {
|
export const useChartDataSelection = (includedTraffic?: number) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@ -35,11 +36,11 @@ export const useChartDataSelection = (includedTraffic?: number) => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const timestamp = Date.parse(tooltipItems[0].label);
|
const month = parseMonthString(tooltipItems[0].label);
|
||||||
if (Number.isNaN(timestamp)) {
|
if (Number.isNaN(month.getTime())) {
|
||||||
return 'Current month to date';
|
return 'Current month to date';
|
||||||
}
|
}
|
||||||
return new Date(timestamp).toLocaleDateString(
|
return month.toLocaleDateString(
|
||||||
locationSettings?.locale ?? 'en-US',
|
locationSettings?.locale ?? 'en-US',
|
||||||
{
|
{
|
||||||
month: 'long',
|
month: 'long',
|
||||||
|
@ -3,8 +3,8 @@ import { useTrafficSearch } from 'hooks/api/getters/useInstanceTrafficMetrics/us
|
|||||||
import { currentDate } from '../dates';
|
import { currentDate } from '../dates';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
toTrafficUsageChartData as newToChartData,
|
|
||||||
toConnectionChartData,
|
toConnectionChartData,
|
||||||
|
toTrafficUsageChartData,
|
||||||
} from '../chart-functions';
|
} from '../chart-functions';
|
||||||
import {
|
import {
|
||||||
calculateEstimatedMonthlyCost,
|
calculateEstimatedMonthlyCost,
|
||||||
@ -36,7 +36,7 @@ export const useTrafficStats = (
|
|||||||
}
|
}
|
||||||
const traffic = result.data;
|
const traffic = result.data;
|
||||||
|
|
||||||
const chartData = newToChartData(traffic, filter);
|
const chartData = toTrafficUsageChartData(traffic, filter);
|
||||||
const usageTotal = calculateTotalUsage(traffic);
|
const usageTotal = calculateTotalUsage(traffic);
|
||||||
const overageCost = calculateOverageCost(
|
const overageCost = calculateOverageCost(
|
||||||
usageTotal,
|
usageTotal,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { getDaysInMonth } from 'date-fns';
|
import { getDaysInMonth, startOfMonth, subMonths } from 'date-fns';
|
||||||
import { currentDate, formatMonth } from './dates';
|
import { currentDate, formatMonth } from './dates';
|
||||||
import { TRAFFIC_MEASUREMENT_START_DATE } from 'utils/traffic-calculations';
|
import { TRAFFIC_MEASUREMENT_START_DATE } from 'utils/traffic-calculations';
|
||||||
|
|
||||||
@ -38,21 +38,18 @@ export const toSelectablePeriod = (
|
|||||||
|
|
||||||
export const generateSelectablePeriodsFromDate = (now: Date) => {
|
export const generateSelectablePeriodsFromDate = (now: Date) => {
|
||||||
const selectablePeriods = [toSelectablePeriod(now, 'Current month')];
|
const selectablePeriods = [toSelectablePeriod(now, 'Current month')];
|
||||||
|
const startOfCurrentMonth = startOfMonth(now);
|
||||||
for (
|
for (
|
||||||
let subtractMonthCount = 1;
|
let subtractMonthCount = 1;
|
||||||
subtractMonthCount < 12;
|
subtractMonthCount < 12;
|
||||||
subtractMonthCount++
|
subtractMonthCount++
|
||||||
) {
|
) {
|
||||||
// this complicated calc avoids DST issues
|
const targetMonth = subMonths(startOfCurrentMonth, subtractMonthCount);
|
||||||
const utcYear = now.getUTCFullYear();
|
|
||||||
const utcMonth = now.getUTCMonth();
|
|
||||||
const targetMonth = utcMonth - subtractMonthCount;
|
|
||||||
const targetDate = new Date(Date.UTC(utcYear, targetMonth, 1, 0, 0, 0));
|
|
||||||
selectablePeriods.push(
|
selectablePeriods.push(
|
||||||
toSelectablePeriod(
|
toSelectablePeriod(
|
||||||
targetDate,
|
targetMonth,
|
||||||
undefined,
|
undefined,
|
||||||
targetDate >= TRAFFIC_MEASUREMENT_START_DATE,
|
targetMonth >= TRAFFIC_MEASUREMENT_START_DATE,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,11 @@ import type {
|
|||||||
} from 'openapi';
|
} from 'openapi';
|
||||||
import { getDaysInMonth } from 'date-fns';
|
import { getDaysInMonth } from 'date-fns';
|
||||||
import { format } 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_COST = 5;
|
||||||
export const DEFAULT_TRAFFIC_DATA_UNIT_SIZE = 1_000_000;
|
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 = [
|
export const METERED_TRAFFIC_ENDPOINTS = [
|
||||||
'/api/admin',
|
'/api/admin',
|
||||||
|
Loading…
Reference in New Issue
Block a user