From ac2552fbbbdd1f59443a489406d1bede97a08d9f Mon Sep 17 00:00:00 2001 From: Alexander Polyankin <alexander.polyankin@metabase.com> Date: Mon, 21 Oct 2024 09:52:50 -0400 Subject: [PATCH] Hide time inputs in the date picker when the column does not support date+time filters (#48818) --- .../components/DatePicker/DatePicker.tsx | 10 +- .../ExcludeDatePicker/ExcludeDatePicker.tsx | 5 +- .../DatePicker/ExcludeDatePicker/utils.ts | 3 +- .../CurrentDatePicker/CurrentDatePicker.tsx | 22 +- .../CurrentDatePicker.unit.spec.tsx | 17 +- .../CurrentDatePicker/utils.ts | 19 ++ .../DateIntervalPicker/DateIntervalPicker.tsx | 5 +- .../DateIntervalPicker.unit.spec.tsx | 17 +- .../SimpleDateIntervalPicker.tsx | 5 +- .../SimpleDateIntervalPicker.unit.spec.tsx | 12 +- .../DateOffsetIntervalPicker.tsx | 7 +- .../DateOffsetIntervalPicker.unit.spec.tsx | 29 +- .../DateOffsetIntervalPicker/utils.ts | 24 +- .../RelativeDatePicker/RelativeDatePicker.tsx | 20 +- .../RelativeDatePicker.unit.spec.tsx | 6 +- .../DatePicker/RelativeDatePicker/utils.ts | 50 ++-- .../RelativeDatePicker/utils.unit.spec.ts | 8 +- .../SimpleDatePicker/SimpleDatePicker.tsx | 20 +- .../SimpleDatePicker.unit.spec.tsx | 11 +- .../DateRangePicker/DateRangePicker.tsx | 8 +- .../DateRangePicker.unit.spec.tsx | 21 +- .../SingleDatePicker/SingleDatePicker.tsx | 8 +- .../SingleDatePicker.unit.spec.tsx | 19 +- .../SpecificDatePicker/SpecificDatePicker.tsx | 17 +- .../SpecificDatePicker.unit.spec.tsx | 40 ++- .../DatePicker/SpecificDatePicker/utils.ts | 8 + .../components/DatePicker/constants.ts | 5 + .../filters/components/DatePicker/types.ts | 4 + .../filters/components/DatePicker/utils.ts | 15 +- .../DateFilterEditor/DateFilterEditor.tsx | 4 +- .../DateFilterEditor.unit.spec.tsx | 278 ++++++++++-------- .../DateFilterPicker.unit.spec.tsx | 28 ++ .../SimpleDateFilterPicker.tsx | 14 +- .../filters/hooks/use-date-filter/utils.ts | 8 +- 34 files changed, 539 insertions(+), 228 deletions(-) create mode 100644 frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/CurrentDatePicker/utils.ts diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/DatePicker.tsx b/frontend/src/metabase/querying/filters/components/DatePicker/DatePicker.tsx index a6505ed5160..a4f1dd011fc 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/DatePicker.tsx +++ b/frontend/src/metabase/querying/filters/components/DatePicker/DatePicker.tsx @@ -6,14 +6,14 @@ import { ExcludeDatePicker } from "./ExcludeDatePicker"; import { RelativeDatePicker } from "./RelativeDatePicker"; import { SpecificDatePicker } from "./SpecificDatePicker"; import { - DATE_PICKER_EXTRACTION_UNITS, DATE_PICKER_OPERATORS, DATE_PICKER_SHORTCUTS, + DATE_PICKER_UNITS, } from "./constants"; import type { - DatePickerExtractionUnit, DatePickerOperator, DatePickerShortcut, + DatePickerUnit, DatePickerValue, } from "./types"; @@ -21,7 +21,7 @@ interface DatePickerProps { value?: DatePickerValue; availableOperators?: ReadonlyArray<DatePickerOperator>; availableShortcuts?: ReadonlyArray<DatePickerShortcut>; - availableUnits?: ReadonlyArray<DatePickerExtractionUnit>; + availableUnits?: ReadonlyArray<DatePickerUnit>; canUseRelativeOffsets?: boolean; backButton?: ReactNode; isNew?: boolean; @@ -32,7 +32,7 @@ export function DatePicker({ value, availableOperators = DATE_PICKER_OPERATORS, availableShortcuts = DATE_PICKER_SHORTCUTS, - availableUnits = DATE_PICKER_EXTRACTION_UNITS, + availableUnits = DATE_PICKER_UNITS, canUseRelativeOffsets = false, isNew = value == null, backButton, @@ -50,6 +50,7 @@ export function DatePicker({ <SpecificDatePicker value={value?.type === type ? value : undefined} availableOperators={availableOperators} + availableUnits={availableUnits} isNew={isNew} onChange={onChange} onBack={handleBack} @@ -59,6 +60,7 @@ export function DatePicker({ return ( <RelativeDatePicker value={value?.type === type ? value : undefined} + availableUnits={availableUnits} canUseRelativeOffsets={canUseRelativeOffsets} isNew={isNew} onChange={onChange} diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/ExcludeDatePicker/ExcludeDatePicker.tsx b/frontend/src/metabase/querying/filters/components/DatePicker/ExcludeDatePicker/ExcludeDatePicker.tsx index 63e1bbf44db..65642dd607e 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/ExcludeDatePicker/ExcludeDatePicker.tsx +++ b/frontend/src/metabase/querying/filters/components/DatePicker/ExcludeDatePicker/ExcludeDatePicker.tsx @@ -16,6 +16,7 @@ import { MIN_WIDTH } from "../constants"; import type { DatePickerExtractionUnit, DatePickerOperator, + DatePickerUnit, ExcludeDatePickerOperator, ExcludeDatePickerValue, } from "../types"; @@ -33,7 +34,7 @@ import { export interface ExcludeDatePickerProps { value?: ExcludeDatePickerValue; availableOperators: ReadonlyArray<DatePickerOperator>; - availableUnits: ReadonlyArray<DatePickerExtractionUnit>; + availableUnits: ReadonlyArray<DatePickerUnit>; isNew: boolean; onChange: (value: ExcludeDatePickerValue) => void; onBack: () => void; @@ -82,7 +83,7 @@ export function ExcludeDatePicker({ interface ExcludeOptionPickerProps { value: ExcludeDatePickerValue | undefined; availableOperators: ReadonlyArray<DatePickerOperator>; - availableUnits: ReadonlyArray<DatePickerExtractionUnit>; + availableUnits: ReadonlyArray<DatePickerUnit>; onChange: (value: ExcludeDatePickerValue) => void; onSelectUnit: (unit: DatePickerExtractionUnit) => void; onBack: () => void; diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/ExcludeDatePicker/utils.ts b/frontend/src/metabase/querying/filters/components/DatePicker/ExcludeDatePicker/utils.ts index 1534fc3b39e..d49afae68fc 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/ExcludeDatePicker/utils.ts +++ b/frontend/src/metabase/querying/filters/components/DatePicker/ExcludeDatePicker/utils.ts @@ -5,6 +5,7 @@ import _ from "underscore"; import type { DatePickerExtractionUnit, DatePickerOperator, + DatePickerUnit, ExcludeDatePickerOperator, ExcludeDatePickerValue, } from "../types"; @@ -18,7 +19,7 @@ import type { export function getExcludeUnitOptions( availableOperators: ReadonlyArray<DatePickerOperator>, - availableUnits: ReadonlyArray<DatePickerExtractionUnit>, + availableUnits: ReadonlyArray<DatePickerUnit>, ): ExcludeUnitOption[] { if (!availableOperators.includes("!=")) { return []; diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/CurrentDatePicker/CurrentDatePicker.tsx b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/CurrentDatePicker/CurrentDatePicker.tsx index 7b88c9127cd..efa0de08d44 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/CurrentDatePicker/CurrentDatePicker.tsx +++ b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/CurrentDatePicker/CurrentDatePicker.tsx @@ -5,29 +5,37 @@ import * as Lib from "metabase-lib"; import type { DatePickerTruncationUnit, + DatePickerUnit, RelativeDatePickerValue, } from "../../types"; import { formatDateRange } from "../utils"; -import { UNIT_GROUPS } from "./constants"; +import { getCurrentValue, getUnitGroups } from "./utils"; interface CurrentDatePickerProps { - value: RelativeDatePickerValue; + value: RelativeDatePickerValue | undefined; + availableUnits: ReadonlyArray<DatePickerUnit>; onChange: (value: RelativeDatePickerValue) => void; } -export function CurrentDatePicker({ value, onChange }: CurrentDatePickerProps) { +export function CurrentDatePicker({ + value, + availableUnits, + onChange, +}: CurrentDatePickerProps) { + const unitGroups = getUnitGroups(availableUnits); + const getTooltipLabel = (unit: DatePickerTruncationUnit) => { - return formatDateRange({ ...value, unit }); + return formatDateRange(getCurrentValue(unit)); }; const handleClick = (unit: DatePickerTruncationUnit) => { - onChange({ ...value, unit }); + onChange(getCurrentValue(unit)); }; return ( <Stack> - {UNIT_GROUPS.map((group, groupIndex) => ( + {unitGroups.map((group, groupIndex) => ( <Group key={groupIndex}> {group.map(unit => ( <Tooltip @@ -35,7 +43,7 @@ export function CurrentDatePicker({ value, onChange }: CurrentDatePickerProps) { label={t`Right now, this is ${getTooltipLabel(unit)}`} > <Button - variant={unit === value.unit ? "filled" : "default"} + variant={unit === value?.unit ? "filled" : "default"} radius="xl" onClick={() => handleClick(unit)} > diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/CurrentDatePicker/CurrentDatePicker.unit.spec.tsx b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/CurrentDatePicker/CurrentDatePicker.unit.spec.tsx index 3d1ef2b7797..0b295eaa3c7 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/CurrentDatePicker/CurrentDatePicker.unit.spec.tsx +++ b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/CurrentDatePicker/CurrentDatePicker.unit.spec.tsx @@ -2,7 +2,8 @@ import _userEvent from "@testing-library/user-event"; import { renderWithProviders, screen } from "__support__/ui"; -import type { RelativeDatePickerValue } from "../../types"; +import { DATE_PICKER_UNITS } from "../../constants"; +import type { DatePickerUnit, RelativeDatePickerValue } from "../../types"; import { CurrentDatePicker } from "./CurrentDatePicker"; @@ -14,16 +15,26 @@ const DEFAULT_VALUE: RelativeDatePickerValue = { interface SetupOpts { value?: RelativeDatePickerValue; + availableUnits?: ReadonlyArray<DatePickerUnit>; } const userEvent = _userEvent.setup({ advanceTimers: jest.advanceTimersByTime, }); -function setup({ value = DEFAULT_VALUE }: SetupOpts = {}) { +function setup({ + value = DEFAULT_VALUE, + availableUnits = DATE_PICKER_UNITS, +}: SetupOpts = {}) { const onChange = jest.fn(); - renderWithProviders(<CurrentDatePicker value={value} onChange={onChange} />); + renderWithProviders( + <CurrentDatePicker + value={value} + availableUnits={availableUnits} + onChange={onChange} + />, + ); return { onChange }; } diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/CurrentDatePicker/utils.ts b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/CurrentDatePicker/utils.ts new file mode 100644 index 00000000000..74d5242cabe --- /dev/null +++ b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/CurrentDatePicker/utils.ts @@ -0,0 +1,19 @@ +import type { + DatePickerTruncationUnit, + DatePickerUnit, + RelativeDatePickerValue, +} from "../../types"; + +import { UNIT_GROUPS } from "./constants"; + +export function getCurrentValue( + unit: DatePickerTruncationUnit, +): RelativeDatePickerValue { + return { type: "relative", value: "current", unit }; +} + +export function getUnitGroups(availableUnits: ReadonlyArray<DatePickerUnit>) { + return UNIT_GROUPS.map(group => + group.filter(unit => availableUnits.includes(unit)), + ).filter(group => group.length > 0); +} diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateIntervalPicker/DateIntervalPicker.tsx b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateIntervalPicker/DateIntervalPicker.tsx index dc8cda3e636..9d2e528b0aa 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateIntervalPicker/DateIntervalPicker.tsx +++ b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateIntervalPicker/DateIntervalPicker.tsx @@ -13,6 +13,7 @@ import { Tooltip, } from "metabase/ui"; +import type { DatePickerUnit } from "../../types"; import { IncludeCurrentSwitch } from "../IncludeCurrentSwitch"; import type { DateIntervalValue } from "../types"; import { @@ -26,6 +27,7 @@ import { setDefaultOffset, setUnit } from "./utils"; interface DateIntervalPickerProps { value: DateIntervalValue; + availableUnits: ReadonlyArray<DatePickerUnit>; isNew: boolean; canUseRelativeOffsets: boolean; onChange: (value: DateIntervalValue) => void; @@ -34,13 +36,14 @@ interface DateIntervalPickerProps { export function DateIntervalPicker({ value, + availableUnits, isNew, canUseRelativeOffsets, onChange, onSubmit, }: DateIntervalPickerProps) { const interval = getInterval(value); - const unitOptions = getUnitOptions(value); + const unitOptions = getUnitOptions(value, availableUnits); const dateRangeText = formatDateRange(value); const handleIntervalChange = (inputValue: number | "") => { diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateIntervalPicker/DateIntervalPicker.unit.spec.tsx b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateIntervalPicker/DateIntervalPicker.unit.spec.tsx index 2faf0d0ae82..36b17988501 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateIntervalPicker/DateIntervalPicker.unit.spec.tsx +++ b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateIntervalPicker/DateIntervalPicker.unit.spec.tsx @@ -2,7 +2,8 @@ import userEvent from "@testing-library/user-event"; import { renderWithProviders, screen } from "__support__/ui"; -import type { RelativeIntervalDirection } from "../../types"; +import { DATE_PICKER_UNITS } from "../../constants"; +import type { DatePickerUnit, RelativeIntervalDirection } from "../../types"; import type { DateIntervalValue } from "../types"; import { DateIntervalPicker } from "./DateIntervalPicker"; @@ -19,12 +20,14 @@ function getDefaultValue( interface SetupOpts { value: DateIntervalValue; + availableUnits?: ReadonlyArray<DatePickerUnit>; isNew?: boolean; canUseRelativeOffsets?: boolean; } function setup({ value, + availableUnits = DATE_PICKER_UNITS, isNew = false, canUseRelativeOffsets = false, }: SetupOpts) { @@ -34,6 +37,7 @@ function setup({ renderWithProviders( <DateIntervalPicker value={value} + availableUnits={availableUnits} isNew={isNew} canUseRelativeOffsets={canUseRelativeOffsets} onChange={onChange} @@ -151,6 +155,17 @@ describe("DateIntervalPicker", () => { expect(onSubmit).not.toHaveBeenCalled(); }); + it("should allow to set only available units", async () => { + setup({ + value: defaultValue, + availableUnits: ["day", "month"], + }); + await userEvent.click(screen.getByLabelText("Unit")); + expect(screen.getByText("days")).toBeInTheDocument(); + expect(screen.getByText("months")).toBeInTheDocument(); + expect(screen.queryByText("years")).not.toBeInTheDocument(); + }); + it("should allow to include the current unit", async () => { const { onChange, onSubmit } = setup({ value: defaultValue, diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateIntervalPicker/SimpleDateIntervalPicker/SimpleDateIntervalPicker.tsx b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateIntervalPicker/SimpleDateIntervalPicker/SimpleDateIntervalPicker.tsx index 8603e99fa1c..b4468065185 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateIntervalPicker/SimpleDateIntervalPicker/SimpleDateIntervalPicker.tsx +++ b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateIntervalPicker/SimpleDateIntervalPicker/SimpleDateIntervalPicker.tsx @@ -2,6 +2,7 @@ import { t } from "ttag"; import { Group, NumberInput, Select } from "metabase/ui"; +import type { DatePickerUnit } from "../../../types"; import { IncludeCurrentSwitch } from "../../IncludeCurrentSwitch"; import type { DateIntervalValue } from "../../types"; import { getInterval, getUnitOptions, setInterval } from "../../utils"; @@ -9,15 +10,17 @@ import { setUnit } from "../utils"; interface SimpleDateIntervalPickerProps { value: DateIntervalValue; + availableUnits: ReadonlyArray<DatePickerUnit>; onChange: (value: DateIntervalValue) => void; } export function SimpleDateIntervalPicker({ value, + availableUnits, onChange, }: SimpleDateIntervalPickerProps) { const interval = getInterval(value); - const unitOptions = getUnitOptions(value); + const unitOptions = getUnitOptions(value, availableUnits); const handleIntervalChange = (inputValue: number | "") => { if (inputValue !== "") { diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateIntervalPicker/SimpleDateIntervalPicker/SimpleDateIntervalPicker.unit.spec.tsx b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateIntervalPicker/SimpleDateIntervalPicker/SimpleDateIntervalPicker.unit.spec.tsx index 457ceaf36a3..6b160cdd118 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateIntervalPicker/SimpleDateIntervalPicker/SimpleDateIntervalPicker.unit.spec.tsx +++ b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateIntervalPicker/SimpleDateIntervalPicker/SimpleDateIntervalPicker.unit.spec.tsx @@ -1,8 +1,9 @@ import userEvent from "@testing-library/user-event"; import { renderWithProviders, screen } from "__support__/ui"; -import type { RelativeIntervalDirection } from "metabase/querying/filters/components/DatePicker/types"; +import { DATE_PICKER_UNITS } from "../../../constants"; +import type { DatePickerUnit, RelativeIntervalDirection } from "../../../types"; import type { DateIntervalValue } from "../../types"; import { SimpleDateIntervalPicker } from "./SimpleDateIntervalPicker"; @@ -19,15 +20,20 @@ function getDefaultValue( interface SetupOpts { value: DateIntervalValue; + availableUnits?: ReadonlyArray<DatePickerUnit>; isNew?: boolean; canUseRelativeOffsets?: boolean; } -function setup({ value }: SetupOpts) { +function setup({ value, availableUnits = DATE_PICKER_UNITS }: SetupOpts) { const onChange = jest.fn(); renderWithProviders( - <SimpleDateIntervalPicker value={value} onChange={onChange} />, + <SimpleDateIntervalPicker + value={value} + availableUnits={availableUnits} + onChange={onChange} + />, ); return { onChange }; diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateOffsetIntervalPicker/DateOffsetIntervalPicker.tsx b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateOffsetIntervalPicker/DateOffsetIntervalPicker.tsx index 06bc1cc091b..87e9c64f646 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateOffsetIntervalPicker/DateOffsetIntervalPicker.tsx +++ b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateOffsetIntervalPicker/DateOffsetIntervalPicker.tsx @@ -11,6 +11,7 @@ import { Text, } from "metabase/ui"; +import type { DatePickerUnit } from "../../types"; import type { DateIntervalValue, DateOffsetIntervalValue } from "../types"; import { formatDateRange, @@ -32,6 +33,7 @@ import { interface DateOffsetIntervalPickerProps { value: DateOffsetIntervalValue; + availableUnits: ReadonlyArray<DatePickerUnit>; isNew: boolean; onChange: (value: DateIntervalValue) => void; onSubmit: () => void; @@ -39,14 +41,15 @@ interface DateOffsetIntervalPickerProps { export function DateOffsetIntervalPicker({ value, + availableUnits, isNew, onChange, onSubmit, }: DateOffsetIntervalPickerProps) { const interval = getInterval(value); - const unitOptions = getUnitOptions(value); + const unitOptions = getUnitOptions(value, availableUnits); const offsetInterval = getOffsetInterval(value); - const offsetUnitOptions = getOffsetUnitOptions(value); + const offsetUnitOptions = getOffsetUnitOptions(value, availableUnits); const directionText = getDirectionText(value); const dateRangeText = formatDateRange(value); diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateOffsetIntervalPicker/DateOffsetIntervalPicker.unit.spec.tsx b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateOffsetIntervalPicker/DateOffsetIntervalPicker.unit.spec.tsx index d49b3a03d16..35762162b98 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateOffsetIntervalPicker/DateOffsetIntervalPicker.unit.spec.tsx +++ b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateOffsetIntervalPicker/DateOffsetIntervalPicker.unit.spec.tsx @@ -2,7 +2,8 @@ import _userEvent from "@testing-library/user-event"; import { renderWithProviders, screen } from "__support__/ui"; -import type { RelativeIntervalDirection } from "../../types"; +import { DATE_PICKER_UNITS } from "../../constants"; +import type { DatePickerUnit, RelativeIntervalDirection } from "../../types"; import type { DateOffsetIntervalValue } from "../types"; import { DateOffsetIntervalPicker } from "./DateOffsetIntervalPicker"; @@ -25,16 +26,22 @@ const userEvent = _userEvent.setup({ interface SetupOpts { value: DateOffsetIntervalValue; + availableUnits?: ReadonlyArray<DatePickerUnit>; isNew?: boolean; } -function setup({ value, isNew = false }: SetupOpts) { +function setup({ + value, + availableUnits = DATE_PICKER_UNITS, + isNew = false, +}: SetupOpts) { const onChange = jest.fn(); const onSubmit = jest.fn(); renderWithProviders( <DateOffsetIntervalPicker value={value} + availableUnits={availableUnits} isNew={isNew} onChange={onChange} onSubmit={onSubmit} @@ -152,6 +159,24 @@ describe("DateOffsetIntervalPicker", () => { expect(onSubmit).not.toHaveBeenCalled(); }); + it("should allow to set only available units", async () => { + setup({ + value: defaultValue, + availableUnits: ["day", "year"], + }); + + await userEvent.click(screen.getByLabelText("Unit")); + expect(screen.getByText("days")).toBeInTheDocument(); + expect(screen.getByText("years")).toBeInTheDocument(); + expect(screen.queryByText("months")).not.toBeInTheDocument(); + + const suffix = direction === "last" ? "ago" : "from now"; + await userEvent.click(screen.getByLabelText("Starting from unit")); + expect(screen.getByText(`days ${suffix}`)).toBeInTheDocument(); + expect(screen.getByText(`years ${suffix}`)).toBeInTheDocument(); + expect(screen.queryByText(`months ${suffix}`)).not.toBeInTheDocument(); + }); + it("should change the offset interval", async () => { const { onChange, onSubmit } = setup({ value: defaultValue, diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateOffsetIntervalPicker/utils.ts b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateOffsetIntervalPicker/utils.ts index cda9f0c43b2..e755b110824 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateOffsetIntervalPicker/utils.ts +++ b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/DateOffsetIntervalPicker/utils.ts @@ -2,13 +2,13 @@ import { t } from "ttag"; import * as Lib from "metabase-lib"; -import { DATE_PICKER_TRUNCATION_UNITS } from "../../constants"; import type { DatePickerTruncationUnit, + DatePickerUnit, RelativeIntervalDirection, } from "../../types"; import type { DateIntervalValue, DateOffsetIntervalValue } from "../types"; -import { getDirection } from "../utils"; +import { getAvailableTruncationUnits, getDirection } from "../utils"; export function getDirectionText(value: DateOffsetIntervalValue): string { const direction = getDirection(value); @@ -51,16 +51,20 @@ export function removeOffset( return { ...value, offsetValue: undefined, offsetUnit: undefined }; } -export function getOffsetUnitOptions(value: DateOffsetIntervalValue) { +export function getOffsetUnitOptions( + value: DateOffsetIntervalValue, + availableUnits: ReadonlyArray<DatePickerUnit>, +) { + const truncationUnits = getAvailableTruncationUnits(availableUnits); const direction = getDirection(value); - const unitIndex = DATE_PICKER_TRUNCATION_UNITS.indexOf(value.unit); + const unitIndex = truncationUnits.indexOf(value.unit); - return DATE_PICKER_TRUNCATION_UNITS.filter( - (_, index) => index >= unitIndex, - ).map(unit => ({ - value: unit, - label: getOffsetUnitText(unit, direction, value.offsetValue), - })); + return truncationUnits + .filter((_, index) => index >= unitIndex) + .map(unit => ({ + value: unit, + label: getOffsetUnitText(unit, direction, value.offsetValue), + })); } function getOffsetUnitText( diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/RelativeDatePicker.tsx b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/RelativeDatePicker.tsx index eedc38528be..f8af0f8c52e 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/RelativeDatePicker.tsx +++ b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/RelativeDatePicker.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import { Box, Divider, Flex, PopoverBackButton, Tabs } from "metabase/ui"; -import type { RelativeDatePickerValue } from "../types"; +import type { DatePickerUnit, RelativeDatePickerValue } from "../types"; import { CurrentDatePicker } from "./CurrentDatePicker"; import { DateIntervalPicker } from "./DateIntervalPicker"; @@ -18,6 +18,7 @@ import { interface RelativeDatePickerProps { value: RelativeDatePickerValue | undefined; + availableUnits: ReadonlyArray<DatePickerUnit>; canUseRelativeOffsets: boolean; isNew: boolean; onChange: (value: RelativeDatePickerValue) => void; @@ -26,12 +27,15 @@ interface RelativeDatePickerProps { export function RelativeDatePicker({ value: initialValue, + availableUnits, canUseRelativeOffsets, isNew, onChange, onBack, }: RelativeDatePickerProps) { - const [value, setValue] = useState(initialValue ?? DEFAULT_VALUE); + const [value, setValue] = useState<RelativeDatePickerValue | undefined>( + initialValue ?? DEFAULT_VALUE, + ); const direction = getDirection(value); const handleTabChange = (tabValue: string | null) => { @@ -42,7 +46,9 @@ export function RelativeDatePicker({ }; const handleSubmit = () => { - onChange(value); + if (value != null) { + onChange(value); + } }; return ( @@ -63,6 +69,7 @@ export function RelativeDatePicker({ {isOffsetIntervalValue(value) ? ( <DateOffsetIntervalPicker value={value} + availableUnits={availableUnits} isNew={isNew} onChange={setValue} onSubmit={handleSubmit} @@ -70,6 +77,7 @@ export function RelativeDatePicker({ ) : isIntervalValue(value) ? ( <DateIntervalPicker value={value} + availableUnits={availableUnits} isNew={isNew} canUseRelativeOffsets={canUseRelativeOffsets} onChange={setValue} @@ -77,7 +85,11 @@ export function RelativeDatePicker({ /> ) : ( <Box p="md"> - <CurrentDatePicker value={value} onChange={onChange} /> + <CurrentDatePicker + value={value} + availableUnits={availableUnits} + onChange={onChange} + /> </Box> )} </Tabs.Panel> diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/RelativeDatePicker.unit.spec.tsx b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/RelativeDatePicker.unit.spec.tsx index 8f98d6629de..a0577a7f163 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/RelativeDatePicker.unit.spec.tsx +++ b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/RelativeDatePicker.unit.spec.tsx @@ -2,7 +2,8 @@ import userEvent from "@testing-library/user-event"; import { renderWithProviders, screen } from "__support__/ui"; -import type { RelativeDatePickerValue } from "../types"; +import { DATE_PICKER_UNITS } from "../constants"; +import type { DatePickerUnit, RelativeDatePickerValue } from "../types"; import { RelativeDatePicker } from "./RelativeDatePicker"; @@ -11,12 +12,14 @@ const TAB_CASES = TABS.flatMap(fromTab => TABS.map(toTab => [fromTab, toTab])); interface SetupOpts { value?: RelativeDatePickerValue; + availableUnits?: ReadonlyArray<DatePickerUnit>; canUseRelativeOffsets?: boolean; isNew?: boolean; } function setup({ value, + availableUnits = DATE_PICKER_UNITS, canUseRelativeOffsets = false, isNew = false, }: SetupOpts = {}) { @@ -26,6 +29,7 @@ function setup({ renderWithProviders( <RelativeDatePicker value={value} + availableUnits={availableUnits} canUseRelativeOffsets={canUseRelativeOffsets} isNew={isNew} onChange={onChange} diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/utils.ts b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/utils.ts index 7e7d245e90c..082e08c8b01 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/utils.ts +++ b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/utils.ts @@ -3,6 +3,7 @@ import * as Lib from "metabase-lib"; import { DATE_PICKER_TRUNCATION_UNITS } from "../constants"; import type { DatePickerTruncationUnit, + DatePickerUnit, DatePickerValue, RelativeDatePickerValue, RelativeIntervalDirection, @@ -12,48 +13,51 @@ import { DEFAULT_VALUE } from "./constants"; import type { DateIntervalValue, DateOffsetIntervalValue } from "./types"; export function isRelativeValue( - value?: DatePickerValue, + value: DatePickerValue | undefined, ): value is RelativeDatePickerValue { - return value?.type === "relative"; + return value != null && value.type === "relative"; } export function isIntervalValue( - value: RelativeDatePickerValue, + value: RelativeDatePickerValue | undefined, ): value is DateIntervalValue { - return value.value !== "current"; + return value != null && value.value !== "current"; } export function isOffsetIntervalValue( - value: RelativeDatePickerValue, + value: RelativeDatePickerValue | undefined, ): value is DateOffsetIntervalValue { return ( + value != null && isIntervalValue(value) && value.offsetValue != null && value.offsetUnit != null ); } -export function getDirectionDefaultValue(direction: RelativeIntervalDirection) { - return setDirectionAndCoerceUnit(DEFAULT_VALUE, direction); -} - export function getDirection( - value: RelativeDatePickerValue, + value: RelativeDatePickerValue | undefined, ): RelativeIntervalDirection { - if (value.value === "current") { + if (value == null || value.value === "current") { return "current"; } else { return value.value < 0 ? "last" : "next"; } } +export function getDirectionDefaultValue(direction: RelativeIntervalDirection) { + return setDirectionAndCoerceUnit(DEFAULT_VALUE, direction); +} + export function setDirection( - value: RelativeDatePickerValue, + value: RelativeDatePickerValue = DEFAULT_VALUE, direction: RelativeIntervalDirection, - fallbackUnit: DatePickerTruncationUnit = "hour", -): RelativeDatePickerValue { + fallbackUnit?: DatePickerTruncationUnit, +): RelativeDatePickerValue | undefined { if (direction === "current") { - return { type: "relative", value: "current", unit: fallbackUnit }; + return fallbackUnit + ? { type: "relative", value: "current", unit: fallbackUnit } + : undefined; } const sign = direction === "last" ? -1 : 1; @@ -103,10 +107,22 @@ export function setInterval( }; } -export function getUnitOptions(value: DateIntervalValue) { +export function getAvailableTruncationUnits( + availableUnits: ReadonlyArray<DatePickerUnit>, +) { + return DATE_PICKER_TRUNCATION_UNITS.filter(unit => + availableUnits.includes(unit), + ); +} + +export function getUnitOptions( + value: DateIntervalValue, + availableUnits: ReadonlyArray<DatePickerUnit>, +) { + const truncationUnits = getAvailableTruncationUnits(availableUnits); const interval = getInterval(value); - return DATE_PICKER_TRUNCATION_UNITS.map(unit => ({ + return truncationUnits.map(unit => ({ value: unit, label: Lib.describeTemporalUnit(unit, interval).toLowerCase(), })); diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/utils.unit.spec.ts b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/utils.unit.spec.ts index 00385847f65..5389b4f6fd4 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/utils.unit.spec.ts +++ b/frontend/src/metabase/querying/filters/components/DatePicker/RelativeDatePicker/utils.unit.spec.ts @@ -15,17 +15,13 @@ describe("setDirection", () => { "month", "quarter", "year", - ])('should fallback to "hour" for "%s" unit', unit => { + ])('should remove the value for "%s" unit', unit => { const value: RelativeDatePickerValue = { type: "relative", value: 1, unit, }; - expect(setDirection(value, "current")).toEqual({ - type: "relative", - value: "current", - unit: "hour", - }); + expect(setDirection(value, "current")).toBeUndefined(); }); }); diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/SimpleDatePicker/SimpleDatePicker.tsx b/frontend/src/metabase/querying/filters/components/DatePicker/SimpleDatePicker/SimpleDatePicker.tsx index 467866b6489..71a73000803 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/SimpleDatePicker/SimpleDatePicker.tsx +++ b/frontend/src/metabase/querying/filters/components/DatePicker/SimpleDatePicker/SimpleDatePicker.tsx @@ -11,16 +11,22 @@ import { isIntervalValue, isRelativeValue } from "../RelativeDatePicker/utils"; import { SimpleSpecificDatePicker } from "../SpecificDatePicker/SimpleSpecificDatePicker"; import { isSpecificValue } from "../SpecificDatePicker/utils"; import { DATE_PICKER_OPERATORS } from "../constants"; -import type { DatePickerOperator, DatePickerValue } from "../types"; +import type { + DatePickerOperator, + DatePickerUnit, + DatePickerValue, +} from "../types"; interface SimpleDatePickerProps { value?: DatePickerValue; availableOperators?: ReadonlyArray<DatePickerOperator>; + availableUnits: ReadonlyArray<DatePickerUnit>; onChange: (value: DatePickerValue | undefined) => void; } export function SimpleDatePicker({ value: initialValue, + availableUnits, availableOperators = DATE_PICKER_OPERATORS, onChange, }: SimpleDatePickerProps) { @@ -40,10 +46,18 @@ export function SimpleDatePicker({ onChange={setValue} /> {isRelativeValue(value) && isIntervalValue(value) && ( - <SimpleDateIntervalPicker value={value} onChange={setValue} /> + <SimpleDateIntervalPicker + value={value} + availableUnits={availableUnits} + onChange={setValue} + /> )} {isRelativeValue(value) && !isIntervalValue(value) && ( - <CurrentDatePicker value={value} onChange={setValue} /> + <CurrentDatePicker + value={value} + availableUnits={availableUnits} + onChange={setValue} + /> )} {isSpecificValue(value) && ( <SimpleSpecificDatePicker value={value} onChange={setValue} /> diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/SimpleDatePicker/SimpleDatePicker.unit.spec.tsx b/frontend/src/metabase/querying/filters/components/DatePicker/SimpleDatePicker/SimpleDatePicker.unit.spec.tsx index 790b1d015f6..78604a4ca6d 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/SimpleDatePicker/SimpleDatePicker.unit.spec.tsx +++ b/frontend/src/metabase/querying/filters/components/DatePicker/SimpleDatePicker/SimpleDatePicker.unit.spec.tsx @@ -2,19 +2,25 @@ import userEvent from "@testing-library/user-event"; import { renderWithProviders, screen } from "__support__/ui"; -import { DATE_PICKER_OPERATORS } from "../constants"; -import type { DatePickerOperator, DatePickerValue } from "../types"; +import { DATE_PICKER_OPERATORS, DATE_PICKER_UNITS } from "../constants"; +import type { + DatePickerOperator, + DatePickerUnit, + DatePickerValue, +} from "../types"; import { SimpleDatePicker } from "./SimpleDatePicker"; interface SetupOpts { value?: DatePickerValue; availableOperators?: ReadonlyArray<DatePickerOperator>; + availableUnits?: ReadonlyArray<DatePickerUnit>; } function setup({ value, availableOperators = DATE_PICKER_OPERATORS, + availableUnits = DATE_PICKER_UNITS, }: SetupOpts = {}) { const onChange = jest.fn(); @@ -22,6 +28,7 @@ function setup({ <SimpleDatePicker value={value} availableOperators={availableOperators} + availableUnits={availableUnits} onChange={onChange} />, ); diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/DateRangePicker/DateRangePicker.tsx b/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/DateRangePicker/DateRangePicker.tsx index 06c0cbf3f0e..b73706180c4 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/DateRangePicker/DateRangePicker.tsx +++ b/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/DateRangePicker/DateRangePicker.tsx @@ -12,6 +12,7 @@ import type { DateRangePickerValue } from "./types"; export interface DateRangePickerProps { value: DateRangePickerValue; isNew: boolean; + hasTimeToggle: boolean; onChange: (value: DateRangePickerValue) => void; onSubmit: () => void; } @@ -19,6 +20,7 @@ export interface DateRangePickerProps { export function DateRangePicker({ value: { dateRange, hasTime }, isNew, + hasTimeToggle, onChange, onSubmit, }: DateRangePickerProps) { @@ -50,8 +52,10 @@ export function DateRangePicker({ /> </Box> <Divider /> - <Group p="sm" position="apart"> - <TimeToggle hasTime={hasTime} onClick={handleTimeToggle} /> + <Group p="sm" position={hasTimeToggle ? "apart" : "right"}> + {hasTimeToggle && ( + <TimeToggle hasTime={hasTime} onClick={handleTimeToggle} /> + )} <Button variant="filled" type="submit"> {isNew ? t`Add filter` : t`Update filter`} </Button> diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/DateRangePicker/DateRangePicker.unit.spec.tsx b/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/DateRangePicker/DateRangePicker.unit.spec.tsx index 4000df28fa7..71647e7575b 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/DateRangePicker/DateRangePicker.unit.spec.tsx +++ b/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/DateRangePicker/DateRangePicker.unit.spec.tsx @@ -14,6 +14,7 @@ const END_DATE_TIME = new Date(2020, 1, 9, 20, 30); interface SetupOpts { value?: DateRangePickerValue; isNew?: boolean; + hasTimeToggle?: boolean; } const userEvent = _userEvent.setup({ @@ -23,6 +24,7 @@ const userEvent = _userEvent.setup({ function setup({ value = { dateRange: [START_DATE, END_DATE], hasTime: false }, isNew = false, + hasTimeToggle = false, }: SetupOpts = {}) { const onChange = jest.fn(); const onSubmit = jest.fn(); @@ -31,6 +33,7 @@ function setup({ <DateRangePicker value={value} isNew={isNew} + hasTimeToggle={hasTimeToggle} onChange={onChange} onSubmit={onSubmit} />, @@ -65,6 +68,7 @@ describe("SingleDatePicker", () => { dateRange: [START_DATE_TIME, END_DATE_TIME], hasTime: true, }, + hasTimeToggle: true, }); const calendars = screen.getAllByRole("table"); @@ -100,6 +104,7 @@ describe("SingleDatePicker", () => { dateRange: [START_DATE_TIME, END_DATE], hasTime: true, }, + hasTimeToggle: true, }); const input = screen.getByLabelText("Start date"); @@ -135,6 +140,7 @@ describe("SingleDatePicker", () => { dateRange: [START_DATE, END_DATE_TIME], hasTime: true, }, + hasTimeToggle: true, }); const input = screen.getByLabelText("End date"); @@ -149,7 +155,9 @@ describe("SingleDatePicker", () => { }); it("should be able to add time", async () => { - const { onChange, onSubmit } = setup(); + const { onChange, onSubmit } = setup({ + hasTimeToggle: true, + }); expect(screen.queryByLabelText("Start time")).not.toBeInTheDocument(); expect(screen.queryByLabelText("End time")).not.toBeInTheDocument(); @@ -167,6 +175,7 @@ describe("SingleDatePicker", () => { dateRange: [START_DATE_TIME, END_DATE_TIME], hasTime: true, }, + hasTimeToggle: true, }); const input = screen.getByLabelText("Start time"); @@ -186,6 +195,7 @@ describe("SingleDatePicker", () => { dateRange: [START_DATE_TIME, END_DATE_TIME], hasTime: true, }, + hasTimeToggle: true, }); const input = screen.getByLabelText("End time"); @@ -205,6 +215,7 @@ describe("SingleDatePicker", () => { dateRange: [START_DATE_TIME, END_DATE_TIME], hasTime: true, }, + hasTimeToggle: true, }); await userEvent.click(screen.getByText("Remove time")); @@ -215,4 +226,12 @@ describe("SingleDatePicker", () => { }); expect(onSubmit).not.toHaveBeenCalled(); }); + + it("should not allow to add time when the time toggle is disabled", () => { + setup({ + value: { dateRange: [START_DATE_TIME, END_DATE_TIME], hasTime: false }, + hasTimeToggle: false, + }); + expect(screen.queryByText("Add time")).not.toBeInTheDocument(); + }); }); diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/SingleDatePicker/SingleDatePicker.tsx b/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/SingleDatePicker/SingleDatePicker.tsx index 3a716831a37..b19491e2db9 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/SingleDatePicker/SingleDatePicker.tsx +++ b/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/SingleDatePicker/SingleDatePicker.tsx @@ -12,6 +12,7 @@ import type { SingleDatePickerValue } from "./types"; interface SingleDatePickerProps { value: SingleDatePickerValue; isNew: boolean; + hasTimeToggle: boolean; onChange: (value: SingleDatePickerValue) => void; onSubmit: () => void; } @@ -19,6 +20,7 @@ interface SingleDatePickerProps { export function SingleDatePicker({ value: { date, hasTime }, isNew, + hasTimeToggle, onChange, onSubmit, }: SingleDatePickerProps) { @@ -45,8 +47,10 @@ export function SingleDatePicker({ /> </Box> <Divider /> - <Group p="sm" position="apart"> - <TimeToggle hasTime={hasTime} onClick={handleTimeToggle} /> + <Group p="sm" position={hasTimeToggle ? "apart" : "right"}> + {hasTimeToggle && ( + <TimeToggle hasTime={hasTime} onClick={handleTimeToggle} /> + )} <Button variant="filled" type="submit"> {isNew ? t`Add filter` : t`Update filter`} </Button> diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/SingleDatePicker/SingleDatePicker.unit.spec.tsx b/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/SingleDatePicker/SingleDatePicker.unit.spec.tsx index bc4b5beb997..57b97144965 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/SingleDatePicker/SingleDatePicker.unit.spec.tsx +++ b/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/SingleDatePicker/SingleDatePicker.unit.spec.tsx @@ -11,6 +11,7 @@ const DATE_TIME = new Date(2020, 0, 10, 10, 20); interface SetupOpts { value?: SingleDatePickerValue; isNew?: boolean; + hasTimeToggle?: boolean; } const userEvent = _userEvent.setup({ @@ -20,6 +21,7 @@ const userEvent = _userEvent.setup({ function setup({ value = { date: DATE, hasTime: false }, isNew = false, + hasTimeToggle = false, }: SetupOpts = {}) { const onChange = jest.fn(); const onSubmit = jest.fn(); @@ -28,6 +30,7 @@ function setup({ <SingleDatePicker value={value} isNew={isNew} + hasTimeToggle={hasTimeToggle} onChange={onChange} onSubmit={onSubmit} />, @@ -57,6 +60,7 @@ describe("SingleDatePicker", () => { it("should be able to set the date via the calendar when there is time", async () => { const { onChange, onSubmit } = setup({ value: { date: DATE_TIME, hasTime: true }, + hasTimeToggle: true, }); await userEvent.click(screen.getByText("12")); @@ -88,6 +92,7 @@ describe("SingleDatePicker", () => { it("should be able to set the date via the input when there is time", async () => { const { onChange, onSubmit } = setup({ value: { date: DATE_TIME, hasTime: true }, + hasTimeToggle: true, }); const input = screen.getByLabelText("Date"); @@ -103,7 +108,9 @@ describe("SingleDatePicker", () => { }); it("should be able to add time", async () => { - const { onChange, onSubmit } = setup(); + const { onChange, onSubmit } = setup({ + hasTimeToggle: true, + }); await userEvent.click(screen.getByText("Add time")); @@ -114,6 +121,7 @@ describe("SingleDatePicker", () => { it("should be able to update the time", async () => { const { onChange, onSubmit } = setup({ value: { date: DATE_TIME, hasTime: true }, + hasTimeToggle: true, }); const input = screen.getByLabelText("Time"); @@ -130,6 +138,7 @@ describe("SingleDatePicker", () => { it("should be able to remove time", async () => { const { onChange, onSubmit } = setup({ value: { date: DATE_TIME, hasTime: true }, + hasTimeToggle: true, }); await userEvent.click(screen.getByText("Remove time")); @@ -140,4 +149,12 @@ describe("SingleDatePicker", () => { }); expect(onSubmit).not.toHaveBeenCalled(); }); + + it("should not allow to add time when the time toggle is disabled", () => { + setup({ + value: { date: DATE, hasTime: false }, + hasTimeToggle: false, + }); + expect(screen.queryByText("Add time")).not.toBeInTheDocument(); + }); }); diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/SpecificDatePicker.tsx b/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/SpecificDatePicker.tsx index 21117db7d52..f94baa9491e 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/SpecificDatePicker.tsx +++ b/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/SpecificDatePicker.tsx @@ -2,7 +2,11 @@ import { useMemo, useState } from "react"; import { Divider, Flex, PopoverBackButton, Tabs } from "metabase/ui"; -import type { DatePickerOperator, SpecificDatePickerValue } from "../types"; +import type { + DatePickerOperator, + DatePickerUnit, + SpecificDatePickerValue, +} from "../types"; import { DateRangePicker, type DateRangePickerValue } from "./DateRangePicker"; import { @@ -11,6 +15,7 @@ import { } from "./SingleDatePicker"; import { TabList } from "./SpecificDatePicker.styled"; import { + canSetTime, coerceValue, getDate, getDefaultValue, @@ -24,6 +29,7 @@ import { interface SpecificDatePickerProps { value?: SpecificDatePickerValue; availableOperators: ReadonlyArray<DatePickerOperator>; + availableUnits: ReadonlyArray<DatePickerUnit>; isNew: boolean; onChange: (value: SpecificDatePickerValue) => void; onBack: () => void; @@ -32,15 +38,14 @@ interface SpecificDatePickerProps { export function SpecificDatePicker({ value: initialValue, availableOperators, + availableUnits, isNew, onChange, onBack, }: SpecificDatePickerProps) { - const tabs = useMemo(() => { - return getTabs(availableOperators); - }, [availableOperators]); - + const tabs = useMemo(() => getTabs(availableOperators), [availableOperators]); const [value, setValue] = useState(() => initialValue ?? getDefaultValue()); + const hasTimeToggle = canSetTime(value, availableUnits); const handleTabChange = (tabValue: string | null) => { const tab = tabs.find(tab => tab.operator === tabValue); @@ -83,6 +88,7 @@ export function SpecificDatePicker({ <DateRangePicker value={{ dateRange: value.values, hasTime: value.hasTime }} isNew={isNew} + hasTimeToggle={hasTimeToggle} onChange={handleDateRangeChange} onSubmit={handleSubmit} /> @@ -90,6 +96,7 @@ export function SpecificDatePicker({ <SingleDatePicker value={{ date: getDate(value), hasTime: value.hasTime }} isNew={isNew} + hasTimeToggle={hasTimeToggle} onChange={handleDateChange} onSubmit={handleSubmit} /> diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/SpecificDatePicker.unit.spec.tsx b/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/SpecificDatePicker.unit.spec.tsx index 573eb2754a8..f71e676908a 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/SpecificDatePicker.unit.spec.tsx +++ b/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/SpecificDatePicker.unit.spec.tsx @@ -2,14 +2,19 @@ import _userEvent from "@testing-library/user-event"; import { renderWithProviders, screen, within } from "__support__/ui"; -import { DATE_PICKER_OPERATORS } from "../constants"; -import type { DatePickerOperator, SpecificDatePickerValue } from "../types"; +import { DATE_PICKER_OPERATORS, DATE_PICKER_UNITS } from "../constants"; +import type { + DatePickerOperator, + DatePickerUnit, + SpecificDatePickerValue, +} from "../types"; import { SpecificDatePicker } from "./SpecificDatePicker"; interface SetupOpts { value?: SpecificDatePickerValue; availableOperators?: ReadonlyArray<DatePickerOperator>; + availableUnits?: ReadonlyArray<DatePickerUnit>; isNew?: boolean; } @@ -20,6 +25,7 @@ const userEvent = _userEvent.setup({ function setup({ value, availableOperators = DATE_PICKER_OPERATORS, + availableUnits = DATE_PICKER_UNITS, isNew = false, }: SetupOpts = {}) { const onChange = jest.fn(); @@ -29,6 +35,7 @@ function setup({ <SpecificDatePicker value={value} availableOperators={availableOperators} + availableUnits={availableUnits} isNew={isNew} onChange={onChange} onBack={onBack} @@ -125,4 +132,33 @@ describe("SpecificDatePicker", () => { hasTime: false, }); }); + + it("should not allow to add time when time units are not supported", async () => { + setup({ availableUnits: ["day", "month"] }); + await userEvent.click(screen.getByText("On")); + expect(screen.queryByText("Add time")).not.toBeInTheDocument(); + }); + + it("should allow to remove time even when time units are not supported", async () => { + const { onChange } = setup({ + value: { + type: "specific", + operator: "=", + values: [new Date(2020, 0, 1, 10, 20)], + hasTime: true, + }, + availableUnits: ["day", "month"], + }); + + await userEvent.click(screen.getByText("Remove time")); + await userEvent.click(screen.getByText("Update filter")); + + expect(onChange).toHaveBeenCalledWith({ + type: "specific", + operator: "=", + values: [new Date(2020, 0, 1)], + hasTime: false, + }); + expect(screen.queryByText("Add time")).not.toBeInTheDocument(); + }); }); diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/utils.ts b/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/utils.ts index 086e85ed319..4301da1fdb2 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/utils.ts +++ b/frontend/src/metabase/querying/filters/components/DatePicker/SpecificDatePicker/utils.ts @@ -2,6 +2,7 @@ import dayjs from "dayjs"; import type { DatePickerOperator, + DatePickerUnit, DatePickerValue, SpecificDatePickerOperator, SpecificDatePickerValue, @@ -52,6 +53,13 @@ export function getOperatorDefaultValue( } } +export function canSetTime( + value: SpecificDatePickerValue, + availableUnits: ReadonlyArray<DatePickerUnit>, +) { + return value.hasTime || availableUnits.includes("minute"); +} + export function setOperator( value: SpecificDatePickerValue, operator: SpecificDatePickerOperator, diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/constants.ts b/frontend/src/metabase/querying/filters/components/DatePicker/constants.ts index d28d477ff9a..070ded24d21 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/constants.ts +++ b/frontend/src/metabase/querying/filters/components/DatePicker/constants.ts @@ -45,3 +45,8 @@ export const DATE_PICKER_EXTRACTION_UNITS = [ "month-of-year", "quarter-of-year", ] as const; + +export const DATE_PICKER_UNITS = [ + ...DATE_PICKER_TRUNCATION_UNITS, + ...DATE_PICKER_EXTRACTION_UNITS, +] as const; diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/types.ts b/frontend/src/metabase/querying/filters/components/DatePicker/types.ts index 488ae489fb4..d7b7afb52cf 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/types.ts +++ b/frontend/src/metabase/querying/filters/components/DatePicker/types.ts @@ -18,6 +18,10 @@ export type ExcludeDatePickerOperator = export type DatePickerShortcut = (typeof DATE_PICKER_SHORTCUTS)[number]; +export type DatePickerUnit = + | DatePickerExtractionUnit + | DatePickerTruncationUnit; + export type DatePickerExtractionUnit = (typeof DATE_PICKER_EXTRACTION_UNITS)[number]; diff --git a/frontend/src/metabase/querying/filters/components/DatePicker/utils.ts b/frontend/src/metabase/querying/filters/components/DatePicker/utils.ts index 9d3d07d09e0..998859e8d05 100644 --- a/frontend/src/metabase/querying/filters/components/DatePicker/utils.ts +++ b/frontend/src/metabase/querying/filters/components/DatePicker/utils.ts @@ -7,6 +7,7 @@ import type { DatePickerExtractionUnit, DatePickerOperator, DatePickerTruncationUnit, + DatePickerUnit, } from "./types"; export function isDatePickerOperator( @@ -16,11 +17,8 @@ export function isDatePickerOperator( return operators.includes(operator); } -export function isDatePickerExtractionUnit( - unit: string, -): unit is DatePickerExtractionUnit { - const units: ReadonlyArray<string> = DATE_PICKER_EXTRACTION_UNITS; - return units.includes(unit); +export function isDatePickerUnit(unit: string): unit is DatePickerUnit { + return isDatePickerTruncationUnit(unit) || isDatePickerExtractionUnit(unit); } export function isDatePickerTruncationUnit( @@ -29,3 +27,10 @@ export function isDatePickerTruncationUnit( const units: ReadonlyArray<string> = DATE_PICKER_TRUNCATION_UNITS; return units.includes(unit); } + +export function isDatePickerExtractionUnit( + unit: string, +): unit is DatePickerExtractionUnit { + const units: ReadonlyArray<string> = DATE_PICKER_EXTRACTION_UNITS; + return units.includes(unit); +} diff --git a/frontend/src/metabase/querying/filters/components/FilterModal/DateFilterEditor/DateFilterEditor.tsx b/frontend/src/metabase/querying/filters/components/FilterModal/DateFilterEditor/DateFilterEditor.tsx index 68a7b93dcc0..46609e7eab8 100644 --- a/frontend/src/metabase/querying/filters/components/FilterModal/DateFilterEditor/DateFilterEditor.tsx +++ b/frontend/src/metabase/querying/filters/components/FilterModal/DateFilterEditor/DateFilterEditor.tsx @@ -9,8 +9,8 @@ import { Button, Flex, Grid, Icon, Popover } from "metabase/ui"; import { DatePicker, - type DatePickerExtractionUnit, type DatePickerOperator, + type DatePickerUnit, type DatePickerValue, type ShortcutOption, } from "../../DatePicker"; @@ -103,7 +103,7 @@ interface DateFilterPopoverProps { title: string | undefined; value: DatePickerValue | undefined; availableOperators: ReadonlyArray<DatePickerOperator>; - availableUnits: ReadonlyArray<DatePickerExtractionUnit>; + availableUnits: ReadonlyArray<DatePickerUnit>; isExpanded: boolean; onChange: (value: DatePickerValue | undefined) => void; } diff --git a/frontend/src/metabase/querying/filters/components/FilterModal/DateFilterEditor/DateFilterEditor.unit.spec.tsx b/frontend/src/metabase/querying/filters/components/FilterModal/DateFilterEditor/DateFilterEditor.unit.spec.tsx index c144b1f2063..b182aa8b447 100644 --- a/frontend/src/metabase/querying/filters/components/FilterModal/DateFilterEditor/DateFilterEditor.unit.spec.tsx +++ b/frontend/src/metabase/querying/filters/components/FilterModal/DateFilterEditor/DateFilterEditor.unit.spec.tsx @@ -46,162 +46,184 @@ describe("DateFilterEditor", () => { const findColumn = columnFinder(defaultQuery, availableColumns); const column = findColumn("ORDERS", "CREATED_AT"); - describe("new filter", () => { - it("should add a relative date filter from a shortcut", async () => { - const { getNextFilterName } = setup({ - query: defaultQuery, - stageIndex, - column, - }); + it("should add a relative date filter from a shortcut", async () => { + const { getNextFilterName } = setup({ + query: defaultQuery, + stageIndex, + column, + }); - await userEvent.click(screen.getByText("Last month")); + await userEvent.click(screen.getByText("Last month")); - expect(getNextFilterName()).toBe("Created At is in the previous month"); - }); + expect(getNextFilterName()).toBe("Created At is in the previous month"); + }); - it("should remove a relative date filter from a shortcut", async () => { - const { query, filter } = createQueryWithFilter( - defaultQuery, - stageIndex, - Lib.relativeDateFilterClause({ - column, - value: "current", - bucket: "day", - offsetValue: null, - offsetBucket: null, - options: {}, - }), - ); - const { getNextFilterName } = setup({ - query, - stageIndex, + it("should remove a relative date filter from a shortcut", async () => { + const { query, filter } = createQueryWithFilter( + defaultQuery, + stageIndex, + Lib.relativeDateFilterClause({ column, - filter, - }); + value: "current", + bucket: "day", + offsetValue: null, + offsetBucket: null, + options: {}, + }), + ); + const { getNextFilterName } = setup({ + query, + stageIndex, + column, + filter, + }); + + const button = screen.getByRole("button", { name: "Today" }); + expect(button).toHaveAttribute("aria-selected", "true"); - const button = screen.getByRole("button", { name: "Today" }); - expect(button).toHaveAttribute("aria-selected", "true"); + await userEvent.click(button); + expect(getNextFilterName()).toBeNull(); + }); - await userEvent.click(button); - expect(getNextFilterName()).toBeNull(); + it("should add a relative date filter", async () => { + const { getNextFilterName } = setup({ + query: defaultQuery, + stageIndex, + column, }); - it("should add a relative date filter", async () => { - const { getNextFilterName } = setup({ - query: defaultQuery, - stageIndex, - column, - }); + await userEvent.click(screen.getByLabelText("More options")); + await userEvent.click(await screen.findByText("Last 30 days")); - await userEvent.click(screen.getByLabelText("More options")); - await userEvent.click(await screen.findByText("Last 30 days")); + expect(getNextFilterName()).toBe("Created At is in the previous 30 days"); + }); - expect(getNextFilterName()).toBe("Created At is in the previous 30 days"); + it("should remove a relative date filter", async () => { + const { query, filter } = createQueryWithFilter( + defaultQuery, + stageIndex, + Lib.relativeDateFilterClause({ + column, + value: -30, + bucket: "day", + offsetValue: null, + offsetBucket: null, + options: {}, + }), + ); + const { getNextFilterName } = setup({ + query, + stageIndex, + column, + filter, }); + expect(screen.getByText("Previous 30 Days")).toBeInTheDocument(); - it("should remove a relative date filter", async () => { - const { query, filter } = createQueryWithFilter( - defaultQuery, - stageIndex, - Lib.relativeDateFilterClause({ - column, - value: -30, - bucket: "day", - offsetValue: null, - offsetBucket: null, - options: {}, - }), - ); - const { getNextFilterName } = setup({ - query, - stageIndex, - column, - filter, - }); - expect(screen.getByText("Previous 30 Days")).toBeInTheDocument(); + await userEvent.click(screen.getByLabelText("Clear")); + expect(getNextFilterName()).toBe(null); + }); - await userEvent.click(screen.getByLabelText("Clear")); - expect(getNextFilterName()).toBe(null); + it("should add a specific date filter", async () => { + const { getNextFilterName } = setup({ + query: defaultQuery, + stageIndex, + column, }); - it("should add a specific date filter", async () => { - const { getNextFilterName } = setup({ - query: defaultQuery, - stageIndex, - column, - }); + await userEvent.click(screen.getByLabelText("More options")); + await userEvent.click(await screen.findByText("Specific dates…")); + await userEvent.click(screen.getByText("After")); + await userEvent.clear(screen.getByLabelText("Date")); + await userEvent.type(screen.getByLabelText("Date"), "Feb 15, 2020"); + await userEvent.click(screen.getByText("Add filter")); - await userEvent.click(screen.getByLabelText("More options")); - await userEvent.click(await screen.findByText("Specific dates…")); - await userEvent.click(screen.getByText("After")); - await userEvent.clear(screen.getByLabelText("Date")); - await userEvent.type(screen.getByLabelText("Date"), "Feb 15, 2020"); - await userEvent.click(screen.getByText("Add filter")); + expect(getNextFilterName()).toBe("Created At is after Feb 15, 2020"); + }); - expect(getNextFilterName()).toBe("Created At is after Feb 15, 2020"); + it("should remove a specific date filter", async () => { + const { query, filter } = createQueryWithFilter( + defaultQuery, + stageIndex, + Lib.specificDateFilterClause(defaultQuery, stageIndex, { + operator: "=", + column, + values: [new Date(2020, 1, 15)], + hasTime: false, + }), + ); + const { getNextFilterName } = setup({ + query, + stageIndex, + column, + filter, }); + expect(screen.getByText("Feb 15, 2020")).toBeInTheDocument(); - it("should remove a specific date filter", async () => { - const { query, filter } = createQueryWithFilter( - defaultQuery, - stageIndex, - Lib.specificDateFilterClause(defaultQuery, stageIndex, { - operator: "=", - column, - values: [new Date(2020, 1, 15)], - hasTime: false, - }), - ); - const { getNextFilterName } = setup({ - query, - stageIndex, - column, - filter, - }); - expect(screen.getByText("Feb 15, 2020")).toBeInTheDocument(); + await userEvent.click(screen.getByLabelText("Clear")); + expect(getNextFilterName()).toBe(null); + }); - await userEvent.click(screen.getByLabelText("Clear")); - expect(getNextFilterName()).toBe(null); + it("should add an exclude date filter", async () => { + const { getNextFilterName } = setup({ + query: defaultQuery, + stageIndex, + column, }); - it("should add an exclude date filter", async () => { - const { getNextFilterName } = setup({ - query: defaultQuery, - stageIndex, - column, - }); + await userEvent.click(screen.getByLabelText("More options")); + await userEvent.click(await screen.findByText("Exclude…")); + await userEvent.click(screen.getByText("Hours of the day…")); + await userEvent.click(screen.getByText("5 PM")); + await userEvent.click(screen.getByText("Add filter")); - await userEvent.click(screen.getByLabelText("More options")); - await userEvent.click(await screen.findByText("Exclude…")); - await userEvent.click(screen.getByText("Hours of the day…")); - await userEvent.click(screen.getByText("5 PM")); - await userEvent.click(screen.getByText("Add filter")); + expect(getNextFilterName()).toBe("Created At excludes the hour of 5 PM"); + }); - expect(getNextFilterName()).toBe("Created At excludes the hour of 5 PM"); + it("should remove an exclude date filter", async () => { + const { query, filter } = createQueryWithFilter( + defaultQuery, + stageIndex, + Lib.excludeDateFilterClause(defaultQuery, stageIndex, { + operator: "!=", + column, + values: [17], + bucket: "hour-of-day", + }), + ); + const { getNextFilterName } = setup({ + query, + stageIndex, + column, + filter, }); + expect(screen.getByText("Excludes 5 PM")).toBeInTheDocument(); - it("should remove an exclude date filter", async () => { - const { query, filter } = createQueryWithFilter( - defaultQuery, - stageIndex, - Lib.excludeDateFilterClause(defaultQuery, stageIndex, { - operator: "!=", - column, - values: [17], - bucket: "hour-of-day", - }), - ); - const { getNextFilterName } = setup({ - query, - stageIndex, - column, - filter, - }); - expect(screen.getByText("Excludes 5 PM")).toBeInTheDocument(); + await userEvent.click(screen.getByLabelText("Clear")); + expect(getNextFilterName()).toBe(null); + }); - await userEvent.click(screen.getByLabelText("Clear")); - expect(getNextFilterName()).toBe(null); + it("should not allow to set time for a date only column", async () => { + setup({ + query: defaultQuery, + stageIndex, + column: findColumn("PEOPLE", "BIRTH_DATE"), }); + + await userEvent.click(screen.getByLabelText("More options")); + await userEvent.click(screen.getByText("Specific dates…")); + await userEvent.click(screen.getByText("On")); + expect(screen.queryByText("Add time")).not.toBeInTheDocument(); + + await userEvent.click(screen.getByLabelText("Back")); + await userEvent.click(screen.getByText("Relative dates…")); + await userEvent.click(screen.getByDisplayValue("days")); + expect(screen.getByText("days")).toBeInTheDocument(); + expect(screen.queryByText("hours")).not.toBeInTheDocument(); + + await userEvent.click(screen.getByLabelText("Back")); + await userEvent.click(screen.getByText("Exclude…")); + expect(screen.getByText("Days of the week…")).toBeInTheDocument(); + expect(screen.queryByText("Hours of the day…")).not.toBeInTheDocument(); }); }); diff --git a/frontend/src/metabase/querying/filters/components/FilterPicker/DateFilterPicker/DateFilterPicker.unit.spec.tsx b/frontend/src/metabase/querying/filters/components/FilterPicker/DateFilterPicker/DateFilterPicker.unit.spec.tsx index 8e412a41b2d..dd821eb14b9 100644 --- a/frontend/src/metabase/querying/filters/components/FilterPicker/DateFilterPicker/DateFilterPicker.unit.spec.tsx +++ b/frontend/src/metabase/querying/filters/components/FilterPicker/DateFilterPicker/DateFilterPicker.unit.spec.tsx @@ -3,6 +3,7 @@ import userEvent from "@testing-library/user-event"; import { renderWithProviders, screen } from "__support__/ui"; import { checkNotNull } from "metabase/lib/types"; import * as Lib from "metabase-lib"; +import { columnFinder } from "metabase-lib/test-helpers"; import { createQuery, @@ -72,6 +73,10 @@ function setup({ query, column, filter, isNew = false }: SetupOpts) { describe("DateFilterPicker", () => { const initialQuery = createQuery(); + const findColumn = columnFinder( + initialQuery, + Lib.filterableColumns(initialQuery, -1), + ); const column = findDateTimeColumn(initialQuery); it("should add a filter via shortcut", async () => { @@ -221,4 +226,27 @@ describe("DateFilterPicker", () => { bucket: "day-of-week", }); }); + + it("should not allow to set time for a date only column", async () => { + setup({ + query: initialQuery, + column: findColumn("PEOPLE", "BIRTH_DATE"), + isNew: true, + }); + + await userEvent.click(screen.getByText("Specific dates…")); + await userEvent.click(screen.getByText("On")); + expect(screen.queryByText("Add time")).not.toBeInTheDocument(); + + await userEvent.click(screen.getByLabelText("Back")); + await userEvent.click(screen.getByText("Relative dates…")); + await userEvent.click(screen.getByDisplayValue("days")); + expect(screen.getByText("days")).toBeInTheDocument(); + expect(screen.queryByText("hours")).not.toBeInTheDocument(); + + await userEvent.click(screen.getByLabelText("Back")); + await userEvent.click(screen.getByText("Exclude…")); + expect(screen.getByText("Days of the week…")).toBeInTheDocument(); + expect(screen.queryByText("Hours of the day…")).not.toBeInTheDocument(); + }); }); diff --git a/frontend/src/metabase/querying/filters/components/FilterPicker/DateFilterPicker/SimpleDateFilterPicker/SimpleDateFilterPicker.tsx b/frontend/src/metabase/querying/filters/components/FilterPicker/DateFilterPicker/SimpleDateFilterPicker/SimpleDateFilterPicker.tsx index 2d50ca03af8..ef3aad4444d 100644 --- a/frontend/src/metabase/querying/filters/components/FilterPicker/DateFilterPicker/SimpleDateFilterPicker/SimpleDateFilterPicker.tsx +++ b/frontend/src/metabase/querying/filters/components/FilterPicker/DateFilterPicker/SimpleDateFilterPicker/SimpleDateFilterPicker.tsx @@ -18,12 +18,13 @@ export function SimpleDateFilterPicker({ filter, onChange, }: SimpleDateFilterPickerProps) { - const { value, availableOperators, getFilterClause } = useDateFilter({ - query, - stageIndex, - column, - filter, - }); + const { value, availableOperators, availableUnits, getFilterClause } = + useDateFilter({ + query, + stageIndex, + column, + filter, + }); const handleChange = (value: DatePickerValue | undefined) => { if (value) { @@ -38,6 +39,7 @@ export function SimpleDateFilterPicker({ <SimpleDatePicker value={value} availableOperators={availableOperators} + availableUnits={availableUnits} onChange={handleChange} /> </div> diff --git a/frontend/src/metabase/querying/filters/hooks/use-date-filter/utils.ts b/frontend/src/metabase/querying/filters/hooks/use-date-filter/utils.ts index 88ace709039..20e7e0c1d9b 100644 --- a/frontend/src/metabase/querying/filters/hooks/use-date-filter/utils.ts +++ b/frontend/src/metabase/querying/filters/hooks/use-date-filter/utils.ts @@ -1,14 +1,14 @@ import * as Lib from "metabase-lib"; import { - type DatePickerExtractionUnit, type DatePickerOperator, + type DatePickerUnit, type DatePickerValue, type ExcludeDatePickerValue, type RelativeDatePickerValue, type SpecificDatePickerValue, - isDatePickerExtractionUnit, isDatePickerOperator, + isDatePickerUnit, } from "../../components/DatePicker"; export function getPickerValue( @@ -163,8 +163,8 @@ export function getPickerUnits( query: Lib.Query, stageIndex: number, column: Lib.ColumnMetadata, -): DatePickerExtractionUnit[] { +): DatePickerUnit[] { return Lib.availableTemporalBuckets(query, stageIndex, column) .map(operator => Lib.displayInfo(query, stageIndex, operator).shortName) - .filter(isDatePickerExtractionUnit); + .filter(isDatePickerUnit); } -- GitLab