Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Visualizations customizable settings #4793

Merged
merged 14 commits into from
Apr 25, 2020
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/app/components/QueryLink.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from "react";
import PropTypes from "prop-types";
import { VisualizationType } from "@/visualizations/prop-types";
import VisualizationName from "@/visualizations/components/VisualizationName";
import VisualizationName from "@/components/visualizations/VisualizationName";

import "./QueryLink.less";

Expand Down
4 changes: 2 additions & 2 deletions client/app/components/dashboards/ExpandedWidgetDialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ 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 VisualizationRenderer from "@/visualizations/components/VisualizationRenderer";
import VisualizationName from "@/visualizations/components/VisualizationName";
import VisualizationRenderer from "@/components/visualizations/VisualizationRenderer";
import VisualizationName from "@/components/visualizations/VisualizationName";

function ExpandedWidgetDialog({ dialog, widget }) {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import QueryLink from "@/components/QueryLink";
import { FiltersType } from "@/components/Filters";
import ExpandedWidgetDialog from "@/components/dashboards/ExpandedWidgetDialog";
import EditParameterMappingsDialog from "@/components/dashboards/EditParameterMappingsDialog";
import VisualizationRenderer from "@/visualizations/components/VisualizationRenderer";
import VisualizationRenderer from "@/components/visualizations/VisualizationRenderer";
import Widget from "./Widget";

function visualizationWidgetMenuOptions({ widget, canEditDashboard, onParametersEdit }) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import Modal from "antd/lib/modal";
import Select from "antd/lib/select";
import Input from "antd/lib/input";
import { wrap as wrapDialog, DialogPropType } from "@/components/DialogWrapper";
import ErrorBoundary, { ErrorMessage } from "@/components/ErrorBoundary";
import Filters, { filterData } from "@/components/Filters";
import notification from "@/services/notification";
import Visualization from "@/services/visualization";
import recordEvent from "@/services/recordEvent";
import getQueryResultData from "@/lib/getQueryResultData";
import { VisualizationType } from "@/visualizations/prop-types";
import { Renderer, Editor } from "@/components/visualizations/visualizationComponents";
import registeredVisualizations, { getDefaultVisualization, newVisualization } from "@/visualizations";

import "./EditVisualizationDialog.less";
Expand Down Expand Up @@ -146,8 +146,6 @@ function EditVisualizationDialog({ dialog, visualization, query, queryResult })
confirmDialogClose(nameChanged || optionsChanged).then(dialog.dismiss);
}

const { Renderer, Editor } = registeredVisualizations[type];

// When editing existing visualization chart type selector is disabled, so add only existing visualization's
// descriptor there (to properly render the component). For new visualizations show all types except of deprecated
const availableVisualizations = isNew
Expand Down Expand Up @@ -196,7 +194,13 @@ function EditVisualizationDialog({ dialog, visualization, query, queryResult })
/>
</div>
<div data-test="VisualizationEditor">
<Editor data={data} options={options} visualizationName={name} onOptionsChange={onOptionsChanged} />
<Editor
type={type}
data={data}
options={options}
visualizationName={name}
onOptionsChange={onOptionsChanged}
/>
</div>
</div>
<div className="visualization-preview">
Expand All @@ -205,17 +209,14 @@ function EditVisualizationDialog({ dialog, visualization, query, queryResult })
</label>
<Filters filters={filters} onChange={setFilters} />
<div className="scrollbox" data-test="VisualizationPreview">
<ErrorBoundary
ref={errorHandlerRef}
renderError={() => <ErrorMessage>Error while rendering visualization.</ErrorMessage>}>
<Renderer
data={filteredData}
options={options}
visualizationName={name}
onOptionsChange={onOptionsChanged}
context="query"
/>
</ErrorBoundary>
<Renderer
type={type}
data={filteredData}
options={options}
visualizationName={name}
onOptionsChange={onOptionsChanged}
context="query"
/>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { isEqual, map, find } from "lodash";
import { map, find } from "lodash";
import React, { useState, useMemo, useEffect, useRef } from "react";
import PropTypes from "prop-types";
import getQueryResultData from "@/lib/getQueryResultData";
import ErrorBoundary, { ErrorMessage } from "@/components/ErrorBoundary";
import Filters, { FiltersType, filterData } from "@/components/Filters";
import { VisualizationType } from "@/visualizations/prop-types";
import registeredVisualizations from "@/visualizations";
import { Renderer } from "@/components/visualizations/visualizationComponents";

function combineFilters(localFilters, globalFilters) {
// tiny optimization - to avoid unnecessary updates
Expand All @@ -31,9 +30,6 @@ export default function VisualizationRenderer(props) {
const filtersRef = useRef();
filtersRef.current = filters;

const lastOptions = useRef();
const errorHandlerRef = useRef();

// Reset local filters when query results updated
useEffect(() => {
setFilters(combineFilters(data.filters, props.filters));
Expand All @@ -55,46 +51,23 @@ export default function VisualizationRenderer(props) {
);

const { showFilters, visualization } = props;
const { Renderer, getOptions } = registeredVisualizations[visualization.type];

let options = getOptions(visualization.options, data);
let options = { ...visualization.options };

// define pagination size based on context for Table visualization
if (visualization.type === "TABLE") {
options.paginationSize = props.context === "widget" ? "small" : "default";
}

// Avoid unnecessary updates (which may be expensive or cause issues with
// internal state of some visualizations like Table) - compare options deeply
// and use saved reference if nothing changed
// More details: https://github.com/getredash/redash/pull/3963#discussion_r306935810
if (isEqual(lastOptions.current, options)) {
options = lastOptions.current;
}
lastOptions.current = options;

useEffect(() => {
if (errorHandlerRef.current) {
errorHandlerRef.current.reset();
}
}, [props.visualization.options, data]);

return (
<div className="visualization-renderer">
<ErrorBoundary
ref={errorHandlerRef}
renderError={() => <ErrorMessage>Error while rendering visualization.</ErrorMessage>}>
{showFilters && <Filters filters={filters} onChange={setFilters} />}
<div className="visualization-renderer-wrapper">
<Renderer
key={`visualization${visualization.id}`}
options={options}
data={filteredData}
visualizationName={visualization.name}
/>
</div>
</ErrorBoundary>
</div>
<Renderer
key={`visualization${visualization.id}`}
type={visualization.type}
options={options}
data={filteredData}
visualizationName={visualization.name}
addonBefore={showFilters && <Filters filters={filters} onChange={setFilters} />}
/>
);
}

Expand Down
8 changes: 2 additions & 6 deletions client/app/components/visualizations/editor/ContextHelp.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import PropTypes from "prop-types";
import Popover from "antd/lib/popover";
import Tooltip from "antd/lib/tooltip";
import Icon from "antd/lib/icon";
import HelpTrigger from "@/components/HelpTrigger";
import { visualizationsSettings } from "@/visualizations/components/visualizationsSettings";

import "./context-help.less";

Expand All @@ -28,11 +28,7 @@ ContextHelp.defaultProps = {
ContextHelp.defaultIcon = <Icon className="m-l-5 m-r-5" type="question-circle" theme="filled" />;

function NumberFormatSpecs() {
return (
<HelpTrigger type="NUMBER_FORMAT_SPECS" className="visualization-editor-context-help">
{ContextHelp.defaultIcon}
</HelpTrigger>
);
return visualizationsSettings.NumberFormatSpecs;
}

function DateTimeFormatSpecs() {
Expand Down
15 changes: 15 additions & 0 deletions client/app/components/visualizations/visualizationComponents.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from "react";
import HelpTrigger from "@/components/HelpTrigger";
import { Renderer, Editor } from "@/visualizations/components";
import { updateVisualizationsSettings } from "@/visualizations/components/visualizationsSettings";
import { ContextHelp } from "@/components/visualizations/editor";

const NumberFormatSpecs = (
<HelpTrigger type="NUMBER_FORMAT_SPECS" className="visualization-editor-context-help">
{ContextHelp.defaultIcon}
</HelpTrigger>
);

updateVisualizationsSettings({ NumberFormatSpecs });

export { Renderer, Editor };
4 changes: 2 additions & 2 deletions client/app/pages/queries/VisualizationEmbed.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import { Moment } from "@/components/proptypes";
import TimeAgo from "@/components/TimeAgo";
import Timer from "@/components/Timer";
import QueryResultsLink from "@/components/EditVisualizationButton/QueryResultsLink";
import VisualizationName from "@/visualizations/components/VisualizationName";
import VisualizationRenderer from "@/visualizations/components/VisualizationRenderer";
import VisualizationName from "@/components/visualizations/VisualizationName";
import VisualizationRenderer from "@/components/visualizations/VisualizationRenderer";
import { VisualizationType } from "@/visualizations/prop-types";
import logoUrl from "@/assets/images/redash_icon_small.png";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import cx from "classnames";
import { find, orderBy } from "lodash";
import useMedia from "use-media";
import Tabs from "antd/lib/tabs";
import VisualizationRenderer from "@/visualizations/components/VisualizationRenderer";
import VisualizationRenderer from "@/components/visualizations/VisualizationRenderer";
import Button from "antd/lib/button";
import Modal from "antd/lib/modal";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { isFunction, extend, filter, find } from "lodash";
import { useCallback, useRef } from "react";
import EditVisualizationDialog from "@/visualizations/components/EditVisualizationDialog";
import EditVisualizationDialog from "@/components/visualizations/EditVisualizationDialog";

export default function useEditVisualizationDialog(query, queryResult, onChange) {
const onChangeRef = useRef();
Expand Down
14 changes: 14 additions & 0 deletions client/app/visualizations/components/Editor.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from "react";
import PropTypes from "prop-types";
import { EditorPropTypes } from "@/visualizations/prop-types";
import registeredVisualizations from "@/visualizations";

export default function Editor({ type, ...otherProps }) {
const { Editor } = registeredVisualizations[type];
return <Editor {...otherProps} />;
}

Editor.propTypes = {
type: PropTypes.string.isRequired,
...EditorPropTypes,
};
58 changes: 58 additions & 0 deletions client/app/visualizations/components/Renderer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { isEqual } from "lodash";
import React, { useEffect, useRef } from "react";
import PropTypes from "prop-types";
import ErrorBoundary, { ErrorMessage } from "@/components/ErrorBoundary";
import { RendererPropTypes } from "@/visualizations/prop-types";
import registeredVisualizations from "@/visualizations";

export default function Renderer({
type,
data,
options: optionsProp,
visualizationName,
addonBefore,
addonAfter,
...otherProps
}) {
const lastOptions = useRef();
const errorHandlerRef = useRef();

const { Renderer, getOptions } = registeredVisualizations[type];

// Avoid unnecessary updates (which may be expensive or cause issues with
// internal state of some visualizations like Table) - compare options deeply
// and use saved reference if nothing changed
// More details: https://github.com/getredash/redash/pull/3963#discussion_r306935810
let options = getOptions(optionsProp, data);
if (isEqual(lastOptions.current, options)) {
options = lastOptions.current;
}
lastOptions.current = options;

useEffect(() => {
if (errorHandlerRef.current) {
errorHandlerRef.current.reset();
}
}, [optionsProp, data]);

return (
<div className="visualization-renderer">
{addonBefore}
<ErrorBoundary
ref={errorHandlerRef}
renderError={() => <ErrorMessage>Error while rendering visualization.</ErrorMessage>}>
<div className="visualization-renderer-wrapper">
<Renderer options={options} data={data} visualizationName={visualizationName} {...otherProps} />
</div>
</ErrorBoundary>
{addonAfter}
</div>
);
}

Renderer.propTypes = {
type: PropTypes.string.isRequired,
addonBefore: PropTypes.node,
addonAfter: PropTypes.node,
...RendererPropTypes,
};
4 changes: 4 additions & 0 deletions client/app/visualizations/components/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Renderer from "./Renderer";
import Editor from "./Editor";

export { Renderer, Editor };
28 changes: 28 additions & 0 deletions client/app/visualizations/components/visualizationsSettings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from "react";
import { extend } from "lodash";
import Icon from "antd/lib/icon";
import Tooltip from "antd/lib/tooltip";

export const visualizationsSettings = {
NumberFormatSpecs: (
<Tooltip
title={
<React.Fragment>
Formatting Numbers
<i className="fa fa-external-link m-l-5" />
</React.Fragment>
}>
<a
className="visualization-editor-context-help"
href="https://redash.io/help/user-guide/visualizations/formatting-numbers"
target="_blank"
rel="noopener noreferrer">
<Icon className="m-l-5 m-r-5" type="question-circle" theme="filled" />
</a>
</Tooltip>
arikfr marked this conversation as resolved.
Show resolved Hide resolved
),
};

export function updateVisualizationsSettings(options) {
extend(visualizationsSettings, options);
}