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"
/>
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 ? (
-
+
) : (