Skip to content

Commit

Permalink
fix(plugin-chart-handlebars): fix overflow, debounce and control reset (
Browse files Browse the repository at this point in the history
apache#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
  • Loading branch information
villebro authored May 11, 2022
1 parent a3a3887 commit 8d562ce
Show file tree
Hide file tree
Showing 15 changed files with 56 additions and 171 deletions.
13 changes: 7 additions & 6 deletions superset-frontend/plugins/plugin-chart-handlebars/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <div> 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<HandlebarsStylesProps>`
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
? `<style>${formData.styleTemplate}</style>`
Expand All @@ -58,13 +41,6 @@ export default function Handlebars(props: HandlebarsProps) {

const rootElem = createRef<HTMLDivElement>();

// 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 (
<Styles ref={rootElem} height={height} width={width}>
<HandlebarsViewer data={{ data }} templateSource={templateSource} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
});
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -35,3 +36,8 @@ export const PAGE_SIZE_OPTIONS = formatSelectOptions<number>([
100,
200,
]);

export const debounceFunc = debounce(
(func: (val: string) => void, source: string) => func(source),
SLOW_DEBOUNCE,
);
Original file line number Diff line number Diff line change
Expand Up @@ -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,
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand All @@ -52,6 +52,7 @@ export const allColumns: typeof sharedControls.groupby = {
: [],
}),
visibility: isRawMode,
resetOnHide: false,
};

const dndAllColumns: typeof sharedControls.groupby = {
Expand All @@ -75,6 +76,7 @@ const dndAllColumns: typeof sharedControls.groupby = {
return newState;
},
visibility: isRawMode,
resetOnHide: false,
};

export const allColumnsControlSetItem: ControlSetItem = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -37,7 +38,6 @@ export const groupByControlSetItem: ControlSetItem = {
controls.percent_metrics?.value,
controlState.value,
]);

return newState;
},
rerender: ['metrics', 'percent_metrics'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -37,17 +38,14 @@ const HandlebarsTemplateControl = (
props?.value ? props?.value : props?.default ? props?.default : '',
);

const updateConfig = (source: string) => {
props.onChange(source);
};
return (
<div>
<ControlHeader>{props.label}</ControlHeader>
<CodeEditor
theme="dark"
value={val}
onChange={source => {
updateConfig(source || '');
debounceFunc(props.onChange, source || '');
}}
/>
</div>
Expand All @@ -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: `<ul class="data_list">
{{#each data}}
<li>{{this}}</li>
{{/each}}
</ul>`,
default: `<ul class="data-list">
{{#each data}}
<li>{{stringify this}}</li>
{{/each}}
</ul>`,
isInt: false,
renderTrigger: true,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ export const includeTimeControlSetItem: ControlSetItem = {
),
default: false,
visibility: isAggMode,
resetOnHide: false,
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,6 @@ export const timeSeriesLimitMetricControlSetItem: ControlSetItem = {
name: 'timeseries_limit_metric',
override: {
visibility: isAggMode,
resetOnHide: false,
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -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 || [],
Expand Down Expand Up @@ -86,6 +87,7 @@ export const metricsControlSetItem: ControlSetItem = {
]),
}),
rerender: ['groupby', 'percent_metrics'],
resetOnHide: false,
},
};

Expand All @@ -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,
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const orderByControlSetItem: ControlSetItem = {
choices: datasource?.order_by_choices || [],
}),
visibility: isRawMode,
resetOnHide: false,
},
};

Expand All @@ -43,5 +44,6 @@ export const orderDescendingControlSetItem: ControlSetItem = {
default: true,
description: t('Whether to sort descending or ascending'),
visibility: isAggMode,
resetOnHide: false,
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -35,9 +36,6 @@ const StyleControl = (props: CustomControlConfig<StyleCustomControlProps>) => {
props?.value ? props?.value : props?.default ? props?.default : '',
);

const updateConfig = (source: string) => {
props.onChange(source);
};
return (
<div>
<ControlHeader>{props.label}</ControlHeader>
Expand All @@ -46,7 +44,7 @@ const StyleControl = (props: CustomControlConfig<StyleCustomControlProps>) => {
mode="css"
value={val}
onChange={source => {
updateConfig(source || '');
debounceFunc(props.onChange, source || '');
}}
/>
</div>
Expand All @@ -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,

Expand Down
Loading

0 comments on commit 8d562ce

Please sign in to comment.