mirror of
https://github.com/apache/superset.git
synced 2024-09-12 08:39:45 -04:00
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
This commit is contained in:
parent
1d50665da0
commit
d5ea537b0e
@ -26,19 +26,20 @@
|
|||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@superset-ui/chart-controls": "0.18.25",
|
"handlebars": "^4.7.7"
|
||||||
"@superset-ui/core": "0.18.25",
|
|
||||||
"ace-builds": "^1.4.13",
|
|
||||||
"emotion": "^11.0.0",
|
|
||||||
"handlebars": "^4.7.7",
|
|
||||||
"react-ace": "^9.4.4"
|
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
"@superset-ui/chart-controls": "*",
|
||||||
|
"@superset-ui/core": "*",
|
||||||
|
"ace-builds": "^1.4.14",
|
||||||
|
"lodash": "^4.17.11",
|
||||||
"moment": "^2.26.0",
|
"moment": "^2.26.0",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
|
"react-ace": "^9.4.4",
|
||||||
"react-dom": "^16.13.1"
|
"react-dom": "^16.13.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/lodash": "^4.14.149",
|
||||||
"@types/jest": "^26.0.0",
|
"@types/jest": "^26.0.0",
|
||||||
"jest": "^26.0.1"
|
"jest": "^26.0.1"
|
||||||
}
|
}
|
||||||
|
@ -17,36 +17,19 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { styled } from '@superset-ui/core';
|
import { styled } from '@superset-ui/core';
|
||||||
import React, { createRef, useEffect } from 'react';
|
import React, { createRef } from 'react';
|
||||||
import { HandlebarsViewer } from './components/Handlebars/HandlebarsViewer';
|
import { HandlebarsViewer } from './components/Handlebars/HandlebarsViewer';
|
||||||
import { HandlebarsProps, HandlebarsStylesProps } from './types';
|
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>`
|
const Styles = styled.div<HandlebarsStylesProps>`
|
||||||
padding: ${({ theme }) => theme.gridUnit * 4}px;
|
padding: ${({ theme }) => theme.gridUnit * 4}px;
|
||||||
border-radius: ${({ theme }) => theme.gridUnit * 2}px;
|
border-radius: ${({ theme }) => theme.gridUnit * 2}px;
|
||||||
height: ${({ height }) => height};
|
height: ${({ height }) => height}px;
|
||||||
width: ${({ width }) => width};
|
width: ${({ width }) => width}px;
|
||||||
overflow-y: scroll;
|
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) {
|
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 { data, height, width, formData } = props;
|
||||||
const styleTemplateSource = formData.styleTemplate
|
const styleTemplateSource = formData.styleTemplate
|
||||||
? `<style>${formData.styleTemplate}</style>`
|
? `<style>${formData.styleTemplate}</style>`
|
||||||
@ -58,13 +41,6 @@ export default function Handlebars(props: HandlebarsProps) {
|
|||||||
|
|
||||||
const rootElem = createRef<HTMLDivElement>();
|
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 (
|
return (
|
||||||
<Styles ref={rootElem} height={height} width={width}>
|
<Styles ref={rootElem} height={height} width={width}>
|
||||||
<HandlebarsViewer data={{ data }} templateSource={templateSource} />
|
<HandlebarsViewer data={{ data }} templateSource={templateSource} />
|
||||||
|
@ -20,6 +20,7 @@ import { SafeMarkdown, styled } from '@superset-ui/core';
|
|||||||
import Handlebars from 'handlebars';
|
import Handlebars from 'handlebars';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
|
import { isPlainObject } from 'lodash';
|
||||||
|
|
||||||
export interface HandlebarsViewerProps {
|
export interface HandlebarsViewerProps {
|
||||||
templateSource: string;
|
templateSource: string;
|
||||||
@ -64,3 +65,11 @@ Handlebars.registerHelper('dateFormat', function (context, block) {
|
|||||||
const f = block.hash.format || 'YYYY-MM-DD';
|
const f = block.hash.format || 'YYYY-MM-DD';
|
||||||
return moment(context).format(f);
|
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);
|
||||||
|
});
|
||||||
|
@ -16,8 +16,9 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
import { debounce } from 'lodash';
|
||||||
import { formatSelectOptions } from '@superset-ui/chart-controls';
|
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';
|
import i18n from './i18n';
|
||||||
|
|
||||||
addLocaleData(i18n);
|
addLocaleData(i18n);
|
||||||
@ -35,3 +36,8 @@ export const PAGE_SIZE_OPTIONS = formatSelectOptions<number>([
|
|||||||
100,
|
100,
|
||||||
200,
|
200,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
export const debounceFunc = debounce(
|
||||||
|
(func: (val: string) => void, source: string) => func(source),
|
||||||
|
SLOW_DEBOUNCE,
|
||||||
|
);
|
||||||
|
@ -50,81 +50,6 @@ import { styleControlSetItem } from './controls/style';
|
|||||||
addLocaleData(i18n);
|
addLocaleData(i18n);
|
||||||
|
|
||||||
const config: ControlPanelConfig = {
|
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: [
|
controlPanelSections: [
|
||||||
sections.legacyTimeseriesTime,
|
sections.legacyTimeseriesTime,
|
||||||
{
|
{
|
||||||
|
@ -31,7 +31,7 @@ import {
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { getQueryMode, isRawMode } from './shared';
|
import { getQueryMode, isRawMode } from './shared';
|
||||||
|
|
||||||
export const allColumns: typeof sharedControls.groupby = {
|
const allColumns: typeof sharedControls.groupby = {
|
||||||
type: 'SelectControl',
|
type: 'SelectControl',
|
||||||
label: t('Columns'),
|
label: t('Columns'),
|
||||||
description: t('Columns to display'),
|
description: t('Columns to display'),
|
||||||
@ -52,6 +52,7 @@ export const allColumns: typeof sharedControls.groupby = {
|
|||||||
: [],
|
: [],
|
||||||
}),
|
}),
|
||||||
visibility: isRawMode,
|
visibility: isRawMode,
|
||||||
|
resetOnHide: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const dndAllColumns: typeof sharedControls.groupby = {
|
const dndAllColumns: typeof sharedControls.groupby = {
|
||||||
@ -75,6 +76,7 @@ const dndAllColumns: typeof sharedControls.groupby = {
|
|||||||
return newState;
|
return newState;
|
||||||
},
|
},
|
||||||
visibility: isRawMode,
|
visibility: isRawMode,
|
||||||
|
resetOnHide: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const allColumnsControlSetItem: ControlSetItem = {
|
export const allColumnsControlSetItem: ControlSetItem = {
|
||||||
|
@ -28,6 +28,7 @@ export const groupByControlSetItem: ControlSetItem = {
|
|||||||
name: 'groupby',
|
name: 'groupby',
|
||||||
override: {
|
override: {
|
||||||
visibility: isAggMode,
|
visibility: isAggMode,
|
||||||
|
resetOnHide: false,
|
||||||
mapStateToProps: (state: ControlPanelState, controlState: ControlState) => {
|
mapStateToProps: (state: ControlPanelState, controlState: ControlState) => {
|
||||||
const { controls } = state;
|
const { controls } = state;
|
||||||
const originalMapStateToProps = sharedControls?.groupby?.mapStateToProps;
|
const originalMapStateToProps = sharedControls?.groupby?.mapStateToProps;
|
||||||
@ -37,7 +38,6 @@ export const groupByControlSetItem: ControlSetItem = {
|
|||||||
controls.percent_metrics?.value,
|
controls.percent_metrics?.value,
|
||||||
controlState.value,
|
controlState.value,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return newState;
|
return newState;
|
||||||
},
|
},
|
||||||
rerender: ['metrics', 'percent_metrics'],
|
rerender: ['metrics', 'percent_metrics'],
|
||||||
|
@ -25,6 +25,7 @@ import { t, validateNonEmpty } from '@superset-ui/core';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { CodeEditor } from '../../components/CodeEditor/CodeEditor';
|
import { CodeEditor } from '../../components/CodeEditor/CodeEditor';
|
||||||
import { ControlHeader } from '../../components/ControlHeader/controlHeader';
|
import { ControlHeader } from '../../components/ControlHeader/controlHeader';
|
||||||
|
import { debounceFunc } from '../../consts';
|
||||||
|
|
||||||
interface HandlebarsCustomControlProps {
|
interface HandlebarsCustomControlProps {
|
||||||
value: string;
|
value: string;
|
||||||
@ -37,9 +38,6 @@ const HandlebarsTemplateControl = (
|
|||||||
props?.value ? props?.value : props?.default ? props?.default : '',
|
props?.value ? props?.value : props?.default ? props?.default : '',
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateConfig = (source: string) => {
|
|
||||||
props.onChange(source);
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ControlHeader>{props.label}</ControlHeader>
|
<ControlHeader>{props.label}</ControlHeader>
|
||||||
@ -47,7 +45,7 @@ const HandlebarsTemplateControl = (
|
|||||||
theme="dark"
|
theme="dark"
|
||||||
value={val}
|
value={val}
|
||||||
onChange={source => {
|
onChange={source => {
|
||||||
updateConfig(source || '');
|
debounceFunc(props.onChange, source || '');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -61,11 +59,11 @@ export const handlebarsTemplateControlSetItem: ControlSetItem = {
|
|||||||
type: HandlebarsTemplateControl,
|
type: HandlebarsTemplateControl,
|
||||||
label: t('Handlebars Template'),
|
label: t('Handlebars Template'),
|
||||||
description: t('A handlebars template that is applied to the data'),
|
description: t('A handlebars template that is applied to the data'),
|
||||||
default: `<ul class="data_list">
|
default: `<ul class="data-list">
|
||||||
{{#each data}}
|
{{#each data}}
|
||||||
<li>{{this}}</li>
|
<li>{{stringify this}}</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>`,
|
</ul>`,
|
||||||
isInt: false,
|
isInt: false,
|
||||||
renderTrigger: true,
|
renderTrigger: true,
|
||||||
|
|
||||||
|
@ -30,5 +30,6 @@ export const includeTimeControlSetItem: ControlSetItem = {
|
|||||||
),
|
),
|
||||||
default: false,
|
default: false,
|
||||||
visibility: isAggMode,
|
visibility: isAggMode,
|
||||||
|
resetOnHide: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -34,5 +34,6 @@ export const timeSeriesLimitMetricControlSetItem: ControlSetItem = {
|
|||||||
name: 'timeseries_limit_metric',
|
name: 'timeseries_limit_metric',
|
||||||
override: {
|
override: {
|
||||||
visibility: isAggMode,
|
visibility: isAggMode,
|
||||||
|
resetOnHide: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -33,6 +33,7 @@ const percentMetrics: typeof sharedControls.metrics = {
|
|||||||
),
|
),
|
||||||
multi: true,
|
multi: true,
|
||||||
visibility: isAggMode,
|
visibility: isAggMode,
|
||||||
|
resetOnHide: false,
|
||||||
mapStateToProps: ({ datasource, controls }, controlState) => ({
|
mapStateToProps: ({ datasource, controls }, controlState) => ({
|
||||||
columns: datasource?.columns || [],
|
columns: datasource?.columns || [],
|
||||||
savedMetrics: datasource?.metrics || [],
|
savedMetrics: datasource?.metrics || [],
|
||||||
@ -86,6 +87,7 @@ export const metricsControlSetItem: ControlSetItem = {
|
|||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
rerender: ['groupby', 'percent_metrics'],
|
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.',
|
'Show total aggregations of selected metrics. Note that row limit does not apply to the result.',
|
||||||
),
|
),
|
||||||
visibility: isAggMode,
|
visibility: isAggMode,
|
||||||
|
resetOnHide: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -32,6 +32,7 @@ export const orderByControlSetItem: ControlSetItem = {
|
|||||||
choices: datasource?.order_by_choices || [],
|
choices: datasource?.order_by_choices || [],
|
||||||
}),
|
}),
|
||||||
visibility: isRawMode,
|
visibility: isRawMode,
|
||||||
|
resetOnHide: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -43,5 +44,6 @@ export const orderDescendingControlSetItem: ControlSetItem = {
|
|||||||
default: true,
|
default: true,
|
||||||
description: t('Whether to sort descending or ascending'),
|
description: t('Whether to sort descending or ascending'),
|
||||||
visibility: isAggMode,
|
visibility: isAggMode,
|
||||||
|
resetOnHide: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -25,6 +25,7 @@ import { t } from '@superset-ui/core';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { CodeEditor } from '../../components/CodeEditor/CodeEditor';
|
import { CodeEditor } from '../../components/CodeEditor/CodeEditor';
|
||||||
import { ControlHeader } from '../../components/ControlHeader/controlHeader';
|
import { ControlHeader } from '../../components/ControlHeader/controlHeader';
|
||||||
|
import { debounceFunc } from '../../consts';
|
||||||
|
|
||||||
interface StyleCustomControlProps {
|
interface StyleCustomControlProps {
|
||||||
value: string;
|
value: string;
|
||||||
@ -35,9 +36,6 @@ const StyleControl = (props: CustomControlConfig<StyleCustomControlProps>) => {
|
|||||||
props?.value ? props?.value : props?.default ? props?.default : '',
|
props?.value ? props?.value : props?.default ? props?.default : '',
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateConfig = (source: string) => {
|
|
||||||
props.onChange(source);
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ControlHeader>{props.label}</ControlHeader>
|
<ControlHeader>{props.label}</ControlHeader>
|
||||||
@ -46,7 +44,7 @@ const StyleControl = (props: CustomControlConfig<StyleCustomControlProps>) => {
|
|||||||
mode="css"
|
mode="css"
|
||||||
value={val}
|
value={val}
|
||||||
onChange={source => {
|
onChange={source => {
|
||||||
updateConfig(source || '');
|
debounceFunc(props.onChange, source || '');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -60,7 +58,11 @@ export const styleControlSetItem: ControlSetItem = {
|
|||||||
type: StyleControl,
|
type: StyleControl,
|
||||||
label: t('CSS Styles'),
|
label: t('CSS Styles'),
|
||||||
description: t('CSS applied to the chart'),
|
description: t('CSS applied to the chart'),
|
||||||
default: '',
|
default: `/*
|
||||||
|
.data-list {
|
||||||
|
background-color: yellow;
|
||||||
|
}
|
||||||
|
*/`,
|
||||||
isInt: false,
|
isInt: false,
|
||||||
renderTrigger: true,
|
renderTrigger: true,
|
||||||
|
|
||||||
|
@ -19,49 +19,13 @@
|
|||||||
import { ChartProps, TimeseriesDataRecord } from '@superset-ui/core';
|
import { ChartProps, TimeseriesDataRecord } from '@superset-ui/core';
|
||||||
|
|
||||||
export default function transformProps(chartProps: ChartProps) {
|
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 { width, height, formData, queriesData } = chartProps;
|
||||||
const data = queriesData[0].data as TimeseriesDataRecord[];
|
const data = queriesData[0].data as TimeseriesDataRecord[];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
data,
|
||||||
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!
|
|
||||||
formData,
|
formData,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -31,15 +31,12 @@ describe('Handlebars tranformProps', () => {
|
|||||||
height: 500,
|
height: 500,
|
||||||
viz_type: 'handlebars',
|
viz_type: 'handlebars',
|
||||||
};
|
};
|
||||||
|
const data = [{ name: 'Hulk', sum__num: 1, __timestamp: 599616000000 }];
|
||||||
const chartProps = new ChartProps<QueryFormData>({
|
const chartProps = new ChartProps<QueryFormData>({
|
||||||
formData,
|
formData,
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 600,
|
height: 600,
|
||||||
queriesData: [
|
queriesData: [{ data }],
|
||||||
{
|
|
||||||
data: [{ name: 'Hulk', sum__num: 1, __timestamp: 599616000000 }],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should tranform chart props for viz', () => {
|
it('should tranform chart props for viz', () => {
|
||||||
@ -47,9 +44,7 @@ describe('Handlebars tranformProps', () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 600,
|
height: 600,
|
||||||
data: [
|
data,
|
||||||
{ name: 'Hulk', sum__num: 1, __timestamp: new Date(599616000000) },
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user