From d5ea537b0eb3e102677d63811b99cf2c4b31a3ab Mon Sep 17 00:00:00 2001
From: Ville Brofeldt <33317356+villebro@users.noreply.github.com>
Date: Thu, 28 Apr 2022 16:38:23 +0300
Subject: [PATCH] fix(plugin-chart-handlebars): fix overflow, debounce and
control reset (#19879)
* fix(plugin-chart-handlebars): fix overflow
* add debounce, fix reset controls
* fix deps
* remove redundant code
* improve examples
* add last missing resetOnHides
* fix test
* use isPlainObject
---
.../plugin-chart-handlebars/package.json | 13 ++--
.../src/Handlebars.tsx | 32 +-------
.../Handlebars/HandlebarsViewer.tsx | 9 +++
.../plugin-chart-handlebars/src/consts.ts | 8 +-
.../src/plugin/controlPanel.tsx | 75 -------------------
.../src/plugin/controls/columns.tsx | 4 +-
.../src/plugin/controls/groupBy.tsx | 2 +-
.../src/plugin/controls/handlebarTemplate.tsx | 16 ++--
.../src/plugin/controls/includeTime.ts | 1 +
.../src/plugin/controls/limits.ts | 1 +
.../src/plugin/controls/metrics.tsx | 3 +
.../src/plugin/controls/orderBy.tsx | 2 +
.../src/plugin/controls/style.tsx | 12 +--
.../src/plugin/transformProps.ts | 38 +---------
.../test/plugin/transformProps.test.ts | 11 +--
15 files changed, 56 insertions(+), 171 deletions(-)
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/package.json b/superset-frontend/plugins/plugin-chart-handlebars/package.json
index c83be8bfdd..da88ada90b 100644
--- a/superset-frontend/plugins/plugin-chart-handlebars/package.json
+++ b/superset-frontend/plugins/plugin-chart-handlebars/package.json
@@ -26,19 +26,20 @@
"access": "public"
},
"dependencies": {
- "@superset-ui/chart-controls": "0.18.25",
- "@superset-ui/core": "0.18.25",
- "ace-builds": "^1.4.13",
- "emotion": "^11.0.0",
- "handlebars": "^4.7.7",
- "react-ace": "^9.4.4"
+ "handlebars": "^4.7.7"
},
"peerDependencies": {
+ "@superset-ui/chart-controls": "*",
+ "@superset-ui/core": "*",
+ "ace-builds": "^1.4.14",
+ "lodash": "^4.17.11",
"moment": "^2.26.0",
"react": "^16.13.1",
+ "react-ace": "^9.4.4",
"react-dom": "^16.13.1"
},
"devDependencies": {
+ "@types/lodash": "^4.14.149",
"@types/jest": "^26.0.0",
"jest": "^26.0.1"
}
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/Handlebars.tsx b/superset-frontend/plugins/plugin-chart-handlebars/src/Handlebars.tsx
index c14e925056..c219e4256e 100644
--- a/superset-frontend/plugins/plugin-chart-handlebars/src/Handlebars.tsx
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/Handlebars.tsx
@@ -17,36 +17,19 @@
* under the License.
*/
import { styled } from '@superset-ui/core';
-import React, { createRef, useEffect } from 'react';
+import React, { createRef } from 'react';
import { HandlebarsViewer } from './components/Handlebars/HandlebarsViewer';
import { HandlebarsProps, HandlebarsStylesProps } from './types';
-// The following Styles component is a
element, which has been styled using Emotion
-// For docs, visit https://emotion.sh/docs/styled
-
-// Theming variables are provided for your use via a ThemeProvider
-// imported from @superset-ui/core. For variables available, please visit
-// https://github.com/apache-superset/superset-ui/blob/master/packages/superset-ui-core/src/style/index.ts
-
const Styles = styled.div
`
padding: ${({ theme }) => theme.gridUnit * 4}px;
border-radius: ${({ theme }) => theme.gridUnit * 2}px;
- height: ${({ height }) => height};
- width: ${({ width }) => width};
- overflow-y: scroll;
+ height: ${({ height }) => height}px;
+ width: ${({ width }) => width}px;
+ overflow: auto;
`;
-/**
- * ******************* WHAT YOU CAN BUILD HERE *******************
- * In essence, a chart is given a few key ingredients to work with:
- * * Data: provided via `props.data`
- * * A DOM element
- * * FormData (your controls!) provided as props by transformProps.ts
- */
-
export default function Handlebars(props: HandlebarsProps) {
- // height and width are the height and width of the DOM element as it exists in the dashboard.
- // There is also a `data` prop, which is, of course, your DATA 🎉
const { data, height, width, formData } = props;
const styleTemplateSource = formData.styleTemplate
? ``
@@ -58,13 +41,6 @@ export default function Handlebars(props: HandlebarsProps) {
const rootElem = createRef();
- // Often, you just want to get a hold of the DOM and go nuts.
- // Here, you can do that with createRef, and the useEffect hook.
- useEffect(() => {
- // const root = rootElem.current as HTMLElement;
- // console.log('Plugin element', root);
- });
-
return (
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/components/Handlebars/HandlebarsViewer.tsx b/superset-frontend/plugins/plugin-chart-handlebars/src/components/Handlebars/HandlebarsViewer.tsx
index 6b3a69b0c7..67ddb83439 100644
--- a/superset-frontend/plugins/plugin-chart-handlebars/src/components/Handlebars/HandlebarsViewer.tsx
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/components/Handlebars/HandlebarsViewer.tsx
@@ -20,6 +20,7 @@ import { SafeMarkdown, styled } from '@superset-ui/core';
import Handlebars from 'handlebars';
import moment from 'moment';
import React, { useMemo, useState } from 'react';
+import { isPlainObject } from 'lodash';
export interface HandlebarsViewerProps {
templateSource: string;
@@ -64,3 +65,11 @@ Handlebars.registerHelper('dateFormat', function (context, block) {
const f = block.hash.format || 'YYYY-MM-DD';
return moment(context).format(f);
});
+
+// usage: {{ }}
+Handlebars.registerHelper('stringify', (obj: any, obj2: any) => {
+ // calling without an argument
+ if (obj2 === undefined)
+ throw Error('Please call with an object. Example: `stringify myObj`');
+ return isPlainObject(obj) ? JSON.stringify(obj) : String(obj);
+});
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/consts.ts b/superset-frontend/plugins/plugin-chart-handlebars/src/consts.ts
index e6b215ede3..562f654431 100644
--- a/superset-frontend/plugins/plugin-chart-handlebars/src/consts.ts
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/consts.ts
@@ -16,8 +16,9 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { debounce } from 'lodash';
import { formatSelectOptions } from '@superset-ui/chart-controls';
-import { addLocaleData, t } from '@superset-ui/core';
+import { addLocaleData, SLOW_DEBOUNCE, t } from '@superset-ui/core';
import i18n from './i18n';
addLocaleData(i18n);
@@ -35,3 +36,8 @@ export const PAGE_SIZE_OPTIONS = formatSelectOptions([
100,
200,
]);
+
+export const debounceFunc = debounce(
+ (func: (val: string) => void, source: string) => func(source),
+ SLOW_DEBOUNCE,
+);
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controlPanel.tsx
index 32b3a55a79..da0ba7d589 100644
--- a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controlPanel.tsx
@@ -50,81 +50,6 @@ import { styleControlSetItem } from './controls/style';
addLocaleData(i18n);
const config: ControlPanelConfig = {
- /**
- * The control panel is split into two tabs: "Query" and
- * "Chart Options". The controls that define the inputs to
- * the chart data request, such as columns and metrics, usually
- * reside within "Query", while controls that affect the visual
- * appearance or functionality of the chart are under the
- * "Chart Options" section.
- *
- * There are several predefined controls that can be used.
- * Some examples:
- * - groupby: columns to group by (tranlated to GROUP BY statement)
- * - series: same as groupby, but single selection.
- * - metrics: multiple metrics (translated to aggregate expression)
- * - metric: sane as metrics, but single selection
- * - adhoc_filters: filters (translated to WHERE or HAVING
- * depending on filter type)
- * - row_limit: maximum number of rows (translated to LIMIT statement)
- *
- * If a control panel has both a `series` and `groupby` control, and
- * the user has chosen `col1` as the value for the `series` control,
- * and `col2` and `col3` as values for the `groupby` control,
- * the resulting query will contain three `groupby` columns. This is because
- * we considered `series` control a `groupby` query field and its value
- * will automatically append the `groupby` field when the query is generated.
- *
- * It is also possible to define custom controls by importing the
- * necessary dependencies and overriding the default parameters, which
- * can then be placed in the `controlSetRows` section
- * of the `Query` section instead of a predefined control.
- *
- * import { validateNonEmpty } from '@superset-ui/core';
- * import {
- * sharedControls,
- * ControlConfig,
- * ControlPanelConfig,
- * } from '@superset-ui/chart-controls';
- *
- * const myControl: ControlConfig<'SelectControl'> = {
- * name: 'secondary_entity',
- * config: {
- * ...sharedControls.entity,
- * type: 'SelectControl',
- * label: t('Secondary Entity'),
- * mapStateToProps: state => ({
- * sharedControls.columnChoices(state.datasource)
- * .columns.filter(c => c.groupby)
- * })
- * validators: [validateNonEmpty],
- * },
- * }
- *
- * In addition to the basic drop down control, there are several predefined
- * control types (can be set via the `type` property) that can be used. Some
- * commonly used examples:
- * - SelectControl: Dropdown to select single or multiple values,
- usually columns
- * - MetricsControl: Dropdown to select metrics, triggering a modal
- to define Metric details
- * - AdhocFilterControl: Control to choose filters
- * - CheckboxControl: A checkbox for choosing true/false values
- * - SliderControl: A slider with min/max values
- * - TextControl: Control for text data
- *
- * For more control input types, check out the `incubator-superset` repo
- * and open this file: superset-frontend/src/explore/components/controls/index.js
- *
- * To ensure all controls have been filled out correctly, the following
- * validators are provided
- * by the `@superset-ui/core/lib/validator`:
- * - validateNonEmpty: must have at least one value
- * - validateInteger: must be an integer value
- * - validateNumber: must be an intger or decimal value
- */
-
- // For control input types, see: superset-frontend/src/explore/components/controls/index.js
controlPanelSections: [
sections.legacyTimeseriesTime,
{
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/columns.tsx b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/columns.tsx
index 0582bfc23f..fd24bb75fb 100644
--- a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/columns.tsx
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/columns.tsx
@@ -31,7 +31,7 @@ import {
import React from 'react';
import { getQueryMode, isRawMode } from './shared';
-export const allColumns: typeof sharedControls.groupby = {
+const allColumns: typeof sharedControls.groupby = {
type: 'SelectControl',
label: t('Columns'),
description: t('Columns to display'),
@@ -52,6 +52,7 @@ export const allColumns: typeof sharedControls.groupby = {
: [],
}),
visibility: isRawMode,
+ resetOnHide: false,
};
const dndAllColumns: typeof sharedControls.groupby = {
@@ -75,6 +76,7 @@ const dndAllColumns: typeof sharedControls.groupby = {
return newState;
},
visibility: isRawMode,
+ resetOnHide: false,
};
export const allColumnsControlSetItem: ControlSetItem = {
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/groupBy.tsx b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/groupBy.tsx
index 0df08bc1d4..e3bea44b64 100644
--- a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/groupBy.tsx
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/groupBy.tsx
@@ -28,6 +28,7 @@ export const groupByControlSetItem: ControlSetItem = {
name: 'groupby',
override: {
visibility: isAggMode,
+ resetOnHide: false,
mapStateToProps: (state: ControlPanelState, controlState: ControlState) => {
const { controls } = state;
const originalMapStateToProps = sharedControls?.groupby?.mapStateToProps;
@@ -37,7 +38,6 @@ export const groupByControlSetItem: ControlSetItem = {
controls.percent_metrics?.value,
controlState.value,
]);
-
return newState;
},
rerender: ['metrics', 'percent_metrics'],
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/handlebarTemplate.tsx b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/handlebarTemplate.tsx
index 4d86cdc928..efe027b86f 100644
--- a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/handlebarTemplate.tsx
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/handlebarTemplate.tsx
@@ -25,6 +25,7 @@ import { t, validateNonEmpty } from '@superset-ui/core';
import React from 'react';
import { CodeEditor } from '../../components/CodeEditor/CodeEditor';
import { ControlHeader } from '../../components/ControlHeader/controlHeader';
+import { debounceFunc } from '../../consts';
interface HandlebarsCustomControlProps {
value: string;
@@ -37,9 +38,6 @@ const HandlebarsTemplateControl = (
props?.value ? props?.value : props?.default ? props?.default : '',
);
- const updateConfig = (source: string) => {
- props.onChange(source);
- };
return (
{props.label}
@@ -47,7 +45,7 @@ const HandlebarsTemplateControl = (
theme="dark"
value={val}
onChange={source => {
- updateConfig(source || '');
+ debounceFunc(props.onChange, source || '');
}}
/>
@@ -61,11 +59,11 @@ export const handlebarsTemplateControlSetItem: ControlSetItem = {
type: HandlebarsTemplateControl,
label: t('Handlebars Template'),
description: t('A handlebars template that is applied to the data'),
- default: `
- {{#each data}}
- - {{this}}
- {{/each}}
-
`,
+ default: `
+ {{#each data}}
+ - {{stringify this}}
+ {{/each}}
+
`,
isInt: false,
renderTrigger: true,
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/includeTime.ts b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/includeTime.ts
index 7004f45fe3..9525cc1acf 100644
--- a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/includeTime.ts
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/includeTime.ts
@@ -30,5 +30,6 @@ export const includeTimeControlSetItem: ControlSetItem = {
),
default: false,
visibility: isAggMode,
+ resetOnHide: false,
},
};
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/limits.ts b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/limits.ts
index 701dc27aae..2c28d92742 100644
--- a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/limits.ts
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/limits.ts
@@ -34,5 +34,6 @@ export const timeSeriesLimitMetricControlSetItem: ControlSetItem = {
name: 'timeseries_limit_metric',
override: {
visibility: isAggMode,
+ resetOnHide: false,
},
};
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/metrics.tsx b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/metrics.tsx
index 88777c9c31..7df35e6a66 100644
--- a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/metrics.tsx
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/metrics.tsx
@@ -33,6 +33,7 @@ const percentMetrics: typeof sharedControls.metrics = {
),
multi: true,
visibility: isAggMode,
+ resetOnHide: false,
mapStateToProps: ({ datasource, controls }, controlState) => ({
columns: datasource?.columns || [],
savedMetrics: datasource?.metrics || [],
@@ -86,6 +87,7 @@ export const metricsControlSetItem: ControlSetItem = {
]),
}),
rerender: ['groupby', 'percent_metrics'],
+ resetOnHide: false,
},
};
@@ -99,5 +101,6 @@ export const showTotalsControlSetItem: ControlSetItem = {
'Show total aggregations of selected metrics. Note that row limit does not apply to the result.',
),
visibility: isAggMode,
+ resetOnHide: false,
},
};
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/orderBy.tsx b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/orderBy.tsx
index 728934d719..b7c8f8e240 100644
--- a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/orderBy.tsx
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/orderBy.tsx
@@ -32,6 +32,7 @@ export const orderByControlSetItem: ControlSetItem = {
choices: datasource?.order_by_choices || [],
}),
visibility: isRawMode,
+ resetOnHide: false,
},
};
@@ -43,5 +44,6 @@ export const orderDescendingControlSetItem: ControlSetItem = {
default: true,
description: t('Whether to sort descending or ascending'),
visibility: isAggMode,
+ resetOnHide: false,
},
};
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/style.tsx b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/style.tsx
index 4d6f259eeb..d3776e7782 100644
--- a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/style.tsx
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/style.tsx
@@ -25,6 +25,7 @@ import { t } from '@superset-ui/core';
import React from 'react';
import { CodeEditor } from '../../components/CodeEditor/CodeEditor';
import { ControlHeader } from '../../components/ControlHeader/controlHeader';
+import { debounceFunc } from '../../consts';
interface StyleCustomControlProps {
value: string;
@@ -35,9 +36,6 @@ const StyleControl = (props: CustomControlConfig) => {
props?.value ? props?.value : props?.default ? props?.default : '',
);
- const updateConfig = (source: string) => {
- props.onChange(source);
- };
return (
{props.label}
@@ -46,7 +44,7 @@ const StyleControl = (props: CustomControlConfig) => {
mode="css"
value={val}
onChange={source => {
- updateConfig(source || '');
+ debounceFunc(props.onChange, source || '');
}}
/>
@@ -60,7 +58,11 @@ export const styleControlSetItem: ControlSetItem = {
type: StyleControl,
label: t('CSS Styles'),
description: t('CSS applied to the chart'),
- default: '',
+ default: `/*
+.data-list {
+ background-color: yellow;
+}
+*/`,
isInt: false,
renderTrigger: true,
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/transformProps.ts b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/transformProps.ts
index cb83e112d8..fe0e5329a7 100644
--- a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/transformProps.ts
@@ -19,49 +19,13 @@
import { ChartProps, TimeseriesDataRecord } from '@superset-ui/core';
export default function transformProps(chartProps: ChartProps) {
- /**
- * This function is called after a successful response has been
- * received from the chart data endpoint, and is used to transform
- * the incoming data prior to being sent to the Visualization.
- *
- * The transformProps function is also quite useful to return
- * additional/modified props to your data viz component. The formData
- * can also be accessed from your Handlebars.tsx file, but
- * doing supplying custom props here is often handy for integrating third
- * party libraries that rely on specific props.
- *
- * A description of properties in `chartProps`:
- * - `height`, `width`: the height/width of the DOM element in which
- * the chart is located
- * - `formData`: the chart data request payload that was sent to the
- * backend.
- * - `queriesData`: the chart data response payload that was received
- * from the backend. Some notable properties of `queriesData`:
- * - `data`: an array with data, each row with an object mapping
- * the column/alias to its value. Example:
- * `[{ col1: 'abc', metric1: 10 }, { col1: 'xyz', metric1: 20 }]`
- * - `rowcount`: the number of rows in `data`
- * - `query`: the query that was issued.
- *
- * Please note: the transformProps function gets cached when the
- * application loads. When making changes to the `transformProps`
- * function during development with hot reloading, changes won't
- * be seen until restarting the development server.
- */
const { width, height, formData, queriesData } = chartProps;
const data = queriesData[0].data as TimeseriesDataRecord[];
return {
width,
height,
-
- data: data.map(item => ({
- ...item,
- // convert epoch to native Date
- // eslint-disable-next-line no-underscore-dangle
- __timestamp: new Date(item.__timestamp as number),
- })),
- // and now your control data, manipulated as needed, and passed through as props!
+ data,
formData,
};
}
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/test/plugin/transformProps.test.ts b/superset-frontend/plugins/plugin-chart-handlebars/test/plugin/transformProps.test.ts
index 24aa3c3745..f9bab5a91a 100644
--- a/superset-frontend/plugins/plugin-chart-handlebars/test/plugin/transformProps.test.ts
+++ b/superset-frontend/plugins/plugin-chart-handlebars/test/plugin/transformProps.test.ts
@@ -31,15 +31,12 @@ describe('Handlebars tranformProps', () => {
height: 500,
viz_type: 'handlebars',
};
+ const data = [{ name: 'Hulk', sum__num: 1, __timestamp: 599616000000 }];
const chartProps = new ChartProps({
formData,
width: 800,
height: 600,
- queriesData: [
- {
- data: [{ name: 'Hulk', sum__num: 1, __timestamp: 599616000000 }],
- },
- ],
+ queriesData: [{ data }],
});
it('should tranform chart props for viz', () => {
@@ -47,9 +44,7 @@ describe('Handlebars tranformProps', () => {
expect.objectContaining({
width: 800,
height: 600,
- data: [
- { name: 'Hulk', sum__num: 1, __timestamp: new Date(599616000000) },
- ],
+ data,
}),
);
});