diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json
index 046e1536e87d5..9ca8a5ca29250 100644
--- a/superset-frontend/package-lock.json
+++ b/superset-frontend/package-lock.json
@@ -43,6 +43,7 @@
"@superset-ui/legacy-preset-chart-deckgl": "file:./plugins/legacy-preset-chart-deckgl",
"@superset-ui/legacy-preset-chart-nvd3": "file:./plugins/legacy-preset-chart-nvd3",
"@superset-ui/plugin-chart-echarts": "file:./plugins/plugin-chart-echarts",
+ "@superset-ui/plugin-chart-handlebars": "file:./plugins/plugin-chart-handlebars",
"@superset-ui/plugin-chart-pivot-table": "file:./plugins/plugin-chart-pivot-table",
"@superset-ui/plugin-chart-table": "file:./plugins/plugin-chart-table",
"@superset-ui/plugin-chart-word-cloud": "file:./plugins/plugin-chart-word-cloud",
@@ -21998,6 +21999,10 @@
"resolved": "plugins/plugin-chart-echarts",
"link": true
},
+ "node_modules/@superset-ui/plugin-chart-handlebars": {
+ "resolved": "plugins/plugin-chart-handlebars",
+ "link": true
+ },
"node_modules/@superset-ui/plugin-chart-pivot-table": {
"resolved": "plugins/plugin-chart-pivot-table",
"link": true
@@ -32492,6 +32497,11 @@
"node": ">= 4"
}
},
+ "node_modules/emotion": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/emotion/-/emotion-11.0.0.tgz",
+ "integrity": "sha512-QW3CRqic3aRw1OBOcnvxaHEpCmxtlGwZ5tM9dV5rY3Rn+F41E8EgTPOqJ5VfsqQ5ZXHDs2zSDyUwGI0ZfC2+5A=="
+ },
"node_modules/emotion-rgba": {
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/emotion-rgba/-/emotion-rgba-0.0.9.tgz",
@@ -60299,6 +60309,27 @@
"react": "^16.13.1"
}
},
+ "plugins/plugin-chart-handlebars": {
+ "version": "0.0.0",
+ "license": "Apache-2.0",
+ "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"
+ },
+ "devDependencies": {
+ "@types/jest": "^26.0.0",
+ "jest": "^26.0.1"
+ },
+ "peerDependencies": {
+ "moment": "^2.26.0",
+ "react": "^16.13.1",
+ "react-dom": "^16.13.1"
+ }
+ },
"plugins/plugin-chart-pivot-table": {
"name": "@superset-ui/plugin-chart-pivot-table",
"version": "0.18.25",
@@ -77699,6 +77730,19 @@
"moment": "^2.26.0"
}
},
+ "@superset-ui/plugin-chart-handlebars": {
+ "version": "file:plugins/plugin-chart-handlebars",
+ "requires": {
+ "@superset-ui/chart-controls": "0.18.25",
+ "@superset-ui/core": "0.18.25",
+ "@types/jest": "^26.0.0",
+ "ace-builds": "^1.4.13",
+ "emotion": "^11.0.0",
+ "handlebars": "^4.7.7",
+ "jest": "^26.0.1",
+ "react-ace": "^9.4.4"
+ }
+ },
"@superset-ui/plugin-chart-pivot-table": {
"version": "file:plugins/plugin-chart-pivot-table",
"requires": {
@@ -86171,6 +86215,11 @@
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q=="
},
+ "emotion": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/emotion/-/emotion-11.0.0.tgz",
+ "integrity": "sha512-QW3CRqic3aRw1OBOcnvxaHEpCmxtlGwZ5tM9dV5rY3Rn+F41E8EgTPOqJ5VfsqQ5ZXHDs2zSDyUwGI0ZfC2+5A=="
+ },
"emotion-rgba": {
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/emotion-rgba/-/emotion-rgba-0.0.9.tgz",
diff --git a/superset-frontend/package.json b/superset-frontend/package.json
index c477a1d6e3e15..5cf75e7c44ff4 100644
--- a/superset-frontend/package.json
+++ b/superset-frontend/package.json
@@ -103,6 +103,7 @@
"@superset-ui/legacy-preset-chart-deckgl": "file:./plugins/legacy-preset-chart-deckgl",
"@superset-ui/legacy-preset-chart-nvd3": "file:./plugins/legacy-preset-chart-nvd3",
"@superset-ui/plugin-chart-echarts": "file:./plugins/plugin-chart-echarts",
+ "@superset-ui/plugin-chart-handlebars": "file:./plugins/plugin-chart-handlebars",
"@superset-ui/plugin-chart-pivot-table": "file:./plugins/plugin-chart-pivot-table",
"@superset-ui/plugin-chart-table": "file:./plugins/plugin-chart-table",
"@superset-ui/plugin-chart-word-cloud": "file:./plugins/plugin-chart-word-cloud",
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/README.md b/superset-frontend/plugins/plugin-chart-handlebars/README.md
new file mode 100644
index 0000000000000..5b5468cc053a4
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/README.md
@@ -0,0 +1,74 @@
+
+
+## @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 renders the data using a handlebars template.
+
+### 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
+
+```
+
+### 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
+```
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/package.json b/superset-frontend/plugins/plugin-chart-handlebars/package.json
new file mode 100644
index 0000000000000..c83be8bfdd86c
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/package.json
@@ -0,0 +1,45 @@
+{
+ "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.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"
+ },
+ "peerDependencies": {
+ "moment": "^2.26.0",
+ "react": "^16.13.1",
+ "react-dom": "^16.13.1"
+ },
+ "devDependencies": {
+ "@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
new file mode 100644
index 0000000000000..c14e925056be6
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/Handlebars.tsx
@@ -0,0 +1,73 @@
+/**
+ * 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
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;
+`;
+
+/**
+ * ******************* 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
+ ? ``
+ : '';
+ const handlebarTemplateSource = formData.handlebarsTemplate
+ ? formData.handlebarsTemplate
+ : '{{data}}';
+ const templateSource = `${handlebarTemplateSource}\n${styleTemplateSource} `;
+
+ 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/CodeEditor/CodeEditor.tsx b/superset-frontend/plugins/plugin-chart-handlebars/src/components/CodeEditor/CodeEditor.tsx
new file mode 100644
index 0000000000000..5128fd8275388
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/components/CodeEditor/CodeEditor.tsx
@@ -0,0 +1,80 @@
+/**
+ * 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 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 = ({
+ 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 (
+
+ );
+};
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/components/ControlHeader/controlHeader.tsx b/superset-frontend/plugins/plugin-chart-handlebars/src/components/ControlHeader/controlHeader.tsx
new file mode 100644
index 0000000000000..2dac822f8f2bb
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/components/ControlHeader/controlHeader.tsx
@@ -0,0 +1,33 @@
+/**
+ * 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 React, { ReactNode } from 'react';
+
+interface ControlHeaderProps {
+ children: ReactNode;
+}
+
+export const ControlHeader = ({
+ children,
+}: ControlHeaderProps): JSX.Element => (
+
+);
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
new file mode 100644
index 0000000000000..6b3a69b0c731f
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/components/Handlebars/HandlebarsViewer.tsx
@@ -0,0 +1,66 @@
+/**
+ * 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 { SafeMarkdown, styled } from '@superset-ui/core';
+import Handlebars from 'handlebars';
+import moment from 'moment';
+import React, { useMemo, useState } from 'react';
+
+export interface HandlebarsViewerProps {
+ templateSource: string;
+ data: any;
+}
+
+export const HandlebarsViewer = ({
+ templateSource,
+ data,
+}: HandlebarsViewerProps) => {
+ const [renderedTemplate, setRenderedTemplate] = useState('');
+ const [error, setError] = useState('');
+
+ useMemo(() => {
+ try {
+ const template = Handlebars.compile(templateSource);
+ const result = template(data);
+ setRenderedTemplate(result);
+ setError('');
+ } catch (error) {
+ setRenderedTemplate('');
+ setError(error.message);
+ }
+ }, [templateSource, data]);
+
+ const Error = styled.pre`
+ white-space: pre-wrap;
+ `;
+
+ if (error) {
+ return {error};
+ }
+
+ if (renderedTemplate) {
+ return ;
+ }
+ return Loading...
;
+};
+
+// usage: {{dateFormat my_date format="MMMM YYYY"}}
+Handlebars.registerHelper('dateFormat', function (context, block) {
+ const f = block.hash.format || 'YYYY-MM-DD';
+ return moment(context).format(f);
+});
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/consts.ts b/superset-frontend/plugins/plugin-chart-handlebars/src/consts.ts
new file mode 100644
index 0000000000000..e6b215ede3e66
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/consts.ts
@@ -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([
+ [0, t('page_size.all')],
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 10,
+ 20,
+ 50,
+ 100,
+ 200,
+]);
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/i18n.ts b/superset-frontend/plugins/plugin-chart-handlebars/src/i18n.ts
new file mode 100644
index 0000000000000..5d015b5665975
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/i18n.ts
@@ -0,0 +1,65 @@
+/**
+ * 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 { 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> = {
+ 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;
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/images/thumbnail.png b/superset-frontend/plugins/plugin-chart-handlebars/src/images/thumbnail.png
new file mode 100644
index 0000000000000..342bc23206413
Binary files /dev/null and b/superset-frontend/plugins/plugin-chart-handlebars/src/images/thumbnail.png differ
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/index.ts b/superset-frontend/plugins/plugin-chart-handlebars/src/index.ts
new file mode 100644
index 0000000000000..c39fe12b95253
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/index.ts
@@ -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
+ */
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/buildQuery.ts b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/buildQuery.ts
new file mode 100644
index 0000000000000..36bcb965158f4
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/buildQuery.ts
@@ -0,0 +1,31 @@
+/**
+ * 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 { buildQueryContext, QueryFormData } from '@superset-ui/core';
+
+export default function buildQuery(formData: QueryFormData) {
+ const { metric, sort_by_metric, groupby } = formData;
+
+ return buildQueryContext(formData, baseQueryObject => [
+ {
+ ...baseQueryObject,
+ ...(sort_by_metric && { orderby: [[metric, false]] }),
+ ...(groupby && { groupby }),
+ },
+ ]);
+}
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controlPanel.tsx
new file mode 100644
index 0000000000000..32b3a55a79fa1
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controlPanel.tsx
@@ -0,0 +1,158 @@
+/**
+ * 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 {
+ ControlPanelConfig,
+ emitFilterControl,
+ sections,
+} from '@superset-ui/chart-controls';
+import { addLocaleData, t } from '@superset-ui/core';
+import i18n from '../i18n';
+import { allColumnsControlSetItem } from './controls/columns';
+import { groupByControlSetItem } from './controls/groupBy';
+import { handlebarsTemplateControlSetItem } from './controls/handlebarTemplate';
+import { includeTimeControlSetItem } from './controls/includeTime';
+import {
+ rowLimitControlSetItem,
+ timeSeriesLimitMetricControlSetItem,
+} from './controls/limits';
+import {
+ metricsControlSetItem,
+ percentMetricsControlSetItem,
+ showTotalsControlSetItem,
+} from './controls/metrics';
+import {
+ orderByControlSetItem,
+ orderDescendingControlSetItem,
+} from './controls/orderBy';
+import {
+ serverPageLengthControlSetItem,
+ serverPaginationControlSetRow,
+} from './controls/pagination';
+import { queryModeControlSetItem } from './controls/queryMode';
+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,
+ {
+ label: t('Query'),
+ expanded: true,
+ controlSetRows: [
+ [queryModeControlSetItem],
+ [groupByControlSetItem],
+ [metricsControlSetItem, allColumnsControlSetItem],
+ [percentMetricsControlSetItem],
+ [timeSeriesLimitMetricControlSetItem, orderByControlSetItem],
+ serverPaginationControlSetRow,
+ [rowLimitControlSetItem, serverPageLengthControlSetItem],
+ [includeTimeControlSetItem, orderDescendingControlSetItem],
+ [showTotalsControlSetItem],
+ ['adhoc_filters'],
+ emitFilterControl,
+ ],
+ },
+ {
+ label: t('Options'),
+ expanded: true,
+ controlSetRows: [
+ [handlebarsTemplateControlSetItem],
+ [styleControlSetItem],
+ ],
+ },
+ ],
+};
+
+export default config;
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
new file mode 100644
index 0000000000000..0582bfc23f9bf
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/columns.tsx
@@ -0,0 +1,85 @@
+/**
+ * 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 {
+ ColumnOption,
+ ControlSetItem,
+ ExtraControlProps,
+ sharedControls,
+} from '@superset-ui/chart-controls';
+import {
+ ensureIsArray,
+ FeatureFlag,
+ isFeatureEnabled,
+ t,
+} from '@superset-ui/core';
+import React from 'react';
+import { getQueryMode, isRawMode } from './shared';
+
+export const allColumns: typeof sharedControls.groupby = {
+ type: 'SelectControl',
+ label: t('Columns'),
+ description: t('Columns to display'),
+ multi: true,
+ freeForm: true,
+ allowAll: true,
+ commaChoosesOption: false,
+ default: [],
+ optionRenderer: c => ,
+ valueRenderer: c => ,
+ valueKey: 'column_name',
+ mapStateToProps: ({ datasource, controls }, controlState) => ({
+ options: datasource?.columns || [],
+ queryMode: getQueryMode(controls),
+ externalValidationErrors:
+ isRawMode({ controls }) && ensureIsArray(controlState.value).length === 0
+ ? [t('must have a value')]
+ : [],
+ }),
+ visibility: isRawMode,
+};
+
+const dndAllColumns: typeof sharedControls.groupby = {
+ type: 'DndColumnSelect',
+ label: t('Columns'),
+ description: t('Columns to display'),
+ default: [],
+ mapStateToProps({ datasource, controls }, controlState) {
+ const newState: ExtraControlProps = {};
+ if (datasource) {
+ const options = datasource.columns;
+ newState.options = Object.fromEntries(
+ options.map(option => [option.column_name, option]),
+ );
+ }
+ newState.queryMode = getQueryMode(controls);
+ newState.externalValidationErrors =
+ isRawMode({ controls }) && ensureIsArray(controlState.value).length === 0
+ ? [t('must have a value')]
+ : [];
+ return newState;
+ },
+ visibility: isRawMode,
+};
+
+export const allColumnsControlSetItem: ControlSetItem = {
+ name: 'all_columns',
+ config: isFeatureEnabled(FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP)
+ ? dndAllColumns
+ : allColumns,
+};
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
new file mode 100644
index 0000000000000..0df08bc1d46ce
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/groupBy.tsx
@@ -0,0 +1,45 @@
+/**
+ * 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 {
+ ControlPanelState,
+ ControlSetItem,
+ ControlState,
+ sharedControls,
+} from '@superset-ui/chart-controls';
+import { isAggMode, validateAggControlValues } from './shared';
+
+export const groupByControlSetItem: ControlSetItem = {
+ name: 'groupby',
+ override: {
+ visibility: isAggMode,
+ mapStateToProps: (state: ControlPanelState, controlState: ControlState) => {
+ const { controls } = state;
+ const originalMapStateToProps = sharedControls?.groupby?.mapStateToProps;
+ const newState = originalMapStateToProps?.(state, controlState) ?? {};
+ newState.externalValidationErrors = validateAggControlValues(controls, [
+ controls.metrics?.value,
+ 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
new file mode 100644
index 0000000000000..4d86cdc928fe2
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/handlebarTemplate.tsx
@@ -0,0 +1,77 @@
+/**
+ * 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 {
+ ControlSetItem,
+ CustomControlConfig,
+ sharedControls,
+} from '@superset-ui/chart-controls';
+import { t, validateNonEmpty } from '@superset-ui/core';
+import React from 'react';
+import { CodeEditor } from '../../components/CodeEditor/CodeEditor';
+import { ControlHeader } from '../../components/ControlHeader/controlHeader';
+
+interface HandlebarsCustomControlProps {
+ value: string;
+}
+
+const HandlebarsTemplateControl = (
+ props: CustomControlConfig,
+) => {
+ const val = String(
+ props?.value ? props?.value : props?.default ? props?.default : '',
+ );
+
+ const updateConfig = (source: string) => {
+ props.onChange(source);
+ };
+ return (
+
+ {props.label}
+ {
+ updateConfig(source || '');
+ }}
+ />
+
+ );
+};
+
+export const handlebarsTemplateControlSetItem: ControlSetItem = {
+ name: 'handlebarsTemplate',
+ config: {
+ ...sharedControls.entity,
+ type: HandlebarsTemplateControl,
+ label: t('Handlebars Template'),
+ description: t('A handlebars template that is applied to the data'),
+ default: `
+ {{#each data}}
+ - {{this}}
+ {{/each}}
+
`,
+ isInt: false,
+ renderTrigger: true,
+
+ validators: [validateNonEmpty],
+ mapStateToProps: ({ controls }) => ({
+ value: controls?.handlebars_template?.value,
+ }),
+ },
+};
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
new file mode 100644
index 0000000000000..7004f45fe3bed
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/includeTime.ts
@@ -0,0 +1,34 @@
+/**
+ * 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 { ControlSetItem } from '@superset-ui/chart-controls';
+import { t } from '@superset-ui/core';
+import { isAggMode } from './shared';
+
+export const includeTimeControlSetItem: ControlSetItem = {
+ name: 'include_time',
+ config: {
+ type: 'CheckboxControl',
+ label: t('Include time'),
+ description: t(
+ 'Whether to include the time granularity as defined in the time section',
+ ),
+ default: false,
+ visibility: isAggMode,
+ },
+};
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
new file mode 100644
index 0000000000000..701dc27aae1f2
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/limits.ts
@@ -0,0 +1,38 @@
+/**
+ * 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 {
+ ControlPanelsContainerProps,
+ ControlSetItem,
+} from '@superset-ui/chart-controls';
+import { isAggMode } from './shared';
+
+export const rowLimitControlSetItem: ControlSetItem = {
+ name: 'row_limit',
+ override: {
+ visibility: ({ controls }: ControlPanelsContainerProps) =>
+ !controls?.server_pagination?.value,
+ },
+};
+
+export const timeSeriesLimitMetricControlSetItem: ControlSetItem = {
+ name: 'timeseries_limit_metric',
+ override: {
+ visibility: isAggMode,
+ },
+};
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
new file mode 100644
index 0000000000000..88777c9c3173a
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/metrics.tsx
@@ -0,0 +1,103 @@
+/**
+ * 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 {
+ ControlPanelState,
+ ControlSetItem,
+ ControlState,
+ sharedControls,
+} from '@superset-ui/chart-controls';
+import { FeatureFlag, isFeatureEnabled, t } from '@superset-ui/core';
+import { getQueryMode, isAggMode, validateAggControlValues } from './shared';
+
+const percentMetrics: typeof sharedControls.metrics = {
+ type: 'MetricsControl',
+ label: t('Percentage metrics'),
+ description: t(
+ 'Metrics for which percentage of total are to be displayed. Calculated from only data within the row limit.',
+ ),
+ multi: true,
+ visibility: isAggMode,
+ mapStateToProps: ({ datasource, controls }, controlState) => ({
+ columns: datasource?.columns || [],
+ savedMetrics: datasource?.metrics || [],
+ datasource,
+ datasourceType: datasource?.type,
+ queryMode: getQueryMode(controls),
+ externalValidationErrors: validateAggControlValues(controls, [
+ controls.groupby?.value,
+ controls.metrics?.value,
+ controlState.value,
+ ]),
+ }),
+ rerender: ['groupby', 'metrics'],
+ default: [],
+ validators: [],
+};
+
+const dndPercentMetrics = {
+ ...percentMetrics,
+ type: 'DndMetricSelect',
+};
+
+export const percentMetricsControlSetItem: ControlSetItem = {
+ name: 'percent_metrics',
+ config: {
+ ...(isFeatureEnabled(FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP)
+ ? dndPercentMetrics
+ : percentMetrics),
+ },
+};
+
+export const metricsControlSetItem: ControlSetItem = {
+ name: 'metrics',
+ override: {
+ validators: [],
+ visibility: isAggMode,
+ mapStateToProps: (
+ { controls, datasource, form_data }: ControlPanelState,
+ controlState: ControlState,
+ ) => ({
+ columns: datasource?.columns.filter(c => c.filterable) || [],
+ savedMetrics: datasource?.metrics || [],
+ // current active adhoc metrics
+ selectedMetrics:
+ form_data.metrics || (form_data.metric ? [form_data.metric] : []),
+ datasource,
+ externalValidationErrors: validateAggControlValues(controls, [
+ controls.groupby?.value,
+ controls.percent_metrics?.value,
+ controlState.value,
+ ]),
+ }),
+ rerender: ['groupby', 'percent_metrics'],
+ },
+};
+
+export const showTotalsControlSetItem: ControlSetItem = {
+ name: 'show_totals',
+ config: {
+ type: 'CheckboxControl',
+ label: t('Show totals'),
+ default: false,
+ description: t(
+ 'Show total aggregations of selected metrics. Note that row limit does not apply to the result.',
+ ),
+ visibility: isAggMode,
+ },
+};
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
new file mode 100644
index 0000000000000..728934d71910c
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/orderBy.tsx
@@ -0,0 +1,47 @@
+/**
+ * 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 { ControlSetItem } from '@superset-ui/chart-controls';
+import { t } from '@superset-ui/core';
+import { isAggMode, isRawMode } from './shared';
+
+export const orderByControlSetItem: ControlSetItem = {
+ name: 'order_by_cols',
+ config: {
+ type: 'SelectControl',
+ label: t('Ordering'),
+ description: t('Order results by selected columns'),
+ multi: true,
+ default: [],
+ mapStateToProps: ({ datasource }) => ({
+ choices: datasource?.order_by_choices || [],
+ }),
+ visibility: isRawMode,
+ },
+};
+
+export const orderDescendingControlSetItem: ControlSetItem = {
+ name: 'order_desc',
+ config: {
+ type: 'CheckboxControl',
+ label: t('Sort descending'),
+ default: true,
+ description: t('Whether to sort descending or ascending'),
+ visibility: isAggMode,
+ },
+};
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/pagination.tsx b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/pagination.tsx
new file mode 100644
index 0000000000000..bf4c1207174d1
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/pagination.tsx
@@ -0,0 +1,57 @@
+/**
+ * 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 {
+ ControlPanelsContainerProps,
+ ControlSetItem,
+ ControlSetRow,
+} from '@superset-ui/chart-controls';
+import { FeatureFlag, isFeatureEnabled, t } from '@superset-ui/core';
+import { PAGE_SIZE_OPTIONS } from '../../consts';
+
+export const serverPaginationControlSetRow: ControlSetRow =
+ isFeatureEnabled(FeatureFlag.DASHBOARD_CROSS_FILTERS) ||
+ isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS)
+ ? [
+ {
+ name: 'server_pagination',
+ config: {
+ type: 'CheckboxControl',
+ label: t('Server pagination'),
+ description: t(
+ 'Enable server side pagination of results (experimental feature)',
+ ),
+ default: false,
+ },
+ },
+ ]
+ : [];
+
+export const serverPageLengthControlSetItem: ControlSetItem = {
+ name: 'server_page_length',
+ config: {
+ type: 'SelectControl',
+ freeForm: true,
+ label: t('Server Page Length'),
+ default: 10,
+ choices: PAGE_SIZE_OPTIONS,
+ description: t('Rows per page, 0 means no pagination'),
+ visibility: ({ controls }: ControlPanelsContainerProps) =>
+ Boolean(controls?.server_pagination?.value),
+ },
+};
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/queryMode.tsx b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/queryMode.tsx
new file mode 100644
index 0000000000000..b895b97f28a42
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/queryMode.tsx
@@ -0,0 +1,42 @@
+/**
+ * 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 {
+ ControlConfig,
+ ControlSetItem,
+ QueryModeLabel,
+} from '@superset-ui/chart-controls';
+import { QueryMode, t } from '@superset-ui/core';
+import { getQueryMode } from './shared';
+
+const queryMode: ControlConfig<'RadioButtonControl'> = {
+ type: 'RadioButtonControl',
+ label: t('Query mode'),
+ default: null,
+ options: [
+ [QueryMode.aggregate, QueryModeLabel[QueryMode.aggregate]],
+ [QueryMode.raw, QueryModeLabel[QueryMode.raw]],
+ ],
+ mapStateToProps: ({ controls }) => ({ value: getQueryMode(controls) }),
+ rerender: ['all_columns', 'groupby', 'metrics', 'percent_metrics'],
+};
+
+export const queryModeControlSetItem: ControlSetItem = {
+ name: 'query_mode',
+ config: queryMode,
+};
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/shared.ts b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/shared.ts
new file mode 100644
index 0000000000000..5f364a2880b8c
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/shared.ts
@@ -0,0 +1,61 @@
+/**
+ * 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 {
+ ControlPanelsContainerProps,
+ ControlStateMapping,
+} from '@superset-ui/chart-controls';
+import {
+ ensureIsArray,
+ QueryFormColumn,
+ QueryMode,
+ t,
+} from '@superset-ui/core';
+
+export function getQueryMode(controls: ControlStateMapping): QueryMode {
+ const mode = controls?.query_mode?.value;
+ if (mode === QueryMode.aggregate || mode === QueryMode.raw) {
+ return mode as QueryMode;
+ }
+ const rawColumns = controls?.all_columns?.value as
+ | QueryFormColumn[]
+ | undefined;
+ const hasRawColumns = rawColumns && rawColumns.length > 0;
+ return hasRawColumns ? QueryMode.raw : QueryMode.aggregate;
+}
+
+/**
+ * Visibility check
+ */
+export function isQueryMode(mode: QueryMode) {
+ return ({ controls }: Pick) =>
+ getQueryMode(controls) === mode;
+}
+
+export const isAggMode = isQueryMode(QueryMode.aggregate);
+export const isRawMode = isQueryMode(QueryMode.raw);
+
+export const validateAggControlValues = (
+ controls: ControlStateMapping,
+ values: any[],
+) => {
+ const areControlsEmpty = values.every(val => ensureIsArray(val).length === 0);
+ return areControlsEmpty && isAggMode({ controls })
+ ? [t('Group By, Metrics or Percentage Metrics must have a value')]
+ : [];
+};
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
new file mode 100644
index 0000000000000..4d6f259eeb501
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/style.tsx
@@ -0,0 +1,72 @@
+/**
+ * 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 {
+ ControlSetItem,
+ CustomControlConfig,
+ sharedControls,
+} from '@superset-ui/chart-controls';
+import { t } from '@superset-ui/core';
+import React from 'react';
+import { CodeEditor } from '../../components/CodeEditor/CodeEditor';
+import { ControlHeader } from '../../components/ControlHeader/controlHeader';
+
+interface StyleCustomControlProps {
+ value: string;
+}
+
+const StyleControl = (props: CustomControlConfig) => {
+ const val = String(
+ props?.value ? props?.value : props?.default ? props?.default : '',
+ );
+
+ const updateConfig = (source: string) => {
+ props.onChange(source);
+ };
+ return (
+
+ {props.label}
+ {
+ updateConfig(source || '');
+ }}
+ />
+
+ );
+};
+
+export const styleControlSetItem: ControlSetItem = {
+ name: 'styleTemplate',
+ config: {
+ ...sharedControls.entity,
+ type: StyleControl,
+ label: t('CSS Styles'),
+ description: t('CSS applied to the chart'),
+ default: '',
+ isInt: false,
+ renderTrigger: true,
+
+ validators: [],
+ mapStateToProps: ({ controls }) => ({
+ value: controls?.handlebars_template?.value,
+ }),
+ },
+};
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/index.ts b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/index.ts
new file mode 100644
index 0000000000000..db5ad528f8f6a
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/index.ts
@@ -0,0 +1,51 @@
+/**
+ * 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 { ChartMetadata, ChartPlugin, t } from '@superset-ui/core';
+import thumbnail from '../images/thumbnail.png';
+import buildQuery from './buildQuery';
+import controlPanel from './controlPanel';
+import transformProps from './transformProps';
+
+export default class HandlebarsChartPlugin extends ChartPlugin {
+ /**
+ * The constructor is used to pass relevant metadata and callbacks that get
+ * registered in respective registries that are used throughout the library
+ * and application. A more thorough description of each property is given in
+ * the respective imported file.
+ *
+ * It is worth noting that `buildQuery` and is optional, and only needed for
+ * advanced visualizations that require either post processing operations
+ * (pivoting, rolling aggregations, sorting etc) or submitting multiple queries.
+ */
+ constructor() {
+ const metadata = new ChartMetadata({
+ description: 'Write a handlebars template to render the data',
+ name: t('Handlebars'),
+ thumbnail,
+ });
+
+ super({
+ buildQuery,
+ controlPanel,
+ loadChart: () => import('../Handlebars'),
+ metadata,
+ transformProps,
+ });
+ }
+}
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/transformProps.ts b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/transformProps.ts
new file mode 100644
index 0000000000000..cb83e112d863d
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/transformProps.ts
@@ -0,0 +1,67 @@
+/**
+ * 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 { 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!
+ formData,
+ };
+}
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/types.ts b/superset-frontend/plugins/plugin-chart-handlebars/src/types.ts
new file mode 100644
index 0000000000000..2a363059fa7d8
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/types.ts
@@ -0,0 +1,65 @@
+/**
+ * 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 { ColumnConfig } from '@superset-ui/chart-controls';
+import {
+ QueryFormData,
+ QueryFormMetric,
+ QueryMode,
+ TimeGranularity,
+ TimeseriesDataRecord,
+} from '@superset-ui/core';
+
+export interface HandlebarsStylesProps {
+ height: number;
+ width: number;
+}
+
+interface HandlebarsCustomizeProps {
+ handlebarsTemplate?: string;
+ styleTemplate?: string;
+}
+
+export type HandlebarsQueryFormData = QueryFormData &
+ HandlebarsStylesProps &
+ HandlebarsCustomizeProps & {
+ align_pn?: boolean;
+ color_pn?: boolean;
+ include_time?: boolean;
+ include_search?: boolean;
+ query_mode?: QueryMode;
+ page_length?: string | number | null; // null means auto-paginate
+ metrics?: QueryFormMetric[] | null;
+ percent_metrics?: QueryFormMetric[] | null;
+ timeseries_limit_metric?: QueryFormMetric[] | QueryFormMetric | null;
+ groupby?: QueryFormMetric[] | null;
+ all_columns?: QueryFormMetric[] | null;
+ order_desc?: boolean;
+ table_timestamp_format?: string;
+ emit_filter?: boolean;
+ granularitySqla?: string;
+ time_grain_sqla?: TimeGranularity;
+ column_config?: Record;
+ };
+
+export type HandlebarsProps = HandlebarsStylesProps &
+ HandlebarsCustomizeProps & {
+ data: TimeseriesDataRecord[];
+ // add typing here for the props you pass in from transformProps.ts!
+ formData: HandlebarsQueryFormData;
+ };
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/test/index.test.ts b/superset-frontend/plugins/plugin-chart-handlebars/test/index.test.ts
new file mode 100644
index 0000000000000..9121daeca4d91
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/test/index.test.ts
@@ -0,0 +1,33 @@
+/**
+ * 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 { HandlebarsChartPlugin } from '../src';
+
+/**
+ * The example tests in this file act as a starting point, and
+ * we encourage you to build more. These tests check that the
+ * plugin loads properly, and focus on `transformProps`
+ * to ake sure that data, controls, and props are all
+ * treated correctly (e.g. formData from plugin controls
+ * properly transform the data and/or any resulting props).
+ */
+describe('@superset-ui/plugin-chart-handlebars', () => {
+ it('exists', () => {
+ expect(HandlebarsChartPlugin).toBeDefined();
+ });
+});
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/test/plugin/buildQuery.test.ts b/superset-frontend/plugins/plugin-chart-handlebars/test/plugin/buildQuery.test.ts
new file mode 100644
index 0000000000000..217ee50485f8a
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/test/plugin/buildQuery.test.ts
@@ -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 { HandlebarsQueryFormData } from '../../src/types';
+import buildQuery from '../../src/plugin/buildQuery';
+
+describe('Handlebars buildQuery', () => {
+ const formData: HandlebarsQueryFormData = {
+ datasource: '5__table',
+ granularitySqla: 'ds',
+ groupby: ['foo'],
+ viz_type: 'my_chart',
+ width: 500,
+ height: 500,
+ };
+
+ it('should build groupby with series in form data', () => {
+ const queryContext = buildQuery(formData);
+ const [query] = queryContext.queries;
+ expect(query.groupby).toEqual(['foo']);
+ });
+});
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
new file mode 100644
index 0000000000000..24aa3c3745a21
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/test/plugin/transformProps.test.ts
@@ -0,0 +1,56 @@
+/**
+ * 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 { ChartProps, QueryFormData } from '@superset-ui/core';
+import { HandlebarsQueryFormData } from '../../src/types';
+import transformProps from '../../src/plugin/transformProps';
+
+describe('Handlebars tranformProps', () => {
+ const formData: HandlebarsQueryFormData = {
+ colorScheme: 'bnbColors',
+ datasource: '3__table',
+ granularitySqla: 'ds',
+ metric: 'sum__num',
+ groupby: ['name'],
+ width: 500,
+ height: 500,
+ viz_type: 'handlebars',
+ };
+ const chartProps = new ChartProps({
+ formData,
+ width: 800,
+ height: 600,
+ queriesData: [
+ {
+ data: [{ name: 'Hulk', sum__num: 1, __timestamp: 599616000000 }],
+ },
+ ],
+ });
+
+ it('should tranform chart props for viz', () => {
+ expect(transformProps(chartProps)).toEqual(
+ expect.objectContaining({
+ width: 800,
+ height: 600,
+ data: [
+ { name: 'Hulk', sum__num: 1, __timestamp: new Date(599616000000) },
+ ],
+ }),
+ );
+ });
+});
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/tsconfig.json b/superset-frontend/plugins/plugin-chart-handlebars/tsconfig.json
new file mode 100644
index 0000000000000..b6bfaa2d98446
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "declarationDir": "lib",
+ "outDir": "lib",
+ "rootDir": "src"
+ },
+ "exclude": [
+ "lib",
+ "test"
+ ],
+ "extends": "../../tsconfig.json",
+ "include": [
+ "src/**/*",
+ "types/**/*",
+ "../../types/**/*"
+ ],
+ "references": [
+ {
+ "path": "../../packages/superset-ui-chart-controls"
+ },
+ {
+ "path": "../../packages/superset-ui-core"
+ }
+ ]
+}
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/types/external.d.ts b/superset-frontend/plugins/plugin-chart-handlebars/types/external.d.ts
new file mode 100644
index 0000000000000..8f7985ceaf135
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-handlebars/types/external.d.ts
@@ -0,0 +1,22 @@
+/**
+ * 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.
+ */
+declare module '*.png' {
+ const value: any;
+ export default value;
+}
diff --git a/superset-frontend/src/visualizations/presets/MainPreset.js b/superset-frontend/src/visualizations/presets/MainPreset.js
index dc3736ff1728b..837cd98a7aa53 100644
--- a/superset-frontend/src/visualizations/presets/MainPreset.js
+++ b/superset-frontend/src/visualizations/presets/MainPreset.js
@@ -78,6 +78,7 @@ import {
GroupByFilterPlugin,
} from 'src/filters/components/';
import { PivotTableChartPlugin as PivotTableChartPluginV2 } from '@superset-ui/plugin-chart-pivot-table';
+import { HandlebarsChartPlugin } from '@superset-ui/plugin-chart-handlebars';
import FilterBoxChartPlugin from '../FilterBox/FilterBoxChartPlugin';
import TimeTableChartPlugin from '../TimeTable';
@@ -164,6 +165,7 @@ export default class MainPreset extends Preset {
new TimeColumnFilterPlugin().configure({ key: 'filter_timecolumn' }),
new TimeGrainFilterPlugin().configure({ key: 'filter_timegrain' }),
new EchartsTreeChartPlugin().configure({ key: 'tree_chart' }),
+ new HandlebarsChartPlugin().configure({ key: 'handlebars' }),
...experimentalplugins,
],
});