- );
-};
-
-VizTypeControl.propTypes = propTypes;
-VizTypeControl.defaultProps = defaultProps;
-
-export default VizTypeControl;
diff --git a/superset-frontend/src/explore/components/controls/VizTypeControl/index.tsx b/superset-frontend/src/explore/components/controls/VizTypeControl/index.tsx
new file mode 100644
index 0000000000..7837eb04ae
--- /dev/null
+++ b/superset-frontend/src/explore/components/controls/VizTypeControl/index.tsx
@@ -0,0 +1,152 @@
+/**
+ * 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, { useCallback, useState } from 'react';
+import PropTypes from 'prop-types';
+import { t, getChartMetadataRegistry, styled } from '@superset-ui/core';
+import { usePluginContext } from 'src/components/DynamicPlugins';
+import Modal from 'src/components/Modal';
+import { Tooltip } from 'src/components/Tooltip';
+import Label, { Type } from 'src/components/Label';
+import ControlHeader from 'src/explore/components/ControlHeader';
+import VizTypeGallery, {
+ MAX_ADVISABLE_VIZ_GALLERY_WIDTH,
+} from './VizTypeGallery';
+
+const propTypes = {
+ description: PropTypes.string,
+ label: PropTypes.string,
+ name: PropTypes.string.isRequired,
+ onChange: PropTypes.func,
+ value: PropTypes.string.isRequired,
+ labelType: PropTypes.string,
+};
+
+interface VizTypeControlProps {
+ description?: string;
+ label?: string;
+ name: string;
+ onChange: (vizType: string | null) => void;
+ value: string | null;
+ labelType?: Type;
+ isModalOpenInit?: boolean;
+}
+
+const defaultProps = {
+ onChange: () => {},
+ labelType: 'default',
+};
+
+const metadataRegistry = getChartMetadataRegistry();
+
+export const VIZ_TYPE_CONTROL_TEST_ID = 'viz-type-control';
+
+function VizSupportValidation({ vizType }: { vizType: string }) {
+ const state = usePluginContext();
+ if (state.loading || metadataRegistry.has(vizType)) {
+ return null;
+ }
+ return (
+
+ {' '}
+ {t('This visualization type is not supported.')}
+
+ );
+}
+
+const UnpaddedModal = styled(Modal)`
+ .ant-modal-body {
+ padding: 0;
+ }
+`;
+
+/** Manages the viz type and the viz picker modal */
+const VizTypeControl = (props: VizTypeControlProps) => {
+ const { value: initialValue, onChange, isModalOpenInit, labelType } = props;
+ const { mountedPluginMetadata } = usePluginContext();
+ const [showModal, setShowModal] = useState(!!isModalOpenInit);
+ // a trick to force re-initialization of the gallery each time the modal opens,
+ // ensuring that the modal always opens to the correct category.
+ const [modalKey, setModalKey] = useState(0);
+ const [selectedViz, setSelectedViz] = useState(initialValue);
+
+ const openModal = useCallback(() => {
+ setShowModal(true);
+ }, []);
+
+ const onSubmit = useCallback(() => {
+ onChange(selectedViz);
+ setShowModal(false);
+ }, [selectedViz, onChange]);
+
+ const onCancel = useCallback(() => {
+ setShowModal(false);
+ setModalKey(key => key + 1);
+ // make sure the modal re-opens to the last submitted viz
+ setSelectedViz(initialValue);
+ }, [initialValue]);
+
+ const labelContent = initialValue
+ ? mountedPluginMetadata[initialValue]?.name || `${initialValue}`
+ : t('Select Viz Type');
+
+ return (
+
+
+
+ <>
+
+ {initialValue && }
+ >
+
+
+
+ {/* When the key increments, it forces react to re-init the gallery component */}
+
+
+
+ );
+};
+
+VizTypeControl.propTypes = propTypes;
+VizTypeControl.defaultProps = defaultProps;
+
+export default VizTypeControl;
diff --git a/superset-frontend/src/visualizations/TimeTable/TimeTableChartPlugin.js b/superset-frontend/src/visualizations/TimeTable/TimeTableChartPlugin.js
index c8e49a8881..6e070ae997 100644
--- a/superset-frontend/src/visualizations/TimeTable/TimeTableChartPlugin.js
+++ b/superset-frontend/src/visualizations/TimeTable/TimeTableChartPlugin.js
@@ -21,8 +21,21 @@ import transformProps from './transformProps';
import thumbnail from './images/thumbnail.png';
const metadata = new ChartMetadata({
+ category: t('Table'),
name: t('Time-series Table'),
- description: '',
+ description: t(
+ 'Compare multiple time series charts (as sparklines) and related metrics quickly.',
+ ),
+ tags: [
+ t('Advanced-Analytics'),
+ t('Multi-Variables'),
+ t('Comparison'),
+ t('Legacy'),
+ t('Percentages'),
+ t('Tabular'),
+ t('Text'),
+ t('Trend'),
+ ],
thumbnail,
useLegacyApi: true,
});
diff --git a/superset-frontend/webpack.config.js b/superset-frontend/webpack.config.js
index 3de6365aac..767aaa751e 100644
--- a/superset-frontend/webpack.config.js
+++ b/superset-frontend/webpack.config.js
@@ -279,7 +279,7 @@ const config = {
// viz thumbnails are used in `addSlice` and `explore` page
thumbnail: {
name: 'thumbnail',
- test: /thumbnail(Large)?\.png/i,
+ test: /thumbnail(Large)?\.(png|jpg)/i,
priority: 20,
enforce: true,
},