This is an automated email from the ASF dual-hosted git repository. maximebeauchemin pushed a commit to branch alerts_refactor in repository https://gitbox.apache.org/repos/asf/superset.git
commit bd2e067a0da6f3eb6dfa4672c01d06045852da22 Author: Maxime Beauchemin <[email protected]> AuthorDate: Tue Jan 14 23:28:19 2025 -0800 chore: refactor Alert-related components --- .../packages/superset-ui-core/src/style/index.tsx | 7 - .../src/ReactCalendar.jsx | 8 +- .../spec/helpers/{theming.ts => theming.tsx} | 7 +- superset-frontend/src/components/Alert/index.tsx | 46 +-- .../src/components/AlteredSliceTag/index.tsx | 2 +- .../ErrorMessage/DatabaseErrorMessage.tsx | 28 +- .../ErrorMessage/DatasetNotFoundErrorMessage.tsx | 12 +- .../components/ErrorMessage/ErrorAlert.stories.tsx | 151 +++++++++ .../components/ErrorMessage/ErrorAlert.test.tsx | 232 +++++--------- .../src/components/ErrorMessage/ErrorAlert.tsx | 342 +++++++-------------- .../ErrorMessage/ErrorMessageWithStackTrace.tsx | 37 +-- .../ErrorMessage/InvalidSQLErrorMessage.test.tsx | 136 ++++---- .../ErrorMessage/InvalidSQLErrorMessage.tsx | 16 +- .../ErrorMessage/OAuth2RedirectMessage.tsx | 9 +- .../ErrorMessage/ParameterErrorMessage.test.tsx | 2 +- .../ErrorMessage/ParameterErrorMessage.tsx | 15 +- .../ErrorMessage/TimeoutErrorMessage.tsx | 15 +- .../src/components/Label/Label.stories.tsx | 1 - superset-frontend/src/components/Label/index.tsx | 15 +- .../components/WarningIconWithTooltip/index.tsx | 2 +- .../DashboardBuilder/DashboardWrapper.tsx | 2 +- .../src/explore/components/ControlHeader.tsx | 9 +- .../explore/components/ControlPanelsContainer.tsx | 6 +- .../src/explore/components/ExploreAlert.tsx | 8 +- .../controls/ColorSchemeControl/index.tsx | 2 +- .../FormattingPopoverContent.tsx | 4 +- .../features/alerts/components/AlertStatusIcon.tsx | 4 +- superset-frontend/src/theme/index.ts | 9 - 28 files changed, 481 insertions(+), 646 deletions(-) diff --git a/superset-frontend/packages/superset-ui-core/src/style/index.tsx b/superset-frontend/packages/superset-ui-core/src/style/index.tsx index ee0b6e10ac..c4964172be 100644 --- a/superset-frontend/packages/superset-ui-core/src/style/index.tsx +++ b/superset-frontend/packages/superset-ui-core/src/style/index.tsx @@ -98,13 +98,6 @@ const defaultTheme = { light2: '#FAEDEE', }, warning: { - base: '#FF7F44', - dark1: '#BF5E33', - dark2: '#7F3F21', - light1: '#FEC0A1', - light2: '#FFF2EC', - }, - alert: { base: '#FCC700', dark1: '#BC9501', dark2: '#7D6300', diff --git a/superset-frontend/plugins/legacy-plugin-chart-calendar/src/ReactCalendar.jsx b/superset-frontend/plugins/legacy-plugin-chart-calendar/src/ReactCalendar.jsx index f3fcc807d8..46cb1de852 100644 --- a/superset-frontend/plugins/legacy-plugin-chart-calendar/src/ReactCalendar.jsx +++ b/superset-frontend/plugins/legacy-plugin-chart-calendar/src/ReactCalendar.jsx @@ -157,13 +157,13 @@ export default styled(Calendar)` } .cal-heatmap-container .q1 { - background-color: ${theme.colors.alert.light2}; - fill: ${theme.colors.alert.light2}; + background-color: ${theme.colors.warning.light2}; + fill: ${theme.colors.warning.light2}; } .cal-heatmap-container .q2 { - background-color: ${theme.colors.alert.light1}; - fill: ${theme.colors.alert.light1}; + background-color: ${theme.colors.warning.light1}; + fill: ${theme.colors.warning.light1}; } .cal-heatmap-container .q3 { diff --git a/superset-frontend/spec/helpers/theming.ts b/superset-frontend/spec/helpers/theming.tsx similarity index 85% rename from superset-frontend/spec/helpers/theming.ts rename to superset-frontend/spec/helpers/theming.tsx index e364b41345..c54d46a384 100644 --- a/superset-frontend/spec/helpers/theming.ts +++ b/superset-frontend/spec/helpers/theming.tsx @@ -18,9 +18,11 @@ */ import { shallow as enzymeShallow, mount as enzymeMount } from 'enzyme'; // eslint-disable-next-line no-restricted-imports -import { supersetTheme } from '@superset-ui/core'; import { ReactElement } from 'react'; +import { render } from '@testing-library/react'; +import { ThemeProvider, supersetTheme } from '@superset-ui/core'; import { ProviderWrapper } from './ProviderWrapper'; +import '@testing-library/jest-dom'; type optionsType = { wrappingComponentProps?: any; @@ -55,3 +57,6 @@ export function styledShallow( }, }); } + +export const renderWithTheme = (component: JSX.Element) => + render(<ThemeProvider theme={supersetTheme}>{component}</ThemeProvider>); diff --git a/superset-frontend/src/components/Alert/index.tsx b/superset-frontend/src/components/Alert/index.tsx index 6a85739950..c567091aea 100644 --- a/superset-frontend/src/components/Alert/index.tsx +++ b/superset-frontend/src/components/Alert/index.tsx @@ -19,8 +19,6 @@ import { PropsWithChildren } from 'react'; import { Alert as AntdAlert } from 'antd-v5'; import { AlertProps as AntdAlertProps } from 'antd-v5/lib/alert'; -import { css, useTheme } from '@superset-ui/core'; -import Icons from 'src/components/Icons'; export type AlertProps = PropsWithChildren< Omit<AntdAlertProps, 'children'> & { roomBelow?: boolean } @@ -32,59 +30,17 @@ export default function Alert(props: AlertProps) { description, showIcon = true, closable = true, - roomBelow = false, children, } = props; - const theme = useTheme(); - const { colors } = theme; - const { alert: alertColor, error, info, success } = colors; - - let baseColor = info; - let AlertIcon = Icons.InfoSolid; - if (type === 'error') { - baseColor = error; - AlertIcon = Icons.ErrorSolid; - } else if (type === 'warning') { - baseColor = alertColor; - AlertIcon = Icons.AlertSolid; - } else if (type === 'success') { - baseColor = success; - AlertIcon = Icons.CircleCheckSolid; - } - return ( <AntdAlert role="alert" aria-live={type === 'error' ? 'assertive' : 'polite'} showIcon={showIcon} - icon={ - showIcon && ( - <span - role="img" - aria-label={`${type} icon`} - style={{ - color: baseColor.base, - }} - > - <AlertIcon /> - </span> - ) - } - closeIcon={closable && <Icons.XSmall aria-label="close icon" />} + closeIcon={closable} message={children || 'Default message'} description={description} - css={css` - margin-bottom: ${roomBelow ? theme.gridUnit * 4 : 0}px; - a { - text-decoration: underline; - } - .antd5-alert-message { - font-weight: ${description - ? theme.typography.weights.bold - : 'inherit'}; - } - `} {...props} /> ); diff --git a/superset-frontend/src/components/AlteredSliceTag/index.tsx b/superset-frontend/src/components/AlteredSliceTag/index.tsx index 9aca6b46b8..46fc58b3ff 100644 --- a/superset-frontend/src/components/AlteredSliceTag/index.tsx +++ b/superset-frontend/src/components/AlteredSliceTag/index.tsx @@ -221,7 +221,7 @@ const AlteredSliceTag: FC<AlteredSliceTagProps> = props => { <Label icon={<Icons.Warning iconSize="m" />} className="label" - type="alert" + type="warning" onClick={() => {}} > {t('Altered')} diff --git a/superset-frontend/src/components/ErrorMessage/DatabaseErrorMessage.tsx b/superset-frontend/src/components/ErrorMessage/DatabaseErrorMessage.tsx index fed692f9e6..7e9e69ea7e 100644 --- a/superset-frontend/src/components/ErrorMessage/DatabaseErrorMessage.tsx +++ b/superset-frontend/src/components/ErrorMessage/DatabaseErrorMessage.tsx @@ -34,12 +34,16 @@ interface DatabaseErrorExtra { function DatabaseErrorMessage({ error, - source = 'dashboard', + source, subtitle, }: ErrorMessageComponentProps<DatabaseErrorExtra | null>) { const { extra, level, message } = error; - const isVisualization = ['dashboard', 'explore'].includes(source); + const isVisualization = ['dashboard', 'explore'].includes(source || ''); + const [firstLine, ...remainingLines] = message.split('\n'); + const alertMessage = firstLine; + const alertDescription = + remainingLines.length > 0 ? remainingLines.join('\n') : null; const body = extra && ( <> @@ -75,23 +79,13 @@ function DatabaseErrorMessage({ </> ); - const copyText = extra?.issue_codes - ? t('%(message)s\nThis may be triggered by: \n%(issues)s', { - message, - issues: extra.issue_codes - .map(issueCode => issueCode.message) - .join('\n'), - }) - : message; - return ( <ErrorAlert - title={t('%s Error', extra?.engine_name || t('DB engine'))} - subtitle={subtitle} - level={level} - source={source} - copyText={copyText} - body={body} + errorType={t('%s Error', extra?.engine_name || t('DB engine'))} + message={alertMessage} + description={alertDescription} + type={level} + descriptionDetails={body} /> ); } diff --git a/superset-frontend/src/components/ErrorMessage/DatasetNotFoundErrorMessage.tsx b/superset-frontend/src/components/ErrorMessage/DatasetNotFoundErrorMessage.tsx index 31f86bfcfc..238b345c34 100644 --- a/superset-frontend/src/components/ErrorMessage/DatasetNotFoundErrorMessage.tsx +++ b/superset-frontend/src/components/ErrorMessage/DatasetNotFoundErrorMessage.tsx @@ -23,19 +23,15 @@ import ErrorAlert from './ErrorAlert'; function DatasetNotFoundErrorMessage({ error, - source = 'dashboard', subtitle, }: ErrorMessageComponentProps) { const { level, message } = error; - return ( <ErrorAlert - title={t('Missing dataset')} - subtitle={subtitle} - level={level} - source={source} - copyText={message} - body={null} + errorType={t('Missing dataset')} + message={subtitle} + description={message} + type={level} /> ); } diff --git a/superset-frontend/src/components/ErrorMessage/ErrorAlert.stories.tsx b/superset-frontend/src/components/ErrorMessage/ErrorAlert.stories.tsx new file mode 100644 index 0000000000..b7595c00fa --- /dev/null +++ b/superset-frontend/src/components/ErrorMessage/ErrorAlert.stories.tsx @@ -0,0 +1,151 @@ +/** + * 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 { Meta, StoryFn } from '@storybook/react'; +import { Layout, Row, Col, Card } from 'antd-v5'; +import ErrorAlert from './ErrorAlert'; + +const { Content } = Layout; + +const longDescription = `This is a detailed description to test long content display. +Line breaks are included here to demonstrate pre-wrap styling. +This is useful for verbose error messages.`; + +const sqlErrorDescription = `SQL Error: Syntax error near unexpected token. +Please check your query and ensure it follows the correct syntax.`; + +const detailsExample = `Additional details about the issue are provided here. +This content is shown when the user clicks "Show more".`; + +const ErrorCard: React.FC<{ children: React.ReactNode }> = ({ children }) => ( + <Card>{children}</Card> +); + +export default { + title: 'Components/ErrorAlert', + component: ErrorAlert, +} as Meta; + +export const Gallery: StoryFn = () => ( + <Layout> + <Content style={{ padding: '24px' }}> + <h2>Non-Compact Errors</h2> + <Row gutter={[16, 16]}> + <Col xs={48} sm={24} md={16} lg={16} xl={12}> + <ErrorCard> + <ErrorAlert message="Only message props was passed here" /> + </ErrorCard> + </Col> + <Col xs={48} sm={24} md={16} lg={16} xl={12}> + <ErrorCard> + <ErrorAlert + errorType="Database Connection Error" + type="warning" + message="Failed to connect to database" + descriptionDetails={detailsExample} + descriptionDetailsCollapsed + /> + </ErrorCard> + </Col> + <Col xs={48} sm={24} md={16} lg={16} xl={12}> + <ErrorCard> + <ErrorAlert + errorType="Error" + message="SQL Syntax Error - No defaults set here" + description={sqlErrorDescription} + descriptionDetails={detailsExample} + descriptionDetailsCollapsed + descriptionPre + /> + </ErrorCard> + </Col> + <Col xs={48} sm={24} md={16} lg={16} xl={12}> + <ErrorCard> + <ErrorAlert + errorType="Error" + message="See the details below" + type="error" + description={longDescription} + descriptionDetails={detailsExample} + descriptionDetailsCollapsed={false} + /> + </ErrorCard> + </Col> + <Col xs={48} sm={24} md={16} lg={16} xl={12}> + <ErrorCard> + <ErrorAlert + errorType="Informational Warning" + message="This is a non-pre-wrap styled description" + type="info" + description={longDescription} + descriptionDetails={detailsExample} + descriptionDetailsCollapsed={false} + descriptionPre={false} + /> + </ErrorCard> + </Col> + <Col xs={24} sm={12} md={8} lg={8} xl={6}> + <ErrorCard> + <ErrorAlert + errorType="Error" + message="Something went wrong" + type="error" + /> + </ErrorCard> + </Col> + <Col xs={24} sm={12} md={8} lg={8} xl={6}> + <ErrorCard> + <ErrorAlert + errorType="Warning" + message="Be cautious" + type="warning" + /> + </ErrorCard> + </Col> + </Row> + <h2>Compact Errors (with Modal)</h2> + <Row gutter={[16, 16]}> + <Col xs={24} sm={12} md={8} lg={8} xl={6}> + <ErrorCard> + <ErrorAlert + errorType="Error" + message="Compact mode example" + type="error" + compact + descriptionDetailsCollapsed + description={sqlErrorDescription} + descriptionDetails={detailsExample} + /> + </ErrorCard> + </Col> + <Col xs={24} sm={12} md={8} lg={8} xl={6}> + <ErrorCard> + <ErrorAlert + errorType="Warning" + message="Compact mode example" + type="warning" + compact + descriptionDetails={detailsExample} + descriptionDetailsCollapsed + /> + </ErrorCard> + </Col> + </Row> + </Content> + </Layout> +); diff --git a/superset-frontend/src/components/ErrorMessage/ErrorAlert.test.tsx b/superset-frontend/src/components/ErrorMessage/ErrorAlert.test.tsx index 9f9fef9b75..7407927875 100644 --- a/superset-frontend/src/components/ErrorMessage/ErrorAlert.test.tsx +++ b/superset-frontend/src/components/ErrorMessage/ErrorAlert.test.tsx @@ -17,168 +17,76 @@ * under the License. */ -import userEvent from '@testing-library/user-event'; -import { render, screen } from 'spec/helpers/testing-library'; -import { ErrorLevel, ErrorSource, supersetTheme } from '@superset-ui/core'; -import { isCurrentUserBot } from 'src/utils/isBot'; +import { screen, fireEvent } from '@testing-library/react'; +import { renderWithTheme } from 'spec/helpers/theming'; import ErrorAlert from './ErrorAlert'; -jest.mock( - 'src/components/Icons/Icon', - () => - ({ fileName }: { fileName: string }) => ( - <span role="img" aria-label={fileName.replace('_', '-')} /> - ), -); - -jest.mock('src/utils/isBot', () => ({ - isCurrentUserBot: jest.fn(), -})); - -const mockedProps = { - body: 'Error body', - level: 'warning' as ErrorLevel, - copyText: 'Copy text', - subtitle: 'Error subtitle', - title: 'Error title', - source: 'dashboard' as ErrorSource, - description: 'we are unable to connect db.', -}; - -beforeEach(() => { - (isCurrentUserBot as jest.Mock).mockReturnValue(false); -}); - -afterEach(() => { - jest.clearAllMocks(); -}); - -test('should render', () => { - const { container } = render(<ErrorAlert {...mockedProps} />); - expect(container).toBeInTheDocument(); -}); - -test('should render warning icon', () => { - render(<ErrorAlert {...mockedProps} />); - expect( - screen.getByRole('img', { name: 'warning-solid' }), - ).toBeInTheDocument(); -}); - -test('should render error icon', () => { - const errorProps = { - ...mockedProps, - level: 'error' as ErrorLevel, - }; - render(<ErrorAlert {...errorProps} />); - expect(screen.getByRole('img', { name: 'error-solid' })).toBeInTheDocument(); -}); - -test('should render the error title', () => { - const titleProps = { - ...mockedProps, - source: 'explore' as ErrorSource, - }; - render(<ErrorAlert {...titleProps} />); - expect(screen.getByText('Error title')).toBeInTheDocument(); -}); - -test('should render the error description', () => { - render(<ErrorAlert {...mockedProps} />, { useRedux: true }); - expect(screen.getByText('we are unable to connect db.')).toBeInTheDocument(); -}); - -test('should render the error subtitle', () => { - render(<ErrorAlert {...mockedProps} />, { useRedux: true }); - const button = screen.getByText('See more'); - userEvent.click(button); - expect(screen.getByText('Error subtitle')).toBeInTheDocument(); -}); - -test('should render the error body', () => { - render(<ErrorAlert {...mockedProps} />, { useRedux: true }); - const button = screen.getByText('See more'); - userEvent.click(button); - expect(screen.getByText('Error body')).toBeInTheDocument(); -}); - -test('should render the See more button', () => { - const seemoreProps = { - ...mockedProps, - source: 'explore' as ErrorSource, - }; - render(<ErrorAlert {...seemoreProps} />); - expect(screen.getByRole('button')).toBeInTheDocument(); - expect(screen.getByText('See more')).toBeInTheDocument(); -}); - -test('should render the error subtitle and body defaultly for the screen capture request', () => { - const seemoreProps = { - ...mockedProps, - source: 'explore' as ErrorSource, - }; - (isCurrentUserBot as jest.Mock).mockReturnValue(true); - render(<ErrorAlert {...seemoreProps} />); - expect(screen.getByText('Error subtitle')).toBeInTheDocument(); - expect(screen.getByText('Error body')).toBeInTheDocument(); -}); - -test('should render the modal', () => { - render(<ErrorAlert {...mockedProps} />, { useRedux: true }); - const button = screen.getByText('See more'); - userEvent.click(button); - expect(screen.getByRole('dialog')).toBeInTheDocument(); - expect(screen.getByText('Close')).toBeInTheDocument(); -}); - -test('should NOT render the modal', () => { - const expandableProps = { - ...mockedProps, - source: 'explore' as ErrorSource, - }; - render(<ErrorAlert {...expandableProps} />, { useRedux: true }); - const button = screen.getByText('See more'); - userEvent.click(button); - expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); -}); - -test('should render the See less button', () => { - const expandableProps = { - ...mockedProps, - source: 'explore' as ErrorSource, - }; - render(<ErrorAlert {...expandableProps} />); - const button = screen.getByText('See more'); - userEvent.click(button); - expect(screen.getByText('See less')).toBeInTheDocument(); - expect(screen.queryByText('See more')).not.toBeInTheDocument(); -}); - -test('should render the Copy button', () => { - render(<ErrorAlert {...mockedProps} />, { useRedux: true }); - const button = screen.getByText('See more'); - userEvent.click(button); - expect(screen.getByText('Copy message')).toBeInTheDocument(); -}); - -test('should render with warning theme', () => { - render(<ErrorAlert {...mockedProps} />); - expect(screen.getByRole('alert')).toHaveStyle( - ` - backgroundColor: ${supersetTheme.colors.warning.light2}; - `, - ); -}); - -test('should render with error theme', () => { - const errorProps = { - ...mockedProps, - level: 'error' as ErrorLevel, - }; - render(<ErrorAlert {...errorProps} />); - expect(screen.getByRole('alert')).toHaveStyle( - ` - backgroundColor: ${supersetTheme.colors.error.light2}; - `, - ); +describe('ErrorAlert', () => { + it('renders the error message correctly', () => { + renderWithTheme( + <ErrorAlert + errorType="Error" + message="Something went wrong" + type="error" + />, + ); + + expect(screen.getByText('Error')).toBeInTheDocument(); + expect(screen.getByText('Something went wrong')).toBeInTheDocument(); + }); + + it('renders the description when provided', () => { + const description = 'This is a detailed description'; + renderWithTheme( + <ErrorAlert + errorType="Error" + message="Something went wrong" + type="error" + description={description} + />, + ); + + expect(screen.getByText(description)).toBeInTheDocument(); + }); + + it('toggles description details visibility when show more/less is clicked', () => { + const descriptionDetails = 'Additional details about the error.'; + renderWithTheme( + <ErrorAlert + errorType="Error" + message="Something went wrong" + type="error" + descriptionDetails={descriptionDetails} + descriptionDetailsCollapsed + />, + ); + + const showMoreButton = screen.getByText('See more'); + expect(showMoreButton).toBeInTheDocument(); + + fireEvent.click(showMoreButton); + expect(screen.getByText(descriptionDetails)).toBeInTheDocument(); + + const showLessButton = screen.getByText('See less'); + fireEvent.click(showLessButton); + expect(screen.queryByText(descriptionDetails)).not.toBeInTheDocument(); + }); + + it('renders compact mode with a tooltip and modal', () => { + renderWithTheme( + <ErrorAlert + errorType="Error" + message="Compact mode example" + type="error" + compact + descriptionDetails="Detailed description in compact mode." + />, + ); + + const iconTrigger = screen.getByText('Error'); + expect(iconTrigger).toBeInTheDocument(); + + fireEvent.click(iconTrigger); + expect(screen.getByText('Compact mode example')).toBeInTheDocument(); + }); }); diff --git a/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx b/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx index e441505beb..2d2c3d50b6 100644 --- a/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx +++ b/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx @@ -16,250 +16,128 @@ * specific language governing permissions and limitations * under the License. */ -import { useState, ReactNode } from 'react'; -import { - ErrorLevel, - ErrorSource, - styled, - useTheme, - t, -} from '@superset-ui/core'; -import { noOp } from 'src/utils/common'; -import Modal from 'src/components/Modal'; -import Button from 'src/components/Button'; -import { isCurrentUserBot } from 'src/utils/isBot'; - -import Icons from 'src/components/Icons'; -import CopyToClipboard from '../CopyToClipboard'; - -const ErrorAlertDiv = styled.div<{ level: ErrorLevel }>` - align-items: center; - background-color: ${({ level, theme }) => theme.colors[level].light2}; - border-radius: ${({ theme }) => theme.borderRadius}px; - border: 1px solid ${({ level, theme }) => theme.colors[level].base}; - color: ${({ level, theme }) => theme.colors[level].dark2}; - padding: ${({ theme }) => 2 * theme.gridUnit}px; - width: 100%; - - .top-row { - display: flex; - justify-content: space-between; - } - - .error-body { - padding-top: ${({ theme }) => theme.gridUnit}px; - padding-left: ${({ theme }) => 8 * theme.gridUnit}px; - } - - .icon { - margin-right: ${({ theme }) => 2 * theme.gridUnit}px; - } - - .link { - color: ${({ level, theme }) => theme.colors[level].dark2}; - text-decoration: underline; - &:focus-visible { - border: 1px solid ${({ theme }) => theme.colors.primary.base}; - padding: ${({ theme }) => theme.gridUnit / 2}px; - margin: -${({ theme }) => theme.gridUnit / 2 + 1}px; - border-radius: ${({ theme }) => theme.borderRadius}px; - } -`; - -const ErrorModal = styled(Modal)<{ level: ErrorLevel }>` - color: ${({ level, theme }) => theme.colors[level].dark2}; - overflow-wrap: break-word; - - .antd5-modal-header { - background-color: ${({ level, theme }) => theme.colors[level].light2}; - padding: ${({ theme }) => 4 * theme.gridUnit}px; - } - - .icon { - margin-right: ${({ theme }) => 2 * theme.gridUnit}px; - } - - .header { - display: flex; - align-items: center; - font-size: ${({ theme }) => theme.typography.sizes.l}px; - } -`; - -const LeftSideContent = styled.div` - align-items: center; - display: flex; -`; - -interface ErrorAlertProps { - body: ReactNode; - copyText?: string; - level: ErrorLevel; - source?: ErrorSource; - subtitle: ReactNode; - title: ReactNode; - description?: string; +import { useState } from 'react'; +import { Modal, Tooltip } from 'antd'; +import { ExclamationCircleOutlined, WarningOutlined } from '@ant-design/icons'; +import Alert from 'src/components/Alert'; +import { t, useTheme } from '@superset-ui/core'; + +export interface ErrorAlertProps { + errorType?: string; // Strong text on the first line + message: React.ReactNode | string; // Text shown on the first line + type?: 'warning' | 'error' | 'info'; // Allows only 'warning' or 'error' + description?: React.ReactNode; // Text shown under the first line, not collapsible + descriptionDetails?: React.ReactNode | string; // Text shown under the first line, collapsible + descriptionDetailsCollapsed?: boolean; // Hides the collapsible section unless "Show more" is clicked, default true + descriptionPre?: boolean; // Uses pre-style to break lines, default true + compact?: boolean; // Shows the error icon with tooltip and modal, default false + children?: React.ReactNode; // Additional content to show in the modal + closable?: boolean; // Show close button, default true + showIcon?: boolean; // Show icon, default true } -export default function ErrorAlert({ - body, - copyText, - level = 'error', - source = 'dashboard', - subtitle, - title, +const ErrorAlert: React.FC<ErrorAlertProps> = ({ + errorType = t('Error'), + message, + type = 'error', description, -}: ErrorAlertProps) { - const theme = useTheme(); - - const [isModalOpen, setIsModalOpen] = useState(false); - const [isBodyExpanded, setIsBodyExpanded] = useState(isCurrentUserBot()); + descriptionDetails, + descriptionDetailsCollapsed = true, + descriptionPre = true, + compact = false, + children, + closable = true, + showIcon = true, +}) => { + const [isDescriptionVisible, setIsDescriptionVisible] = useState( + !descriptionDetailsCollapsed, + ); + const [showModal, setShowModal] = useState(false); - const isExpandable = - isCurrentUserBot() || ['explore', 'sqllab'].includes(source); - const iconColor = theme.colors[level].base; + const toggleDescription = () => { + setIsDescriptionVisible(!isDescriptionVisible); + }; - return ( - <ErrorAlertDiv level={level} role="alert"> - <div className="top-row"> - <LeftSideContent> - {level === 'error' ? ( - <Icons.ErrorSolid className="icon" iconColor={iconColor} /> - ) : ( - <Icons.WarningSolid className="icon" iconColor={iconColor} /> + const theme = useTheme(); + const renderTrigger = () => { + const icon = + type === 'warning' ? <WarningOutlined /> : <ExclamationCircleOutlined />; + const color = + type === 'warning' ? theme.colors.warning.base : theme.colors.error.base; + return ( + <div style={{ cursor: 'pointer' }}> + <span style={{ color }}>{icon} </span> + {errorType} + </div> + ); + }; + const preStyle = { + whiteSpace: 'pre-wrap', + // fontFamily: theme.antd.fontFamilyCode, + }; + const renderDescription = () => ( + <div> + {description && ( + <p style={descriptionPre ? preStyle : {}} data-testid="description"> + {description} + </p> + )} + {descriptionDetails && ( + <div> + {isDescriptionVisible && ( + <p style={descriptionPre ? preStyle : {}}>{descriptionDetails}</p> )} - <strong>{title}</strong> - </LeftSideContent> - {!isExpandable && !description && ( <span role="button" tabIndex={0} - className="link" - onClick={() => setIsModalOpen(true)} - onKeyDown={event => { - if (event.key === 'Enter') { - setIsModalOpen(true); - } - }} + onClick={toggleDescription} + style={{ textDecoration: 'underline', cursor: 'pointer' }} > - {t('See more')} + {isDescriptionVisible ? t('See less') : t('See more')} </span> - )} - </div> - {description && ( - <div className="error-body"> - <p>{description}</p> - {!isExpandable && ( - <span - role="button" - tabIndex={0} - className="link" - onClick={() => setIsModalOpen(true)} - onKeyDown={event => { - if (event.key === 'Enter') { - setIsModalOpen(true); - } - }} - > - {t('See more')} - </span> - )} </div> )} - {isExpandable ? ( - <div className="error-body"> - <p>{subtitle}</p> - {body && ( - <> - {!isBodyExpanded && ( - <span - role="button" - tabIndex={0} - className="link" - onClick={() => setIsBodyExpanded(true)} - onKeyDown={event => { - if (event.key === 'Enter') { - setIsBodyExpanded(true); - } - }} - > - {t('See more')} - </span> - )} - {isBodyExpanded && ( - <> - <br /> - {body} - <span - role="button" - tabIndex={0} - className="link" - onClick={() => setIsBodyExpanded(false)} - onKeyDown={event => { - if (event.key === 'Enter') { - setIsBodyExpanded(false); - } - }} - > - {t('See less')} - </span> - </> - )} - </> - )} - </div> - ) : ( - <ErrorModal - level={level} - show={isModalOpen} - onHide={() => setIsModalOpen(false)} - destroyOnClose - title={ - <div className="header"> - {level === 'error' ? ( - <Icons.ErrorSolid className="icon" iconColor={iconColor} /> - ) : ( - <Icons.WarningSolid className="icon" iconColor={iconColor} /> - )} - <div className="title">{title}</div> - </div> - } - footer={ - <> - {copyText && ( - <CopyToClipboard - text={copyText} - shouldShowText={false} - wrapped={false} - copyNode={<Button onClick={noOp}>{t('Copy message')}</Button>} - /> - )} - <Button - cta - buttonStyle="primary" - onClick={() => setIsModalOpen(false)} - tabIndex={0} - onKeyDown={event => { - if (event.key === 'Enter') { - setIsModalOpen(false); - } - }} - > - {t('Close')} - </Button> - </> - } - > - <> - <p>{subtitle}</p> - {/* This break was in the original design of the modal but - the spacing looks really off if there is only - subtitle or a body */} - {subtitle && body && <br />} - {body} - </> - </ErrorModal> + </div> + ); + + const renderAlert = () => ( + <Alert + description={renderDescription()} + type={type} + showIcon + closable={closable} + > + <strong>{errorType}</strong> + {message && ( + <> + : <span>{message}</span> + </> )} - </ErrorAlertDiv> + </Alert> ); -} + + if (compact) { + return ( + <> + <Tooltip title={`${errorType}: ${message}`}> + <span role="button" onClick={() => setShowModal(true)} tabIndex={0}> + {renderTrigger()} + </span> + </Tooltip> + <Modal + title={errorType} + visible={showModal} + onCancel={() => setShowModal(false)} + footer={null} + > + {renderAlert()} + {children} + </Modal> + </> + ); + } + + return renderAlert(); +}; + +export default ErrorAlert; diff --git a/superset-frontend/src/components/ErrorMessage/ErrorMessageWithStackTrace.tsx b/superset-frontend/src/components/ErrorMessage/ErrorMessageWithStackTrace.tsx index 9597b16006..596d336bf8 100644 --- a/superset-frontend/src/components/ErrorMessage/ErrorMessageWithStackTrace.tsx +++ b/superset-frontend/src/components/ErrorMessage/ErrorMessageWithStackTrace.tsx @@ -32,6 +32,7 @@ type Props = { stackTrace?: string; source?: ErrorSource; description?: string; + descriptionDetails?: ReactNode; errorMitigationFunction?: () => void; fallback?: ReactNode; }; @@ -45,6 +46,7 @@ export default function ErrorMessageWithStackTrace({ stackTrace, source, description, + descriptionDetails, fallback, }: Props) { // Check if a custom error message component was registered for this message @@ -66,28 +68,27 @@ export default function ErrorMessageWithStackTrace({ if (fallback) { return <>{fallback}</>; } + const computedDescriptionDetails = + descriptionDetails || + (link || stackTrace ? ( + <> + {link && ( + <a href={link} target="_blank" rel="noopener noreferrer"> + (Request Access) + </a> + )} + <br /> + {stackTrace && <pre>{stackTrace}</pre>} + </> + ) : undefined); return ( <ErrorAlert - level="warning" - title={title} - subtitle={subtitle} - copyText={copyText} + type="warning" + errorType={title} + message={subtitle} description={description} - source={source} - body={ - link || stackTrace ? ( - <> - {link && ( - <a href={link} target="_blank" rel="noopener noreferrer"> - (Request Access) - </a> - )} - <br /> - {stackTrace && <pre>{stackTrace}</pre>} - </> - ) : undefined - } + descriptionDetails={computedDescriptionDetails} /> ); } diff --git a/superset-frontend/src/components/ErrorMessage/InvalidSQLErrorMessage.test.tsx b/superset-frontend/src/components/ErrorMessage/InvalidSQLErrorMessage.test.tsx index 38f4355679..79d2cff228 100644 --- a/superset-frontend/src/components/ErrorMessage/InvalidSQLErrorMessage.test.tsx +++ b/superset-frontend/src/components/ErrorMessage/InvalidSQLErrorMessage.test.tsx @@ -1,20 +1,19 @@ /** * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file + * 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 + * 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. + * 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 { render } from '@testing-library/react'; @@ -44,83 +43,66 @@ const defaultProps = { subtitle: 'Test subtitle', }; -const setup = (overrides = {}) => ( - <ThemeProvider theme={supersetTheme}> - <InvalidSQLErrorMessage {...defaultProps} {...overrides} />; - </ThemeProvider> -); - -// Mock the ErrorAlert component -jest.mock('./ErrorAlert', () => ({ - __esModule: true, - default: ({ - title, - subtitle, - level, - source, - body, - }: { - title: React.ReactNode; - subtitle?: React.ReactNode; - level: ErrorLevel; - source: ErrorSource; - body: React.ReactNode; - }) => ( - <div data-test="error-alert"> - <div data-test="title">{title}</div> - <div data-test="subtitle">{subtitle}</div> - <div data-test="level">{level}</div> - <div data-test="source">{source}</div> - <div data-test="body">{body}</div> - </div> - ), -})); +const renderComponent = (overrides = {}) => + render( + <ThemeProvider theme={supersetTheme}> + <InvalidSQLErrorMessage {...defaultProps} {...overrides} /> + </ThemeProvider>, + ); describe('InvalidSQLErrorMessage', () => { - it('renders ErrorAlert with correct props', () => { - const { getByTestId } = render(setup()); + it('renders the error message with correct properties', () => { + const { getByText } = renderComponent(); - expect(getByTestId('error-alert')).toBeInTheDocument(); - expect(getByTestId('title')).toHaveTextContent('Unable to parse SQL'); - expect(getByTestId('subtitle')).toHaveTextContent('Test subtitle'); - expect(getByTestId('level')).toHaveTextContent('error'); - expect(getByTestId('source')).toHaveTextContent('test'); + // Validate main properties + expect(getByText('Unable to parse SQL')).toBeInTheDocument(); + expect(getByText('Test subtitle')).toBeInTheDocument(); + expect(getByText('SELECT * FFROM table')).toBeInTheDocument(); }); - it('displays the error line and column indicator', () => { - const { getByTestId } = render(setup()); + it('displays the SQL error line and column indicator', () => { + const { getByText, container } = renderComponent(); + + // Validate SQL and caret indicator + expect(getByText('SELECT * FFROM table')).toBeInTheDocument(); - const body = getByTestId('body'); - expect(body).toContainHTML('<pre>SELECT * FFROM table</pre>'); - expect(body).toContainHTML('<pre> ^</pre>'); + // Check for caret (`^`) under the error column + const preTags = container.querySelectorAll('pre'); + const secondPre = preTags[1]; + console.log({ secondPre }); + expect(secondPre).toHaveTextContent('^'); }); - it('handles missing line number', () => { - const { getByTestId } = render( - setup({ - error: { - ...defaultProps.error, - extra: { ...defaultProps.error.extra, line: null }, - }, - }), - ); + it('handles missing line number gracefully', () => { + const overrides = { + error: { + ...defaultProps.error, + extra: { ...defaultProps.error.extra, line: null }, + }, + }; + const { getByText, container } = renderComponent(overrides); + + // Check that the full SQL is displayed + expect(getByText('SELECT * FFROM table')).toBeInTheDocument(); - const body = getByTestId('body'); - expect(body).toBeEmptyDOMElement(); + // Validate absence of caret indicator + const caret = container.querySelector('pre'); + expect(caret).not.toHaveTextContent('^'); }); + it('handles missing column number gracefully', () => { + const overrides = { + error: { + ...defaultProps.error, + extra: { ...defaultProps.error.extra, column: null }, + }, + }; + const { getByText, container } = renderComponent(overrides); - it('handles missing column number', () => { - const { getByTestId } = render( - setup({ - error: { - ...defaultProps.error, - extra: { ...defaultProps.error.extra, column: null }, - }, - }), - ); + // Check that the full SQL is displayed + expect(getByText('SELECT * FFROM table')).toBeInTheDocument(); - const body = getByTestId('body'); - expect(body).toHaveTextContent('SELECT * FFROM table'); - expect(body).not.toHaveTextContent('^'); + // Validate absence of caret indicator + const caret = container.querySelector('pre'); + expect(caret).not.toHaveTextContent('^'); }); }); diff --git a/superset-frontend/src/components/ErrorMessage/InvalidSQLErrorMessage.tsx b/superset-frontend/src/components/ErrorMessage/InvalidSQLErrorMessage.tsx index c7b701772d..21236e92a0 100644 --- a/superset-frontend/src/components/ErrorMessage/InvalidSQLErrorMessage.tsx +++ b/superset-frontend/src/components/ErrorMessage/InvalidSQLErrorMessage.tsx @@ -40,21 +40,23 @@ function InvalidSQLErrorMessage({ const { sql, line, column } = extra; const lines = sql.split('\n'); - const errorLine = line !== null ? lines[line - 1] : null; + let errorLine; + if (line !== null) errorLine = lines[line - 1]; + else if (lines.length > 0) { + errorLine = lines[0]; + } const body = errorLine && ( <> <pre>{errorLine}</pre> {column !== null && <pre>{' '.repeat(column - 1)}^</pre>} </> ); - return ( <ErrorAlert - title={t('Unable to parse SQL')} - subtitle={subtitle} - level={level} - source={source} - body={body} + errorType={t('Unable to parse SQL')} + message={subtitle} + type={level} + description={body} /> ); } diff --git a/superset-frontend/src/components/ErrorMessage/OAuth2RedirectMessage.tsx b/superset-frontend/src/components/ErrorMessage/OAuth2RedirectMessage.tsx index 6b1c6731b9..0e2bad17d1 100644 --- a/superset-frontend/src/components/ErrorMessage/OAuth2RedirectMessage.tsx +++ b/superset-frontend/src/components/ErrorMessage/OAuth2RedirectMessage.tsx @@ -168,11 +168,10 @@ function OAuth2RedirectMessage({ return ( <ErrorAlert - title={t('Authorization needed')} - subtitle={subtitle} - level={level} - source={source} - body={body} + errorType={t('Authorization needed')} + message={subtitle} + type={level} + description={body} /> ); } diff --git a/superset-frontend/src/components/ErrorMessage/ParameterErrorMessage.test.tsx b/superset-frontend/src/components/ErrorMessage/ParameterErrorMessage.test.tsx index b4d1324059..173820dc90 100644 --- a/superset-frontend/src/components/ErrorMessage/ParameterErrorMessage.test.tsx +++ b/superset-frontend/src/components/ErrorMessage/ParameterErrorMessage.test.tsx @@ -51,7 +51,7 @@ const mockedProps = { message: 'Error message', }, source: 'dashboard' as ErrorSource, - subtitle: 'Error message', + subtitle: 'Error message subtitle', }; test('should render', () => { diff --git a/superset-frontend/src/components/ErrorMessage/ParameterErrorMessage.tsx b/superset-frontend/src/components/ErrorMessage/ParameterErrorMessage.tsx index f854b34d02..5fbf31d1ff 100644 --- a/superset-frontend/src/components/ErrorMessage/ParameterErrorMessage.tsx +++ b/superset-frontend/src/components/ErrorMessage/ParameterErrorMessage.tsx @@ -107,18 +107,13 @@ function ParameterErrorMessage({ </> ); - const copyText = `${message} -${triggerMessage} -${extra.issue_codes.map(issueCode => issueCode.message).join('\n')}`; - return ( <ErrorAlert - title={t('Parameter error')} - subtitle={subtitle} - level={level} - source={source} - copyText={copyText} - body={body} + errorType={t('Parameter error')} + type={level} + message={message} + description={subtitle} + descriptionDetails={body} /> ); } diff --git a/superset-frontend/src/components/ErrorMessage/TimeoutErrorMessage.tsx b/superset-frontend/src/components/ErrorMessage/TimeoutErrorMessage.tsx index b4280db391..f0df59e147 100644 --- a/superset-frontend/src/components/ErrorMessage/TimeoutErrorMessage.tsx +++ b/superset-frontend/src/components/ErrorMessage/TimeoutErrorMessage.tsx @@ -88,19 +88,12 @@ function TimeoutErrorMessage({ </> ); - const copyText = t('%(subtitle)s\nThis may be triggered by:\n %(issue)s', { - subtitle, - issue: extra.issue_codes.map(issueCode => issueCode.message).join('\n'), - }); - return ( <ErrorAlert - title={t('Timeout error')} - subtitle={subtitle} - level={level} - source={source} - copyText={copyText} - body={body} + errorType={t('Timeout error')} + message={subtitle} + type={level} + descriptionDetails={body} /> ); } diff --git a/superset-frontend/src/components/Label/Label.stories.tsx b/superset-frontend/src/components/Label/Label.stories.tsx index 20225811d3..b19666e1fc 100644 --- a/superset-frontend/src/components/Label/Label.stories.tsx +++ b/superset-frontend/src/components/Label/Label.stories.tsx @@ -30,7 +30,6 @@ export default { // Explicitly type the options array as an array of `Type` export const options: Type[] = [ 'default', - 'alert', 'info', 'success', 'warning', diff --git a/superset-frontend/src/components/Label/index.tsx b/superset-frontend/src/components/Label/index.tsx index b745084076..13766fec86 100644 --- a/superset-frontend/src/components/Label/index.tsx +++ b/superset-frontend/src/components/Label/index.tsx @@ -31,7 +31,6 @@ import PublishedLabel from 'src/components/Label/reusable/PublishedLabel'; export type OnClickHandler = MouseEventHandler<HTMLElement>; export type Type = - | 'alert' | 'success' | 'warning' | 'danger' @@ -64,16 +63,8 @@ export default function Label(props: LabelProps) { icon, ...rest } = props; - const { - alert, - primary, - secondary, - grayscale, - success, - warning, - error, - info, - } = colors; + const { primary, secondary, grayscale, success, warning, error, info } = + colors; let baseColor; if (type === 'primary') { @@ -82,8 +73,6 @@ export default function Label(props: LabelProps) { baseColor = secondary; } else if (type === 'success') { baseColor = success; - } else if (type === 'alert') { - baseColor = alert; } else if (type === 'warning') { baseColor = warning; } else if (type === 'danger') { diff --git a/superset-frontend/src/components/WarningIconWithTooltip/index.tsx b/superset-frontend/src/components/WarningIconWithTooltip/index.tsx index 1e3bae090f..546047728c 100644 --- a/superset-frontend/src/components/WarningIconWithTooltip/index.tsx +++ b/superset-frontend/src/components/WarningIconWithTooltip/index.tsx @@ -38,7 +38,7 @@ function WarningIconWithTooltip({ title={<SafeMarkdown source={warningMarkdown} />} > <Icons.AlertSolid - iconColor={theme.colors.alert.base} + iconColor={theme.colors.warning.base} iconSize={size} css={{ marginRight: marginRight ?? theme.gridUnit * 2 }} /> diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardWrapper.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardWrapper.tsx index 3f563fc428..1cb8746598 100644 --- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardWrapper.tsx +++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardWrapper.tsx @@ -106,7 +106,7 @@ const StyledDiv = styled.div` } i.warning { - color: ${theme.colors.alert.base}; + color: ${theme.colors.warning.base}; } `} `; diff --git a/superset-frontend/src/explore/components/ControlHeader.tsx b/superset-frontend/src/explore/components/ControlHeader.tsx index 0959015634..4778f80ac3 100644 --- a/superset-frontend/src/explore/components/ControlHeader.tsx +++ b/superset-frontend/src/explore/components/ControlHeader.tsx @@ -79,8 +79,8 @@ const ControlHeader: FC<ControlHeaderProps> = ({ return 'unset'; } - return colors.alert.base; - }, [colors.error.base, colors.alert.base, validationErrors.length]); + return colors.warning.base; + }, [colors.error.base, colors.warning.base, validationErrors.length]); if (!label) { return null; @@ -151,7 +151,10 @@ const ControlHeader: FC<ControlHeaderProps> = ({ {warning && ( <span> <Tooltip id="error-tooltip" placement="top" title={warning}> - <Icons.AlertSolid iconColor={colors.alert.base} iconSize="s" /> + <Icons.AlertSolid + iconColor={colors.warning.base} + iconSize="s" + /> </Tooltip>{' '} </span> )} diff --git a/superset-frontend/src/explore/components/ControlPanelsContainer.tsx b/superset-frontend/src/explore/components/ControlPanelsContainer.tsx index 4490cd64a0..27a8f2dd65 100644 --- a/superset-frontend/src/explore/components/ControlPanelsContainer.tsx +++ b/superset-frontend/src/explore/components/ControlPanelsContainer.tsx @@ -587,7 +587,7 @@ export const ControlPanelsContainer = (props: ControlPanelsContainerProps) => { const errorColor = sectionHasHadNoErrors.current[sectionId] ? colors.error.base - : colors.alert.base; + : colors.warning.base; const PanelHeader = () => ( <span data-test="collapsible-control-panel-header"> @@ -747,7 +747,7 @@ export const ControlPanelsContainer = (props: ControlPanelsContainerProps) => { const errorColor = dataTabHasHadNoErrors.current ? colors.error.base - : colors.alert.base; + : colors.warning.base; return ( <> @@ -777,7 +777,7 @@ export const ControlPanelsContainer = (props: ControlPanelsContainerProps) => { ); }, [ colors.error.base, - colors.alert.base, + colors.warning.base, dataTabHasHadNoErrors, props.errorMessage, ]); diff --git a/superset-frontend/src/explore/components/ExploreAlert.tsx b/superset-frontend/src/explore/components/ExploreAlert.tsx index ae7200e984..15c7ad3b4b 100644 --- a/superset-frontend/src/explore/components/ExploreAlert.tsx +++ b/superset-frontend/src/explore/components/ExploreAlert.tsx @@ -58,16 +58,16 @@ const AlertContainer = styled.div` } &.alert-type-warning { - border-color: ${theme.colors.alert.base}; - background-color: ${theme.colors.alert.light2}; + border-color: ${theme.colors.warning.base}; + background-color: ${theme.colors.warning.light2}; p { - color: ${theme.colors.alert.dark2}; + color: ${theme.colors.warning.dark2}; } & a:hover, & span[role='button']:hover { - color: ${theme.colors.alert.dark1}; + color: ${theme.colors.warning.dark1}; } } `} diff --git a/superset-frontend/src/explore/components/controls/ColorSchemeControl/index.tsx b/superset-frontend/src/explore/components/controls/ColorSchemeControl/index.tsx index fd9dc85f0f..43946b40ee 100644 --- a/superset-frontend/src/explore/components/controls/ColorSchemeControl/index.tsx +++ b/superset-frontend/src/explore/components/controls/ColorSchemeControl/index.tsx @@ -69,7 +69,7 @@ export interface ColorSchemeControlProps { } const StyledAlert = styled(Icons.AlertSolid)` - color: ${({ theme }) => theme.colors.alert.base}; + color: ${({ theme }) => theme.colors.warning.base}; `; const CUSTOM_LABEL_ALERT = t( diff --git a/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopoverContent.tsx b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopoverContent.tsx index 79b0829dad..8e68bc0209 100644 --- a/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopoverContent.tsx +++ b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopoverContent.tsx @@ -41,10 +41,10 @@ const JustifyEnd = styled.div` const colorSchemeOptions = (theme: SupersetTheme) => [ { value: theme.colors.success.light1, label: t('success') }, - { value: theme.colors.alert.light1, label: t('alert') }, + { value: theme.colors.warning.light1, label: t('alert') }, { value: theme.colors.error.light1, label: t('error') }, { value: theme.colors.success.dark1, label: t('success dark') }, - { value: theme.colors.alert.dark1, label: t('alert dark') }, + { value: theme.colors.warning.dark1, label: t('alert dark') }, { value: theme.colors.error.dark1, label: t('error dark') }, ]; diff --git a/superset-frontend/src/features/alerts/components/AlertStatusIcon.tsx b/superset-frontend/src/features/alerts/components/AlertStatusIcon.tsx index f3f743264c..d558e5a437 100644 --- a/superset-frontend/src/features/alerts/components/AlertStatusIcon.tsx +++ b/superset-frontend/src/features/alerts/components/AlertStatusIcon.tsx @@ -34,11 +34,11 @@ function getStatusColor( case AlertState.Success: return isReportEnabled ? theme.colors.success.base - : theme.colors.alert.base; + : theme.colors.warning.base; case AlertState.Noop: return theme.colors.success.base; case AlertState.Grace: - return theme.colors.alert.base; + return theme.colors.warning.base; default: return theme.colors.grayscale.base; } diff --git a/superset-frontend/src/theme/index.ts b/superset-frontend/src/theme/index.ts index 7b61bfdeca..80b971cc8f 100644 --- a/superset-frontend/src/theme/index.ts +++ b/superset-frontend/src/theme/index.ts @@ -56,15 +56,6 @@ const baseConfig: ThemeConfig = { zIndexPopupBase: supersetTheme.zIndex.max, }, components: { - Alert: { - borderRadius: supersetTheme.borderRadius, - colorBgContainer: supersetTheme.colors.grayscale.light5, - colorBorder: supersetTheme.colors.grayscale.light3, - fontSize: supersetTheme.typography.sizes.m, - fontSizeLG: supersetTheme.typography.sizes.m, - fontSizeIcon: supersetTheme.typography.sizes.l, - colorText: supersetTheme.colors.grayscale.dark1, - }, Avatar: { containerSize: 32, fontSize: supersetTheme.typography.sizes.s,
