diff --git a/frontend/src/metabase/modes/components/TimeseriesFilterWidget.jsx b/frontend/src/metabase/modes/components/TimeseriesFilterWidget.jsx index 1d00a9941d35d3c486448426b1234b67dcf9804f..d8ea217ceb03da75c8759a67082ef0c890835341 100644 --- a/frontend/src/metabase/modes/components/TimeseriesFilterWidget.jsx +++ b/frontend/src/metabase/modes/components/TimeseriesFilterWidget.jsx @@ -18,6 +18,7 @@ import _ from "underscore"; export default class TimeseriesFilterWidget extends Component { state = { + dimension: null, filter: null, filterIndex: -1, currentFilter: null, @@ -34,34 +35,34 @@ export default class TimeseriesFilterWidget extends Component { const filters = query.filters(); const dimensions = breakouts.map(b => b.dimension()); - const dimension = dimensions[0]; + const firstDimension = dimensions[0]; - const timeseriesDimension = - dimension instanceof FieldDimension - ? dimension.withoutTemporalBucketing() - : dimension; + const dimension = + firstDimension instanceof FieldDimension + ? firstDimension.withoutTemporalBucketing() + : firstDimension; const filterIndex = _.findIndex( filters, filter => Filter.isFieldFilter(filter) && - _.isEqual(filter[1], timeseriesDimension.mbql()), + _.isEqual(filter[1], dimension.mbql()), ); let filter, currentFilter; if (filterIndex >= 0) { filter = currentFilter = filters[filterIndex]; } else { - filter = ["time-interval", timeseriesDimension.mbql(), -30, "day"]; + filter = null; // All time } - this.setState({ filter, filterIndex, currentFilter }); + this.setState({ dimension, filter, filterIndex, currentFilter }); } } render() { const { className, card, setDatasetQuery } = this.props; - const { filter, filterIndex, currentFilter } = this.state; + const { dimension, filter, filterIndex, currentFilter } = this.state; let currentDescription; if (currentFilter) { @@ -94,7 +95,8 @@ export default class TimeseriesFilterWidget extends Component { > <DatePicker className="m2" - filter={this.state.filter} + dimension={dimension} + filter={filter} onFilterChange={newFilter => { this.setState({ filter: newFilter }); }} diff --git a/frontend/src/metabase/query_builder/components/filters/pickers/LegacyDatePicker/DatePicker.jsx b/frontend/src/metabase/query_builder/components/filters/pickers/LegacyDatePicker/DatePicker.jsx index 668c0b69374e073363b580277cf26e65d37d4b09..5e7500b24016b2718a944d422701d17c3440c396 100644 --- a/frontend/src/metabase/query_builder/components/filters/pickers/LegacyDatePicker/DatePicker.jsx +++ b/frontend/src/metabase/query_builder/components/filters/pickers/LegacyDatePicker/DatePicker.jsx @@ -281,7 +281,8 @@ export default class DatePicker extends Component { }; static propTypes = { - filter: PropTypes.array.isRequired, + dimension: PropTypes.array, + filter: PropTypes.array, onFilterChange: PropTypes.func.isRequired, className: PropTypes.string, hideEmptinessOperators: PropTypes.bool, @@ -296,28 +297,41 @@ export default class DatePicker extends Component { if (!this.props.hideEmptinessOperators) { operators = operators.concat(EMPTINESS_OPERATORS); } + if (this.props.includeAllTime) { + operators = [ALL_TIME_OPERATOR, ...operators]; + } - const operator = getOperator(this.props.filter, operators) || operators[0]; - this.props.onFilterChange(operator.init(this.props.filter)); + const { filter } = this.props; + const operator = getOperator(filter, operators) || operators[0]; + this.adjustFilter(operator); this.setState({ operators }); } + adjustFilter(operator, timeFilter = null) { + const { onFilterChange } = this.props; + const filter = timeFilter || this.props.filter; + if (onFilterChange) { + if (filter) { + onFilterChange(operator.init(filter)); + } else { + // from All Time (null filter) + const { dimension } = this.props; + onFilterChange(operator.init(["time-interval", dimension?.mbql()])); + } + } + } + render() { const { className, filter, onFilterChange, - includeAllTime, isSidebar, disableOperatorSelection, } = this.props; - let { operators } = this.state; - - if (includeAllTime) { - operators = [ALL_TIME_OPERATOR, ...operators]; - } + const { operators } = this.state; const operator = getOperator(this.props.filter, operators); const Widget = operator && operator.widget; @@ -339,7 +353,7 @@ export default class DatePicker extends Component { })} operator={operator && operator.name} operators={operators} - onOperatorChange={operator => onFilterChange(operator.init(filter))} + onOperatorChange={operator => this.adjustFilter(operator)} /> )} {Widget && ( @@ -350,7 +364,7 @@ export default class DatePicker extends Component { hideHoursAndMinutes={this.props.hideTimeSelectors} onFilterChange={filter => { if (operator && operator.init) { - onFilterChange(operator.init(filter)); + this.adjustFilter(operator, filter); } else { onFilterChange(filter); } diff --git a/frontend/test/metabase/scenarios/question/reproductions/22247-timeseries-filter-all-time.cy.spec.js b/frontend/test/metabase/scenarios/question/reproductions/22247-timeseries-filter-all-time.cy.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..ca265bd1111688da65879d99fb07fdadbff8ea32 --- /dev/null +++ b/frontend/test/metabase/scenarios/question/reproductions/22247-timeseries-filter-all-time.cy.spec.js @@ -0,0 +1,106 @@ +import { restore, popover, openProductsTable } from "__support__/e2e/cypress"; + +describe("time-series filter widget", () => { + beforeEach(() => { + cy.intercept("POST", "/api/dataset").as("dataset"); + + restore(); + cy.signInAsAdmin(); + openProductsTable(); + }); + + it("should properly display All Time as the initial filtering (metabase#22247)", () => { + cy.findAllByText("Summarize") + .first() + .click(); + cy.findAllByText("Created At") + .last() + .click(); + cy.wait("@dataset"); + cy.findByText("Done").click(); + + cy.findByText("All Time").click(); + popover().within(() => { + cy.findByText("Previous").should("not.exist"); + cy.findByText("Next").should("not.exist"); + + cy.findByTextEnsureVisible("All Time"); + cy.findByTextEnsureVisible("Apply"); + }); + }); + + it("should allow switching from All Time filter", () => { + cy.findAllByText("Summarize") + .first() + .click(); + cy.findAllByText("Created At") + .last() + .click(); + cy.wait("@dataset"); + cy.findByText("Done").click(); + + // switch to previous 30 quarters + cy.findByText("All Time").click(); + popover().within(() => { + cy.findByText("All Time").click(); + }); + cy.get(".List-item") + .contains("Previous") + .click(); + cy.findByTextEnsureVisible("days").click(); + cy.get(".List-item") + .contains("quarters") + .click(); + cy.button("Apply").click(); + cy.wait("@dataset"); + + cy.findByTextEnsureVisible("Created At Previous 30 Quarters"); + cy.findByTextEnsureVisible("Previous 30 Quarters"); + }); + + it("should stay in-sync with the actual filter", () => { + cy.findAllByText("Filter") + .first() + .click(); + cy.findAllByText("Created At") + .last() + .click(); + cy.findByText("Last 3 Months").click(); + cy.wait("@dataset"); + + cy.findByText("Created At Previous 3 Months").click(); + cy.findByText("months").click(); + cy.findByText("years").click(); + cy.button("Add filter").click(); + cy.wait("@dataset"); + + cy.findAllByText("Summarize") + .first() + .click(); + cy.findAllByText("Created At") + .last() + .click(); + cy.wait("@dataset"); + cy.findByText("Done").click(); + + cy.findByTextEnsureVisible("Created At Previous 3 Years"); + + cy.findByText("Previous 3 Years").click(); + popover().within(() => { + cy.findByText("Previous").should("be.visible"); + cy.findByText("All Time").should("not.exist"); + cy.findByText("Next").should("not.exist"); + }); + + // switch to All Time filter + popover().within(() => { + cy.findByText("Previous").click(); + }); + cy.findByText("All Time").click(); + cy.button("Apply").click(); + cy.wait("@dataset"); + + cy.findByText("Created At Previous 3 Years").should("not.exist"); + cy.findByTextEnsureVisible("All Time"); + }); +});