blob: bd5ec4c2cb4fdcef74f01d6f6ce943eb2ac6fa50 [file] [log] [blame]
/**
* 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, { useState, ReactNode } from 'react';
import { styled, supersetTheme, t } from '@superset-ui/core';
import { noOp } from 'src/utils/common';
import Modal from 'src/components/Modal';
import Button from 'src/components/Button';
import Icon from '../Icon';
import { ErrorLevel, ErrorSource } from './types';
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;
}
`;
const ErrorModal = styled(Modal)<{ level: ErrorLevel }>`
color: ${({ level, theme }) => theme.colors[level].dark2};
overflow-wrap: break-word;
.ant-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;
}
export default function ErrorAlert({
body,
copyText,
level,
source = 'dashboard',
subtitle,
title,
}: ErrorAlertProps) {
const [isModalOpen, setIsModalOpen] = useState(false);
const [isBodyExpanded, setIsBodyExpanded] = useState(false);
const isExpandable = ['explore', 'sqllab'].includes(source);
return (
<ErrorAlertDiv level={level} role="alert">
<div className="top-row">
<LeftSideContent>
<Icon
className="icon"
name={level === 'error' ? 'error-solid' : 'warning-solid'}
color={supersetTheme.colors[level].base}
/>
<strong>{title}</strong>
</LeftSideContent>
{!isExpandable && (
<span
role="button"
tabIndex={0}
className="link"
onClick={() => 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)}
>
{t('See more')}
</span>
)}
{isBodyExpanded && (
<>
<br />
{body}
<span
role="button"
tabIndex={0}
className="link"
onClick={() => setIsBodyExpanded(false)}
>
{t('See less')}
</span>
</>
)}
</>
)}
</div>
) : (
<ErrorModal
level={level}
show={isModalOpen}
onHide={() => setIsModalOpen(false)}
title={
<div className="header">
<Icon
className="icon"
name={level === 'error' ? 'error-solid' : 'warning-solid'}
color={supersetTheme.colors[level].base}
/>
<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)}
>
{t('Close')}
</Button>
</>
}
>
<>
<p>{subtitle}</p>
<br />
{body}
</>
</ErrorModal>
)}
</ErrorAlertDiv>
);
}