feat: Dynamic imports for the Icons component (#14318)

* Add aria-label and twotone

* Enhance LazyIcon

* Fix tests and solve ject warnings

* Add new line

* Revert package-lock to master

* Fix failing test

* Implement icon overrides

* Fix failing storybook

* Clean up

* Improve var name
This commit is contained in:
Geido
2021-04-29 21:49:39 +03:00
committed by GitHub
parent a2831382a9
commit 545e257537
21 changed files with 333 additions and 396 deletions

View File

@@ -0,0 +1,19 @@
<!--
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"></svg>

After

Width:  |  Height:  |  Size: 866 B

View File

@@ -21,7 +21,7 @@ module.exports = {
moduleNameMapper: {
'\\.(css|less)$': '<rootDir>/spec/__mocks__/styleMock.js',
'\\.(gif|ttf|eot|png)$': '<rootDir>/spec/__mocks__/fileMock.js',
'\\.svg$': '<rootDir>/spec/__mocks__/svgrMock.js',
'\\.svg$': '<rootDir>/spec/__mocks__/svgrMock.tsx',
'^src/(.*)$': '<rootDir>/src/$1',
'^spec/(.*)$': '<rootDir>/spec/$1',
},

View File

@@ -16,5 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
export default 'SvgrURL';
export const ReactComponent = 'svg';
import React, { SVGProps } from 'react';
const SvgrMock = React.forwardRef<SVGSVGElement, SVGProps<SVGSVGElement>>(
(props, ref) => <svg ref={ref} {...props} />,
);
SvgrMock.displayName = 'SvgrMock';
export const ReactComponent = SvgrMock;
export default SvgrMock;

View File

@@ -31,6 +31,7 @@ import {
import { sliceId } from 'spec/fixtures/mockChartQueries';
import { dashboardFilters } from 'spec/fixtures/mockDashboardFilters';
import { dashboardWithFilter } from 'spec/fixtures/mockDashboardLayout';
import Icons from 'src/components/Icons';
import { FeatureFlag } from 'src/featureFlags';
describe('FiltersBadge', () => {
@@ -129,7 +130,7 @@ describe('FiltersBadge', () => {
).toHaveText('1');
// to look at the shape of the wrapper use:
// console.log(wrapper.dive().debug())
expect(wrapper.dive().find('Icon[name="alert-solid"]')).toExist();
expect(wrapper.dive().find(Icons.AlertSolid)).toExist();
});
});
@@ -216,7 +217,7 @@ describe('FiltersBadge', () => {
expect(
wrapper.dive().find('[data-test="incompatible-filter-count"]'),
).toHaveText('1');
expect(wrapper.dive().find('Icon[name="alert-solid"]')).toExist();
expect(wrapper.dive().find(Icons.AlertSolid)).toExist();
});
});
});

View File

@@ -24,7 +24,7 @@ import { Menu } from 'src/common/components';
import DatasourceModal from 'src/datasource/DatasourceModal';
import ChangeDatasourceModal from 'src/datasource/ChangeDatasourceModal';
import DatasourceControl from 'src/explore/components/controls/DatasourceControl';
import Icon from 'src/components/Icon';
import Icons from 'src/components/Icons';
import { Tooltip } from 'src/components/Tooltip';
const defaultProps = {
@@ -119,8 +119,7 @@ describe('DatasourceControl', () => {
it('should render health check message', () => {
const wrapper = setup();
const alert = wrapper.find(Icon);
expect(alert.at(1).prop('name')).toBe('alert-solid');
expect(wrapper.find(Icons.AlertSolid)).toExist();
const tooltip = wrapper.find(Tooltip).at(0);
expect(tooltip.prop('title')).toBe(
defaultProps.datasource.health_check_message,

View File

@@ -17,31 +17,37 @@
* under the License.
*/
import React from 'react';
import { render, screen } from 'spec/helpers/testing-library';
import { render, screen, waitFor } from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event';
import Alert, { AlertProps } from 'src/components/Alert';
type AlertType = Pick<AlertProps, 'type'>;
type AlertTypeValue = AlertType[keyof AlertType];
test('renders with default props', () => {
test('renders with default props', async () => {
render(<Alert message="Message" />);
expect(screen.getByRole('alert')).toHaveTextContent('Message');
expect(screen.queryByLabelText(`info icon`)).toBeInTheDocument();
expect(screen.queryByLabelText('close icon')).toBeInTheDocument();
expect(await screen.findByLabelText(`info icon`)).toBeInTheDocument();
expect(await screen.findByLabelText('close icon')).toBeInTheDocument();
});
test('renders each type', () => {
test('renders each type', async () => {
const types: AlertTypeValue[] = ['info', 'error', 'warning', 'success'];
types.forEach(type => {
for (let i = 0; i < types.length; i += 1) {
const type = types[i];
render(<Alert type={type} message="Message" />);
expect(screen.queryByLabelText(`${type} icon`)).toBeInTheDocument();
});
// eslint-disable-next-line no-await-in-loop
expect(await screen.findByLabelText(`${type} icon`)).toBeInTheDocument();
}
});
test('renders without close button', () => {
test('renders without close button', async () => {
render(<Alert message="Message" closable={false} />);
expect(screen.queryByLabelText('close icon')).not.toBeInTheDocument();
await waitFor(() => {
expect(screen.queryByLabelText('close icon')).not.toBeInTheDocument();
});
});
test('disappear when closed', () => {
@@ -50,10 +56,12 @@ test('disappear when closed', () => {
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
});
test('renders without icon', () => {
test('renders without icon', async () => {
const type = 'info';
render(<Alert type={type} message="Message" showIcon={false} />);
expect(screen.queryByLabelText(`${type} icon`)).not.toBeInTheDocument();
await waitFor(() => {
expect(screen.queryByLabelText(`${type} icon`)).not.toBeInTheDocument();
});
});
test('renders message', () => {

View File

@@ -22,7 +22,8 @@ import {
AlertProps as AntdAlertProps,
} from 'src/common/components';
import { useTheme } from '@superset-ui/core';
import Icon, { IconName } from 'src/components/Icon';
import Icon from 'src/components/Icon';
import Icons from 'src/components/Icons';
export type AlertProps = PropsWithChildren<AntdAlertProps>;
@@ -40,23 +41,23 @@ export default function Alert(props: AlertProps) {
const { alert, error, info, success } = colors;
let baseColor = info;
let iconName: IconName = 'info-solid';
let AlertIcon = Icons.InfoSolid;
if (type === 'error') {
baseColor = error;
iconName = 'error-solid';
AlertIcon = Icons.ErrorSolid;
} else if (type === 'warning') {
baseColor = alert;
iconName = 'alert-solid';
AlertIcon = Icons.AlertSolid;
} else if (type === 'success') {
baseColor = success;
iconName = 'circle-check-solid';
AlertIcon = Icons.CircleCheckSolid;
}
return (
<AntdAlert
role="alert"
showIcon={showIcon}
icon={<Icon name={iconName} aria-label={`${type} icon`} />}
icon={<AlertIcon aria-label={`${type} icon`} />}
closeText={closable && <Icon name="x-small" aria-label="close icon" />}
css={{
padding: '6px 10px',

View File

@@ -19,12 +19,14 @@
import React from 'react';
import * as AntdIcons from '@ant-design/icons/lib/icons';
import Icon from './Icon';
import { StyledIcon } from './Icon';
import IconType from './IconType';
const AntdEnhancedIcons = Object.keys(AntdIcons)
.map(k => ({
[k]: (props: IconType) => <Icon component={AntdIcons[k]} {...props} />,
[k]: (props: IconType) => (
<StyledIcon component={AntdIcons[k]} {...props} />
),
}))
.reduce((l, r) => ({ ...l, ...r }));

View File

@@ -17,34 +17,54 @@
* under the License.
*/
import React from 'react';
import React, { useEffect, useRef, useState } from 'react';
import AntdIcon from '@ant-design/icons';
import { styled } from '@superset-ui/core';
import { CustomIconComponentProps } from '@ant-design/icons/lib/components/Icon';
import { ReactComponent as TransparentIcon } from 'images/icons/transparent.svg';
import IconType from './IconType';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const EnhancedIcon = ({ iconColor, iconSize, ...rest }: IconType) => (
const AntdIconComponent = ({ iconColor, iconSize, ...rest }: IconType) => (
<AntdIcon viewBox={rest.viewBox || '0 0 24 24'} {...rest} />
);
const Icon = styled(EnhancedIcon)<IconType>`
export const StyledIcon = styled(AntdIconComponent)<IconType>`
${({ iconColor }) => iconColor && `color: ${iconColor};`};
font-size: ${({ iconSize, theme }) =>
iconSize ? `${theme.typography.sizes[iconSize]}px` : '24px'};
iconSize
? `${theme.typography.sizes[iconSize] || theme.typography.sizes.m}px`
: '24px'};
`;
export const renderIcon = (
SVGComponent:
| React.ComponentClass<
CustomIconComponentProps | React.SVGProps<SVGSVGElement>,
any
>
| React.FunctionComponent<
CustomIconComponentProps | React.SVGProps<SVGSVGElement>
>
| undefined,
props: IconType,
) => <Icon component={SVGComponent} {...props} />;
interface IconProps extends IconType {
fileName: string;
}
export const Icon = (props: IconProps) => {
const { fileName, ...iconProps } = props;
const [, setLoaded] = useState(false);
const ImportedSVG = useRef<React.FC<React.SVGProps<SVGSVGElement>>>();
const name = fileName.replace('_', '-');
useEffect(() => {
async function importIcon(): Promise<void> {
ImportedSVG.current = (
await import(
`!!@svgr/webpack?-svgo,+titleProp,+ref!images/icons/${fileName}.svg`
)
).default;
setLoaded(true);
}
importIcon();
}, [fileName, ImportedSVG]);
return (
<StyledIcon
component={ImportedSVG.current || TransparentIcon}
aria-label={name}
{...iconProps}
/>
);
};
export default Icon;

View File

@@ -22,6 +22,7 @@ import AntdIcon from '@ant-design/icons';
type AntdIconType = typeof AntdIcon.defaultProps;
type IconType = AntdIconType & {
iconColor?: string;
twoToneColor?: string;
iconSize?: 's' | 'm' | 'l' | 'xl' | 'xxl';
};

View File

@@ -78,6 +78,11 @@ InteractiveIcons.argTypes = {
defaultValue: null,
control: { type: 'select', options: palette },
},
// @TODO twoToneColor is being ignored
twoToneColor: {
defaultValue: null,
control: { type: 'select', options: palette },
},
theme: {
table: {
disable: true,

View File

@@ -1,284 +0,0 @@
/**
* 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 { ReactComponent as AlertIcon } from 'images/icons/alert.svg';
import { ReactComponent as AlertSolidIcon } from 'images/icons/alert_solid.svg';
import { ReactComponent as AlertSolidSmallIcon } from 'images/icons/alert_solid_small.svg';
import { ReactComponent as BinocularsIcon } from 'images/icons/binoculars.svg';
import { ReactComponent as BoltIcon } from 'images/icons/bolt.svg';
import { ReactComponent as BoltSmallIcon } from 'images/icons/bolt_small.svg';
import { ReactComponent as BoltSmallRunIcon } from 'images/icons/bolt_small_run.svg';
import { ReactComponent as CalendarIcon } from 'images/icons/calendar.svg';
import { ReactComponent as CancelIcon } from 'images/icons/cancel.svg';
import { ReactComponent as CancelSolidIcon } from 'images/icons/cancel_solid.svg';
import { ReactComponent as CancelXIcon } from 'images/icons/cancel-x.svg';
import { ReactComponent as CardViewIcon } from 'images/icons/card_view.svg';
import { ReactComponent as CardsIcon } from 'images/icons/cards.svg';
import { ReactComponent as CardsLockedIcon } from 'images/icons/cards_locked.svg';
import { ReactComponent as CaretDownIcon } from 'images/icons/caret_down.svg';
import { ReactComponent as CaretLeftIcon } from 'images/icons/caret_left.svg';
import { ReactComponent as CaretRightIcon } from 'images/icons/caret_right.svg';
import { ReactComponent as CaretUpIcon } from 'images/icons/caret_up.svg';
import { ReactComponent as CertifiedIcon } from 'images/icons/certified.svg';
import { ReactComponent as CheckIcon } from 'images/icons/check.svg';
import { ReactComponent as CheckboxHalfIcon } from 'images/icons/checkbox-half.svg';
import { ReactComponent as CheckboxOffIcon } from 'images/icons/checkbox-off.svg';
import { ReactComponent as CheckboxOnIcon } from 'images/icons/checkbox-on.svg';
import { ReactComponent as CircleCheckIcon } from 'images/icons/circle_check.svg';
import { ReactComponent as CircleCheckSolidIcon } from 'images/icons/circle_check_solid.svg';
import { ReactComponent as CircleIcon } from 'images/icons/circle.svg';
import { ReactComponent as ClockIcon } from 'images/icons/clock.svg';
import { ReactComponent as CloseIcon } from 'images/icons/close.svg';
import { ReactComponent as CodeIcon } from 'images/icons/code.svg';
import { ReactComponent as CogIcon } from 'images/icons/cog.svg';
import { ReactComponent as CollapseIcon } from 'images/icons/collapse.svg';
import { ReactComponent as ColorPaletteIcon } from 'images/icons/color_palette.svg';
import { ReactComponent as ComponentsIcon } from 'images/icons/components.svg';
import { ReactComponent as CopyIcon } from 'images/icons/copy.svg';
import { ReactComponent as CursorTargeIcon } from 'images/icons/cursor_target.svg';
import { ReactComponent as DatabaseIcon } from 'images/icons/database.svg';
import { ReactComponent as DatasetPhysicalIcon } from 'images/icons/dataset_physical.svg';
import { ReactComponent as DatasetVirtualGreyscaleIcon } from 'images/icons/dataset_virtual_greyscale.svg';
import { ReactComponent as DatasetVirtualIcon } from 'images/icons/dataset_virtual.svg';
import { ReactComponent as DownloadIcon } from 'images/icons/download.svg';
import { ReactComponent as EditAltIcon } from 'images/icons/edit_alt.svg';
import { ReactComponent as EditIcon } from 'images/icons/edit.svg';
import { ReactComponent as EmailIcon } from 'images/icons/email.svg';
import { ReactComponent as ErrorIcon } from 'images/icons/error.svg';
import { ReactComponent as ErrorSolidIcon } from 'images/icons/error_solid.svg';
import { ReactComponent as ErrorSolidSmallIcon } from 'images/icons/error_solid_small.svg';
import { ReactComponent as ExclamationIcon } from 'images/icons/exclamation.svg';
import { ReactComponent as ExpandIcon } from 'images/icons/expand.svg';
import { ReactComponent as EyeIcon } from 'images/icons/eye.svg';
import { ReactComponent as EyeSlashIcon } from 'images/icons/eye_slash.svg';
import { ReactComponent as FavoriteSelectedIcon } from 'images/icons/favorite-selected.svg';
import { ReactComponent as FavoriteSmallSelectedIcon } from 'images/icons/favorite_small_selected.svg';
import { ReactComponent as FavoriteUnselectedIcon } from 'images/icons/favorite-unselected.svg';
import { ReactComponent as FieldABCIcon } from 'images/icons/field_abc.svg';
import { ReactComponent as FieldBooleanIcon } from 'images/icons/field_boolean.svg';
import { ReactComponent as FieldDateIcon } from 'images/icons/field_date.svg';
import { ReactComponent as FieldDerivedIcon } from 'images/icons/field_derived.svg';
import { ReactComponent as FieldNumIcon } from 'images/icons/field_num.svg';
import { ReactComponent as FieldStructIcon } from 'images/icons/field_struct.svg';
import { ReactComponent as FileIcon } from 'images/icons/file.svg';
import { ReactComponent as FilterIcon } from 'images/icons/filter.svg';
import { ReactComponent as FilterSmallIcon } from 'images/icons/filter_small.svg';
import { ReactComponent as FolderIcon } from 'images/icons/folder.svg';
import { ReactComponent as FullIcon } from 'images/icons/full.svg';
import { ReactComponent as FunctionIcon } from 'images/icons/function_x.svg';
import { ReactComponent as GearIcon } from 'images/icons/gear.svg';
import { ReactComponent as GridIcon } from 'images/icons/grid.svg';
import { ReactComponent as ImageIcon } from 'images/icons/image.svg';
import { ReactComponent as ImportIcon } from 'images/icons/import.svg';
import { ReactComponent as InfoIcon } from 'images/icons/info.svg';
import { ReactComponent as InfoSolidIcon } from 'images/icons/info-solid.svg';
import { ReactComponent as InfoSolidSmallIcon } from 'images/icons/info_solid_small.svg';
import { ReactComponent as JoinIcon } from 'images/icons/join.svg';
import { ReactComponent as KeyboardIcon } from 'images/icons/keyboard.svg';
import { ReactComponent as LayersIcon } from 'images/icons/layers.svg';
import { ReactComponent as LightbulbIcon } from 'images/icons/lightbulb.svg';
import { ReactComponent as LinkIcon } from 'images/icons/link.svg';
import { ReactComponent as ListIcon } from 'images/icons/list.svg';
import { ReactComponent as ListViewIcon } from 'images/icons/list_view.svg';
import { ReactComponent as LocationIcon } from 'images/icons/location.svg';
import { ReactComponent as LockLockedIcon } from 'images/icons/lock_locked.svg';
import { ReactComponent as LockUnlockedIcon } from 'images/icons/lock_unlocked.svg';
import { ReactComponent as MapIcon } from 'images/icons/map.svg';
import { ReactComponent as MessageIcon } from 'images/icons/message.svg';
import { ReactComponent as MinusIcon } from 'images/icons/minus.svg';
import { ReactComponent as MinusSolidIcon } from 'images/icons/minus_solid.svg';
import { ReactComponent as MoreHorizIcon } from 'images/icons/more_horiz.svg';
import { ReactComponent as MoveIcon } from 'images/icons/move.svg';
import { ReactComponent as NavChartsIcon } from 'images/icons/nav_charts.svg';
import { ReactComponent as NavDashboardIcon } from 'images/icons/nav_dashboard.svg';
import { ReactComponent as NavDataIcon } from 'images/icons/nav_data.svg';
import { ReactComponent as NavExploreIcon } from 'images/icons/nav_explore.svg';
import { ReactComponent as NavHomeIcon } from 'images/icons/nav_home.svg';
import { ReactComponent as NavLabIcon } from 'images/icons/nav_lab.svg';
import { ReactComponent as NoteIcon } from 'images/icons/note.svg';
import { ReactComponent as OfflineIcon } from 'images/icons/offline.svg';
import { ReactComponent as PaperclipIcon } from 'images/icons/paperclip.svg';
import { ReactComponent as PlaceholderIcon } from 'images/icons/placeholder.svg';
import { ReactComponent as PlusIcon } from 'images/icons/plus.svg';
import { ReactComponent as PlusLargeIcon } from 'images/icons/plus_large.svg';
import { ReactComponent as PlusSmallIcon } from 'images/icons/plus_small.svg';
import { ReactComponent as PlusSolidIcon } from 'images/icons/plus_solid.svg';
import { ReactComponent as QueuedIcon } from 'images/icons/queued.svg';
import { ReactComponent as RefreshIcon } from 'images/icons/refresh.svg';
import { ReactComponent as RunningIcon } from 'images/icons/running.svg';
import { ReactComponent as SaveIcon } from 'images/icons/save.svg';
import { ReactComponent as SQLIcon } from 'images/icons/sql.svg';
import { ReactComponent as SearchIcon } from 'images/icons/search.svg';
import { ReactComponent as ServerIcon } from 'images/icons/server.svg';
import { ReactComponent as ShareIcon } from 'images/icons/share.svg';
import { ReactComponent as SlackIcon } from 'images/icons/slack.svg';
import { ReactComponent as SortAscIcon } from 'images/icons/sort_asc.svg';
import { ReactComponent as SortDescIcon } from 'images/icons/sort_desc.svg';
import { ReactComponent as SortIcon } from 'images/icons/sort.svg';
import { ReactComponent as TableIcon } from 'images/icons/table.svg';
import { ReactComponent as TagIcon } from 'images/icons/tag.svg';
import { ReactComponent as TrashIcon } from 'images/icons/trash.svg';
import { ReactComponent as TriangleChangeIcon } from 'images/icons/triangle_change.svg';
import { ReactComponent as TriangleDownIcon } from 'images/icons/triangle_down.svg';
import { ReactComponent as TriangleUpIcon } from 'images/icons/triangle_up.svg';
import { ReactComponent as UpLevelIcon } from 'images/icons/up-level.svg';
import { ReactComponent as UserIcon } from 'images/icons/user.svg';
import { ReactComponent as WarningIcon } from 'images/icons/warning.svg';
import { ReactComponent as WarningSolidIcon } from 'images/icons/warning_solid.svg';
import { ReactComponent as XLargeIcon } from 'images/icons/x-large.svg';
import { ReactComponent as XSmallIcon } from 'images/icons/x-small.svg';
import AntdEnhancedIcons from './AntdEnhanced';
import { renderIcon } from './Icon';
import IconType from './IconType';
export default {
...AntdEnhancedIcons,
Alert: (props: IconType) => renderIcon(AlertIcon, props),
AlertSolid: (props: IconType) => renderIcon(AlertSolidIcon, props),
AlertSolidSmall: (props: IconType) => renderIcon(AlertSolidSmallIcon, props),
Binoculars: (props: IconType) => renderIcon(BinocularsIcon, props),
Bolt: (props: IconType) => renderIcon(BoltIcon, props),
BoltSmall: (props: IconType) => renderIcon(BoltSmallIcon, props),
BoltSmallRun: (props: IconType) => renderIcon(BoltSmallRunIcon, props),
Calendar: (props: IconType) => renderIcon(CalendarIcon, props),
Cancel: (props: IconType) => renderIcon(CancelIcon, props),
CancelSolid: (props: IconType) => renderIcon(CancelSolidIcon, props),
CancelX: (props: IconType) => renderIcon(CancelXIcon, props),
CardView: (props: IconType) => renderIcon(CardViewIcon, props),
Cards: (props: IconType) => renderIcon(CardsIcon, props),
CardsLocked: (props: IconType) => renderIcon(CardsLockedIcon, props),
CaretDown: (props: IconType) => renderIcon(CaretDownIcon, props),
CaretLeft: (props: IconType) => renderIcon(CaretLeftIcon, props),
CaretRight: (props: IconType) => renderIcon(CaretRightIcon, props),
CaretUp: (props: IconType) => renderIcon(CaretUpIcon, props),
Certified: (props: IconType) => renderIcon(CertifiedIcon, props),
Check: (props: IconType) => renderIcon(CheckIcon, props),
CheckboxHalf: (props: IconType) => renderIcon(CheckboxHalfIcon, props),
CheckboxOff: (props: IconType) => renderIcon(CheckboxOffIcon, props),
CheckboxOn: (props: IconType) => renderIcon(CheckboxOnIcon, props),
CircleCheck: (props: IconType) => renderIcon(CircleCheckIcon, props),
CircleCheckSolid: (props: IconType) =>
renderIcon(CircleCheckSolidIcon, props),
Circle: (props: IconType) => renderIcon(CircleIcon, props),
Clock: (props: IconType) => renderIcon(ClockIcon, props),
Close: (props: IconType) => renderIcon(CloseIcon, props),
Code: (props: IconType) => renderIcon(CodeIcon, props),
Cog: (props: IconType) => renderIcon(CogIcon, props),
Collapse: (props: IconType) => renderIcon(CollapseIcon, props),
ColorPalette: (props: IconType) => renderIcon(ColorPaletteIcon, props),
Components: (props: IconType) => renderIcon(ComponentsIcon, props),
Copy: (props: IconType) => renderIcon(CopyIcon, props),
CursorTarget: (props: IconType) => renderIcon(CursorTargeIcon, props),
Database: (props: IconType) => renderIcon(DatabaseIcon, props),
DatasetPhysical: (props: IconType) => renderIcon(DatasetPhysicalIcon, props),
DatasetVirtualGreyscale: (props: IconType) =>
renderIcon(DatasetVirtualGreyscaleIcon, props),
DatasetVirtual: (props: IconType) => renderIcon(DatasetVirtualIcon, props),
Download: (props: IconType) => renderIcon(DownloadIcon, props),
EditAlt: (props: IconType) => renderIcon(EditAltIcon, props),
Edit: (props: IconType) => renderIcon(EditIcon, props),
Email: (props: IconType) => renderIcon(EmailIcon, props),
Error: (props: IconType) => renderIcon(ErrorIcon, props),
ErrorSolid: (props: IconType) => renderIcon(ErrorSolidIcon, props),
ErrorSolidSmall: (props: IconType) => renderIcon(ErrorSolidSmallIcon, props),
Exclamation: (props: IconType) => renderIcon(ExclamationIcon, props),
Expand: (props: IconType) => renderIcon(ExpandIcon, props),
Eye: (props: IconType) => renderIcon(EyeIcon, props),
EyeSlash: (props: IconType) => renderIcon(EyeSlashIcon, props),
FavoriteSelected: (props: IconType) =>
renderIcon(FavoriteSelectedIcon, props),
FavoriteSmallSelected: (props: IconType) =>
renderIcon(FavoriteSmallSelectedIcon, props),
FavoriteUnselected: (props: IconType) =>
renderIcon(FavoriteUnselectedIcon, props),
FieldABCIcon: (props: IconType) => renderIcon(FieldABCIcon, props),
FieldBoolean: (props: IconType) => renderIcon(FieldBooleanIcon, props),
FieldDate: (props: IconType) => renderIcon(FieldDateIcon, props),
FieldDerived: (props: IconType) => renderIcon(FieldDerivedIcon, props),
FieldNum: (props: IconType) => renderIcon(FieldNumIcon, props),
FieldStruct: (props: IconType) => renderIcon(FieldStructIcon, props),
File: (props: IconType) => renderIcon(FileIcon, props),
Filter: (props: IconType) => renderIcon(FilterIcon, props),
FilterSmall: (props: IconType) => renderIcon(FilterSmallIcon, props),
Folder: (props: IconType) => renderIcon(FolderIcon, props),
Full: (props: IconType) => renderIcon(FullIcon, props),
Function: (props: IconType) => renderIcon(FunctionIcon, props),
Gear: (props: IconType) => renderIcon(GearIcon, props),
Grid: (props: IconType) => renderIcon(GridIcon, props),
Image: (props: IconType) => renderIcon(ImageIcon, props),
Import: (props: IconType) => renderIcon(ImportIcon, props),
Info: (props: IconType) => renderIcon(InfoIcon, props),
InfoSolid: (props: IconType) => renderIcon(InfoSolidIcon, props),
InfoSolidSmall: (props: IconType) => renderIcon(InfoSolidSmallIcon, props),
Join: (props: IconType) => renderIcon(JoinIcon, props),
Keyboard: (props: IconType) => renderIcon(KeyboardIcon, props),
Layers: (props: IconType) => renderIcon(LayersIcon, props),
Lightbulb: (props: IconType) => renderIcon(LightbulbIcon, props),
Link: (props: IconType) => renderIcon(LinkIcon, props),
List: (props: IconType) => renderIcon(ListIcon, props),
ListView: (props: IconType) => renderIcon(ListViewIcon, props),
Location: (props: IconType) => renderIcon(LocationIcon, props),
LockLocked: (props: IconType) => renderIcon(LockLockedIcon, props),
LockUnlocked: (props: IconType) => renderIcon(LockUnlockedIcon, props),
Map: (props: IconType) => renderIcon(MapIcon, props),
Message: (props: IconType) => renderIcon(MessageIcon, props),
Minus: (props: IconType) => renderIcon(MinusIcon, props),
MinusSolid: (props: IconType) => renderIcon(MinusSolidIcon, props),
MoreHoriz: (props: IconType) => renderIcon(MoreHorizIcon, props),
Move: (props: IconType) => renderIcon(MoveIcon, props),
NavCharts: (props: IconType) => renderIcon(NavChartsIcon, props),
NavDashboard: (props: IconType) => renderIcon(NavDashboardIcon, props),
NavData: (props: IconType) => renderIcon(NavDataIcon, props),
NavExplore: (props: IconType) => renderIcon(NavExploreIcon, props),
NavHome: (props: IconType) => renderIcon(NavHomeIcon, props),
NavLab: (props: IconType) => renderIcon(NavLabIcon, props),
Note: (props: IconType) => renderIcon(NoteIcon, props),
Offline: (props: IconType) => renderIcon(OfflineIcon, props),
Paperclip: (props: IconType) => renderIcon(PaperclipIcon, props),
Placeholder: (props: IconType) => renderIcon(PlaceholderIcon, props),
Plus: (props: IconType) => renderIcon(PlusIcon, props),
PlusLarge: (props: IconType) => renderIcon(PlusLargeIcon, props),
PlusSmall: (props: IconType) => renderIcon(PlusSmallIcon, props),
PlusSolid: (props: IconType) => renderIcon(PlusSolidIcon, props),
Queued: (props: IconType) => renderIcon(QueuedIcon, props),
Refresh: (props: IconType) => renderIcon(RefreshIcon, props),
Running: (props: IconType) => renderIcon(RunningIcon, props),
Save: (props: IconType) => renderIcon(SaveIcon, props),
SQL: (props: IconType) => renderIcon(SQLIcon, props),
Search: (props: IconType) => renderIcon(SearchIcon, props),
Server: (props: IconType) => renderIcon(ServerIcon, props),
Share: (props: IconType) => renderIcon(ShareIcon, props),
Slack: (props: IconType) => renderIcon(SlackIcon, props),
SortAsc: (props: IconType) => renderIcon(SortAscIcon, props),
SortDesc: (props: IconType) => renderIcon(SortDescIcon, props),
Sort: (props: IconType) => renderIcon(SortIcon, props),
Table: (props: IconType) => renderIcon(TableIcon, props),
Tag: (props: IconType) => renderIcon(TagIcon, props),
Trash: (props: IconType) => renderIcon(TrashIcon, props),
TriangleChange: (props: IconType) => renderIcon(TriangleChangeIcon, props),
TriangleDown: (props: IconType) => renderIcon(TriangleDownIcon, props),
TriangleUp: (props: IconType) => renderIcon(TriangleUpIcon, props),
UpLevel: (props: IconType) => renderIcon(UpLevelIcon, props),
User: (props: IconType) => renderIcon(UserIcon, props),
Warning: (props: IconType) => renderIcon(WarningIcon, props),
WarningSolid: (props: IconType) => renderIcon(WarningSolidIcon, props),
XLarge: (props: IconType) => renderIcon(XLargeIcon, props),
XSmall: (props: IconType) => renderIcon(XSmallIcon, props),
};

View File

@@ -0,0 +1,166 @@
/**
* 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 from 'react';
import _ from 'lodash';
import AntdEnhancedIcons from './AntdEnhanced';
import Icon from './Icon';
import IconType from './IconType';
const IconFileNames = [
'alert',
'alert_solid',
'alert_solid_small',
'binoculars',
'bolt',
'bolt_small',
'bolt_small_run',
'calendar',
'cancel',
'cancel_solid',
'cancel-x',
'card_view',
'cards',
'cards_locked',
'caret_down',
'caret_left',
'caret_right',
'caret_up',
'certified',
'check',
'checkbox-half',
'checkbox-off',
'checkbox-on',
'circle_check',
'circle_check_solid',
'circle',
'clock',
'close',
'code',
'cog',
'collapse',
'color_palette',
'components',
'copy',
'cursor_target',
'database',
'dataset_physical',
'dataset_virtual_greyscale',
'dataset_virtual',
'download',
'edit_alt',
'edit',
'email',
'error',
'error_solid',
'error_solid_small',
'exclamation',
'expand',
'eye',
'eye_slash',
'favorite-selected',
'favorite_small_selected',
'favorite-unselected',
'field_abc',
'field_boolean',
'field_date',
'field_derived',
'field_num',
'field_struct',
'file',
'filter',
'filter_small',
'folder',
'full',
'function_x',
'gear',
'grid',
'image',
'import',
'info',
'info-solid',
'info_solid_small',
'join',
'keyboard',
'layers',
'lightbulb',
'link',
'list',
'list_view',
'location',
'lock_locked',
'lock_unlocked',
'map',
'message',
'minus',
'minus_solid',
'more_horiz',
'move',
'nav_charts',
'nav_dashboard',
'nav_data',
'nav_explore',
'nav_home',
'nav_lab',
'note',
'offline',
'paperclip',
'placeholder',
'plus',
'plus_large',
'plus_small',
'plus_solid',
'queued',
'refresh',
'running',
'save',
'sql',
'search',
'server',
'share',
'slack',
'sort_asc',
'sort_desc',
'sort',
'table',
'tag',
'trash',
'triangle_change',
'triangle_down',
'triangle_up',
'up-level',
'user',
'warning',
'warning_solid',
'x-large',
'x-small',
];
const iconOverrides: Record<string, React.FC> = {};
IconFileNames.forEach(fileName => {
const keyName = _.startCase(fileName).replace(/ /g, '');
iconOverrides[keyName] = (props: IconType) => (
<Icon fileName={fileName} {...props} />
);
});
export default {
...AntdEnhancedIcons,
...iconOverrides,
};

View File

@@ -34,7 +34,7 @@ describe('LastUpdated', () => {
it('renders a refresh action', () => {
const mockAction = jest.fn();
wrapper = mount(<LastUpdated updatedAt={updatedAt} update={mockAction} />);
const props = wrapper.find('[data-test="refresh"]').props();
const props = wrapper.find('[data-test="refresh"]').first().props();
if (props.onClick) {
props.onClick({} as React.MouseEvent);
}

View File

@@ -20,7 +20,7 @@ import React from 'react';
import { render, screen, waitFor } from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event';
import { supersetTheme } from '@superset-ui/core';
import Icon from 'src/components/Icon';
import Icons from 'src/components/Icons';
import Button from 'src/components/Button';
import Popover from '.';
@@ -47,15 +47,13 @@ test('it should not render a title or content when not visible', () => {
expect(title).not.toBeInTheDocument();
});
test('renders with icon child', () => {
test('renders with icon child', async () => {
render(
<Popover content="Content sample" title="Popover title">
<Icon name="alert" role="img">
Click me
</Icon>
<Icons.Alert>Click me</Icons.Alert>
</Popover>,
);
expect(screen.getByRole('img')).toBeInTheDocument();
expect(await screen.findByRole('img')).toBeInTheDocument();
});
test('fires an event when visibility is changed', async () => {

View File

@@ -21,7 +21,7 @@ import { render, screen } from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event';
import { supersetTheme } from '@superset-ui/core';
import Button from 'src/components/Button';
import Icon from 'src/components/Icon';
import Icons from 'src/components/Icons';
import { Tooltip } from '.';
test('starts hidden with default props', () => {
@@ -62,9 +62,7 @@ test('renders with theme', () => {
test('renders with icon child', async () => {
render(
<Tooltip title="Simple tooltip">
<Icon name="alert" role="img">
Hover me
</Icon>
<Icons.Alert>Hover me</Icons.Alert>
</Tooltip>,
);
userEvent.hover(screen.getByRole('img'));

View File

@@ -18,7 +18,7 @@
*/
import React from 'react';
import { useTheme, SafeMarkdown } from '@superset-ui/core';
import Icon from 'src/components/Icon';
import Icons from 'src/components/Icons';
import { Tooltip } from 'src/components/Tooltip';
export interface WarningIconWithTooltipProps {
@@ -28,7 +28,6 @@ export interface WarningIconWithTooltipProps {
function WarningIconWithTooltip({
warningMarkdown,
size = 24,
}: WarningIconWithTooltipProps) {
const theme = useTheme();
return (
@@ -36,12 +35,7 @@ function WarningIconWithTooltip({
id="warning-tooltip"
title={<SafeMarkdown source={warningMarkdown} />}
>
<Icon
color={theme.colors.alert.base}
height={size}
width={size}
name="alert-solid"
/>
<Icons.AlertSolid iconColor={theme.colors.alert.base} />
</Tooltip>
);
}

View File

@@ -19,6 +19,7 @@
import React from 'react';
import cx from 'classnames';
import Icon from 'src/components/Icon';
import Icons from 'src/components/Icons';
import DetailsPanelPopover from './DetailsPanel';
import { Pill } from './Styles';
import { Indicator } from './selectors';
@@ -77,7 +78,7 @@ const FiltersBadge = ({
{incompatibleIndicators.length ? (
<>
{' '}
<Icon name="alert-solid" />
<Icons.AlertSolid />
<span data-test="incompatible-filter-count">
{incompatibleIndicators.length}
</span>

View File

@@ -143,7 +143,7 @@ test('should search and render matching metrics', async () => {
});
});
test('should render a warning', () => {
test('should render a warning', async () => {
const deprecatedDatasource = {
...datasource,
extra: JSON.stringify({ warning_markdown: 'This is a warning.' }),
@@ -160,5 +160,7 @@ test('should render a warning', () => {
},
}),
);
expect(screen.getByTestId('alert-solid')).toBeInTheDocument();
expect(
await screen.findByRole('img', { name: 'alert-solid' }),
).toBeInTheDocument();
});

View File

@@ -23,6 +23,7 @@ import { t, styled, supersetTheme } from '@superset-ui/core';
import { Dropdown, Menu } from 'src/common/components';
import { Tooltip } from 'src/components/Tooltip';
import Icon from 'src/components/Icon';
import Icons from 'src/components/Icons';
import ChangeDatasourceModal from 'src/datasource/ChangeDatasourceModal';
import DatasourceModal from 'src/datasource/DatasourceModal';
import { postForm } from 'src/explore/exploreUtils';
@@ -202,10 +203,7 @@ class DatasourceControl extends React.PureComponent {
)}
{healthCheckMessage && (
<Tooltip title={healthCheckMessage}>
<Icon
name="alert-solid"
color={supersetTheme.colors.warning.base}
/>
<Icons.AlertSolid iconColor={supersetTheme.colors.warning.base} />
</Tooltip>
)}
{extra?.warning_markdown && ( // eslint-disable-line camelcase

View File

@@ -16,34 +16,34 @@
* specific language governing permissions and limitations
* under the License.
*/
import { styled, t } from '@superset-ui/core';
import { t, supersetTheme, withTheme } from '@superset-ui/core';
import React from 'react';
import { Tooltip } from 'src/components/Tooltip';
import Icon, { IconName } from 'src/components/Icon';
import Icons from 'src/components/Icons';
import { AlertState } from '../types';
const StatusIcon = styled(Icon, {
shouldForwardProp: prop => prop !== 'status' && prop !== 'isReportEnabled',
})<{ status: string; isReportEnabled: boolean }>`
color: ${({ status, theme, isReportEnabled }) => {
switch (status) {
case AlertState.working:
return theme.colors.primary.base;
case AlertState.error:
return theme.colors.error.base;
case AlertState.success:
return isReportEnabled
? theme.colors.success.base
: theme.colors.alert.base;
case AlertState.noop:
return theme.colors.success.base;
case AlertState.grace:
return theme.colors.alert.base;
default:
return theme.colors.grayscale.base;
}
}};
`;
function getStatusColor(
status: string,
isReportEnabled: boolean,
theme: typeof supersetTheme,
) {
switch (status) {
case AlertState.working:
return theme.colors.primary.base;
case AlertState.error:
return theme.colors.error.base;
case AlertState.success:
return isReportEnabled
? theme.colors.success.base
: theme.colors.alert.base;
case AlertState.noop:
return theme.colors.success.base;
case AlertState.grace:
return theme.colors.alert.base;
default:
return theme.colors.grayscale.base;
}
}
export default function AlertStatusIcon({
state,
@@ -53,59 +53,58 @@ export default function AlertStatusIcon({
isReportEnabled: boolean;
}) {
const lastStateConfig = {
name: '',
icon: Icons.Check,
label: '',
status: '',
};
switch (state) {
case AlertState.success:
lastStateConfig.name = isReportEnabled ? 'check' : 'alert-solid-small';
lastStateConfig.icon = isReportEnabled
? Icons.Check
: Icons.AlertSolidSmall;
lastStateConfig.label = isReportEnabled
? t('Report sent')
: t('Alert triggered, notification sent');
lastStateConfig.status = AlertState.success;
break;
case AlertState.working:
lastStateConfig.name = 'running';
lastStateConfig.icon = Icons.Running;
lastStateConfig.label = isReportEnabled
? t('Report sending')
: t('Alert running');
lastStateConfig.status = AlertState.working;
break;
case AlertState.error:
lastStateConfig.name = 'x-small';
lastStateConfig.icon = Icons.XSmall;
lastStateConfig.label = isReportEnabled
? t('Report failed')
: t('Alert failed');
lastStateConfig.status = AlertState.error;
break;
case AlertState.noop:
lastStateConfig.name = 'check';
lastStateConfig.icon = Icons.Check;
lastStateConfig.label = t('Nothing triggered');
lastStateConfig.status = AlertState.noop;
break;
case AlertState.grace:
lastStateConfig.name = 'alert-solid-small';
lastStateConfig.icon = Icons.AlertSolidSmall;
lastStateConfig.label = t('Alert Triggered, In Grace Period');
lastStateConfig.status = AlertState.grace;
break;
default:
lastStateConfig.name = 'check';
lastStateConfig.icon = Icons.Check;
lastStateConfig.label = t('Nothing triggered');
lastStateConfig.status = AlertState.noop;
}
const Icon = lastStateConfig.icon;
const AlertIcon = withTheme(({ theme }) => (
<Icon
iconColor={getStatusColor(lastStateConfig.status, isReportEnabled, theme)}
/>
));
return (
<Tooltip title={lastStateConfig.label} placement="bottomLeft">
<StatusIcon
name={lastStateConfig.name as IconName}
status={lastStateConfig.status}
isReportEnabled={isReportEnabled}
viewBox={
lastStateConfig.name === 'alert-solid-small'
? '-6 -6 24 24'
: '0 0 24 24'
}
/>
<AlertIcon />
</Tooltip>
);
}