Skip to content
This repository has been archived by the owner on Dec 10, 2021. It is now read-only.

feat(plugin-chart-handlebars): initial commit #1390

Closed
55 changes: 55 additions & 0 deletions plugins/plugin-chart-handlebars/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
## @superset-ui/plugin-chart-handlebars

[![Version](https://img.shields.io/npm/v/@superset-ui/plugin-chart-handlebars.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/plugin-chart-handlebars)

This plugin provides Write a handlebars template to render the data for Superset.

### Usage

Configure `key`, which can be any `string`, and register the plugin. This `key` will be used to
lookup this chart throughout the app.

```js
import HandlebarsChartPlugin from '@superset-ui/plugin-chart-handlebars';

new HandlebarsChartPlugin().configure({ key: 'handlebars' }).register();
```

Then use it via `SuperChart`. See
[storybook](https://apache-superset.github.io/superset-ui/?selectedKind=plugin-chart-handlebars) for
more details.

```js
<SuperChart
chartType="handlebars"
width={600}
height={600}
formData={...}
queriesData={[{
data: {...},
}]}
/>
```

### File structure generated

```
├── package.json
├── README.md
├── tsconfig.json
├── src
│   ├── Handlebars.tsx
│   ├── images
│   │   └── thumbnail.png
│   ├── index.ts
│   ├── plugin
│   │   ├── buildQuery.ts
│   │   ├── controlPanel.ts
│   │   ├── index.ts
│   │   └── transformProps.ts
│   └── types.ts
├── test
│   └── index.test.ts
└── types
└── external.d.ts
```
44 changes: 44 additions & 0 deletions plugins/plugin-chart-handlebars/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"name": "@superset-ui/plugin-chart-handlebars",
"version": "0.0.0",
"description": "Superset Chart - Write a handlebars template to render the data",
"sideEffects": false,
"main": "lib/index.js",
"module": "esm/index.js",
"files": [
"esm",
"lib"
],
"repository": {
"type": "git",
"url": "git+https://github.com/apache-superset/superset-ui.git"
},
"keywords": [
"superset"
],
"author": "Superset",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/apache-superset/superset-ui/issues"
},
"homepage": "https://github.com/apache-superset/superset-ui#readme",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@superset-ui/chart-controls": "0.18.22",
"@superset-ui/core": "0.18.22",
"ace-builds": "^1.4.13",
"emotion": "^11.0.0",
"handlebars": "^4.7.7",
"react-ace": "^9.4.4"
},
"peerDependencies": {
"react": "^16.13.1",
"react-dom": "^16.13.1"
},
"devDependencies": {
"@types/jest": "^26.0.0",
"jest": "^26.0.1"
}
}
74 changes: 74 additions & 0 deletions plugins/plugin-chart-handlebars/src/Handlebars.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { styled } from '@superset-ui/core';
import React, { createRef, useEffect } 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;
`;

/**
* ******************* 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>`
: '';
const handlebarTemplateSource = formData.handlebarsTemplate
? formData.handlebarsTemplate
: '{{data}}';
const templateSource = `${handlebarTemplateSource}\n${styleTemplateSource} `;

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}>
<h3>{props.headerText}</h3>
<HandlebarsViewer data={{ data }} templateSource={templateSource} />
</Styles>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React, { FC } from 'react';
import AceEditor, { IAceEditorProps } from 'react-ace';

// must go after AceEditor import
import 'ace-builds/src-min-noconflict/mode-handlebars';
import 'ace-builds/src-min-noconflict/mode-css';
import 'ace-builds/src-noconflict/theme-github';
import 'ace-builds/src-noconflict/theme-monokai';

export type CodeEditorMode = 'handlebars' | 'css';
export type CodeEditorTheme = 'light' | 'dark';

export interface CodeEditorProps extends IAceEditorProps {
mode?: CodeEditorMode;
theme?: CodeEditorTheme;
name?: string;
}

export const CodeEditor: FC<CodeEditorProps> = ({
mode,
theme,
name,
width,
height,
value,
...rest
}: CodeEditorProps) => {
const m_name = name || Math.random().toString(36).substring(7);
const m_theme = theme === 'light' ? 'github' : 'monokai';
const m_mode = mode || 'handlebars';
const m_height = height || '300px';
const m_width = width || '100%';

return (
<div className="code-editor" style={{ minHeight: height, width: m_width }}>
<AceEditor
mode={m_mode}
theme={m_theme}
name={m_name}
height={m_height}
width={m_width}
fontSize={14}
showPrintMargin
focus
editorProps={{ $blockScrolling: true }}
wrapEnabled
highlightActiveLine
value={value}
setOptions={{
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
enableSnippets: true,
showLineNumbers: true,
tabSize: 2,
showGutter: true,
}}
{...rest}
/>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React, { ReactNode } from 'react';

interface ControlHeaderProps {
children: ReactNode;
}

export const ControlHeader = ({
children,
}: ControlHeaderProps): JSX.Element => (
<div className="ControlHeader">
<div className="pull-left">
<span role="button">{children}</span>
</div>
</div>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { SafeMarkdown } from '@superset-ui/core';
import Handlebars from 'handlebars';
import React, { useMemo, useState } from 'react';

export interface HandlebarsViewerProps {
templateSource: string;
data: any;
}

export const HandlebarsViewer = ({
templateSource,
data,
}: HandlebarsViewerProps) => {
const [renderedTemplate, setRenderedTemplate] = useState('');

useMemo(() => {
const template = Handlebars.compile(templateSource);
const result = template(data);
setRenderedTemplate(result);
}, [templateSource, data]);

if (renderedTemplate) {
return <SafeMarkdown source={renderedTemplate} />;
}
return <p>Loading...</p>;
};
37 changes: 37 additions & 0 deletions plugins/plugin-chart-handlebars/src/consts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { formatSelectOptions } from '@superset-ui/chart-controls';
import { addLocaleData, t } from '@superset-ui/core';
import i18n from './i18n';

addLocaleData(i18n);

export const PAGE_SIZE_OPTIONS = formatSelectOptions<number>([
[0, t('page_size.all')],
1,
2,
3,
4,
5,
10,
20,
50,
100,
200,
]);
47 changes: 47 additions & 0 deletions plugins/plugin-chart-handlebars/src/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Locale } from '@superset-ui/core';

const en = {
'Query Mode': [''],
Aggregate: [''],
'Raw Records': [''],
'Emit Filter Events': [''],
'Show Cell Bars': [''],
'page_size.show': ['Show'],
'page_size.all': ['All'],
'page_size.entries': ['entries'],
'table.previous_page': ['Previous'],
'table.next_page': ['Next'],
'search.num_records': ['%s record', '%s records...'],
};

const translations: Partial<Record<Locale, typeof en>> = {
en,
fr: {
'Query Mode': [''],
Aggregate: [''],
'Raw Records': [''],
'Emit Filter Events': [''],
'Show Cell Bars': [''],
'page_size.show': ['Afficher'],
'page_size.all': ['tous'],
'page_size.entries': ['entrées'],
'table.previous_page': ['Précédent'],
'table.next_page': ['Suivante'],
'search.num_records': ['%s enregistrement', '%s enregistrements...'],
},
zh: {
'Query Mode': ['查询模式'],
Aggregate: ['分组聚合'],
'Raw Records': ['原始数据'],
'Emit Filter Events': ['关联看板过滤器'],
'Show Cell Bars': ['为指标添加条状图背景'],
'page_size.show': ['每页显示'],
'page_size.all': ['全部'],
'page_size.entries': ['条'],
'table.previous_page': ['上一页'],
'table.next_page': ['下一页'],
'search.num_records': ['%s条记录...'],
},
};

export default translations;
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions plugins/plugin-chart-handlebars/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// eslint-disable-next-line import/prefer-default-export
export { default as HandlebarsChartPlugin } from './plugin';
/**
* Note: this file exports the default export from Handlebars.tsx.
* If you want to export multiple visualization modules, you will need to
* either add additional plugin folders (similar in structure to ./plugin)
* OR export multiple instances of `ChartPlugin` extensions in ./plugin/index.ts
* which in turn load exports from Handlebars.tsx
*/