diff --git a/e2e/test/scenarios/dashboard-filters/dashboard-filters-sql-number.cy.spec.js b/e2e/test/scenarios/dashboard-filters/dashboard-filters-sql-number.cy.spec.js index 2b6ae6a10f13b6499dbef2525743ea76c5fd9126..5e6fec3e652c882e905eedb9d5ca9b20389397fb 100644 --- a/e2e/test/scenarios/dashboard-filters/dashboard-filters-sql-number.cy.spec.js +++ b/e2e/test/scenarios/dashboard-filters/dashboard-filters-sql-number.cy.spec.js @@ -5,8 +5,10 @@ import { filterWidget, editDashboard, saveDashboard, + getDashboardCard, setFilter, visitQuestion, + sidebar, visitDashboard, } from "e2e/support/helpers"; @@ -38,7 +40,7 @@ describe("scenarios > dashboard > filters > SQL > text/category", () => { setFilter("Number", filter); - cy.findByText("Select…").click(); + clickSelect(); popover().contains(filter).click(); }); @@ -60,15 +62,13 @@ describe("scenarios > dashboard > filters > SQL > text/category", () => { ); }); - it(`should work when set as the default filter`, () => { + it("should work when set as the default filter", () => { setFilter("Number", "Equal to"); - // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage - cy.findByText("Default value").next().click(); + sidebar().findByText("Default value").next().click(); addWidgetNumberFilter("3.8"); - // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage - cy.findByText("Select…").click(); + clickSelect(); popover().contains("Equal to").click(); saveDashboard(); @@ -90,3 +90,106 @@ describe("scenarios > dashboard > filters > SQL > text/category", () => { }); }); }); + +describe("scenarios > dashboard > filters > SQL > number", () => { + const questionDetails = { + name: "Question 1", + native: { + query: + "SELECT * from products where true [[ and price > {{price}}]] [[ and rating > {{rating}} ]] limit 5;", + "template-tags": { + price: { + type: "number", + name: "price", + id: "b22a5ce2-fe1d-44e3-8df4-f8951f7921bc", + "display-name": "Price", + }, + rating: { + type: "number", + name: "rating", + id: "68821a54-f0f3-4f09-8c32-6f7c0e5e5399", + "display-name": "Rating", + }, + }, + }, + }; + + const filterDetails = [ + { + name: "Rating", + slug: "rating", + id: "10c0d4ba", + type: "number/=", + sectionId: "number", + }, + { + name: "Price", + slug: "price", + id: "88b1a9dd", + type: "number/=", + sectionId: "number", + }, + ]; + + const parameterMapping = filterDetails.map(filter => ({ + parameter_id: filter.id, + target: ["variable", ["template-tag", filter.slug]], + })); + + const dashboardDetails = { + name: "Dashboard #31975", + parameters: filterDetails, + }; + const dashcardDetails = { + row: 0, + col: 0, + size_x: 16, + size_y: 8, + }; + + beforeEach(() => { + restore(); + cy.signInAsAdmin(); + + cy.createNativeQuestionAndDashboard({ + questionDetails, + dashboardDetails, + }).then(({ body: { id, card_id, dashboard_id } }) => { + cy.request("PUT", `/api/dashboard/${dashboard_id}/cards`, { + cards: [ + { + id, + card_id, + ...dashcardDetails, + parameter_mappings: parameterMapping.map(mapping => ({ + ...mapping, + card_id, + })), + }, + ], + }); + + visitDashboard(dashboard_id); + }); + }); + + it("should keep filter value on blur (metabase#31975)", () => { + cy.findByPlaceholderText("Price").type("95").blur(); + cy.findByPlaceholderText("Rating").type("3.8").blur(); + + cy.findAllByTestId("table-row") + .should("have.length", 2) + // first line price + .and("contain", "98.82") + // first line rating + .and("contain", "4.3") + // second line price + .and("contain", "95.93") + // second line rating + .and("contain", "4.4"); + }); +}); + +function clickSelect() { + getDashboardCard().findByText("Select…").click(); +} diff --git a/frontend/src/metabase/components/TextWidget/TextWidget.stories.tsx b/frontend/src/metabase/components/TextWidget/TextWidget.stories.tsx index 1be9dd131869c3259fdb112c1f780b335d4a27b0..c725110229b875d33e392f1aba92031871c35c76 100644 --- a/frontend/src/metabase/components/TextWidget/TextWidget.stories.tsx +++ b/frontend/src/metabase/components/TextWidget/TextWidget.stories.tsx @@ -1,6 +1,6 @@ import type { ComponentStory } from "@storybook/react"; import { useArgs } from "@storybook/client-api"; -import TextWidget from "./TextWidget"; +import { TextWidget } from "./TextWidget"; export default { title: "Parameters/TextWidget", diff --git a/frontend/src/metabase/components/TextWidget/TextWidget.tsx b/frontend/src/metabase/components/TextWidget/TextWidget.tsx index 58af004c1139e5e569228dd1560a8596718d544e..410c7d1498b4791df431326a6f8ab2aa79f3ee6f 100644 --- a/frontend/src/metabase/components/TextWidget/TextWidget.tsx +++ b/frontend/src/metabase/components/TextWidget/TextWidget.tsx @@ -2,7 +2,6 @@ import { Component } from "react"; import ReactDOM from "react-dom"; import { t } from "ttag"; import { forceRedraw } from "metabase/lib/dom"; -import { KEYCODE_ENTER, KEYCODE_ESCAPE } from "metabase/lib/keyboard"; type Props = { value: string | number; @@ -20,7 +19,7 @@ type State = { isFocused: boolean; }; -class TextWidget extends Component<Props, State> { +export class TextWidget extends Component<Props, State> { static defaultProps = { isEditing: false, commitImmediately: false, @@ -82,9 +81,9 @@ class TextWidget extends Component<Props, State> { }} onKeyUp={e => { const target = e.target as HTMLInputElement; - if (e.keyCode === KEYCODE_ESCAPE) { + if (e.key === "Escape") { target.blur(); - } else if (e.keyCode === KEYCODE_ENTER) { + } else if (e.key === "Enter") { setValue(this.state.value ?? null); target.blur(); } @@ -94,7 +93,9 @@ class TextWidget extends Component<Props, State> { }} onBlur={() => { changeFocus(false); - this.setState({ value: this.props.value }); + if (this.state.value !== this.props.value) { + setValue(this.state.value ?? null); + } }} placeholder={isEditing ? t`Enter a default value…` : defaultPlaceholder} disabled={disabled} @@ -102,6 +103,3 @@ class TextWidget extends Component<Props, State> { ); } } - -// eslint-disable-next-line import/no-default-export -- deprecated usage -export default TextWidget; diff --git a/frontend/src/metabase/components/TextWidget/TextWidget.unit.spec.tsx b/frontend/src/metabase/components/TextWidget/TextWidget.unit.spec.tsx index fbbbbede7fc815726c1eaea583450ec13b2743a9..76a9ac513d39931d31d9e596c58b40b073a26547 100644 --- a/frontend/src/metabase/components/TextWidget/TextWidget.unit.spec.tsx +++ b/frontend/src/metabase/components/TextWidget/TextWidget.unit.spec.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import { fireEvent, render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; -import TextWidget from "./TextWidget"; +import { TextWidget } from "./TextWidget"; const TextInputWithStateWrapper = ({ value }: { value?: number | string }) => { const [val, setVal] = useState<number | string | null>(value ?? ""); diff --git a/frontend/src/metabase/components/TextWidget/index.ts b/frontend/src/metabase/components/TextWidget/index.ts index 93b7d2dc3d3050c9f700538f3b95f177436c2c34..9ce758df73768fa985eca7ad6ef8c5e9d5ac134f 100644 --- a/frontend/src/metabase/components/TextWidget/index.ts +++ b/frontend/src/metabase/components/TextWidget/index.ts @@ -1,2 +1 @@ -// eslint-disable-next-line import/no-default-export -- deprecated usage -export { default } from "./TextWidget"; +export { TextWidget } from "./TextWidget"; diff --git a/frontend/src/metabase/dashboard/actions/parameters.js b/frontend/src/metabase/dashboard/actions/parameters.js index fc6dda3cc9be2610eeea00a3098eefd0079043c9..72b0a30193e1198b5397be9d88ebc228ab963bf8 100644 --- a/frontend/src/metabase/dashboard/actions/parameters.js +++ b/frontend/src/metabase/dashboard/actions/parameters.js @@ -189,6 +189,7 @@ export const setParameterValue = createThunkAction( SET_PARAMETER_VALUE, (parameterId, value) => (_dispatch, getState) => { const isSettingDraftParameterValues = !getIsAutoApplyFilters(getState()); + return { id: parameterId, value: normalizeValue(value), @@ -202,6 +203,10 @@ function normalizeValue(value) { return null; } + if (value === "") { + return null; + } + return value; } diff --git a/frontend/src/metabase/parameters/components/ParameterValueWidget.jsx b/frontend/src/metabase/parameters/components/ParameterValueWidget.jsx index 550cd43fc7b953e7f07783941822f4307d5b7f7c..7e7914d653b6c160441cb0e9fe08938fc85c7137 100644 --- a/frontend/src/metabase/parameters/components/ParameterValueWidget.jsx +++ b/frontend/src/metabase/parameters/components/ParameterValueWidget.jsx @@ -16,7 +16,7 @@ import DateRelativeWidget from "metabase/components/DateRelativeWidget"; import DateMonthYearWidget from "metabase/components/DateMonthYearWidget"; import DateQuarterYearWidget from "metabase/components/DateQuarterYearWidget"; import { DateAllOptionsWidget } from "metabase/components/DateAllOptionsWidget"; -import TextWidget from "metabase/components/TextWidget"; +import { TextWidget } from "metabase/components/TextWidget"; import WidgetStatusIcon from "metabase/parameters/components/WidgetStatusIcon"; import FormattedParameterValue from "metabase/parameters/components/FormattedParameterValue"; import NumberInputWidget from "metabase/parameters/components/widgets/NumberInputWidget";