Skip to content

Conversation

@batmnkh2344
Copy link
Contributor

@batmnkh2344 batmnkh2344 commented Sep 22, 2025

Summary by Sourcery

Propagate client timezone through GraphQL headers to cycle utilities and introduce timezone-aware date handling for cycle progress charts

New Features:

  • Capture client timezone from request headers and include it in Apollo context
  • Add getTimezone utility to extract timezone (defaults to UTC) from headers

Enhancements:

  • Use $dateToString with timezone in MongoDB aggregation for cycle progress
  • Convert start and end dates to zoned time using date-fns-tz in cycle utilities
  • Extend fillMissingDays to accept timezone and format dates accordingly
  • Include x-timezone header in frontend Apollo client requests

Chores:

  • Reorder and export timezone utilities in shared header module

Summary by CodeRabbit

  • New Features
    • Cycle progress metrics are now timezone-aware, using your local timezone for more accurate daily aggregations and chart ranges.
    • The Cycle Progress chart displays active data-point markers on “Started” and “Completed” series for clearer interaction feedback.
    • The app automatically includes your timezone in requests; no setup required.

Important

Propagate client timezone through headers for timezone-aware cycle progress charts in both backend and frontend.

  • Backend:
    • Capture client timezone from headers using getTimezone in timezone.ts.
    • Pass timezone to Apollo context in generateApolloContext in utils.ts.
    • Modify getCycleProgressChart in cycle.ts and utils.ts to use timezone in MongoDB aggregations and date conversions.
  • Frontend:
    • Send x-timezone header in Apollo client requests in apolloClient.ts.
    • Update CycleProgressChart.tsx to show active data-point markers for started and completed series.
  • Misc:
    • Reorder and export timezone utilities in index.ts.

This description was created by Ellipsis for b1a6190. You can customize this summary. It will automatically update as commits are pushed.

@sourcery-ai
Copy link

sourcery-ai bot commented Sep 22, 2025

Reviewer's Guide

This PR adds timezone propagation from the client through HTTP headers into the Apollo context and downstream cycle progress utilities, updating all date aggregations and formatting to use timezone-aware functions for consistent charting.

Sequence diagram for timezone propagation from client to backend

sequenceDiagram
actor User
participant Frontend
participant Backend
participant ApolloContext
participant CycleUtils
User->>Frontend: Initiates request
Frontend->>Backend: Sends request with x-timezone header
Backend->>ApolloContext: Passes headers
ApolloContext->>CycleUtils: Passes timezone from headers
CycleUtils->>CycleUtils: Uses timezone in date calculations
Loading

Class diagram for fillMissingDays utility with timezone support

classDiagram
class fillMissingDays {
  +fillMissingDays(data, baseDate, totalDays, timezone)
}
fillMissingDays : uses toZonedTime(date, timezone)
fillMissingDays : formats date with timezone
Loading

File-Level Changes

Change Details Files
Propagate timezone header into Apollo context and GraphQL resolvers
  • Extract timezone from headers in generateApolloContext and attach to requestInfo
  • Export new timezone util and update header exports
  • Include 'x-timezone' header in Apollo client requests
  • Retrieve timezone in cycleQueries resolver and pass to getCycleProgressChart
backend/erxes-api-shared/src/utils/apollo/utils.ts
backend/erxes-api-shared/src/utils/headers/index.ts
backend/erxes-api-shared/src/utils/headers/timezone.ts
frontend/core-ui/src/providers/apollo-provider/apolloClient.ts
backend/plugins/operation_api/src/modules/cycle/graphql/resolvers/queries/cycle.ts
Make cycle progress chart generation timezone-aware
  • Add timezone parameter to getCycleProgressChart signature and call sites
  • Replace MongoDB $dateFromParts with $dateToString including timezone
  • Use toZonedTime for start/end date calculations
  • Pass timezone into fillMissingDays and apply in date formatting
  • Simplify pipeline projection by using preformatted date strings
backend/plugins/operation_api/src/modules/cycle/utils.ts
backend/plugins/operation_api/src/modules/project/utils/charUtils.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link

coderabbitai bot commented Sep 22, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Introduces timezone handling end-to-end: frontend sends x-timezone header, backend extracts timezone into Apollo context, GraphQL resolver propagates it to cycle progress utilities, and computations become timezone-aware. Also adds a new timezone header utility and minor UI chart interaction tweaks.

Changes

Cohort / File(s) Summary
Timezone header utilities
backend/erxes-api-shared/src/utils/headers/timezone.ts, backend/erxes-api-shared/src/utils/headers/index.ts
New getTimezone(req) reads x-timezone/timezone headers with fallback to UTC; exported via headers index with reordered exports.
Apollo context update
backend/erxes-api-shared/src/utils/apollo/utils.ts
Imports getTimezone; extracts timezone from request headers and adds it to context.requestInfo.
Resolver and cycle utils (timezone-aware)
backend/plugins/operation_api/src/modules/cycle/graphql/resolvers/queries/cycle.ts, backend/plugins/operation_api/src/modules/cycle/utils.ts
Resolver destructures timezone from context and passes it to getCycleProgressChart; utils function gains timezone param, uses date-fns-tz to compute zoned start/end, updates aggregations to be timezone-aware, and adjusts projections/fill logic.
Frontend Apollo header
frontend/core-ui/src/providers/apollo-provider/apolloClient.ts
Sends client IANA timezone via x-timezone header with GraphQL requests.
UI chart interaction tweak
frontend/plugins/operation_ui/src/modules/cycle/components/detail/CycleProgressChart.tsx
Adds activeDot to two Area series for visible active data-point markers.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant UI as Frontend UI
  participant ApolloC as Apollo Client
  participant Net as Network
  participant ApolloS as Apollo Server Context
  participant Resolver as Cycle Resolver
  participant Utils as getCycleProgressChart (utils)
  participant DB as Database

  UI->>ApolloC: Query getCycleProgressChart
  Note right of ApolloC: Resolve client timezone via Intl API
  ApolloC->>Net: HTTP POST GraphQL<br/>Headers: x-timezone=IANA TZ
  Net->>ApolloS: Request with headers
  ApolloS->>ApolloS: Extract timezone via getTimezone()<br/>default 'UTC'
  ApolloS-->>Resolver: context { requestInfo.timezone, models, ... }

  rect rgba(200, 230, 255, 0.3)
    Note over Resolver,Utils: New/changed flow: pass timezone through
    Resolver->>Utils: getCycleProgressChart(id, assigneeId, models, timezone)
    Utils->>Utils: Compute zoned start/end (toZonedTime)
    Utils->>DB: Aggregations with $dateToString { timezone }
    DB-->>Utils: Aggregated chart data
    Utils-->>Resolver: Chart series (zoned)
  end

  Resolver-->>UI: GraphQL response (chart data)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

A whisker twitch, a clock’s soft chime,
I hop through zones that shift in time.
From headers’ hush to charts that gleam,
I stitch the sun into the stream.
Now days align, dots glow just right—
A rabbit’s graph, by day and night. 🐇⏰📈

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "chore: pass timezone through headers" is concise and accurately describes the primary change in the PR — propagating the client timezone via headers into the GraphQL context and downstream cycle utilities — and it avoids noisy details or irrelevant terms, so a reviewer scanning history will understand the main intent.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch operation-cycle-chart

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • You’re threading timezone through a lot of utilities—consider centralizing timezone extraction and conversion at the context or data‐boundary layer so you don’t have to pass it through every function signature.
  • Add validation or normalization for the incoming timezone header (e.g. against the IANA tz database) so that invalid or malformed values don’t break downstream date conversions.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- You’re threading timezone through a lot of utilities—consider centralizing timezone extraction and conversion at the context or data‐boundary layer so you don’t have to pass it through every function signature.
- Add validation or normalization for the incoming timezone header (e.g. against the IANA tz database) so that invalid or malformed values don’t break downstream date conversions.

## Individual Comments

### Comment 1
<location> `backend/erxes-api-shared/src/utils/headers/timezone.ts:3-4` </location>
<code_context>
+import { IncomingHttpHeaders } from 'http';
+
+export const getTimezone = (req: IncomingHttpHeaders) => {
+  return req['x-timezone'] || req['timezone'] || 'UTC';
+};
</code_context>

<issue_to_address>
**suggestion:** Consider normalizing timezone header casing and value.

Direct string key access may miss headers with different casing. Normalize header keys to lowercase or use a header parsing library. Also, validate that the timezone value is a valid IANA timezone string to avoid errors.
</issue_to_address>

### Comment 2
<location> `backend/plugins/operation_api/src/modules/cycle/utils.ts:116-120` </location>
<code_context>
   cycleId: string,
   assigneeId: string | undefined,
   models: IModels,
+  timezone: string,
 ) => {
   const filter: { cycleId: Types.ObjectId; assigneeId?: string } = {
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Timezone parameter is required but not validated.

Please add validation for the timezone parameter or handle errors from downstream date-fns-tz functions to prevent runtime exceptions.

```suggestion
  cycleId: string,
  assigneeId: string | undefined,
  models: IModels,
  timezone: string,
) => {
  // Validate timezone parameter
  if (
    typeof timezone !== 'string' ||
    !/^[A-Za-z_\/]+$/.test(timezone) // basic format check
  ) {
    throw new Error(`Invalid timezone parameter: "${timezone}"`);
  }
  try {
    // Try to convert a known date to the timezone to check validity
    toZonedTime(new Date(), timezone);
  } catch (err) {
    throw new Error(`Invalid timezone parameter: "${timezone}". Error: ${err.message}`);
  }
```
</issue_to_address>

### Comment 3
<location> `frontend/core-ui/src/providers/apollo-provider/apolloClient.ts:35-40` </location>
<code_context>
     headers: {
       ...headers,
       sessioncode: sessionStorage.getItem('sessioncode') || '',
+      'x-timezone': Intl.DateTimeFormat().resolvedOptions().timeZone,
     },
   };
</code_context>

<issue_to_address>
**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.

```suggestion
    // Helper to safely get timezone, fallback to 'UTC'
    function getClientTimezone(): string {
      try {
        const tz = Intl?.DateTimeFormat?.().resolvedOptions?.().timeZone;
        if (typeof tz === 'string' && tz.length > 0) {
          return tz;
        }
      } catch (e) {
        // Ignore errors and fallback
      }
      return 'UTC';
    }

    headers: {
      ...headers,
      sessioncode: sessionStorage.getItem('sessioncode') || '',
      'x-timezone': getClientTimezone(),
    },
  };
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 3 to 4
export const getTimezone = (req: IncomingHttpHeaders) => {
return req['x-timezone'] || req['timezone'] || 'UTC';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider normalizing timezone header casing and value.

Direct string key access may miss headers with different casing. Normalize header keys to lowercase or use a header parsing library. Also, validate that the timezone value is a valid IANA timezone string to avoid errors.

Comment on lines 35 to 40
headers: {
...headers,
sessioncode: sessionStorage.getItem('sessioncode') || '',
'x-timezone': Intl.DateTimeFormat().resolvedOptions().timeZone,
},
};
Copy link

Choose a reason for hiding this comment

The 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
headers: {
...headers,
sessioncode: sessionStorage.getItem('sessioncode') || '',
'x-timezone': Intl.DateTimeFormat().resolvedOptions().timeZone,
},
};
// Helper to safely get timezone, fallback to 'UTC'
function getClientTimezone(): string {
try {
const tz = Intl?.DateTimeFormat?.().resolvedOptions?.().timeZone;
if (typeof tz === 'string' && tz.length > 0) {
return tz;
}
} catch (e) {
// Ignore errors and fallback
}
return 'UTC';
}
headers: {
...headers,
sessioncode: sessionStorage.getItem('sessioncode') || '',
'x-timezone': getClientTimezone(),
},
};

Copy link

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Changes requested ❌

Reviewed everything up to ad97300 in 1 minute and 24 seconds. Click for details.
  • Reviewed 186 lines of code in 7 files
  • Skipped 0 files when reviewing.
  • Skipped posting 4 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. backend/erxes-api-shared/src/utils/headers/timezone.ts:4
  • Draft comment:
    Consider normalizing the header value (e.g. trimming whitespace) to ensure a clean timezone string.
  • Reason this comment was not posted:
    Confidence changes required: 50% <= threshold 50% None
2. backend/plugins/operation_api/src/modules/cycle/utils.ts:182
  • Draft comment:
    Ensure the passed timezone is validated to avoid injecting invalid timezone strings into the aggregation pipeline. Consider providing a fallback (like 'UTC') if the timezone is not valid.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50% 1. MongoDB's $dateToString handles invalid timezones by defaulting to UTC. 2. date-fns-tz's toZonedTime() also handles invalid timezones gracefully. 3. This is a speculative comment asking for validation without evidence of actual issues. 4. The comment starts with "Ensure that..." which is a red flag per our rules. I could be wrong about how MongoDB and date-fns-tz handle invalid timezones. There could be edge cases where invalid timezones cause runtime errors. Both MongoDB and date-fns-tz are well-tested libraries that handle timezone validation internally. If there were issues with invalid timezones, they would surface during normal testing. Delete this comment. It's speculative, starts with "Ensure that...", and suggests validation that's likely already handled by the underlying libraries.
3. backend/plugins/operation_api/src/modules/project/utils/charUtils.ts:38
  • Draft comment:
    Good incorporation of timezone conversion via toZonedTime. For clarity and type safety, consider explicitly typing the timezone parameter in the function signature.
  • Reason this comment was not posted:
    Comment looked like it was already resolved.
4. frontend/core-ui/src/providers/apollo-provider/apolloClient.ts:38
  • Draft comment:
    The 'x-timezone' header is set correctly using the browser's resolved timezone. Consider a fallback in case the browser does not support this feature.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 20% vs. threshold = 50% 1. Intl.DateTimeFormat is supported in all modern browsers since IE11. 2. The code follows the same pattern as the sessioncode line above which also doesn't have a fallback. 3. If timezone detection fails, that likely indicates a serious browser compatibility issue that should be caught in testing. 4. The comment is speculative - "in case the browser does not support this feature" rather than identifying a real issue. The suggestion could prevent runtime errors in very old browsers. The fallback pattern does follow defensive programming practices. Given universal modern browser support and that this is a frontend app, the suggestion is overly cautious and doesn't address a real-world issue. Delete the comment as it suggests handling an edge case that is extremely unlikely in practice and doesn't represent a real issue.

Workflow ID: wflow_vbN5xGEm8QGRH4aN

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

return {};
}
const user: any = extractUserFromHeader(req.headers);
const timezone: any = getTimezone(req.headers);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use an explicit type (string) for the timezone instead of 'any'.

Suggested change
const timezone: any = getTimezone(req.headers);
const timezone: string = getTimezone(req.headers);

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/plugins/operation_api/src/modules/cycle/utils.ts (1)

131-133: Return object when cycle is missing — keep GraphQL/consumer types consistent

getCycleProgressChart returns [] when no cycle is found; GraphQL schema exposes it as JSON and frontend expects an object shape { totalScope, chartData } — return an object instead to avoid runtime/type mismatches.

Locations:

  • backend/plugins/operation_api/src/modules/cycle/utils.ts (if (!cycle) return [] — ~lines 131–133)
  • GraphQL schema: backend/plugins/operation_api/src/modules/cycle/graphql/schemas/cycle.ts (getCycleProgressChart: JSON)
  • Frontend consumer: frontend/plugins/operation_ui/src/modules/cycle/hooks/useGetCycleProgressChart.tsx (IGetCycleProgressChart)

Suggested change:

-  if (!cycle) {
-    return [];
-  }
+  if (!cycle) {
+    return { totalScope: 0, chartData: [] };
+  }
🧹 Nitpick comments (4)
frontend/core-ui/src/providers/apollo-provider/apolloClient.ts (2)

38-39: Always default timezone to a valid string

Guard against undefined and send a consistent value.

-      'x-timezone': Intl.DateTimeFormat().resolvedOptions().timeZone,
+      'x-timezone': Intl.DateTimeFormat().resolvedOptions().timeZone ?? 'UTC',

47-55: Propagate timezone to subscriptions (connectionParams)

Resolvers reached via WS won’t see request headers. Pass timezone (and sessioncode) via connectionParams for parity.

 export const wsLink = new GraphQLWsLink(
   createClient({
     url: `${REACT_APP_API_URL}/graphql`,
+    connectionParams: () => ({
+      sessioncode: sessionStorage.getItem('sessioncode') || '',
+      'x-timezone': Intl.DateTimeFormat().resolvedOptions().timeZone ?? 'UTC',
+    }),
     retryAttempts: 1000,

Please confirm the server’s WS context reads connectionParams (graphql-ws) and that CORS/WS configs allow these fields.

backend/plugins/operation_api/src/modules/project/utils/charUtils.ts (1)

2-2: Avoid double timezone conversion; add safe default

Using toZonedTime here can double-shift if callers already zoned the base date. Prefer formatting directly in the target TZ and default timezone to 'UTC' to keep old call sites safe.

-import { toZonedTime } from 'date-fns-tz';
+import { formatInTimeZone } from 'date-fns-tz';
@@
-export const fillMissingDays = (
+export const fillMissingDays = (
   data: { date: string; started: number; completed: number }[],
   baseDate: Date,
   totalDays = 7,
-  timezone: string,
+  timezone: string = 'UTC',
 ) => {
@@
-    const key = format(toZonedTime(date, timezone), 'yyyy-MM-dd');
+    const key = formatInTimeZone(date, timezone, 'yyyy-MM-dd');

Also applies to: 30-31, 38-38

backend/plugins/operation_api/src/modules/cycle/graphql/resolvers/queries/cycle.ts (1)

83-86: Defensive default when passing timezone to utils

Ensure stable behavior even if context lacks timezone.

-    return getCycleProgressChart(_id, assigneeId, models, timezone);
+    return getCycleProgressChart(_id, assigneeId, models, timezone ?? 'UTC');

Also confirm IContext.requestInfo.timezone is typed and always set in HTTP and WS contexts.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 330ce09 and ad97300.

📒 Files selected for processing (7)
  • backend/erxes-api-shared/src/utils/apollo/utils.ts (3 hunks)
  • backend/erxes-api-shared/src/utils/headers/index.ts (1 hunks)
  • backend/erxes-api-shared/src/utils/headers/timezone.ts (1 hunks)
  • backend/plugins/operation_api/src/modules/cycle/graphql/resolvers/queries/cycle.ts (2 hunks)
  • backend/plugins/operation_api/src/modules/cycle/utils.ts (5 hunks)
  • backend/plugins/operation_api/src/modules/project/utils/charUtils.ts (2 hunks)
  • frontend/core-ui/src/providers/apollo-provider/apolloClient.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Use functional and declarative programming patterns; avoid classes.
Use TypeScript for all code; prefer interfaces over types.
Avoid enums; use maps instead.

Files:

  • backend/erxes-api-shared/src/utils/headers/timezone.ts
  • backend/plugins/operation_api/src/modules/cycle/utils.ts
  • backend/erxes-api-shared/src/utils/apollo/utils.ts
  • frontend/core-ui/src/providers/apollo-provider/apolloClient.ts
  • backend/plugins/operation_api/src/modules/cycle/graphql/resolvers/queries/cycle.ts
  • backend/erxes-api-shared/src/utils/headers/index.ts
  • backend/plugins/operation_api/src/modules/project/utils/charUtils.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError).
Avoid console logs.
Always use absolute paths when importing.
Use the "function" keyword for pure functions.
Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements.

**/*.{ts,tsx,js,jsx}: Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError).
Use the "function" keyword for pure functions.
Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements.
Use absolute path when import.

Files:

  • backend/erxes-api-shared/src/utils/headers/timezone.ts
  • backend/plugins/operation_api/src/modules/cycle/utils.ts
  • backend/erxes-api-shared/src/utils/apollo/utils.ts
  • frontend/core-ui/src/providers/apollo-provider/apolloClient.ts
  • backend/plugins/operation_api/src/modules/cycle/graphql/resolvers/queries/cycle.ts
  • backend/erxes-api-shared/src/utils/headers/index.ts
  • backend/plugins/operation_api/src/modules/project/utils/charUtils.ts
🧬 Code graph analysis (3)
backend/plugins/operation_api/src/modules/cycle/utils.ts (1)
backend/plugins/operation_api/src/modules/project/utils/charUtils.ts (1)
  • fillMissingDays (26-58)
backend/erxes-api-shared/src/utils/apollo/utils.ts (1)
backend/erxes-api-shared/src/utils/headers/timezone.ts (1)
  • getTimezone (3-5)
backend/plugins/operation_api/src/modules/cycle/graphql/resolvers/queries/cycle.ts (1)
backend/plugins/operation_api/src/modules/cycle/utils.ts (1)
  • getCycleProgressChart (115-256)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Sourcery review
  • GitHub Check: core-ui
  • GitHub Check: frontline_api-ci
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
backend/plugins/operation_api/src/modules/cycle/utils.ts (1)

171-220: Validate timezone upstream to prevent Mongo aggregation errors

$dateToString’s timezone throws on invalid values. Ensure getTimezone validation is in place (see shared util suggestion).

backend/erxes-api-shared/src/utils/headers/index.ts (1)

4-5: Barrel export OK — one consumer found; verify no circular imports

Found: backend/erxes-api-shared/src/utils/apollo/utils.ts imports { extractUserFromHeader, getTimezone } from '../headers'.

Confirm backend/erxes-api-shared/src/utils/headers/{timezone.ts,user.ts} do not import from apollo or from modules that depend on apollo. Run:
rg -nP "from '\.\./headers'" backend --type=ts -C2
rg -n "apollo" backend --type=ts -C2

Comment on lines +4 to 7
import { IMainContext } from '../../core-types';
import { extractUserFromHeader, getTimezone } from '../headers';
import { getSubdomain } from '../utils';

Copy link

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.

-    const user: any = extractUserFromHeader(req.headers);
-    const timezone: any = getTimezone(req.headers);
+    const user: any = extractUserFromHeader(req.headers);
+    const timezone: string = getTimezone(req.headers);

Please verify IContext['requestInfo'] includes timezone: string.

Also applies to: 25-26, 41-44


🏁 Script executed:

#!/bin/bash
rg -nP -C2 '\binterface\s+IContext\b' --typescript
rg -nP -C2 'requestInfo\s*:\s*{[^}]*}' --typescript

Length of output: 873


🏁 Script executed:

#!/bin/bash
set -e
# Display first part of the utils.ts file to confirm context usage
sed -n '1,200p' backend/erxes-api-shared/src/utils/apollo/utils.ts
# Search for interface definitions of IContext or IMainContext in .ts files
rg -nP '\binterface\s+(IContext|IMainContext)\b' -g '*.ts' -C2
# Locate IMainContext definition in core-types directory
rg -nP 'export\s+interface\s+IMainContext' -g 'backend/erxes-api-shared/src/core-types/**/*.ts'

Length of output: 7065


🏁 Script executed:

#!/bin/bash
set -e
echo "== core-types/common.ts =="
sed -n '1,240p' backend/erxes-api-shared/src/core-types/common.ts || true

echo
echo "== utils/headers.ts =="
sed -n '1,240p' backend/erxes-api-shared/src/utils/headers.ts || true

echo
echo "== rg getTimezone matches =="
rg -n "getTimezone" -S || true

echo
echo "== rg extractUserFromHeader matches =="
rg -n "extractUserFromHeader" -S || true

echo
echo "== rg requestInfo matches =="
rg -n "requestInfo" -S || true

echo
echo "== gateway/connectionResolver.ts =="
sed -n '1,240p' backend/gateway/src/connectionResolver.ts || true

Length of output: 6772


🏁 Script executed:

#!/bin/bash
set -e
echo "== headers directory =="
ls -la backend/erxes-api-shared/src/utils/headers || true

echo
echo "== timezone.ts =="
sed -n '1,240p' backend/erxes-api-shared/src/utils/headers/timezone.ts || true

echo
echo "== user.ts =="
sed -n '1,240p' backend/erxes-api-shared/src/utils/headers/user.ts || true

echo
echo "== headers index (if present) =="
sed -n '1,240p' backend/erxes-api-shared/src/utils/headers/index.ts || true

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.

  • backend/erxes-api-shared/src/utils/headers/timezone.ts — change to return string (handle Array by taking first element) and add a return type.
  • backend/erxes-api-shared/src/utils/apollo/utils.ts — change the local type:
-    const timezone: any = getTimezone(req.headers);
+    const timezone: string = getTimezone(req.headers);
  • backend/erxes-api-shared/src/core-types/common.ts — replace requestInfo: any with a typed shape that includes timezone: string (and the used fields like secure, cookies, headers).

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In backend/erxes-api-shared/src/utils/apollo/utils.ts around lines 4 to 7,
ensure timezone is treated as a string throughout: change
backend/erxes-api-shared/src/utils/headers/timezone.ts to explicitly return
string (if getTimezone receives an array, take the first element) and add the
return type annotation; update backend/erxes-api-shared/src/core-types/common.ts
to replace requestInfo: any with a concrete typed shape that includes timezone:
string plus the used fields (secure, cookies, headers); then update utils.ts to
call the newly typed getTimezone (or coerce its result with String(...) / take
first element) and propagate the strict timezone:string into the
IMainContext/requestInfo usage so all types align.

Comment on lines 3 to 5
export const getTimezone = (req: IncomingHttpHeaders) => {
return req['x-timezone'] || req['timezone'] || 'UTC';
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Sanitize, normalize, and validate timezone header

IncomingHttpHeaders values can be string | string[] | undefined. Also validate IANA timezones to avoid Mongo $dateToString failures on bad input.

-export const getTimezone = (req: IncomingHttpHeaders) => {
-  return req['x-timezone'] || req['timezone'] || 'UTC';
-};
+export const getTimezone = (headers: IncomingHttpHeaders): string => {
+  const raw = headers['x-timezone'] ?? headers['timezone'];
+  const value = Array.isArray(raw) ? raw[0] : raw;
+  if (typeof value !== 'string') return 'UTC';
+  try {
+    // Intl throws RangeError for invalid time zones
+    new Intl.DateTimeFormat('en-US', { timeZone: value }).format();
+    return value;
+  } catch {
+    return 'UTC';
+  }
+};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const getTimezone = (req: IncomingHttpHeaders) => {
return req['x-timezone'] || req['timezone'] || 'UTC';
};
export const getTimezone = (headers: IncomingHttpHeaders): string => {
const raw = headers['x-timezone'] ?? headers['timezone'];
const value = Array.isArray(raw) ? raw[0] : raw;
if (typeof value !== 'string') return 'UTC';
try {
// Intl throws RangeError for invalid time zones
new Intl.DateTimeFormat('en-US', { timeZone: value }).format();
return value;
} catch {
return 'UTC';
}
};
🤖 Prompt for AI Agents
In backend/erxes-api-shared/src/utils/headers/timezone.ts around lines 3 to 5,
the current getter returns raw header values which may be string | string[] |
undefined and may contain invalid timezones; update it to normalize and
validate: if header is an array, pick the first element; coerce to string, trim
and uppercase/lowercase as appropriate; validate the candidate timezone using a
reliable IANA check (e.g. attempt to construct an Intl.DateTimeFormat with the
timeZone option and catch RangeError) and only return it when valid; otherwise
fall back to 'UTC'. Ensure the function returns a string and handles undefined
safely.

Comment on lines 243 to 253
const start = toZonedTime(cycle.startDate, timezone);
const end = toZonedTime(cycle.endDate, timezone);

const days = differenceInCalendarDays(end, start) + 1;

chartData.chartData = fillMissingDays(
chartDataAggregation,
cycle.startDate,
start,
days,
timezone,
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix double timezone conversion when filling gaps

You zone start/end, then fillMissingDays zones again, shifting dates twice. Pass an unzoned base into fillMissingDays and keep days computed from zoned bounds.

-  const start = toZonedTime(cycle.startDate, timezone);
-  const end = toZonedTime(cycle.endDate, timezone);
-
-  const days = differenceInCalendarDays(end, start) + 1;
-
-  chartData.chartData = fillMissingDays(
-    chartDataAggregation,
-    start,
-    days,
-    timezone,
-  );
+  const startZoned = toZonedTime(cycle.startDate, timezone);
+  const endZoned = toZonedTime(cycle.endDate, timezone);
+
+  const days = differenceInCalendarDays(endZoned, startZoned) + 1;
+
+  chartData.chartData = fillMissingDays(
+    chartDataAggregation,
+    cycle.startDate, // unzoned base; formatting happens in TZ inside fillMissingDays
+    days,
+    timezone,
+  );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const start = toZonedTime(cycle.startDate, timezone);
const end = toZonedTime(cycle.endDate, timezone);
const days = differenceInCalendarDays(end, start) + 1;
chartData.chartData = fillMissingDays(
chartDataAggregation,
cycle.startDate,
start,
days,
timezone,
);
const startZoned = toZonedTime(cycle.startDate, timezone);
const endZoned = toZonedTime(cycle.endDate, timezone);
const days = differenceInCalendarDays(endZoned, startZoned) + 1;
chartData.chartData = fillMissingDays(
chartDataAggregation,
cycle.startDate, // unzoned base; formatting happens in TZ inside fillMissingDays
days,
timezone,
);
🤖 Prompt for AI Agents
In backend/plugins/operation_api/src/modules/cycle/utils.ts around lines 243 to
253, the code first converts cycle.startDate and cycle.endDate to the timezone
with toZonedTime and then passes those zoned dates into fillMissingDays which
also applies timezone conversion, causing dates to be shifted twice; instead
pass the unzoned base start (use the original cycle.startDate) into
fillMissingDays while keeping the days count computed from the already-zoned
start/end, so compute startZoned = toZonedTime(...), endZoned =
toZonedTime(...), days = differenceInCalendarDays(endZoned, startZoned) + 1, and
call fillMissingDays with the original unzoned start date and the computed days
and timezone.

Copy link

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Looks good to me! 👍

Reviewed 1edb77a in 31 seconds. Click for details.
  • Reviewed 20 lines of code in 1 files
  • Skipped 0 files when reviewing.
  • Skipped posting 2 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. frontend/plugins/operation_ui/src/modules/cycle/components/detail/CycleProgressChart.tsx:110
  • Draft comment:
    Inline activeDot config: consider memoizing this object to avoid recreating it on each render if performance becomes an issue.
  • Reason this comment was not posted:
    Confidence changes required: 33% <= threshold 50% None
2. frontend/plugins/operation_ui/src/modules/cycle/components/detail/CycleProgressChart.tsx:121
  • Draft comment:
    Inline activeDot config: consider memoizing this object to avoid recreating it on each render if performance becomes an issue.
  • Reason this comment was not posted:
    Confidence changes required: 33% <= threshold 50% None

Workflow ID: wflow_WQ7MX4Zk8gXz0RGJ

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
frontend/plugins/operation_ui/src/modules/cycle/components/detail/CycleProgressChart.tsx (3)

110-110: Active dots: nice UX improvement; add a subtle ring for contrast

On light/dark themes and over translucent areas, a small stroke improves visibility and accessibility of the active dot.

Apply this diff:

-            activeDot={{ fill: STATUS_COLORS.started, r: 4 }}
+            activeDot={{ fill: STATUS_COLORS.started, r: 4, stroke: 'hsl(var(--background))', strokeWidth: 1.5 }}-            activeDot={{ fill: STATUS_COLORS.completed, r: 4 }}
+            activeDot={{ fill: STATUS_COLORS.completed, r: 4, stroke: 'hsl(var(--background))', strokeWidth: 1.5 }}

Also applies to: 121-121


75-77: Avoid gradient ID collisions when multiple charts render

id="startedGradient"/completedGradient" (and fill variants) are global in the DOM. Rendering two charts on a page will collide and produce incorrect gradients. Use a unique suffix via useId() and pass it to both the defs and the url(#...) references.

Apply this diff:

 import { CHART_CONFIG, STATUS_COLORS } from '@/cycle/constants';
+import { useId } from 'react';
 …
 export const CycleProgressChart = ({
 …
 }) => {
+  const gradientId = useId();-          <ProgressChartGradients data={chartData} />
+          <ProgressChartGradients data={chartData} idSuffix={gradientId} />-          <Area
+          <Area
             dataKey="started"
             type="monotone"
-            stroke="url(#startedGradient)"
-            fill="url(#startedFillGradient)"
+            stroke={`url(#startedGradient-${gradientId})`}
+            fill={`url(#startedFillGradient-${gradientId})`}-          <Area
+          <Area
             dataKey="completed"
             type="monotone"
-            stroke="url(#completedGradient)"
-            fill="url(#completedFillGradient)"
+            stroke={`url(#completedGradient-${gradientId})`}
+            fill={`url(#completedFillGradient-${gradientId})`}-export const ProgressChartGradients = ({
-  data = [],
-}: {
-  data: { date: string; started: number; completed: number; future: boolean }[];
-}) => {
+export const ProgressChartGradients = ({
+  data = [],
+  idSuffix,
+}: {
+  data: { date: string; started: number; completed: number; future: boolean }[];
+  idSuffix: string;
+}) => {-        <linearGradient id={`${line}Gradient`} x1="0" y1="0" x2="1" y2="0">
+        <linearGradient id={`${line}Gradient-${idSuffix}`} x1="0" y1="0" x2="1" y2="0">-        <linearGradient id={`${line}FillGradient`} x1="0" y1="0" x2="1" y2="0">
+        <linearGradient id={`${line}FillGradient-${idSuffix}`} x1="0" y1="0" x2="1" y2="0">

Also applies to: 104-106, 115-117, 139-154, 156-172


60-62: Normalize the baseline date format to match aggregated data

Backend now emits day keys via $dateToString (date-only). Prepending an ISO timestamp can introduce subtle parsing/formatting inconsistencies. Use a date-only string for the baseline row.

-  chartData.unshift({
-    date: yesterdayEnd.toISOString(),
+  chartData.unshift({
+    date: format(yesterdayEnd, 'yyyy-MM-dd'),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad97300 and 1edb77a.

📒 Files selected for processing (1)
  • frontend/plugins/operation_ui/src/modules/cycle/components/detail/CycleProgressChart.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Use functional and declarative programming patterns; avoid classes.
Use TypeScript for all code; prefer interfaces over types.
Avoid enums; use maps instead.

Files:

  • frontend/plugins/operation_ui/src/modules/cycle/components/detail/CycleProgressChart.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError).
Avoid console logs.
Always use absolute paths when importing.
Use the "function" keyword for pure functions.
Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements.

**/*.{ts,tsx,js,jsx}: Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError).
Use the "function" keyword for pure functions.
Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements.
Use absolute path when import.

Files:

  • frontend/plugins/operation_ui/src/modules/cycle/components/detail/CycleProgressChart.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,jsx}: Favor named exports for components.
Use declarative JSX.
Use Shadcn UI, Radix, and Tailwind for components and styling.
Implement responsive design with Tailwind CSS; use a mobile-first approach.

**/*.{tsx,jsx}: Structure files: exported component, subcomponents, helpers, static content, types.
Favor named exports for components.
Use declarative JSX.
Use Shadcn UI, Radix, and Tailwind for components and styling.
Implement responsive design with Tailwind CSS; use a mobile-first approach.

Files:

  • frontend/plugins/operation_ui/src/modules/cycle/components/detail/CycleProgressChart.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use functional components with TypeScript interfaces.
Minimize 'useEffect', and 'setState'.
Wrap client components in Suspense with fallback.
Use dynamic loading for non-critical components.
Limit 'use client': Favor server components and Next.js SSR. Use only for Web API access in small components. Avoid for data fetching or state management.

Files:

  • frontend/plugins/operation_ui/src/modules/cycle/components/detail/CycleProgressChart.tsx
**/*.{webp,tsx,jsx}

📄 CodeRabbit inference engine (.cursorrules)

Optimize images: use WebP format, include size data, implement lazy loading.

Files:

  • frontend/plugins/operation_ui/src/modules/cycle/components/detail/CycleProgressChart.tsx
🧬 Code graph analysis (1)
frontend/plugins/operation_ui/src/modules/cycle/components/detail/CycleProgressChart.tsx (1)
frontend/plugins/operation_ui/src/modules/cycle/constants.ts (1)
  • STATUS_COLORS (3-7)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: payment_api-ci
  • GitHub Check: core-api-ci
  • GitHub Check: Sourcery review
  • GitHub Check: sales_api-ci
  • GitHub Check: core-ui
  • GitHub Check: Analyze (javascript-typescript)

Copy link

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Looks good to me! 👍

Reviewed b1a6190 in 38 seconds. Click for details.
  • Reviewed 76 lines of code in 4 files
  • Skipped 0 files when reviewing.
  • Skipped posting 4 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. backend/erxes-api-shared/src/utils/apollo/utils.ts:25
  • Draft comment:
    Good explicit typing: using 'string' for timezone improves type safety.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None
2. backend/erxes-api-shared/src/utils/headers/timezone.ts:3
  • Draft comment:
    Nice handling of possible array values in timezone headers. Consider validating the timezone string format if needed.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None
3. backend/plugins/operation_api/src/modules/cycle/utils.ts:248
  • Draft comment:
    Removed the timezone parameter in the call to fillMissingDays. Ensure that the baseDate has already been converted to the correct timezone.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None
4. backend/plugins/operation_api/src/modules/project/utils/charUtils.ts:36
  • Draft comment:
    Removed timezone conversion in fillMissingDays (using toZonedTime) to rely on caller conversion. Ensure this aligns with overall timezone handling.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None

Workflow ID: wflow_9K5YpLDbUHAcXICn

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1edb77a and b1a6190.

📒 Files selected for processing (3)
  • backend/erxes-api-shared/src/utils/apollo/utils.ts (3 hunks)
  • backend/erxes-api-shared/src/utils/headers/timezone.ts (1 hunks)
  • backend/plugins/operation_api/src/modules/cycle/utils.ts (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/erxes-api-shared/src/utils/apollo/utils.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Use functional and declarative programming patterns; avoid classes.
Use TypeScript for all code; prefer interfaces over types.
Avoid enums; use maps instead.

Files:

  • backend/plugins/operation_api/src/modules/cycle/utils.ts
  • backend/erxes-api-shared/src/utils/headers/timezone.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError).
Avoid console logs.
Always use absolute paths when importing.
Use the "function" keyword for pure functions.
Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements.

**/*.{ts,tsx,js,jsx}: Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError).
Use the "function" keyword for pure functions.
Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements.
Use absolute path when import.

Files:

  • backend/plugins/operation_api/src/modules/cycle/utils.ts
  • backend/erxes-api-shared/src/utils/headers/timezone.ts
🧬 Code graph analysis (1)
backend/plugins/operation_api/src/modules/cycle/utils.ts (1)
backend/plugins/operation_api/src/modules/project/utils/charUtils.ts (1)
  • fillMissingDays (25-56)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Sourcery review
  • GitHub Check: core-ui
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (3)
backend/plugins/operation_api/src/modules/cycle/utils.ts (1)

182-186: Using $dateToString with timezone is correct for TZ-aware bucketing. Verify upstream TZ is validated.

This looks good, but if an invalid timezone string slips through, the aggregation will error. Ensure getTimezone() validation is in place before reaching here.

backend/erxes-api-shared/src/utils/headers/timezone.ts (2)

3-11: Harden timezone extraction (validate IANA, handle string[], trim; align with style guide).

Current code can pass an invalid timezone into Mongo’s $dateToString and crash requests; it also doesn’t sanitize string[]/whitespace. Switch to a validated IANA timezone with UTC fallback and use a function declaration per guidelines.

-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;
-};
+import { IncomingHttpHeaders } from 'http';
+
+export function getTimezone(headers: IncomingHttpHeaders): string {
+  const raw = headers['x-timezone'] ?? headers['timezone'];
+  const candidate = Array.isArray(raw) ? raw[0] : raw;
+  if (typeof candidate !== 'string') return 'UTC';
+  const tz = candidate.trim();
+  if (!tz) return 'UTC';
+  try {
+    // Throws RangeError for invalid time zones
+    new Intl.DateTimeFormat('en-US', { timeZone: tz }).format();
+    return tz;
+  } catch {
+    return 'UTC';
+  }
+}

3-11: Re-export and Apollo context wiring verified — no action required.

backend/erxes-api-shared/src/utils/headers/index.ts exports getTimezone; backend/erxes-api-shared/src/utils/apollo/utils.ts calls getTimezone(req.headers) and sets requestInfo.timezone; generateApolloContext is used in backend/erxes-api-shared/src/utils/start-plugin.ts and backend/core-api/src/apollo/apolloServer.ts, so downstream resolvers receive the timezone.

Comment on lines +119 to 120
timezone: string,
) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Provide 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\(' -C2

Length 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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
timezone: string,
) => {
export const getCycleProgressChart = async (
cycleId: string,
assigneeId: string | undefined,
models: IModels,
timezone: string = 'UTC',
) => {
🤖 Prompt for AI Agents
In backend/plugins/operation_api/src/modules/cycle/utils.ts around lines 119 to
120, the getCycleProgressChart function's timezone parameter is currently
required which breaks internal callers that omit it; update the function
signature to default timezone to 'UTC' (e.g., timezone = 'UTC') so callers that
don't pass a timezone continue to behave correctly, and ensure any internal
calls relying on undefined still work without modification.

Comment on lines +243 to 249
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);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid double TZ conversion and DST drift; compute days on zoned midnights and pass an unzoned base into fillMissingDays (TZ-aware).

  • Normalize start/end to midnight in the target TZ to prevent off‑by‑one around DST.
  • If fillMissingDays formats dates in TZ, pass the original (unzoned) base plus the timezone; otherwise you risk double conversion or server-TZ formatting.
-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;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants