Skip to content

Conversation

@mohammadreza-92
Copy link

@mohammadreza-92 mohammadreza-92 commented Nov 14, 2025

SUMMARY

  • Adds a PersianCalendarFrame wrapper around the existing DateFilterControl so users can work with time ranges in the Jalali calendar.
  • Integrates react-multi-date-picker, dayjs-jalali and jalaali-js to handle Jalali date parsing/formatting.
  • Supports both relative presets (e.g. “Last 7 days”, “Last 30 days”) and custom Jalali ranges.
  • Persists custom ranges in a stable string format (e.g. YYYY-MM-DD : YYYY-MM-DD) so they can be restored when reopening Explore.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

Before

  • Date filter only allowed selecting Gregorian dates.
  • No native way for users in Persian/Jalali locales to select dates in their own calendar.

After

  • Date filter exposes a Persian calendar frame with:
    • Jalali calendar picker for single date and range mode.
    • Preset relative ranges mapped to Jalali dates.
    • A custom range option that shows the selected Jalali range.
  • Selecting a Jalali date range updates the time range value used by Explore.

(Please add 1–2 screenshots or a short GIF here:
Screenshot 2025-11-14 at 13 43 07
Screenshot 2025-11-14 at 13 31 28


TESTING INSTRUCTIONS

  1. Install frontend dependencies and build:

    • cd superset-frontend
    • npm install (or npm ci depending on project guidelines)
    • npm run build (optional, if you want to check build status locally)
  2. Run unit tests for the new date control:

    • npm test -- spec/explore/components/controls/DateFilterControl/PersianCalendarFrame.test.tsx
  3. Manual testing in the UI:

    1. Open Explore with a dataset that has a datetime column.
    2. Configure a Date/Time range control (or time filter) for that column.
    3. Open the filter popover and switch to the Persian/Jalali calendar date control.
    4. Verify the following behaviours:
      • Selecting a relative preset (e.g. Last 30 days) updates the filter value.
      • Selecting a custom Jalali range (start and end) emits a value like YYYY-MM-DD : YYYY-MM-DD.
      • Re-opening the filter with a persisted custom range shows the selected Jalali range text.
      • Enabling custom range with an empty value defaults both start and end inputs to today’s Jalali date (mapped to ISO string).
  4. Regression checks:

    • Existing Gregorian DateFilter behaviour remains unchanged.
    • No console errors when changing between presets, custom range, or clearing the filter.

ADDITIONAL INFORMATION

  • New dependencies (runtime + types):

    • dayjs-jalali
    • jalaali-js
    • react-multi-date-picker
    • @types/jalaali-js
  • Backward compatibility:

    • The Persian calendar is an additional frame on top of the existing DateFilter control and does not change the default behaviour for existing charts or filters.
    • Stored time range values remain string-based and compatible with current parsing logic.
  • Why this is useful:

    • Users in Persian-speaking regions can now select and reason about time ranges in their native calendar, reducing confusion when building dashboards and reports.

توضیحات فارسی:

این PR یک کنترل تاریخ تقویم شمسی (جلالی) به فیلتر تاریخ Superset اضافه می‌کند.
از این به بعد کاربرانی که با تقویم فارسی کار می‌کنند می‌توانند:

  • بازه‌های زمانی مثل «۷ روز گذشته» و «۳۰ روز گذشته» را روی تقویم شمسی انتخاب کنند؛
  • یک بازه‌ی سفارشی شمسی انتخاب کنند و همان بازه در Explore و نمودارها اعمال می‌شود؛
  • بعد از ذخیره نمودار، با دوباره باز کردن Explore، بازه شمسی ذخیره‌شده را دوباره ببینند.

هیچ رفتار قبلی برای کاربرانی که هنوز از تقویم میلادی استفاده می‌کنند تغییر نمی‌کند؛ این فقط یک قابلیت اضافه برای کاربران فارسی‌زبان است.

@dosubot dosubot bot added the change:frontend Requires changing the frontend label Nov 14, 2025
Copy link

@korbit-ai korbit-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.

I've completed my review and didn't find any issues.

Files scanned
File Path Reviewed
superset-frontend/src/preamble.ts
superset-frontend/src/utils/persianCalendar.ts
superset-frontend/src/explore/components/controls/DateFilterControl/components/JalaliDatePicker.tsx
superset-frontend/src/explore/components/controls/DateFilterControl/components/PersianCalendarFrame.tsx

Explore our documentation to understand the languages and file types we support and the files we ignore.

Check out our docs on how you can make Korbit work best for you and your team.

Loving Korbit!? Share us on LinkedIn Reddit and X

Copy link
Contributor

@bito-code-review bito-code-review bot left a comment

Choose a reason for hiding this comment

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

Code Review Agent Run #a88474

Actionable Suggestions - 1
  • superset-frontend/src/utils/persianCalendar.ts - 1
Review Details
  • Files reviewed - 5 · Commit Range: 3d0eaca..3d0eaca
    • superset-frontend/spec/explore/components/controls/DateFilterControl/PersianCalendarFrame.test.tsx
    • superset-frontend/src/explore/components/controls/DateFilterControl/components/JalaliDatePicker.tsx
    • superset-frontend/src/explore/components/controls/DateFilterControl/components/PersianCalendarFrame.tsx
    • superset-frontend/src/preamble.ts
    • superset-frontend/src/utils/persianCalendar.ts
  • Files skipped - 2
    • superset-frontend/package-lock.json - Reason: Filter setting
    • superset-frontend/package.json - Reason: Filter setting
  • Tools
    • Eslint (Linter) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Default Agent You can customize the agent settings here or contact your Bito workspace admin at [email protected].

Documentation & Help

AI Code Review powered by Bito Logo

Comment on lines 63 to 64
const getWeekdayName = (year: number, month: number, day: number) =>
PERSIAN_WEEKDAYS[new Date(year, month - 1, day).getDay()];
Copy link
Contributor

Choose a reason for hiding this comment

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

Wrong Persian weekday calculation

The weekday calculation in getWeekdayName incorrectly maps Gregorian weekdays to Persian weekdays because the PERSIAN_WEEKDAYS array starts with Saturday (index 0) while JavaScript's getDay() starts with Sunday (index 0). This causes incorrect weekday names to be returned, affecting any code that relies on gregorianToPersian for accurate date information. The fix adjusts the index using (getDay() + 1) % 7 to align the week start days properly.

Code suggestion
Check the AI-generated fix before applying
Suggested change
const getWeekdayName = (year: number, month: number, day: number) =>
PERSIAN_WEEKDAYS[new Date(year, month - 1, day).getDay()];
const getWeekdayName = (year: number, month: number, day: number) =>
PERSIAN_WEEKDAYS[(new Date(year, month - 1, day).getDay() + 1) % 7];

Code Review Run #a88474


Should Bito avoid suggestions like this for future reviews? (Manage Rules)

  • Yes, avoid them

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds Persian (Jalali) calendar support to Superset's date filter controls, enabling users in Persian-speaking regions to select dates using their native calendar system. The implementation wraps the existing DateFilterControl with a new PersianCalendarFrame component.

  • Integrates react-multi-date-picker, dayjs-jalali, and jalaali-js for accurate Jalali calendar handling
  • Supports both relative presets (Last 7/30/90 days, Last year) and custom date range selection
  • Persists custom ranges in a stable format (YYYY-MM-DD : YYYY-MM-DD) for restoration across sessions

Reviewed Changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
superset-frontend/src/utils/persianCalendar.ts New utility module providing Persian/Gregorian date conversion functions and calendar constants
superset-frontend/src/preamble.ts Adds dayjs plugin and global CSS import for react-multi-date-picker
superset-frontend/src/explore/components/controls/DateFilterControl/components/PersianCalendarFrame.tsx Main component implementing Persian calendar date range selection with preset and custom range modes
superset-frontend/src/explore/components/controls/DateFilterControl/components/JalaliDatePicker.tsx Wrapper component around react-multi-date-picker handling date conversions between Jalali and Gregorian calendars
superset-frontend/spec/explore/components/controls/DateFilterControl/PersianCalendarFrame.test.tsx Unit tests covering preset selection, custom range selection, and value persistence
superset-frontend/package.json Adds runtime dependencies (dayjs-jalali, jalaali-js, react-multi-date-picker) and dev dependency (@types/jalaali-js)
superset-frontend/package-lock.json Lock file updates for new dependencies and transitive dependencies
Files not reviewed (1)
  • superset-frontend/package-lock.json: Language not supported

Comment on lines 168 to 204
const convertDateObjectToDayjs = useCallback(
(date: DateObject | null): Dayjs | null => {
if (!date) {
return null;
}
const monthValue = getMonthNumber(date.month as number | Month | undefined);

if (jalaliReady) {
try {
const jalaliDate = dayjs(
`${date.year}/${monthValue}/${date.day}`,
'jYYYY/jM/jD',
);
const gregorianDate = dayjs(jalaliDate.toDate());
if (gregorianDate.isValid()) {
return gregorianDate;
}
} catch (error) {
console.warn('Error converting Jalali to Gregorian:', error);
}
}

try {
const gregorian = persianToGregorian(date.year, monthValue, date.day);
const converted = dayjs(
`${gregorian.year}-${String(gregorian.month).padStart(2, '0')}-${String(
gregorian.day,
).padStart(2, '0')}`,
);
return converted.isValid() ? converted : null;
} catch (error) {
console.error('Error in fallback conversion:', error);
return null;
}
},
[jalaliReady],
);
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

The error handling in the conversion functions only logs to console (console.warn, console.error) but doesn't provide user feedback when date conversion fails. Consider adding proper error handling that can surface errors to the user, especially for the convertDateObjectToDayjs function which silently returns null on errors.

Copilot uses AI. Check for mistakes.
Comment on lines 63 to 64
const getWeekdayName = (year: number, month: number, day: number) =>
PERSIAN_WEEKDAYS[new Date(year, month - 1, day).getDay()];
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

The getWeekdayName function uses Gregorian date to calculate weekday, which may produce incorrect weekday names for Persian dates. The JavaScript Date object expects Gregorian dates, but this function receives year/month/day that appear to be intended as Gregorian coordinates. However, this function is called with Gregorian dates from gregorianToPersian, so it should work correctly.

Actually, on closer inspection, this is being called correctly in line 79 with Gregorian parameters before conversion. However, the parameter names and function placement suggest it might be misunderstood. Consider adding a comment clarifying that this expects Gregorian year/month/day values.

Suggested change
const getWeekdayName = (year: number, month: number, day: number) =>
PERSIAN_WEEKDAYS[new Date(year, month - 1, day).getDay()];
/**
* Returns the Persian weekday name for a given Gregorian date.
* Expects Gregorian year, month, and day as arguments.
* @param gregorianYear - Gregorian year
* @param gregorianMonth - Gregorian month (1-based)
* @param gregorianDay - Gregorian day
*/
const getWeekdayName = (
gregorianYear: number,
gregorianMonth: number,
gregorianDay: number,
) =>
PERSIAN_WEEKDAYS[new Date(gregorianYear, gregorianMonth - 1, gregorianDay).getDay()];

Copilot uses AI. Check for mistakes.
Comment on lines 51 to 52
import 'react-multi-date-picker/styles/layouts/mobile.css';

Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

The global CSS import in the preamble may cause styling conflicts with other parts of the application. Consider scoping this import to only the components that need it, or importing it within the JalaliDatePicker component instead of globally.

Suggested change
import 'react-multi-date-picker/styles/layouts/mobile.css';

Copilot uses AI. Check for mistakes.
Comment on lines 318 to 426
<style>{`
.custom-rmdp {
position: relative;
width: 100%;
}
.custom-rmdp .rmdp-wrapper {
direction: ${isRTL ? 'rtl' : 'ltr'};
z-index: 10000 !important;
background: white;
border: 1px solid #d9d9d9;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
padding: 8px;
width: 100%;
max-width: 100%;
}
.custom-rmdp .rmdp-calendar {
direction: ${isRTL ? 'rtl' : 'ltr'};
z-index: 10000 !important;
width: 100%;
max-width: 100%;
}
.custom-rmdp .rmdp-header {
padding: 8px;
border-bottom: 1px solid #f0f0f0;
margin-bottom: 8px;
}
.custom-rmdp .rmdp-header-values {
font-weight: 600;
font-size: 14px;
color: #1890ff;
}
.custom-rmdp .rmdp-arrow {
border: solid #1890ff;
border-width: 0 2px 2px 0;
padding: 3px;
cursor: pointer;
}
.custom-rmdp .rmdp-arrow:hover {
border-color: #40a9ff;
}
.custom-rmdp .rmdp-arrow-container {
cursor: pointer;
padding: 4px;
}
.custom-rmdp .rmdp-arrow-container:hover {
background-color: #f0f0f0;
border-radius: 4px;
}
.custom-rmdp .rmdp-week-day {
color: #1890ff;
font-weight: 600;
padding: 8px;
}
.custom-rmdp .rmdp-day {
cursor: pointer;
padding: 4px;
margin: 2px;
border-radius: 4px;
}
.custom-rmdp .rmdp-day:hover {
background-color: #e6f7ff !important;
}
.custom-rmdp .rmdp-day.rmdp-selected span:not(.highlight) {
background-color: #1890ff !important;
color: white !important;
border-radius: 4px;
}
.custom-rmdp .rmdp-day.rmdp-today span,
.custom-rmdp .rmdp-day-today span {
background-color: #e6f7ff !important;
border: 1px solid #1890ff !important;
font-weight: bold;
border-radius: 4px;
}
.custom-rmdp .rmdp-day.rmdp-today:hover span,
.custom-rmdp .rmdp-day-today:hover span {
background-color: #bae7ff !important;
}
.custom-rmdp .rmdp-day.rmdp-disabled {
cursor: not-allowed;
opacity: 0.5;
}
.custom-rmdp .rmdp-day.rmdp-disabled:hover {
background-color: transparent !important;
}
.jalali-date-input {
width: 100%;
cursor: pointer;
padding: 4px 11px;
border: 1px solid #d9d9d9;
border-radius: 4px;
}
.jalali-date-input:hover {
border-color: #40a9ff;
}
.jalali-date-input:focus {
border-color: #40a9ff;
outline: 0;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.jalali-date-input--range {
text-align: ${isRTL ? 'right' : 'left'};
}
.rmdp-container,
.rmdp-popup {
z-index: 10000 !important;
}
`}</style>
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

The inline <style> tag creates duplicate styles each time the component renders. This is inefficient and can cause memory leaks with repeated mounting/unmounting. Consider using styled-components, CSS modules, or moving styles to a separate stylesheet to avoid re-creating the same styles on every render.

Copilot uses AI. Check for mistakes.
Comment on lines 72 to 84
const detectRTL = () => {
if (typeof document === 'undefined') {
return false;
}
const doc = document.documentElement;
if (doc?.dir === 'rtl' || doc?.lang?.startsWith('fa')) {
return true;
}
if (typeof navigator !== 'undefined') {
return navigator.language?.startsWith('fa') ?? false;
}
return false;
};
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

The RTL detection logic is duplicated in both PersianCalendarFrame (lines 197-209) and JalaliDatePicker (lines 72-84). Consider extracting this into a shared utility function in persianCalendar.ts to follow the DRY (Don't Repeat Yourself) principle.

Copilot uses AI. Check for mistakes.
Comment on lines 112 to 124
useEffect(() => {
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const jalaliPlugin = require('dayjs-jalali');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const jalaliDayjsModule = jalaliPlugin.default || jalaliPlugin;
dayjs.extend(jalaliDayjsModule);
setJalaliReady(true);
} catch (error) {
console.warn('Failed to load dayjs-jalali plugin:', error);
setJalaliReady(false);
}
}, []);
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

The dayjs-jalali plugin is loaded dynamically with require() but never unloaded. This means calling dayjs.extend() multiple times could have unintended side effects if the component is mounted and unmounted repeatedly. Consider checking if the plugin is already extended before calling extend(), or ensure this side effect is acceptable.

Copilot uses AI. Check for mistakes.
const toPersianDigits = (value: string) =>
value.replace(/\d/g, digit => PERSIAN_DIGITS[Number(digit)]);

const MIN_GREGORIAN_YEAR = 1700;
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

The magic number 1700 for minimum Gregorian year lacks explanation. This appears to be a threshold to distinguish between Gregorian and Jalali year values (since Jalali years are currently in the 1400s), but this assumption could break in the future. Add a constant with a descriptive name and comment explaining why this value is used, or consider a more robust way to distinguish between the two calendar systems.

Suggested change
const MIN_GREGORIAN_YEAR = 1700;
/**
* Threshold year to distinguish between Gregorian and Jalali (Persian) years.
* Jalali years are currently in the 1400s, while Gregorian years are above 1700.
* This threshold is used to infer the calendar system from a year value.
* If the calendars' year ranges ever overlap, this logic should be revisited.
*/
const GREGORIAN_YEAR_THRESHOLD_FOR_CALENDAR_TYPE = 1700;

Copilot uses AI. Check for mistakes.
Comment on lines 197 to 222
const isRTL = useMemo(() => {
if (typeof document === 'undefined') {
return false;
}
const doc = document.documentElement;
if (doc?.dir === 'rtl' || doc?.lang?.startsWith('fa')) {
return true;
}
if (typeof navigator !== 'undefined' && navigator.language?.startsWith('fa')) {
return true;
}
return false;
}, []);

const shouldUsePersianText = useMemo(() => {
if (typeof document !== 'undefined') {
const doc = document.documentElement;
if (doc?.lang?.startsWith('fa')) {
return true;
}
}
if (typeof navigator !== 'undefined' && navigator.language?.startsWith('fa')) {
return true;
}
return false;
}, []);
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

The Persian/Farsi text detection logic is duplicated (lines 197-209 and 211-222). This code appears twice with nearly identical logic. Consider consolidating into a single computed value or extracting to a utility function.

Copilot uses AI. Check for mistakes.
Copy link
Member

@msyavuz msyavuz left a comment

Choose a reason for hiding this comment

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

This would be a great use case for new extensions project. As a seperate component i am not sure about the maintaining this one.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants