From 4cb8eb6165f72804563035933ad6d6d4f63e86fe Mon Sep 17 00:00:00 2001 From: Levko Kravets Date: Fri, 4 Sep 2020 13:01:05 +0300 Subject: [PATCH 1/2] getredash/redash#4944 Query pages: keep selected filters when switching visualizations --- .../visualizations/VisualizationRenderer.jsx | 39 ++++++++++++++----- .../components/QueryVisualizationTabs.jsx | 12 +++++- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/client/app/components/visualizations/VisualizationRenderer.jsx b/client/app/components/visualizations/VisualizationRenderer.jsx index 075af5a47f..e90aceff6f 100644 --- a/client/app/components/visualizations/VisualizationRenderer.jsx +++ b/client/app/components/visualizations/VisualizationRenderer.jsx @@ -1,7 +1,8 @@ -import { map, find } from "lodash"; +import { isEqual, map, find, fromPairs } from "lodash"; import React, { useState, useMemo, useEffect, useRef } from "react"; import PropTypes from "prop-types"; import useQueryResultData from "@/lib/useQueryResultData"; +import useImmutableCallback from "@/lib/hooks/useImmutableCallback"; import Filters, { FiltersType, filterData } from "@/components/Filters"; import { VisualizationType } from "@redash/viz/lib"; import { Renderer } from "@/components/visualizations/visualizationComponents"; @@ -24,23 +25,41 @@ function combineFilters(localFilters, globalFilters) { }); } +function areFiltersEqual(a, b) { + if (a.length !== b.length) { + return false; + } + + a = fromPairs(map(a, item => [item.name, item])); + b = fromPairs(map(b, item => [item.name, item])); + + return isEqual(a, b); +} + export default function VisualizationRenderer(props) { const data = useQueryResultData(props.queryResult); - const [filters, setFilters] = useState(data.filters); + const [filters, setFilters] = useState(() => combineFilters(data.filters, props.filters)); // lazy initialization const filtersRef = useRef(); filtersRef.current = filters; + const handleFiltersChange = useImmutableCallback(newFilters => { + if (!areFiltersEqual(newFilters, filters)) { + setFilters(newFilters); + props.onFiltersChange(newFilters); + } + }); + // Reset local filters when query results updated useEffect(() => { - setFilters(combineFilters(data.filters, props.filters)); - }, [data.filters, props.filters]); + handleFiltersChange(combineFilters(data.filters, props.filters)); + }, [data.filters, props.filters, handleFiltersChange]); // Update local filters when global filters changed. // For correct behavior need to watch only `props.filters` here, // therefore using ref to access current local filters useEffect(() => { - setFilters(combineFilters(filtersRef.current, props.filters)); - }, [props.filters]); + handleFiltersChange(combineFilters(filtersRef.current, props.filters)); + }, [props.filters, handleFiltersChange]); const filteredData = useMemo( () => ({ @@ -66,7 +85,7 @@ export default function VisualizationRenderer(props) { options={options} data={filteredData} visualizationName={visualization.name} - addonBefore={showFilters && } + addonBefore={showFilters && } /> ); } @@ -74,12 +93,14 @@ export default function VisualizationRenderer(props) { VisualizationRenderer.propTypes = { visualization: VisualizationType.isRequired, queryResult: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types - filters: FiltersType, showFilters: PropTypes.bool, + filters: FiltersType, + onFiltersChange: PropTypes.func, context: PropTypes.oneOf(["query", "widget"]).isRequired, }; VisualizationRenderer.defaultProps = { - filters: [], showFilters: true, + filters: [], + onFiltersChange: () => {}, }; diff --git a/client/app/pages/queries/components/QueryVisualizationTabs.jsx b/client/app/pages/queries/components/QueryVisualizationTabs.jsx index 4f63e4abae..d509fb8eb4 100644 --- a/client/app/pages/queries/components/QueryVisualizationTabs.jsx +++ b/client/app/pages/queries/components/QueryVisualizationTabs.jsx @@ -1,4 +1,4 @@ -import React, { useMemo, useCallback } from "react"; +import React, { useState, useMemo, useCallback } from "react"; import PropTypes from "prop-types"; import cx from "classnames"; import { find, orderBy } from "lodash"; @@ -120,6 +120,8 @@ export default function QueryVisualizationTabs({ const isFirstVisualization = useCallback(visId => visId === orderedVisualizations[0].id, [orderedVisualizations]); const isMobile = useMedia({ maxWidth: 768 }); + const [filters, setFilters] = useState([]); + return ( }> {queryResult ? ( - + ) : ( Date: Fri, 4 Sep 2020 13:31:18 +0300 Subject: [PATCH 2/2] Pass current filters to expanded widget modal --- .../components/dashboards/ExpandedWidgetDialog.jsx | 9 ++++++++- .../dashboard-widget/VisualizationWidget.jsx | 12 ++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/client/app/components/dashboards/ExpandedWidgetDialog.jsx b/client/app/components/dashboards/ExpandedWidgetDialog.jsx index ea1bff2732..f83f3508fb 100644 --- a/client/app/components/dashboards/ExpandedWidgetDialog.jsx +++ b/client/app/components/dashboards/ExpandedWidgetDialog.jsx @@ -3,10 +3,11 @@ import PropTypes from "prop-types"; import Button from "antd/lib/button"; import Modal from "antd/lib/modal"; import { wrap as wrapDialog, DialogPropType } from "@/components/DialogWrapper"; +import { FiltersType } from "@/components/Filters"; import VisualizationRenderer from "@/components/visualizations/VisualizationRenderer"; import VisualizationName from "@/components/visualizations/VisualizationName"; -function ExpandedWidgetDialog({ dialog, widget }) { +function ExpandedWidgetDialog({ dialog, widget, filters }) { return ( @@ -29,6 +31,11 @@ function ExpandedWidgetDialog({ dialog, widget }) { ExpandedWidgetDialog.propTypes = { dialog: DialogPropType.isRequired, widget: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types + filters: FiltersType, +}; + +ExpandedWidgetDialog.defaultProps = { + filters: [], }; export default wrapDialog(ExpandedWidgetDialog); diff --git a/client/app/components/dashboards/dashboard-widget/VisualizationWidget.jsx b/client/app/components/dashboards/dashboard-widget/VisualizationWidget.jsx index 2dc3070546..0f3c8363fd 100644 --- a/client/app/components/dashboards/dashboard-widget/VisualizationWidget.jsx +++ b/client/app/components/dashboards/dashboard-widget/VisualizationWidget.jsx @@ -209,7 +209,10 @@ class VisualizationWidget extends React.Component { constructor(props) { super(props); - this.state = { localParameters: props.widget.getLocalParameters() }; + this.state = { + localParameters: props.widget.getLocalParameters(), + localFilters: props.filters, + }; } componentDidMount() { @@ -219,8 +222,12 @@ class VisualizationWidget extends React.Component { onLoad(); } + onLocalFiltersChange = localFilters => { + this.setState({ localFilters }); + }; + expandWidget = () => { - ExpandedWidgetDialog.showModal({ widget: this.props.widget }); + ExpandedWidgetDialog.showModal({ widget: this.props.widget, filters: this.state.localFilters }); }; editParameterMappings = () => { @@ -260,6 +267,7 @@ class VisualizationWidget extends React.Component { visualization={widget.visualization} queryResult={widgetQueryResult} filters={filters} + onFiltersChange={this.onLocalFiltersChange} context="widget" />