feat(explore): Implement viz switcher redesign (#20248)

* add icons

* Implement fast viz switcher component

* Remove unnecessary keys from ControlsPanelContainer

* Rename icons

* Add unit tests

* Add licenses

* Fix test

* Change BigNumberWithTrendline to BigNumber

* fix test

* fix test

* Add currently rendered viz tile

* Move View all charts to the right side

* Add license

* Fix imports

* Fix e2e test
This commit is contained in:
Kamil Gabryjelski 2022-06-14 15:20:54 +02:00 committed by GitHub
parent c3fdd52697
commit 86f146e217
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 644 additions and 83 deletions

View File

@ -99,11 +99,13 @@ describe('VizType control', () => {
cy.visitChartByName('Daily Totals');
cy.verifySliceSuccess({ waitAlias: '@tableChartData' });
cy.get('[data-test="visualization-type"]').contains('Table').click();
cy.contains('View all charts').click();
cy.get('button').contains('Evolution').click(); // change categories
cy.get('[role="button"]').contains('Line Chart').click();
cy.get('button').contains('Select').click();
cy.get('.ant-modal-content').within(() => {
cy.get('button').contains('Evolution').click(); // change categories
cy.get('[role="button"]').contains('Line Chart').click();
cy.get('button').contains('Select').click();
});
cy.get('button[data-test="run-query-button"]').click();
cy.verifySliceSuccess({

View File

@ -0,0 +1,21 @@
<!--
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.
-->
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.7145 17.0006H6.42878V5.8577C6.42878 5.77913 6.3645 5.71484 6.28592 5.71484H5.28592C5.20735 5.71484 5.14307 5.77913 5.14307 5.8577V18.1434C5.14307 18.222 5.20735 18.2863 5.28592 18.2863H18.7145C18.7931 18.2863 18.8574 18.222 18.8574 18.1434V17.1434C18.8574 17.0648 18.7931 17.0006 18.7145 17.0006ZM7.7145 15.8577H17.2859C17.3645 15.8577 17.4288 15.7934 17.4288 15.7148V7.92913C17.4288 7.80056 17.2734 7.73806 17.1841 7.82734L13.4288 11.5827L11.1895 9.36842C11.1626 9.34183 11.1264 9.32692 11.0886 9.32692C11.0508 9.32692 11.0146 9.34183 10.9877 9.36842L7.61271 12.7541C7.5996 12.7673 7.58922 12.7829 7.58217 12.8C7.57512 12.8172 7.57154 12.8356 7.57164 12.8541V15.7148C7.57164 15.7934 7.63592 15.8577 7.7145 15.8577Z" fill="#666666"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,21 @@
<!--
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.
-->
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.5475 17.0006H7.26179V5.8577C7.26179 5.77913 7.1975 5.71484 7.11893 5.71484H6.11893C6.04036 5.71484 5.97607 5.77913 5.97607 5.8577V18.1434C5.97607 18.222 6.04036 18.2863 6.11893 18.2863H19.5475C19.6261 18.2863 19.6904 18.222 19.6904 18.1434V17.1434C19.6904 17.0648 19.6261 17.0006 19.5475 17.0006ZM8.83322 15.572H9.83322C9.91179 15.572 9.97608 15.5077 9.97608 15.4291V12.8577C9.97608 12.7791 9.91179 12.7148 9.83322 12.7148H8.83322C8.75465 12.7148 8.69036 12.7791 8.69036 12.8577V15.4291C8.69036 15.5077 8.75465 15.572 8.83322 15.572ZM11.5475 15.572H12.5475C12.6261 15.572 12.6904 15.5077 12.6904 15.4291V9.71484C12.6904 9.63627 12.6261 9.57199 12.5475 9.57199H11.5475C11.4689 9.57199 11.4046 9.63627 11.4046 9.71484V15.4291C11.4046 15.5077 11.4689 15.572 11.5475 15.572ZM14.2618 15.572H15.2618C15.3404 15.572 15.4046 15.5077 15.4046 15.4291V11.1077C15.4046 11.0291 15.3404 10.9648 15.2618 10.9648H14.2618C14.1832 10.9648 14.1189 11.0291 14.1189 11.1077V15.4291C14.1189 15.5077 14.1832 15.572 14.2618 15.572ZM16.9761 15.572H17.9761C18.0546 15.572 18.1189 15.5077 18.1189 15.4291V8.28627C18.1189 8.2077 18.0546 8.14341 17.9761 8.14341H16.9761C16.8975 8.14341 16.8332 8.2077 16.8332 8.28627V15.4291C16.8332 15.5077 16.8975 15.572 16.9761 15.572Z" fill="#666666"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -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.
-->
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.5 15.0942V13.8545L8.88866 6.92188H9.86557V8.74676H9.2457L6.10669 13.7156V13.795H12.1219V15.0942H4.5ZM9.31512 17.0778V14.7173L9.32504 14.152V6.92188H10.778V17.0778H9.31512Z" fill="#666666"/>
<path d="M15.2336 14.4942L15.2237 12.6842H15.4816L18.5164 9.46085H20.2917L16.8304 13.1305H16.5973L15.2336 14.4942ZM13.8699 17.0778V6.92188H15.3526V17.0778H13.8699ZM18.6801 17.0778L15.9527 13.4577L16.9742 12.4213L20.5 17.0778H18.6801Z" fill="#666666"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,21 @@
<!--
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.
-->
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.5717 6.14509H15.5717V5.00223C15.5717 4.92366 15.5074 4.85938 15.4289 4.85938H14.4289C14.3503 4.85938 14.286 4.92366 14.286 5.00223V6.14509H9.71457V5.00223C9.71457 4.92366 9.65028 4.85938 9.57171 4.85938H8.57171C8.49314 4.85938 8.42885 4.92366 8.42885 5.00223V6.14509H5.42885C5.11278 6.14509 4.85742 6.40045 4.85742 6.71652V18.5737C4.85742 18.8897 5.11278 19.1451 5.42885 19.1451H18.5717C18.8878 19.1451 19.1431 18.8897 19.1431 18.5737V6.71652C19.1431 6.40045 18.8878 6.14509 18.5717 6.14509ZM17.8574 17.8594H6.14314V7.4308H8.42885V8.28795C8.42885 8.36652 8.49314 8.4308 8.57171 8.4308H9.57171C9.65028 8.4308 9.71457 8.36652 9.71457 8.28795V7.4308H14.286V8.28795C14.286 8.36652 14.3503 8.4308 14.4289 8.4308H15.4289C15.5074 8.4308 15.5717 8.36652 15.5717 8.28795V7.4308H17.8574V17.8594ZM15.1431 10.3594H14.1574C14.0664 10.3594 13.9789 10.404 13.9253 10.4772L11.2306 14.1879L10.0753 12.5987C10.0217 12.5254 9.93599 12.4808 9.84314 12.4808H8.85742C8.74135 12.4808 8.67349 12.6129 8.74135 12.7076L10.9985 15.8147C11.0252 15.8513 11.0601 15.881 11.1005 15.9015C11.1408 15.922 11.1854 15.9326 11.2306 15.9326C11.2759 15.9326 11.3205 15.922 11.3608 15.9015C11.4012 15.881 11.4361 15.8513 11.4628 15.8147L15.2592 10.5879C15.3271 10.4915 15.2592 10.3594 15.1431 10.3594Z" fill="#666666"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,21 @@
<!--
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.
-->
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.8805 17.0006H6.5948V5.8577C6.5948 5.77913 6.53051 5.71484 6.45194 5.71484H5.45194C5.37337 5.71484 5.30908 5.77913 5.30908 5.8577V18.1434C5.30908 18.222 5.37337 18.2863 5.45194 18.2863H18.8805C18.9591 18.2863 19.0234 18.222 19.0234 18.1434V17.1434C19.0234 17.0648 18.9591 17.0006 18.8805 17.0006ZM8.48408 14.2452C8.53944 14.3006 8.62873 14.3006 8.68587 14.2452L11.1555 11.7881L13.4341 14.0809C13.4894 14.1363 13.5805 14.1363 13.6359 14.0809L18.5537 9.16484C18.6091 9.10949 18.6091 9.01841 18.5537 8.96306L17.8466 8.25591C17.8197 8.22933 17.7835 8.21441 17.7457 8.21441C17.7079 8.21441 17.6716 8.22933 17.6448 8.25591L13.5377 12.3613L11.2627 10.072C11.2358 10.0454 11.1995 10.0305 11.1618 10.0305C11.124 10.0305 11.0877 10.0454 11.0609 10.072L7.77873 13.3345C7.75214 13.3613 7.73723 13.3976 7.73723 13.4354C7.73723 13.4732 7.75214 13.5094 7.77873 13.5363L8.48408 14.2452Z" fill="#666666"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,28 @@
<!--
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.
-->
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1714_70879)">
<path d="M18.952 12.1055H12.5591V5.7126C12.5591 5.63402 12.4948 5.56974 12.4162 5.56974H11.952C11.0139 5.5682 10.0848 5.75216 9.21816 6.11103C8.35148 6.4699 7.56431 6.99659 6.90195 7.66081C6.24965 8.3111 5.7299 9.08193 5.37159 9.93045C4.99875 10.8105 4.80742 11.7568 4.80909 12.7126C4.80756 13.6506 4.99151 14.5797 5.35038 15.4464C5.70925 16.3131 6.23595 17.1002 6.90016 17.7626C7.55552 18.418 8.31981 18.934 9.16981 19.293C10.0499 19.6658 10.9962 19.8571 11.952 19.8555C12.89 19.857 13.8191 19.673 14.6857 19.3142C15.5524 18.9553 16.3396 18.4286 17.002 17.7644C17.6573 17.109 18.1734 16.3447 18.5323 15.4947C18.9052 14.6147 19.0965 13.6684 19.0948 12.7126V12.2483C19.0948 12.1697 19.0305 12.1055 18.952 12.1055ZM16.1252 16.9233C15.5722 17.472 14.9164 17.9061 14.1954 18.2009C13.4744 18.4957 12.7023 18.6453 11.9234 18.6412C10.3502 18.634 8.87159 18.018 7.75909 16.9055C6.63945 15.7858 6.02338 14.2965 6.02338 12.7126C6.02338 11.1287 6.63945 9.63938 7.75909 8.51974C8.73409 7.54474 9.98945 6.9501 11.3448 6.81438V13.3197H17.8502C17.7127 14.6822 17.1127 15.9447 16.1252 16.9233ZM20.5234 11.1126L20.477 10.609C20.3252 8.96438 19.5948 7.4126 18.4198 6.24117C17.244 5.06759 15.6954 4.34128 14.0412 4.1876L13.5359 4.14117C13.452 4.13402 13.3805 4.19831 13.3805 4.28224V11.1412C13.3805 11.2197 13.4448 11.284 13.5234 11.284L20.3805 11.2662C20.4645 11.2662 20.5305 11.1947 20.5234 11.1126ZM14.5912 10.0733V5.49117C15.7161 5.72661 16.7484 6.2837 17.5627 7.09474C18.3787 7.90902 18.9377 8.94474 19.1698 10.0608L14.5912 10.0733Z" fill="#666666"/>
</g>
<defs>
<clipPath id="clip0_1714_70879">
<rect width="16" height="16" fill="white" transform="translate(4.6665 4)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,28 @@
<!--
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.
-->
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1714_70889)">
<path d="M19.7621 5.71094H4.90492C4.58885 5.71094 4.3335 5.96629 4.3335 6.28237V17.7109C4.3335 18.027 4.58885 18.2824 4.90492 18.2824H19.7621C20.0781 18.2824 20.3335 18.027 20.3335 17.7109V6.28237C20.3335 5.96629 20.0781 5.71094 19.7621 5.71094ZM19.0478 9.42522H15.2621V6.99665H19.0478V9.42522ZM19.0478 13.4252H15.2621V10.5681H19.0478V13.4252ZM10.5478 10.5681H14.1192V13.4252H10.5478V10.5681ZM14.1192 9.42522H10.5478V6.99665H14.1192V9.42522ZM5.61921 10.5681H9.40492V13.4252H5.61921V10.5681ZM5.61921 6.99665H9.40492V9.42522H5.61921V6.99665ZM5.61921 14.5681H9.40492V16.9967H5.61921V14.5681ZM10.5478 14.5681H14.1192V16.9967H10.5478V14.5681ZM19.0478 16.9967H15.2621V14.5681H19.0478V16.9967Z" fill="#666666"/>
</g>
<defs>
<clipPath id="clip0_1714_70889">
<rect width="16" height="16" fill="white" transform="translate(4.3335 4)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -27,6 +27,9 @@ const IconFileNames = [
'alert',
'alert_solid',
'alert_solid_small',
'area-chart-tile',
'bar-chart-tile',
'big-number-chart-tile',
'binoculars',
'bolt',
'bolt_small',
@ -56,6 +59,7 @@ const IconFileNames = [
'cog',
'collapse',
'color_palette',
'current-rendered-tile',
'components',
'copy',
'cursor_target',
@ -101,6 +105,7 @@ const IconFileNames = [
'keyboard',
'layers',
'lightbulb',
'line-chart-tile',
'link',
'list',
'list_view',
@ -123,6 +128,7 @@ const IconFileNames = [
'note',
'offline',
'paperclip',
'pie-chart-tile',
'placeholder',
'plus',
'plus_large',
@ -141,6 +147,7 @@ const IconFileNames = [
'sort_desc',
'sort',
'table',
'table-chart-tile',
'tag',
'trash',
'triangle_change',

View File

@ -535,7 +535,6 @@ export const ControlPanelsContainer = (props: ControlPanelsContainerProps) => {
defaultActiveKey={expandedQuerySections}
expandIconPosition="right"
ghost
key={`query-sections-${props.form_data.datasource}-${props.form_data.viz_type}`}
>
{showDatasourceAlert && <DatasourceAlert />}
{querySections.map(renderControlPanelSection)}
@ -547,7 +546,6 @@ export const ControlPanelsContainer = (props: ControlPanelsContainerProps) => {
defaultActiveKey={expandedCustomizeSections}
expandIconPosition="right"
ghost
key={`customize-sections-${props.form_data.datasource}-${props.form_data.viz_type}`}
>
{customizeSections.map(renderControlPanelSection)}
</Collapse>

View File

@ -0,0 +1,253 @@
/**
* 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, {
ReactElement,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useSelector } from 'react-redux';
import { css, SupersetTheme, t, useTheme } from '@superset-ui/core';
import { usePluginContext } from 'src/components/DynamicPlugins';
import { Tooltip } from 'src/components/Tooltip';
import Icons from 'src/components/Icons';
import { ExplorePageState } from 'src/explore/reducers/getInitialState';
export interface VizMeta {
icon: ReactElement;
name: string;
}
export interface FastVizSwitcherProps {
onChange: (vizName: string) => void;
currentSelection: string | null;
}
interface VizTileProps {
vizMeta: VizMeta;
isActive: boolean;
isRendered: boolean;
onTileClick: (vizType: string) => void;
}
const FEATURED_CHARTS: VizMeta[] = [
{
name: 'echarts_timeseries_line',
icon: <Icons.LineChartTile />,
},
{ name: 'table', icon: <Icons.TableChartTile /> },
{
name: 'big_number_total',
icon: <Icons.BigNumberChartTile />,
},
{ name: 'pie', icon: <Icons.PieChartTile /> },
{
name: 'echarts_timeseries_bar',
icon: <Icons.BarChartTile />,
},
{ name: 'echarts_area', icon: <Icons.AreaChartTile /> },
];
const VizTile = ({
isActive,
isRendered,
vizMeta,
onTileClick,
}: VizTileProps) => {
const { mountedPluginMetadata } = usePluginContext();
const chartNameRef = useRef<HTMLSpanElement>(null);
const theme = useTheme();
const TILE_TRANSITION_TIME = theme.transitionTiming * 2;
const [tooltipVisible, setTooltipVisible] = useState(false);
const [isTransitioning, setIsTransitioning] = useState(false);
const [showTooltip, setShowTooltip] = useState(false);
const chartName = vizMeta.name
? mountedPluginMetadata[vizMeta.name]?.name || `${vizMeta.name}`
: t('Select Viz Type');
const handleTileClick = useCallback(() => {
onTileClick(vizMeta.name);
setIsTransitioning(true);
setTooltipVisible(false);
setTimeout(() => {
setIsTransitioning(false);
}, TILE_TRANSITION_TIME * 1000);
}, [onTileClick, TILE_TRANSITION_TIME, vizMeta.name]);
// Antd tooltip seems to be bugged - when elements move, the tooltip sometimes
// stays visible even when user doesn't hover over the element.
// Here we manually prevent it from displaying after user triggers transition
useEffect(() => {
setShowTooltip(
Boolean(
!isTransitioning &&
(!isActive ||
(chartNameRef.current &&
chartNameRef.current.scrollWidth >
chartNameRef.current.clientWidth)),
),
);
}, [isActive, isTransitioning]);
const containerProps = useMemo(
() =>
!isActive
? { role: 'button', tabIndex: 0, onClick: handleTileClick }
: {},
[handleTileClick, isActive],
);
let tooltipTitle: string | null = null;
if (showTooltip) {
tooltipTitle = isRendered
? t('Currently rendered: %s', chartName)
: chartName;
}
return (
<Tooltip
title={tooltipTitle}
onVisibleChange={visible => setTooltipVisible(visible)}
visible={tooltipVisible && !isTransitioning}
placement="top"
mouseEnterDelay={0.4}
>
<div
{...containerProps}
css={css`
display: flex;
align-items: center;
text-transform: uppercase;
color: ${theme.colors.grayscale.base};
font-weight: ${theme.typography.weights.bold};
border-radius: 6px;
white-space: nowrap;
overflow: hidden;
max-width: fit-content;
${!isActive &&
css`
flex-shrink: 0;
width: ${theme.gridUnit * 6}px;
background-color: transparent;
transition: none;
&:hover svg path {
fill: ${theme.colors.primary.base};
transition: fill ${theme.transitionTiming}s ease-out;
}
`}
${isActive &&
css`
width: 100%;
background-color: ${theme.colors.grayscale.light4};
transition: width ${TILE_TRANSITION_TIME}s ease-out,
background-color ${TILE_TRANSITION_TIME}s ease-out;
cursor: default;
svg path {
fill: ${theme.colors.primary.base};
}
`}
`}
>
{vizMeta.icon}{' '}
<span
css={css`
overflow: hidden;
text-overflow: ellipsis;
min-width: 0;
padding-right: ${theme.gridUnit}px;
`}
ref={chartNameRef}
>
{chartName}
</span>
</div>
</Tooltip>
);
};
export const FastVizSwitcher = React.memo(
({ currentSelection, onChange }: FastVizSwitcherProps) => {
const currentViz = useSelector<ExplorePageState, string | undefined>(
state =>
state.charts &&
Object.values(state.charts)[0]?.latestQueryFormData?.viz_type,
);
const vizTiles = useMemo(() => {
const vizTiles = [...FEATURED_CHARTS];
if (
currentSelection &&
FEATURED_CHARTS.every(
featuredVizMeta => featuredVizMeta.name !== currentSelection,
) &&
currentSelection !== currentViz
) {
vizTiles.unshift({
name: currentSelection,
icon: (
<Icons.MonitorOutlined
iconSize="l"
css={(theme: SupersetTheme) => css`
padding: ${theme.gridUnit}px;
& > * {
line-height: 0;
}
`}
/>
),
});
}
if (
currentViz &&
FEATURED_CHARTS.every(
featuredVizMeta => featuredVizMeta.name !== currentViz,
)
) {
vizTiles.unshift({
name: currentViz,
icon: <Icons.CurrentRenderedTile />,
});
}
return vizTiles;
}, [currentSelection, currentViz]);
return (
<div
css={(theme: SupersetTheme) => css`
display: flex;
justify-content: space-between;
column-gap: ${theme.gridUnit}px;
`}
data-test="fast-viz-switcher"
>
{vizTiles.map(vizMeta => (
<VizTile
vizMeta={vizMeta}
isActive={currentSelection === vizMeta.name}
isRendered={currentViz === vizMeta.name}
onTileClick={onChange}
key={vizMeta.name}
/>
))}
</div>
);
},
);

View File

@ -67,6 +67,7 @@ describe('VizTypeControl', () => {
<DynamicPluginProvider>
<VizTypeControl {...defaultProps} />
</DynamicPluginProvider>,
{ useRedux: true },
);
await waitForEffects();
});

View File

@ -17,21 +17,22 @@
* under the License.
*/
import { Preset } from '@superset-ui/core';
import { render, cleanup, screen, waitFor } from 'spec/helpers/testing-library';
import { Provider } from 'react-redux';
import {
getMockStore,
mockStore,
stateWithoutNativeFilters,
} from 'spec/fixtures/mockStore';
import { render, cleanup, screen, within } from 'spec/helpers/testing-library';
import { stateWithoutNativeFilters } from 'spec/fixtures/mockStore';
import React from 'react';
import userEvent from '@testing-library/user-event';
import { DynamicPluginProvider } from 'src/components/DynamicPlugins';
import { testWithId } from 'src/utils/testUtils';
import {
BigNumberTotalChartPlugin,
EchartsAreaChartPlugin,
EchartsMixedTimeseriesChartPlugin,
EchartsPieChartPlugin,
EchartsTimeseriesBarChartPlugin,
EchartsTimeseriesChartPlugin,
EchartsTimeseriesLineChartPlugin,
} from '@superset-ui/plugin-chart-echarts';
import TableChartPlugin from '@superset-ui/plugin-chart-table';
import { LineChartPlugin } from '@superset-ui/preset-chart-xy';
import TimeTableChartPlugin from '../../../../visualizations/TimeTable';
import VizTypeControl, { VIZ_TYPE_CONTROL_TEST_ID } from './index';
@ -44,6 +45,18 @@ class MainPreset extends Preset {
name: 'Legacy charts',
plugins: [
new LineChartPlugin().configure({ key: 'line' }),
new TableChartPlugin().configure({ key: 'table' }),
new BigNumberTotalChartPlugin().configure({ key: 'big_number_total' }),
new EchartsTimeseriesLineChartPlugin().configure({
key: 'echarts_timeseries_line',
}),
new EchartsAreaChartPlugin().configure({
key: 'echarts_area',
}),
new EchartsTimeseriesBarChartPlugin().configure({
key: 'echarts_timeseries_bar',
}),
new EchartsPieChartPlugin().configure({ key: 'pie' }),
new EchartsTimeseriesChartPlugin().configure({
key: 'echarts_timeseries',
}),
@ -67,7 +80,7 @@ const getTestId = testWithId<string>(VIZ_TYPE_CONTROL_TEST_ID, true);
describe('VizTypeControl', () => {
new MainPreset().register();
const newVizTypeControlProps = {
const defaultProps = {
description: '',
label: '',
name: '',
@ -75,20 +88,17 @@ describe('VizTypeControl', () => {
labelType: 'primary',
onChange: jest.fn(),
isModalOpenInit: true,
} as const;
};
const renderWrapper = (
props = newVizTypeControlProps,
props: typeof defaultProps = defaultProps,
state: object = stateWithoutNativeFilters,
) => {
render(
<Provider
store={state ? getMockStore(stateWithoutNativeFilters) : mockStore}
>
<DynamicPluginProvider>
<VizTypeControl {...props} />
</DynamicPluginProvider>
</Provider>,
<DynamicPluginProvider>
<VizTypeControl {...props} />
</DynamicPluginProvider>,
{ useRedux: true, initialState: state },
);
};
@ -97,6 +107,119 @@ describe('VizTypeControl', () => {
jest.clearAllMocks();
});
it('Fast viz switcher tiles render', () => {
const props = {
...defaultProps,
value: 'echarts_timeseries_line',
isModalOpenInit: false,
};
renderWrapper(props);
expect(screen.getByLabelText('line-chart-tile')).toBeVisible();
expect(screen.getByLabelText('table-chart-tile')).toBeVisible();
expect(screen.getByLabelText('big-number-chart-tile')).toBeVisible();
expect(screen.getByLabelText('pie-chart-tile')).toBeVisible();
expect(screen.getByLabelText('bar-chart-tile')).toBeVisible();
expect(screen.getByLabelText('area-chart-tile')).toBeVisible();
expect(screen.queryByLabelText('monitor')).not.toBeInTheDocument();
expect(
screen.queryByLabelText('current-rendered-tile'),
).not.toBeInTheDocument();
expect(
within(screen.getByTestId('fast-viz-switcher')).getByText(
'Time-series Line Chart',
),
).toBeInTheDocument();
expect(
within(screen.getByTestId('fast-viz-switcher')).getByText('Table'),
).toBeInTheDocument();
expect(
within(screen.getByTestId('fast-viz-switcher')).getByText('Big Number'),
).toBeInTheDocument();
expect(
within(screen.getByTestId('fast-viz-switcher')).getByText('Pie Chart'),
).toBeInTheDocument();
expect(
within(screen.getByTestId('fast-viz-switcher')).getByText(
'Time-series Bar Chart v2',
),
).toBeInTheDocument();
expect(
within(screen.getByTestId('fast-viz-switcher')).getByText(
'Time-series Area Chart',
),
).toBeInTheDocument();
expect(
within(screen.getByTestId('fast-viz-switcher')).queryByText('Line Chart'),
).not.toBeInTheDocument();
});
it('Render viz tiles when non-featured chart is selected', () => {
const props = {
...defaultProps,
value: 'line',
isModalOpenInit: false,
};
renderWrapper(props);
expect(screen.getByLabelText('monitor')).toBeVisible();
expect(
within(screen.getByTestId('fast-viz-switcher')).getByText('Line Chart'),
).toBeVisible();
});
it('Render viz tiles when non-featured is rendered', () => {
const props = {
...defaultProps,
value: 'line',
isModalOpenInit: false,
};
const state = {
charts: {
1: {
latestQueryFormData: {
viz_type: 'line',
},
},
},
};
renderWrapper(props, state);
expect(screen.getByLabelText('current-rendered-tile')).toBeVisible();
expect(
within(screen.getByTestId('fast-viz-switcher')).getByText('Line Chart'),
).toBeVisible();
});
it('Change viz type on click', () => {
const props = {
...defaultProps,
value: 'echarts_timeseries_line',
isModalOpenInit: false,
};
renderWrapper(props);
userEvent.click(
within(screen.getByTestId('fast-viz-switcher')).getByText(
'Time-series Line Chart',
),
);
expect(props.onChange).not.toHaveBeenCalled();
userEvent.click(
within(screen.getByTestId('fast-viz-switcher')).getByText('Table'),
);
expect(props.onChange).toHaveBeenCalledWith('table');
});
it('Open viz gallery modal on "View all charts" click', async () => {
renderWrapper({ ...defaultProps, isModalOpenInit: false });
expect(
screen.queryByText('Select a visualization type'),
).not.toBeInTheDocument();
userEvent.click(screen.getByText('View all charts'));
expect(
await screen.findByText('Select a visualization type'),
).toBeVisible();
});
it('Search visualization type', async () => {
renderWrapper();
@ -104,20 +227,38 @@ describe('VizTypeControl', () => {
userEvent.click(screen.getByRole('button', { name: 'ballot All charts' }));
await waitFor(() => {
expect(visualizations).toHaveTextContent(/Time-series Table/);
});
expect(
await within(visualizations).findByText('Time-series Line Chart'),
).toBeVisible();
// search
userEvent.type(
screen.getByTestId(getTestId('search-input')),
'time series',
);
await waitFor(() => {
expect(visualizations).toHaveTextContent(/Time-series Table/);
expect(visualizations).toHaveTextContent(/Time-series Chart/);
expect(visualizations).toHaveTextContent(/Mixed Time-Series/);
expect(visualizations).not.toHaveTextContent(/Line Chart/);
});
expect(
await within(visualizations).findByText('Time-series Table'),
).toBeVisible();
expect(within(visualizations).getByText('Time-series Chart')).toBeVisible();
expect(within(visualizations).getByText('Mixed Time-Series')).toBeVisible();
expect(
within(visualizations).getByText('Time-series Area Chart'),
).toBeVisible();
expect(
within(visualizations).getByText('Time-series Line Chart'),
).toBeVisible();
expect(
within(visualizations).getByText('Time-series Bar Chart v2'),
).toBeVisible();
expect(
within(visualizations).queryByText('Line Chart'),
).not.toBeInTheDocument();
expect(within(visualizations).queryByText('Table')).not.toBeInTheDocument();
expect(
within(visualizations).queryByText('Big Number'),
).not.toBeInTheDocument();
expect(
within(visualizations).queryByText('Pie Chart'),
).not.toBeInTheDocument();
});
});

View File

@ -17,25 +17,20 @@
* under the License.
*/
import React, { useCallback, useState } from 'react';
import PropTypes from 'prop-types';
import { t, getChartMetadataRegistry, styled } from '@superset-ui/core';
import {
css,
t,
getChartMetadataRegistry,
styled,
SupersetTheme,
} 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 { noOp } from 'src/utils/common';
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,
};
import { FastVizSwitcher } from './FastVizSwitcher';
interface VizTypeControlProps {
description?: string;
@ -43,15 +38,9 @@ interface VizTypeControlProps {
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';
@ -62,7 +51,14 @@ function VizSupportValidation({ vizType }: { vizType: string }) {
return null;
}
return (
<div className="text-danger">
<div
className="text-danger"
css={(theme: SupersetTheme) =>
css`
margin-top: ${theme.gridUnit}px;
`
}
>
<i className="fa fa-exclamation-circle text-danger" />{' '}
<small>{t('This visualization type is not supported.')}</small>
</div>
@ -76,9 +72,11 @@ const UnpaddedModal = styled(Modal)`
`;
/** Manages the viz type and the viz picker modal */
const VizTypeControl = (props: VizTypeControlProps) => {
const { value: initialValue, onChange, isModalOpenInit, labelType } = props;
const { mountedPluginMetadata } = usePluginContext();
const VizTypeControl = ({
value: initialValue,
onChange = noOp,
isModalOpenInit,
}: VizTypeControlProps) => {
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.
@ -101,30 +99,32 @@ const VizTypeControl = (props: VizTypeControlProps) => {
setSelectedViz(initialValue);
}, [initialValue]);
const labelContent = initialValue
? mountedPluginMetadata[initialValue]?.name || `${initialValue}`
: t('Select Viz Type');
return (
<div>
<ControlHeader {...props} />
<Tooltip
id="error-tooltip"
placement="right"
title={t('Click to change visualization type')}
<>
<div
css={(theme: SupersetTheme) => css`
min-width: ${theme.gridUnit * 72}px;
max-width: fit-content;
`}
>
<>
<Label
onClick={openModal}
type={labelType}
data-test="visualization-type"
>
{labelContent}
</Label>
{initialValue && <VizSupportValidation vizType={initialValue} />}
</>
</Tooltip>
<FastVizSwitcher onChange={onChange} currentSelection={initialValue} />
{initialValue && <VizSupportValidation vizType={initialValue} />}
</div>
<div
css={(theme: SupersetTheme) =>
css`
display: flex;
justify-content: flex-end;
margin-top: ${theme.gridUnit * 3}px;
color: ${theme.colors.grayscale.base};
text-decoration: underline;
`
}
>
<span role="button" tabIndex={0} onClick={openModal}>
{t('View all charts')}
</span>
</div>
<UnpaddedModal
show={showModal}
onHide={onCancel}
@ -142,11 +142,8 @@ const VizTypeControl = (props: VizTypeControlProps) => {
onChange={setSelectedViz}
/>
</UnpaddedModal>
</div>
</>
);
};
VizTypeControl.propTypes = propTypes;
VizTypeControl.defaultProps = defaultProps;
export default VizTypeControl;

View File

@ -29,7 +29,7 @@ export const druidTimeSeries: ControlPanelSectionConfig = {
};
export const datasourceAndVizType: ControlPanelSectionConfig = {
label: t('Chart type'),
label: t('Visualization type'),
expanded: true,
controlSetRows: [
['datasource'],