diff --git a/CHANGELOG.md b/CHANGELOG.md index 1050bea7d..6d466e844 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ You can also check the - Fixes - Bar chart tooltip doesn't go off the screen anymore during scroll + - Selecting a date using date picker with weekly temporal dimension doesn't + crash the application anymore # [5.2.0] - 2025-01-22 diff --git a/app/components/dashboard-interactive-filters.tsx b/app/components/dashboard-interactive-filters.tsx index e625c5594..aee6953a4 100644 --- a/app/components/dashboard-interactive-filters.tsx +++ b/app/components/dashboard-interactive-filters.tsx @@ -110,16 +110,11 @@ const valueToTimeRange = (value: number[]) => { }; const presetToTimeRange = ( - presets: Pick, - timeUnit: TimeUnit + presets: Pick ) => { - if (!timeUnit) { - return; - } - const parser = timeUnitToParser[timeUnit]; return [ - toUnixSeconds(parser(presets.from)), - toUnixSeconds(parser(presets.to)), + toUnixSeconds(parseDate(presets.from)), + toUnixSeconds(parseDate(presets.to)), ]; }; @@ -149,27 +144,34 @@ const DashboardTimeRangeSlider = ({ const timeUnit = filter.timeUnit as TimeUnit; const [timeRange, setTimeRange] = useState(() => // timeUnit can still be an empty string - timeUnit ? presetToTimeRange(presets, timeUnit) : undefined + timeUnit ? presetToTimeRange(presets) : undefined ); const valueLabelFormat = useEventCallback((value: number) => { if (!timeUnit) { return ""; } + const date = new Date(value * 1000); + return timeUnitToFormatter[timeUnit](date); }); const handleChangeSlider = useEventCallback((value: number | number[]) => { assert(Array.isArray(value), "Value should be an array of two numbers"); + if (!timeUnit) { return; } + const newTimeRange = valueToTimeRange(value); + if (!newTimeRange) { return; } + setEnableTransition(false); + for (const [_getState, _useStore, store] of Object.values( dashboardInteractiveFilters.stores )) { @@ -180,28 +182,29 @@ const DashboardTimeRangeSlider = ({ useEffect( function initTimeRangeAfterDataFetch() { - if (timeRange || !timeUnit) { + if (timeRange) { return; } - const parser = timeUnitToParser[timeUnit]; + handleChangeSlider([ - toUnixSeconds(parser(presets.from)), - toUnixSeconds(parser(presets.to)), + toUnixSeconds(parseDate(presets.from)), + toUnixSeconds(parseDate(presets.to)), ]); }, [timeRange, timeUnit, presets, handleChangeSlider] ); useEffect(() => { - if (presets.from && presets.to && timeUnit) { - const parser = timeUnitToParser[timeUnit]; + if (presets.from && presets.to) { setTimeRange([ - toUnixSeconds(parser(presets.from)), - toUnixSeconds(parser(presets.to)), + toUnixSeconds(parseDate(presets.from)), + toUnixSeconds(parseDate(presets.to)), ]); } }, [presets.from, presets.to, timeUnit]); + const parser = timeUnitToParser[timeUnit]; + const mountedForSomeTime = useTimeout(500, mounted); const combinedTemporalDimension = useCombinedTemporalDimension(); const sliderRange = useMemo(() => { @@ -214,10 +217,10 @@ const DashboardTimeRangeSlider = ({ } return [ - toUnixSeconds(parseDate(min as string)), - toUnixSeconds(parseDate(max as string)), + toUnixSeconds(parser(min as string)), + toUnixSeconds(parser(max as string)), ]; - }, [combinedTemporalDimension]); + }, [combinedTemporalDimension, parser]); if (!timeRange || !filter.active || !sliderRange) { return null; @@ -235,7 +238,7 @@ const DashboardTimeRangeSlider = ({ valueLabelDisplay={mountedForSomeTime ? "on" : "off"} value={timeRange} marks={combinedTemporalDimension.values.map(({ value }) => ({ - value: toUnixSeconds(parseDate(value as string)), + value: toUnixSeconds(parser(value as string)), }))} /> ); diff --git a/app/configurator/components/layout-configurator.tsx b/app/configurator/components/layout-configurator.tsx index 20aafa91f..fba9c3b62 100644 --- a/app/configurator/components/layout-configurator.tsx +++ b/app/configurator/components/layout-configurator.tsx @@ -494,6 +494,10 @@ const DashboardTimeRangeFilterOptions = ({ } }; + // As the stores are synced, we can take the first one to get the time range. + const firstStore = Object.values(dashboardInteractiveFilters.stores)[0]; + const { timeRange } = firstStore[0](); + return (
!optionValues.includes(formatDate(date))} + isDateDisabled={(date) => { + return !optionValues.includes(formatDate(date)); + }} timeUnit={timeUnit} dateFormat={formatDate} minDate={minDate} @@ -531,9 +537,11 @@ const DashboardTimeRangeFilterOptions = ({ {canRenderDatePickerField(timeUnit) ? ( !optionValues.includes(formatDate(date))} + isDateDisabled={(date) => { + return !optionValues.includes(formatDate(date)); + }} timeUnit={timeUnit} dateFormat={formatDate} minDate={minDate} diff --git a/app/configurator/components/ui-helpers.ts b/app/configurator/components/ui-helpers.ts index e9cc50838..13b6cb35b 100644 --- a/app/configurator/components/ui-helpers.ts +++ b/app/configurator/components/ui-helpers.ts @@ -26,37 +26,50 @@ import { IconName } from "@/icons"; import { getTimeInterval } from "@/intervals"; import { getPalette } from "@/palettes"; +export const timeUnitFormatMap: Record = { + Second: "%Y-%m-%dT%H:%M:%S", + Minute: "%Y-%m-%dT%H:%M", + Hour: "%Y-%m-%dT%H:%M", + Day: "%Y-%m-%d", + Week: "%Y-%W", + Month: "%Y-%m", + Year: "%Y", +}; + // FIXME: We should cover more time formats export const timeUnitToParser: Record< TimeUnit, (dateStr: string) => Date | null > = { - Second: timeParse("%Y-%m-%dT%H:%M:%S"), - Hour: timeParse("%Y-%m-%dT%H:%M"), // same as minute - Minute: timeParse("%Y-%m-%dT%H:%M"), - Week: timeParse("%Y-%m-%d"), // same as day - Day: timeParse("%Y-%m-%d"), - Month: timeParse("%Y-%m"), - Year: timeParse("%Y"), + Second: timeParse(timeUnitFormatMap.Second), + Hour: timeParse(timeUnitFormatMap.Hour), + Minute: timeParse(timeUnitFormatMap.Minute), + Week: timeParse(timeUnitFormatMap.Week), + Day: timeParse(timeUnitFormatMap.Day), + Month: timeParse(timeUnitFormatMap.Month), + Year: timeParse(timeUnitFormatMap.Year), }; export const parseDate = (dateStr: string): Date => timeUnitToParser.Second(dateStr) ?? timeUnitToParser.Minute(dateStr) ?? timeUnitToParser.Day(dateStr) ?? + // We don't parse weeks, because the data points look the same as for month, + // e.g. 2021-04 which results in wrong parsing, as week parser would be always + // used first. timeUnitToParser.Month(dateStr) ?? timeUnitToParser.Year(dateStr) ?? // This should probably not happen new Date(dateStr); export const timeUnitToFormatter: Record string> = { - Year: timeFormat("%Y"), - Month: timeFormat("%Y-%m"), - Week: timeFormat("%Y-%m-%d"), - Day: timeFormat("%Y-%m-%d"), - Hour: timeFormat("%Y-%m-%dT%H:%M"), - Minute: timeFormat("%Y-%m-%dT%H:%M"), - Second: timeFormat("%Y-%m-%dT%H:%M:%S"), + Second: timeFormat(timeUnitFormatMap.Second), + Minute: timeFormat(timeUnitFormatMap.Minute), + Hour: timeFormat(timeUnitFormatMap.Hour), + Day: timeFormat(timeUnitFormatMap.Day), + Week: timeFormat(timeUnitFormatMap.Day), // same as day + Month: timeFormat(timeUnitFormatMap.Month), + Year: timeFormat(timeUnitFormatMap.Year), }; export const mkNumber = (x: $IntentionalAny): number => +x;