Skip to content
Snippets Groups Projects
Unverified Commit f43a1507 authored by Nemanja Glumac's avatar Nemanja Glumac Committed by GitHub
Browse files

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`
parent 3fcc0f6e
No related branches found
No related tags found
No related merge requests found
Showing
with 172 additions and 179 deletions
import { useMemo } from "react"; 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 type { DatePickerOperator, DatePickerValue } from "../types";
import { getAvailableOptions, getOptionType, setOptionType } from "./utils"; import { getAvailableOptions, getOptionType, setOptionType } from "./utils";
...@@ -37,24 +33,13 @@ export function DateOperatorPicker({ ...@@ -37,24 +33,13 @@ export function DateOperatorPicker({
}; };
return ( return (
<Stack> <Select
<Select data={options}
data={options} value={optionType}
value={optionType} onChange={handleChange}
onChange={handleChange} style={{
style={{ flex: 1,
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>
); );
} }
...@@ -42,123 +42,4 @@ describe("DateOperatorPicker", () => { ...@@ -42,123 +42,4 @@ describe("DateOperatorPicker", () => {
unit: "day", 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",
});
});
});
}); });
...@@ -2,6 +2,7 @@ import { t } from "ttag"; ...@@ -2,6 +2,7 @@ import { t } from "ttag";
import { Group, NumberInput, Select } from "metabase/ui"; import { Group, NumberInput, Select } from "metabase/ui";
import { IncludeCurrentSwitch } from "../../IncludeCurrentSwitch";
import type { DateIntervalValue } from "../../types"; import type { DateIntervalValue } from "../../types";
import { getInterval, getUnitOptions, setInterval } from "../../utils"; import { getInterval, getUnitOptions, setInterval } from "../../utils";
import { setUnit } from "../utils"; import { setUnit } from "../utils";
...@@ -32,19 +33,22 @@ export function SimpleDateIntervalPicker({ ...@@ -32,19 +33,22 @@ export function SimpleDateIntervalPicker({
}; };
return ( return (
<Group> <>
<NumberInput <Group>
value={interval} <NumberInput
aria-label={t`Interval`} value={interval}
w="4rem" aria-label={t`Interval`}
onChange={handleIntervalChange} w="4rem"
/> onChange={handleIntervalChange}
<Select />
data={unitOptions} <Select
value={value.unit} data={unitOptions}
aria-label={t`Unit`} value={value.unit}
onChange={handleUnitChange} aria-label={t`Unit`}
/> onChange={handleUnitChange}
</Group> />
</Group>
<IncludeCurrentSwitch value={value} onChange={onChange} />
</>
); );
} }
...@@ -132,4 +132,51 @@ describe("SimpleDateIntervalPicker", () => { ...@@ -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",
});
});
});
}); });
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} />
);
}
export * from "./SimpleRelativeDatePicker";
export * from "./RelativeDatePicker"; export * from "./RelativeDatePicker";
export * from "./SimpleRelativeDatePicker";
...@@ -5,6 +5,11 @@ import { t } from "ttag"; ...@@ -5,6 +5,11 @@ import { t } from "ttag";
import { Button, Stack } from "metabase/ui"; import { Button, Stack } from "metabase/ui";
import { DateOperatorPicker } from "../DateOperatorPicker"; 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 { DATE_PICKER_OPERATORS } from "../constants";
import type { DatePickerOperator, DatePickerValue } from "../types"; import type { DatePickerOperator, DatePickerValue } from "../types";
...@@ -34,6 +39,15 @@ export function SimpleDatePicker({ ...@@ -34,6 +39,15 @@ export function SimpleDatePicker({
availableOperators={availableOperators} availableOperators={availableOperators}
onChange={setValue} 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> <Button type="submit" variant="filled">{t`Apply`}</Button>
</Stack> </Stack>
</form> </form>
......
...@@ -45,4 +45,40 @@ describe("SimpleDatePicker", () => { ...@@ -45,4 +45,40 @@ describe("SimpleDatePicker", () => {
unit: "month", 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();
});
}); });
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)],
});
});
});
...@@ -2,6 +2,7 @@ import dayjs from "dayjs"; ...@@ -2,6 +2,7 @@ import dayjs from "dayjs";
import type { import type {
DatePickerOperator, DatePickerOperator,
DatePickerValue,
SpecificDatePickerOperator, SpecificDatePickerOperator,
SpecificDatePickerValue, SpecificDatePickerValue,
} from "../types"; } from "../types";
...@@ -9,6 +10,12 @@ import type { ...@@ -9,6 +10,12 @@ import type {
import { TABS } from "./constants"; import { TABS } from "./constants";
import type { Tab } from "./types"; import type { Tab } from "./types";
export function isSpecificValue(
value?: DatePickerValue,
): value is SpecificDatePickerValue {
return value?.type === "specific";
}
export function getTabs( export function getTabs(
availableOperators: ReadonlyArray<DatePickerOperator>, availableOperators: ReadonlyArray<DatePickerOperator>,
): Tab[] { ): Tab[] {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment