-
Notifications
You must be signed in to change notification settings - Fork 4
chore: pass timezone through headers #351
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| export * from './sanitize'; | ||
| export * from './user'; | ||
| export * from './get-hostname'; | ||
| export * from './sanitize'; | ||
| export * from './subdomain'; | ||
| export * from './timezone'; | ||
| export * from './user'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import { IncomingHttpHeaders } from 'http'; | ||
|
|
||
| export const getTimezone = (req: IncomingHttpHeaders): string => { | ||
| const timezone = req['x-timezone'] || req['timezone'] || 'UTC'; | ||
|
|
||
| if (Array.isArray(timezone)) { | ||
| return timezone[0]; | ||
| } | ||
|
|
||
| return timezone; | ||
| }; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||||||||||||||
| import { fillMissingDays } from '@/project/utils/charUtils'; | ||||||||||||||||||
| import { STATUS_TYPES } from '@/status/constants/types'; | ||||||||||||||||||
| import { differenceInCalendarDays, startOfDay } from 'date-fns'; | ||||||||||||||||||
| import { differenceInCalendarDays } from 'date-fns'; | ||||||||||||||||||
| import { toZonedTime } from 'date-fns-tz'; | ||||||||||||||||||
| import { Types } from 'mongoose'; | ||||||||||||||||||
| import { IModels } from '~/connectionResolvers'; | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
@@ -115,6 +116,7 @@ export const getCycleProgressChart = async ( | |||||||||||||||||
| cycleId: string, | ||||||||||||||||||
| assigneeId: string | undefined, | ||||||||||||||||||
| models: IModels, | ||||||||||||||||||
| timezone: string, | ||||||||||||||||||
| ) => { | ||||||||||||||||||
|
Comment on lines
+119
to
120
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainProvide a safe default for the timezone parameter. Guard callers that haven’t been migrated by defaulting to 'UTC'. -export const getCycleProgressChart = async (
+export const getCycleProgressChart = async (
cycleId: string,
assigneeId: string | undefined,
models: IModels,
- timezone: string,
+ timezone: string = 'UTC',
) => {Run this to find any remaining call sites not passing a TZ: 🏁 Script executed: #!/bin/bash
rg -n --type=ts 'getCycleProgressChart\(' -C2Length of output: 2668 Default timezone parameter to 'UTC' in getCycleProgressChart Default timezone to 'UTC' so callers that haven't been migrated don't break — resolver already passes timezone, but there is at least one internal call that omits it. -export const getCycleProgressChart = async (
+export const getCycleProgressChart = async (
cycleId: string,
assigneeId: string | undefined,
models: IModels,
- timezone: string,
+ timezone: string = 'UTC',
) => {Remaining caller without TZ: backend/plugins/operation_api/src/modules/cycle/db/models/Cycle.ts:124 (const chartData = await getCycleProgressChart(_id, undefined, models);) 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| const filter: { cycleId: Types.ObjectId; assigneeId?: string } = { | ||||||||||||||||||
| cycleId: new Types.ObjectId(cycleId), | ||||||||||||||||||
|
|
@@ -177,10 +179,10 @@ export const getCycleProgressChart = async ( | |||||||||||||||||
| { | ||||||||||||||||||
| $addFields: { | ||||||||||||||||||
| dayDate: { | ||||||||||||||||||
| $dateFromParts: { | ||||||||||||||||||
| year: { $year: '$statusChangedDate' }, | ||||||||||||||||||
| month: { $month: '$statusChangedDate' }, | ||||||||||||||||||
| day: { $dayOfMonth: '$statusChangedDate' }, | ||||||||||||||||||
| $dateToString: { | ||||||||||||||||||
| format: '%Y-%m-%d', | ||||||||||||||||||
| date: '$statusChangedDate', | ||||||||||||||||||
| timezone, | ||||||||||||||||||
| }, | ||||||||||||||||||
| }, | ||||||||||||||||||
| isStarted: { $eq: ['$statusType', STATUS_TYPES.STARTED] }, | ||||||||||||||||||
|
|
@@ -210,7 +212,7 @@ export const getCycleProgressChart = async ( | |||||||||||||||||
| { | ||||||||||||||||||
| $project: { | ||||||||||||||||||
| _id: 0, | ||||||||||||||||||
| date: { $dateToString: { format: '%Y-%m-%d', date: '$_id' } }, | ||||||||||||||||||
| date: '$_id', | ||||||||||||||||||
| started: 1, | ||||||||||||||||||
| completed: 1, | ||||||||||||||||||
| }, | ||||||||||||||||||
|
|
@@ -238,16 +240,12 @@ export const getCycleProgressChart = async ( | |||||||||||||||||
| chartData: [], | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| const start = startOfDay(new Date(cycle.startDate)); | ||||||||||||||||||
| const end = startOfDay(new Date(cycle.endDate)); | ||||||||||||||||||
| const start = toZonedTime(cycle.startDate, timezone); | ||||||||||||||||||
| const end = toZonedTime(cycle.endDate, timezone); | ||||||||||||||||||
|
|
||||||||||||||||||
| const days = differenceInCalendarDays(end, start) + 1; | ||||||||||||||||||
|
|
||||||||||||||||||
| chartData.chartData = fillMissingDays( | ||||||||||||||||||
| chartDataAggregation, | ||||||||||||||||||
| cycle.startDate, | ||||||||||||||||||
| days, | ||||||||||||||||||
| ); | ||||||||||||||||||
| chartData.chartData = fillMissingDays(chartDataAggregation, start, days); | ||||||||||||||||||
|
|
||||||||||||||||||
|
Comment on lines
+243
to
249
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid double TZ conversion and DST drift; compute days on zoned midnights and pass an unzoned base into fillMissingDays (TZ-aware).
-import { differenceInCalendarDays } from 'date-fns';
+import { differenceInCalendarDays, startOfDay } from 'date-fns';
import { toZonedTime } from 'date-fns-tz';
@@
- const start = toZonedTime(cycle.startDate, timezone);
- const end = toZonedTime(cycle.endDate, timezone);
-
- const days = differenceInCalendarDays(end, start) + 1;
-
- chartData.chartData = fillMissingDays(chartDataAggregation, start, days);
+ const startZoned = startOfDay(toZonedTime(cycle.startDate, timezone));
+ const endZoned = startOfDay(toZonedTime(cycle.endDate, timezone));
+
+ const days = differenceInCalendarDays(endZoned, startZoned) + 1;
+
+ // Pass unzoned base and timezone to avoid double-shifting inside fillMissingDays
+ chartData.chartData = fillMissingDays(
+ chartDataAggregation,
+ cycle.startDate,
+ days,
+ timezone,
+ );If fillMissingDays isn’t TZ-aware yet, update it like below (outside this file): // backend/plugins/operation_api/src/modules/project/utils/charUtils.ts
import { addDays } from 'date-fns';
import { formatInTimeZone } from 'date-fns-tz';
export function fillMissingDays(
data: { date: string; started: number; completed: number }[],
baseDate: Date,
totalDays = 7,
timezone = 'UTC',
) {
const filled: typeof data = [];
const index = new Map(data.map(d => [d.date, d]));
for (let i = 0; i < totalDays; i++) {
const date = addDays(baseDate, i);
const key = formatInTimeZone(date, timezone, 'yyyy-MM-dd');
const item = index.get(key) ?? filled[filled.length - 1] ?? { started: 0, completed: 0, date: key };
filled.push(item.date === key ? item : { date: key, started: item.started, completed: item.completed });
}
return filled;
} |
||||||||||||||||||
| return chartData; | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -35,6 +35,7 @@ const authLink = setContext((_, { headers }) => { | |||||||||||||||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...headers, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| sessioncode: sessionStorage.getItem('sessioncode') || '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'x-timezone': Intl.DateTimeFormat().resolvedOptions().timeZone, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
35
to
40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: Client timezone detection may not be reliable in all environments. Consider adding fallback logic or validation for environments where Intl.DateTimeFormat().resolvedOptions().timeZone is unavailable or inaccurate.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
🧩 Analysis chain
Type timezone strictly; avoid any
getTimezone returns a string after validation; carry that type into context.
Please verify IContext['requestInfo'] includes timezone: string.
Also applies to: 25-26, 41-44
🏁 Script executed:
Length of output: 873
🏁 Script executed:
Length of output: 7065
🏁 Script executed:
Length of output: 6772
🏁 Script executed:
Length of output: 1595
Enforce timezone as string across context
getTimezone can return string|string[]; coerce it to a string, declare its return type, and propagate strict typing into the context and core types.
🤖 Prompt for AI Agents