mirror of
https://github.com/apache/superset.git
synced 2026-04-27 03:55:47 +00:00
chore: refactor Alert-related components (#31858)
This commit is contained in:
committed by
GitHub
parent
5fe6ef268e
commit
6eb87e04c0
@@ -16,250 +16,129 @@
|
||||
* 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 { useState } from 'react';
|
||||
import { Tooltip } from 'src/components/Tooltip';
|
||||
import Modal from 'src/components/Modal';
|
||||
import Button from 'src/components/Button';
|
||||
import { isCurrentUserBot } from 'src/utils/isBot';
|
||||
import { ExclamationCircleOutlined, WarningOutlined } from '@ant-design/icons';
|
||||
import Alert from 'src/components/Alert';
|
||||
import { t, useTheme } from '@superset-ui/core';
|
||||
|
||||
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;
|
||||
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) {
|
||||
descriptionDetails,
|
||||
descriptionDetailsCollapsed = true,
|
||||
descriptionPre = true,
|
||||
compact = false,
|
||||
children,
|
||||
closable = true,
|
||||
showIcon = true,
|
||||
}) => {
|
||||
const [isDescriptionVisible, setIsDescriptionVisible] = useState(
|
||||
!descriptionDetailsCollapsed,
|
||||
);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
const toggleDescription = () => {
|
||||
setIsDescriptionVisible(!isDescriptionVisible);
|
||||
};
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [isBodyExpanded, setIsBodyExpanded] = useState(isCurrentUserBot());
|
||||
|
||||
const isExpandable =
|
||||
isCurrentUserBot() || ['explore', 'sqllab'].includes(source);
|
||||
const iconColor = theme.colors[level].base;
|
||||
|
||||
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 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.typography.families.sansSerif,
|
||||
};
|
||||
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>
|
||||
)}
|
||||
</ErrorAlertDiv>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const renderAlert = (closable: boolean) => (
|
||||
<Alert
|
||||
description={renderDescription()}
|
||||
type={type}
|
||||
showIcon
|
||||
closable={closable}
|
||||
>
|
||||
<strong>{errorType}</strong>
|
||||
{message && (
|
||||
<>
|
||||
: <span>{message}</span>
|
||||
</>
|
||||
)}
|
||||
</Alert>
|
||||
);
|
||||
|
||||
if (compact) {
|
||||
return (
|
||||
<>
|
||||
<Tooltip title={`${errorType}: ${message}`}>
|
||||
<span role="button" onClick={() => setShowModal(true)} tabIndex={0}>
|
||||
{renderTrigger()}
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Modal
|
||||
title={errorType}
|
||||
show={showModal}
|
||||
onHide={() => setShowModal(false)}
|
||||
footer={null}
|
||||
>
|
||||
{renderAlert(false)}
|
||||
{children}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return renderAlert(closable);
|
||||
};
|
||||
|
||||
export default ErrorAlert;
|
||||
|
||||
Reference in New Issue
Block a user