mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +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:
		
							parent
							
								
									0aae3bac0a
								
							
						
					
					
						commit
						dadda7b648
					
				| @ -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={ | ||||
|  | ||||
| @ -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> = ({ | ||||
|  | ||||
| @ -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 }; | ||||
|  | ||||
| @ -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++) { | ||||
|  | ||||
| @ -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()); | ||||
| }; | ||||
|  | ||||
| @ -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', | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
| @ -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, | ||||
|             ), | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -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', | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user