From f43a15076d26c02681d0a4579c4d703c7ea6b9ca Mon Sep 17 00:00:00 2001 From: Nemanja Glumac <31325167+nemanjaglumac@users.noreply.github.com> Date: Tue, 25 Jun 2024 11:17:18 +0200 Subject: [PATCH] Refactor: Time-series chrome component (#44568) * Move SimpleSpecificDatePicker to the form * Extract `isSpecificValue` helper * Integrate the toggle into `SimpleDateIntervalPicker` * Make DateOperatorPicker do only one thing Separation of concerns - lift SimpleRelativeDatePicker up to the form. * Fix unit tests * Add back the test for the `SimpleSpecificDatePicker` * Keep the rendering logic in the `SimpleDatePicker` --- .../DateOperatorPicker/DateOperatorPicker.tsx | 33 ++--- .../DateOperatorPicker.unit.spec.tsx | 119 ------------------ .../SimpleDateIntervalPicker.tsx | 32 ++--- .../SimpleDateIntervalPicker.unit.spec.tsx | 47 +++++++ .../SimpleRelativeDatePicker.tsx | 20 --- .../SimpleRelativeDatePicker/index.ts | 1 - .../DatePicker/RelativeDatePicker/index.ts | 1 - .../SimpleDatePicker/SimpleDatePicker.tsx | 14 +++ .../SimpleDatePicker.unit.spec.tsx | 36 ++++++ .../SimpleSpecificDatePicker.unit.spec.tsx | 41 ++++++ .../DatePicker/SpecificDatePicker/utils.ts | 7 ++ 11 files changed, 172 insertions(+), 179 deletions(-) delete mode 100644 frontend/src/metabase/querying/components/DatePicker/RelativeDatePicker/SimpleRelativeDatePicker/SimpleRelativeDatePicker.tsx delete mode 100644 frontend/src/metabase/querying/components/DatePicker/RelativeDatePicker/SimpleRelativeDatePicker/index.ts create mode 100644 frontend/src/metabase/querying/components/DatePicker/SpecificDatePicker/SimpleSpecificDatePicker/SimpleSpecificDatePicker.unit.spec.tsx diff --git a/frontend/src/metabase/querying/components/DatePicker/DateOperatorPicker/DateOperatorPicker.tsx b/frontend/src/metabase/querying/components/DatePicker/DateOperatorPicker/DateOperatorPicker.tsx index f3a24b60e3e..4055cf49f3d 100644 --- a/frontend/src/metabase/querying/components/DatePicker/DateOperatorPicker/DateOperatorPicker.tsx +++ b/frontend/src/metabase/querying/components/DatePicker/DateOperatorPicker/DateOperatorPicker.tsx @@ -1,11 +1,7 @@ import { useMemo } from "react"; -import { Stack, Select } from "metabase/ui"; +import { Select } from "metabase/ui"; -import { SimpleRelativeDatePicker } from "../RelativeDatePicker"; -import { IncludeCurrentSwitch } from "../RelativeDatePicker/IncludeCurrentSwitch"; -import { isIntervalValue, isRelativeValue } from "../RelativeDatePicker/utils"; -import { SimpleSpecificDatePicker } from "../SpecificDatePicker"; import type { DatePickerOperator, DatePickerValue } from "../types"; import { getAvailableOptions, getOptionType, setOptionType } from "./utils"; @@ -37,24 +33,13 @@ export function DateOperatorPicker({ }; return ( - <Stack> - <Select - data={options} - value={optionType} - onChange={handleChange} - style={{ - flex: 1, - }} - /> - {isRelativeValue(value) && ( - <SimpleRelativeDatePicker value={value} onChange={onChange} /> - )} - {isRelativeValue(value) && isIntervalValue(value) && ( - <IncludeCurrentSwitch value={value} onChange={onChange} /> - )} - {value?.type === "specific" && ( - <SimpleSpecificDatePicker value={value} onChange={onChange} /> - )} - </Stack> + <Select + data={options} + value={optionType} + onChange={handleChange} + style={{ + flex: 1, + }} + /> ); } diff --git a/frontend/src/metabase/querying/components/DatePicker/DateOperatorPicker/DateOperatorPicker.unit.spec.tsx b/frontend/src/metabase/querying/components/DatePicker/DateOperatorPicker/DateOperatorPicker.unit.spec.tsx index f0b99d7fe52..ad5ad59824d 100644 --- a/frontend/src/metabase/querying/components/DatePicker/DateOperatorPicker/DateOperatorPicker.unit.spec.tsx +++ b/frontend/src/metabase/querying/components/DatePicker/DateOperatorPicker/DateOperatorPicker.unit.spec.tsx @@ -42,123 +42,4 @@ describe("DateOperatorPicker", () => { unit: "day", }); }); - - it("should be able to change a specific date value", async () => { - const { onChange } = setup({ - value: { - type: "specific", - operator: "=", - values: [new Date(2015, 1, 10)], - }, - }); - - await userEvent.click(screen.getByText("15")); - - expect(onChange).toHaveBeenCalledWith({ - type: "specific", - operator: "=", - values: [new Date(2015, 1, 15)], - }); - }); - - it("should be able to change a relative date value", async () => { - const { onChange } = setup({ - value: { - type: "relative", - value: 1, - unit: "month", - }, - }); - - await userEvent.type(screen.getByLabelText("Interval"), "2"); - - expect(onChange).toHaveBeenCalledWith({ - type: "relative", - value: 12, - unit: "month", - }); - }); - - describe("Include current switch", () => { - it("should not show 'Include current' switch by default", async () => { - setup(); - - await userEvent.click(screen.getByDisplayValue("All time")); - expect(screen.queryByLabelText(/^Include/)).not.toBeInTheDocument(); - }); - - it("should not show 'Include current' switch by for the specific date value", async () => { - setup(); - - await userEvent.click(screen.getByDisplayValue("All time")); - await userEvent.click(screen.getByText("Between")); - expect(screen.queryByLabelText(/^Include/)).not.toBeInTheDocument(); - }); - - it("should not show 'Include current' switch by for the `Current` relative date", async () => { - setup(); - - await userEvent.click(screen.getByDisplayValue("All time")); - await userEvent.click(screen.getByText("Current")); - expect(screen.queryByLabelText(/^Include/)).not.toBeInTheDocument(); - }); - - it("should show 'Include current' switch and work for the relative `Previous` or `Next`", async () => { - setup({ - value: { - type: "relative", - value: 1, - unit: "month", - }, - }); - - expect(screen.getByDisplayValue("Next")).toBeInTheDocument(); - expect(screen.getByLabelText("Include this month")).toBeInTheDocument(); - }); - - it("should turn the 'Include current' switch on and register that change", async () => { - const { onChange } = setup({ - value: { - type: "relative", - value: 1, - unit: "month", - }, - }); - - await userEvent.click(screen.getByLabelText("Include this month")); - - expect(onChange).toHaveBeenCalledWith({ - options: { - "include-current": true, - }, - type: "relative", - value: 1, - unit: "month", - }); - }); - - it("should turn the 'Include current' switch off and register that change", async () => { - const { onChange } = setup({ - value: { - options: { - "include-current": true, - }, - type: "relative", - value: 1, - unit: "month", - }, - }); - - await userEvent.click(screen.getByLabelText("Include this month")); - - expect(onChange).toHaveBeenCalledWith({ - options: { - "include-current": false, - }, - type: "relative", - value: 1, - unit: "month", - }); - }); - }); }); diff --git a/frontend/src/metabase/querying/components/DatePicker/RelativeDatePicker/DateIntervalPicker/SimpleDateIntervalPicker/SimpleDateIntervalPicker.tsx b/frontend/src/metabase/querying/components/DatePicker/RelativeDatePicker/DateIntervalPicker/SimpleDateIntervalPicker/SimpleDateIntervalPicker.tsx index 873046bcfc0..8603e99fa1c 100644 --- a/frontend/src/metabase/querying/components/DatePicker/RelativeDatePicker/DateIntervalPicker/SimpleDateIntervalPicker/SimpleDateIntervalPicker.tsx +++ b/frontend/src/metabase/querying/components/DatePicker/RelativeDatePicker/DateIntervalPicker/SimpleDateIntervalPicker/SimpleDateIntervalPicker.tsx @@ -2,6 +2,7 @@ import { t } from "ttag"; import { Group, NumberInput, Select } from "metabase/ui"; +import { IncludeCurrentSwitch } from "../../IncludeCurrentSwitch"; import type { DateIntervalValue } from "../../types"; import { getInterval, getUnitOptions, setInterval } from "../../utils"; import { setUnit } from "../utils"; @@ -32,19 +33,22 @@ export function SimpleDateIntervalPicker({ }; return ( - <Group> - <NumberInput - value={interval} - aria-label={t`Interval`} - w="4rem" - onChange={handleIntervalChange} - /> - <Select - data={unitOptions} - value={value.unit} - aria-label={t`Unit`} - onChange={handleUnitChange} - /> - </Group> + <> + <Group> + <NumberInput + value={interval} + aria-label={t`Interval`} + w="4rem" + onChange={handleIntervalChange} + /> + <Select + data={unitOptions} + value={value.unit} + aria-label={t`Unit`} + onChange={handleUnitChange} + /> + </Group> + <IncludeCurrentSwitch value={value} onChange={onChange} /> + </> ); } diff --git a/frontend/src/metabase/querying/components/DatePicker/RelativeDatePicker/DateIntervalPicker/SimpleDateIntervalPicker/SimpleDateIntervalPicker.unit.spec.tsx b/frontend/src/metabase/querying/components/DatePicker/RelativeDatePicker/DateIntervalPicker/SimpleDateIntervalPicker/SimpleDateIntervalPicker.unit.spec.tsx index b438a2e685e..6486018db97 100644 --- a/frontend/src/metabase/querying/components/DatePicker/RelativeDatePicker/DateIntervalPicker/SimpleDateIntervalPicker/SimpleDateIntervalPicker.unit.spec.tsx +++ b/frontend/src/metabase/querying/components/DatePicker/RelativeDatePicker/DateIntervalPicker/SimpleDateIntervalPicker/SimpleDateIntervalPicker.unit.spec.tsx @@ -132,4 +132,51 @@ describe("SimpleDateIntervalPicker", () => { }); }, ); + + describe("Include current switch", () => { + it("should turn the 'Include current' switch on and register that change", async () => { + const { onChange } = setup({ + value: { + type: "relative", + value: 1, + unit: "month", + }, + }); + + await userEvent.click(screen.getByLabelText("Include this month")); + + expect(onChange).toHaveBeenCalledWith({ + options: { + "include-current": true, + }, + type: "relative", + value: 1, + unit: "month", + }); + }); + + it("should turn the 'Include current' switch off and register that change", async () => { + const { onChange } = setup({ + value: { + options: { + "include-current": true, + }, + type: "relative", + value: 1, + unit: "month", + }, + }); + + await userEvent.click(screen.getByLabelText("Include this month")); + + expect(onChange).toHaveBeenCalledWith({ + options: { + "include-current": false, + }, + type: "relative", + value: 1, + unit: "month", + }); + }); + }); }); diff --git a/frontend/src/metabase/querying/components/DatePicker/RelativeDatePicker/SimpleRelativeDatePicker/SimpleRelativeDatePicker.tsx b/frontend/src/metabase/querying/components/DatePicker/RelativeDatePicker/SimpleRelativeDatePicker/SimpleRelativeDatePicker.tsx deleted file mode 100644 index 394a37cefb8..00000000000 --- a/frontend/src/metabase/querying/components/DatePicker/RelativeDatePicker/SimpleRelativeDatePicker/SimpleRelativeDatePicker.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import type { RelativeDatePickerValue } from "../../types"; -import { CurrentDatePicker } from "../CurrentDatePicker"; -import { SimpleDateIntervalPicker } from "../DateIntervalPicker"; -import { isIntervalValue } from "../utils"; - -interface SimpleRelativeDatePickerProps { - value: RelativeDatePickerValue; - onChange: (value: RelativeDatePickerValue) => void; -} - -export function SimpleRelativeDatePicker({ - value, - onChange, -}: SimpleRelativeDatePickerProps) { - return isIntervalValue(value) ? ( - <SimpleDateIntervalPicker value={value} onChange={onChange} /> - ) : ( - <CurrentDatePicker value={value} onChange={onChange} /> - ); -} diff --git a/frontend/src/metabase/querying/components/DatePicker/RelativeDatePicker/SimpleRelativeDatePicker/index.ts b/frontend/src/metabase/querying/components/DatePicker/RelativeDatePicker/SimpleRelativeDatePicker/index.ts deleted file mode 100644 index 05933eeb45b..00000000000 --- a/frontend/src/metabase/querying/components/DatePicker/RelativeDatePicker/SimpleRelativeDatePicker/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./SimpleRelativeDatePicker"; diff --git a/frontend/src/metabase/querying/components/DatePicker/RelativeDatePicker/index.ts b/frontend/src/metabase/querying/components/DatePicker/RelativeDatePicker/index.ts index fe4844a2d02..0ffc957112a 100644 --- a/frontend/src/metabase/querying/components/DatePicker/RelativeDatePicker/index.ts +++ b/frontend/src/metabase/querying/components/DatePicker/RelativeDatePicker/index.ts @@ -1,2 +1 @@ export * from "./RelativeDatePicker"; -export * from "./SimpleRelativeDatePicker"; diff --git a/frontend/src/metabase/querying/components/DatePicker/SimpleDatePicker/SimpleDatePicker.tsx b/frontend/src/metabase/querying/components/DatePicker/SimpleDatePicker/SimpleDatePicker.tsx index 7db8d57aabd..87bd3bbfbd5 100644 --- a/frontend/src/metabase/querying/components/DatePicker/SimpleDatePicker/SimpleDatePicker.tsx +++ b/frontend/src/metabase/querying/components/DatePicker/SimpleDatePicker/SimpleDatePicker.tsx @@ -5,6 +5,11 @@ import { t } from "ttag"; import { Button, Stack } from "metabase/ui"; import { DateOperatorPicker } from "../DateOperatorPicker"; +import { CurrentDatePicker } from "../RelativeDatePicker/CurrentDatePicker"; +import { SimpleDateIntervalPicker } from "../RelativeDatePicker/DateIntervalPicker"; +import { isIntervalValue, isRelativeValue } from "../RelativeDatePicker/utils"; +import { SimpleSpecificDatePicker } from "../SpecificDatePicker"; +import { isSpecificValue } from "../SpecificDatePicker/utils"; import { DATE_PICKER_OPERATORS } from "../constants"; import type { DatePickerOperator, DatePickerValue } from "../types"; @@ -34,6 +39,15 @@ export function SimpleDatePicker({ availableOperators={availableOperators} onChange={setValue} /> + {isRelativeValue(value) && isIntervalValue(value) && ( + <SimpleDateIntervalPicker value={value} onChange={setValue} /> + )} + {isRelativeValue(value) && !isIntervalValue(value) && ( + <CurrentDatePicker value={value} onChange={setValue} /> + )} + {isSpecificValue(value) && ( + <SimpleSpecificDatePicker value={value} onChange={setValue} /> + )} <Button type="submit" variant="filled">{t`Apply`}</Button> </Stack> </form> diff --git a/frontend/src/metabase/querying/components/DatePicker/SimpleDatePicker/SimpleDatePicker.unit.spec.tsx b/frontend/src/metabase/querying/components/DatePicker/SimpleDatePicker/SimpleDatePicker.unit.spec.tsx index a7a062afb06..790b1d015f6 100644 --- a/frontend/src/metabase/querying/components/DatePicker/SimpleDatePicker/SimpleDatePicker.unit.spec.tsx +++ b/frontend/src/metabase/querying/components/DatePicker/SimpleDatePicker/SimpleDatePicker.unit.spec.tsx @@ -45,4 +45,40 @@ describe("SimpleDatePicker", () => { unit: "month", }); }); + + it("should not show 'Include current' switch by default", async () => { + setup(); + + await userEvent.click(screen.getByDisplayValue("All time")); + expect(screen.queryByLabelText(/^Include/)).not.toBeInTheDocument(); + }); + + it("should not show 'Include current' switch by for the specific date value", async () => { + setup(); + + await userEvent.click(screen.getByDisplayValue("All time")); + await userEvent.click(screen.getByText("Between")); + expect(screen.queryByLabelText(/^Include/)).not.toBeInTheDocument(); + }); + + it("should not show 'Include current' switch by for the `Current` relative date", async () => { + setup(); + + await userEvent.click(screen.getByDisplayValue("All time")); + await userEvent.click(screen.getByText("Current")); + expect(screen.queryByLabelText(/^Include/)).not.toBeInTheDocument(); + }); + + it("should show 'Include current' switch and work for the relative `Previous` or `Next`", async () => { + setup({ + value: { + type: "relative", + value: 1, + unit: "month", + }, + }); + + expect(screen.getByDisplayValue("Next")).toBeInTheDocument(); + expect(screen.getByLabelText("Include this month")).toBeInTheDocument(); + }); }); diff --git a/frontend/src/metabase/querying/components/DatePicker/SpecificDatePicker/SimpleSpecificDatePicker/SimpleSpecificDatePicker.unit.spec.tsx b/frontend/src/metabase/querying/components/DatePicker/SpecificDatePicker/SimpleSpecificDatePicker/SimpleSpecificDatePicker.unit.spec.tsx new file mode 100644 index 00000000000..f192e4281b2 --- /dev/null +++ b/frontend/src/metabase/querying/components/DatePicker/SpecificDatePicker/SimpleSpecificDatePicker/SimpleSpecificDatePicker.unit.spec.tsx @@ -0,0 +1,41 @@ +import userEvent from "@testing-library/user-event"; + +import { renderWithProviders, screen } from "__support__/ui"; + +import type { SpecificDatePickerValue } from "../../types"; + +import { SimpleSpecificDatePicker } from "./SimpleSpecificDatePicker"; + +interface SetupOpts { + value: SpecificDatePickerValue; +} + +function setup({ value }: SetupOpts) { + const onChange = jest.fn(); + + renderWithProviders( + <SimpleSpecificDatePicker value={value} onChange={onChange} />, + ); + + return { onChange }; +} + +describe("SimpleSpecificDatePicker", () => { + it("should be able to change a specific date value", async () => { + const { onChange } = setup({ + value: { + type: "specific", + operator: "=", + values: [new Date(2015, 1, 10)], + }, + }); + + await userEvent.click(screen.getByText("15")); + + expect(onChange).toHaveBeenCalledWith({ + type: "specific", + operator: "=", + values: [new Date(2015, 1, 15)], + }); + }); +}); diff --git a/frontend/src/metabase/querying/components/DatePicker/SpecificDatePicker/utils.ts b/frontend/src/metabase/querying/components/DatePicker/SpecificDatePicker/utils.ts index d50dfef7ed9..eb5c24ddeec 100644 --- a/frontend/src/metabase/querying/components/DatePicker/SpecificDatePicker/utils.ts +++ b/frontend/src/metabase/querying/components/DatePicker/SpecificDatePicker/utils.ts @@ -2,6 +2,7 @@ import dayjs from "dayjs"; import type { DatePickerOperator, + DatePickerValue, SpecificDatePickerOperator, SpecificDatePickerValue, } from "../types"; @@ -9,6 +10,12 @@ import type { import { TABS } from "./constants"; import type { Tab } from "./types"; +export function isSpecificValue( + value?: DatePickerValue, +): value is SpecificDatePickerValue { + return value?.type === "specific"; +} + export function getTabs( availableOperators: ReadonlyArray<DatePickerOperator>, ): Tab[] { -- GitLab