feat: refactor error components and add database issue code (#10473)
* feat: refactor error components and add database issue code * Apply suggestions from code review Co-authored-by: John Bodley <4567245+john-bodley@users.noreply.github.com> Co-authored-by: John Bodley <4567245+john-bodley@users.noreply.github.com>
This commit is contained in:
parent
62b873e3da
commit
2055ecc1ba
|
|
@ -37,3 +37,12 @@ Issue 1001
|
|||
The database is under an unusual load.
|
||||
|
||||
Your query may have timed out because of unusually high load on the database engine. You can make your query simpler, or wait until the database is under less load and try again.
|
||||
|
||||
Issue 1002
|
||||
""""""""""
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
The database returned an unexpected error.
|
||||
|
||||
Your query failed because of an error that occurred on the database. This may be due to a syntax error, a bug in your query, or some other internal failure within the database. This is usually not an issue within Superset, but instead a problem with the underlying database that serves your query.
|
||||
|
|
|
|||
|
|
@ -17,6 +17,6 @@
|
|||
under the License.
|
||||
-->
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 9.34784 20.9464 6.8043 19.0711 4.92893C17.1957 3.05357 14.6522 2 12 2Z" fill="#666666"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 9.34784 20.9464 6.8043 19.0711 4.92893C17.1957 3.05357 14.6522 2 12 2Z" fill="currentColor"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.0003 8C11.0003 7.44772 11.448 7 12.0003 7C12.5526 7 13.0003 7.44772 13.0003 8V12C13.0003 12.5523 12.5526 13 12.0003 13C11.448 13 11.0003 12.5523 11.0003 12V8ZM11.2903 15.29C11.3854 15.199 11.4975 15.1276 11.6203 15.08C11.8637 14.98 12.1368 14.98 12.3803 15.08C12.503 15.1276 12.6152 15.199 12.7103 15.29C12.893 15.4816 12.9966 15.7352 13.0003 16C12.9986 16.3326 12.8317 16.6426 12.555 16.8271C12.2783 17.0116 11.9279 17.0464 11.6203 16.92C11.4991 16.8694 11.3875 16.7983 11.2903 16.71C11.1031 16.5213 10.9987 16.2658 11.0003 16C10.9969 15.8688 11.0243 15.7387 11.0803 15.62C11.1309 15.4988 11.2019 15.3872 11.2903 15.29Z" fill="white"/>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
* 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 { t, tn } from '@superset-ui/translation';
|
||||
|
||||
import { ErrorMessageComponentProps } from './types';
|
||||
import IssueCode from './IssueCode';
|
||||
import ErrorAlert from './ErrorAlert';
|
||||
|
||||
interface DatabaseErrorExtra {
|
||||
owners?: string[];
|
||||
issue_codes: {
|
||||
code: number;
|
||||
message: string;
|
||||
}[];
|
||||
engine_name: string | null;
|
||||
}
|
||||
|
||||
function DatabaseErrorMessage({
|
||||
error,
|
||||
source = 'dashboard',
|
||||
}: ErrorMessageComponentProps<DatabaseErrorExtra>) {
|
||||
const { extra, level, message } = error;
|
||||
|
||||
const isVisualization = ['dashboard', 'explore'].includes(source);
|
||||
|
||||
const body = (
|
||||
<>
|
||||
<p>
|
||||
{t('This may be triggered by:')}
|
||||
<br />
|
||||
{extra.issue_codes
|
||||
.map<React.ReactNode>(issueCode => <IssueCode {...issueCode} />)
|
||||
.reduce((prev, curr) => [prev, <br />, curr])}
|
||||
</p>
|
||||
{isVisualization && extra.owners && (
|
||||
<>
|
||||
<br />
|
||||
<p>
|
||||
{tn(
|
||||
'Please reach out to the Chart Owner for assistance.',
|
||||
'Please reach out to the Chart Owners for assistance.',
|
||||
extra.owners.length,
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{tn(
|
||||
'Chart Owner: %s',
|
||||
'Chart Owners: %s',
|
||||
extra.owners.length,
|
||||
extra.owners.join(', '),
|
||||
)}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
const copyText = `${message}
|
||||
${t('This may be triggered by:')}
|
||||
${extra.issue_codes.map(issueCode => issueCode.message).join('\n')}`;
|
||||
|
||||
return (
|
||||
<ErrorAlert
|
||||
title={t('%s Error', extra.engine_name || t('DB Engine'))}
|
||||
subtitle={message}
|
||||
level={level}
|
||||
source={source}
|
||||
copyText={copyText}
|
||||
body={body}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default DatabaseErrorMessage;
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
/**
|
||||
* 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 { Modal } from 'react-bootstrap';
|
||||
import { styled, supersetTheme } from '@superset-ui/style';
|
||||
import { t } from '@superset-ui/translation';
|
||||
import { noOp } from 'src/utils/common';
|
||||
import Button from 'src/views/CRUD/dataset/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};
|
||||
|
||||
.icon {
|
||||
margin-right: ${({ theme }) => 2 * theme.gridUnit}px;
|
||||
}
|
||||
|
||||
.header {
|
||||
align-items: center;
|
||||
background-color: ${({ level, theme }) => theme.colors[level].light2};
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: ${({ theme }) => theme.typography.sizes.l}px;
|
||||
|
||||
// Remove clearfix hack as Superset is only used on modern browsers
|
||||
::before,
|
||||
::after {
|
||||
content: unset;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
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}>
|
||||
<div className="top-row">
|
||||
<LeftSideContent>
|
||||
<Icon
|
||||
className="icon"
|
||||
name={level === 'error' ? 'error' : 'warning'}
|
||||
color={supersetTheme.colors[level].base}
|
||||
/>
|
||||
<strong>{title}</strong>
|
||||
</LeftSideContent>
|
||||
{!isExpandable && (
|
||||
<a href="#" className="link" onClick={() => setIsModalOpen(true)}>
|
||||
{t('See More')}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
{isExpandable ? (
|
||||
<div className="error-body">
|
||||
<p>{subtitle}</p>
|
||||
{body && (
|
||||
<>
|
||||
{!isBodyExpanded && (
|
||||
<a
|
||||
href="#"
|
||||
className="link"
|
||||
onClick={() => setIsBodyExpanded(true)}
|
||||
>
|
||||
{t('See More')}
|
||||
</a>
|
||||
)}
|
||||
{isBodyExpanded && (
|
||||
<>
|
||||
<br />
|
||||
{body}
|
||||
<a
|
||||
href="#"
|
||||
className="link"
|
||||
onClick={() => setIsBodyExpanded(false)}
|
||||
>
|
||||
{t('See Less')}
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<ErrorModal
|
||||
level={level}
|
||||
show={isModalOpen}
|
||||
onHide={() => setIsModalOpen(false)}
|
||||
>
|
||||
<Modal.Header className="header">
|
||||
<LeftSideContent>
|
||||
<Icon
|
||||
className="icon"
|
||||
name={level === 'error' ? 'error' : 'warning'}
|
||||
color={supersetTheme.colors[level].base}
|
||||
/>
|
||||
<div className="title">{title}</div>
|
||||
</LeftSideContent>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => setIsModalOpen(false)}
|
||||
>
|
||||
<Icon name="close" />
|
||||
</span>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<p>{subtitle}</p>
|
||||
<br />
|
||||
{body}
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
{copyText && (
|
||||
<CopyToClipboard
|
||||
text={copyText}
|
||||
shouldShowText={false}
|
||||
wrapped={false}
|
||||
copyNode={<Button onClick={noOp}>{t('Copy Message')}</Button>}
|
||||
/>
|
||||
)}
|
||||
<Button bsStyle="primary" onClick={() => setIsModalOpen(false)}>
|
||||
{t('Close')}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</ErrorModal>
|
||||
)}
|
||||
</ErrorAlertDiv>
|
||||
);
|
||||
}
|
||||
|
|
@ -16,13 +16,12 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
// @ts-ignore
|
||||
import { Alert, Collapse } from 'react-bootstrap';
|
||||
import React from 'react';
|
||||
import { t } from '@superset-ui/translation';
|
||||
|
||||
import getErrorMessageComponentRegistry from './getErrorMessageComponentRegistry';
|
||||
import { SupersetError, ErrorSource } from './types';
|
||||
import ErrorAlert from './ErrorAlert';
|
||||
|
||||
type Props = {
|
||||
error?: SupersetError;
|
||||
|
|
@ -39,8 +38,6 @@ export default function ErrorMessageWithStackTrace({
|
|||
stackTrace,
|
||||
source,
|
||||
}: Props) {
|
||||
const [showStackTrace, setShowStackTrace] = useState(false);
|
||||
|
||||
// Check if a custom error message component was registered for this message
|
||||
if (error) {
|
||||
const ErrorMessageComponent = getErrorMessageComponentRegistry().get(
|
||||
|
|
@ -51,25 +48,26 @@ export default function ErrorMessageWithStackTrace({
|
|||
}
|
||||
}
|
||||
|
||||
// Fallback to the default error message renderer
|
||||
return (
|
||||
<div className={`stack-trace-container${stackTrace ? ' has-trace' : ''}`}>
|
||||
<Alert
|
||||
bsStyle="warning"
|
||||
onClick={() => setShowStackTrace(!showStackTrace)}
|
||||
>
|
||||
{message || t('An error occurred.')}
|
||||
{link && (
|
||||
<a href={link} target="_blank" rel="noopener noreferrer">
|
||||
(Request Access)
|
||||
</a>
|
||||
)}
|
||||
</Alert>
|
||||
{stackTrace && (
|
||||
<Collapse in={showStackTrace}>
|
||||
<pre>{stackTrace}</pre>
|
||||
</Collapse>
|
||||
)}
|
||||
</div>
|
||||
<ErrorAlert
|
||||
level="warning"
|
||||
title={t('Unexpected Error')}
|
||||
subtitle={message}
|
||||
copyText={message}
|
||||
source={source}
|
||||
body={
|
||||
link || stackTrace ? (
|
||||
<>
|
||||
{link && (
|
||||
<a href={link} target="_blank" rel="noopener noreferrer">
|
||||
(Request Access)
|
||||
</a>
|
||||
)}
|
||||
<br />
|
||||
{stackTrace && <pre>{stackTrace}</pre>}
|
||||
</>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,73 +16,12 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
import { Modal } from 'react-bootstrap';
|
||||
import { styled, supersetTheme } from '@superset-ui/style';
|
||||
import React from 'react';
|
||||
import { t, tn } from '@superset-ui/translation';
|
||||
|
||||
import { noOp } from 'src/utils/common';
|
||||
import Button from 'src/views/CRUD/dataset/Button';
|
||||
import Icon from '../Icon';
|
||||
import { ErrorMessageComponentProps } from './types';
|
||||
import CopyToClipboard from '../CopyToClipboard';
|
||||
import IssueCode from './IssueCode';
|
||||
|
||||
const ErrorAlert = styled.div`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.colors.error.light2};
|
||||
border-radius: ${({ theme }) => theme.borderRadius}px;
|
||||
border: 1px solid ${({ theme }) => theme.colors.error.base};
|
||||
color: ${({ theme }) => theme.colors.error.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: ${({ theme }) => theme.colors.error.dark2};
|
||||
text-decoration: underline;
|
||||
}
|
||||
`;
|
||||
|
||||
const ErrorModal = styled(Modal)`
|
||||
color: ${({ theme }) => theme.colors.error.dark2};
|
||||
|
||||
.icon {
|
||||
margin-right: ${({ theme }) => 2 * theme.gridUnit}px;
|
||||
}
|
||||
|
||||
.header {
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.colors.error.light2};
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: ${({ theme }) => theme.typography.sizes.l}px;
|
||||
|
||||
// Remove clearfix hack as Superset is only used on modern browsers
|
||||
::before,
|
||||
::after {
|
||||
content: unset;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const LeftSideContent = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
`;
|
||||
import ErrorAlert from './ErrorAlert';
|
||||
|
||||
interface TimeoutErrorExtra {
|
||||
issue_codes: {
|
||||
|
|
@ -97,21 +36,14 @@ function TimeoutErrorMessage({
|
|||
error,
|
||||
source,
|
||||
}: ErrorMessageComponentProps<TimeoutErrorExtra>) {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [isMessageExpanded, setIsMessageExpanded] = useState(false);
|
||||
const { extra } = error;
|
||||
const { extra, level } = error;
|
||||
|
||||
const isVisualization = (['dashboard', 'explore'] as (
|
||||
| string
|
||||
| undefined
|
||||
)[]).includes(source);
|
||||
|
||||
const isExpandable = (['explore', 'sqllab'] as (
|
||||
| string
|
||||
| undefined
|
||||
)[]).includes(source);
|
||||
|
||||
const title = isVisualization
|
||||
const subtitle = isVisualization
|
||||
? tn(
|
||||
'We’re having trouble loading this visualization. Queries are set to timeout after %s second.',
|
||||
'We’re having trouble loading this visualization. Queries are set to timeout after %s seconds.',
|
||||
|
|
@ -125,7 +57,7 @@ function TimeoutErrorMessage({
|
|||
extra.timeout,
|
||||
);
|
||||
|
||||
const message = (
|
||||
const body = (
|
||||
<>
|
||||
<p>
|
||||
{t('This may be triggered by:')}
|
||||
|
|
@ -157,91 +89,19 @@ function TimeoutErrorMessage({
|
|||
</>
|
||||
);
|
||||
|
||||
const copyText = `${title}
|
||||
const copyText = `${subtitle}
|
||||
${t('This may be triggered by:')}
|
||||
${extra.issue_codes.map(issueCode => issueCode.message).join('\n')}`;
|
||||
|
||||
return (
|
||||
<ErrorAlert>
|
||||
<div className="top-row">
|
||||
<LeftSideContent>
|
||||
<Icon
|
||||
className="icon"
|
||||
name="error"
|
||||
color={supersetTheme.colors.error.base}
|
||||
/>
|
||||
<strong>{t('Timeout Error')}</strong>
|
||||
</LeftSideContent>
|
||||
{!isExpandable && (
|
||||
<a href="#" className="link" onClick={() => setIsModalOpen(true)}>
|
||||
{t('See More')}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
{isExpandable ? (
|
||||
<div className="error-body">
|
||||
<p>{title}</p>
|
||||
{!isMessageExpanded && (
|
||||
<a
|
||||
href="#"
|
||||
className="link"
|
||||
onClick={() => setIsMessageExpanded(true)}
|
||||
>
|
||||
{t('See More')}
|
||||
</a>
|
||||
)}
|
||||
{isMessageExpanded && (
|
||||
<>
|
||||
<br />
|
||||
{message}
|
||||
<a
|
||||
href="#"
|
||||
className="link"
|
||||
onClick={() => setIsMessageExpanded(false)}
|
||||
>
|
||||
{t('See Less')}
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<ErrorModal show={isModalOpen} onHide={() => setIsModalOpen(false)}>
|
||||
<Modal.Header className="header">
|
||||
<LeftSideContent>
|
||||
<Icon
|
||||
className="icon"
|
||||
name="error"
|
||||
color={supersetTheme.colors.error.base}
|
||||
/>
|
||||
<div className="title">{t('Timeout Error')}</div>
|
||||
</LeftSideContent>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => setIsModalOpen(false)}
|
||||
>
|
||||
<Icon name="close" />
|
||||
</span>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<p>{title}</p>
|
||||
<br />
|
||||
{message}
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<CopyToClipboard
|
||||
text={copyText}
|
||||
shouldShowText={false}
|
||||
wrapped={false}
|
||||
copyNode={<Button onClick={noOp}>{t('Copy Message')}</Button>}
|
||||
/>
|
||||
<Button bsStyle="primary" onClick={() => setIsModalOpen(false)}>
|
||||
{t('Close')}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</ErrorModal>
|
||||
)}
|
||||
</ErrorAlert>
|
||||
<ErrorAlert
|
||||
title={t('Timeout Error')}
|
||||
subtitle={subtitle}
|
||||
level={level}
|
||||
source={source}
|
||||
copyText={copyText}
|
||||
body={body}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
import getErrorMessageComponentRegistry from 'src/components/ErrorMessage/getErrorMessageComponentRegistry';
|
||||
import { ErrorTypeEnum } from 'src/components/ErrorMessage/types';
|
||||
import TimeoutErrorMessage from 'src/components/ErrorMessage/TimeoutErrorMessage';
|
||||
import DatabaseErrorMessage from 'src/components/ErrorMessage/DatabaseErrorMessage';
|
||||
|
||||
import setupErrorMessagesExtra from './setupErrorMessagesExtra';
|
||||
|
||||
|
|
@ -33,5 +34,9 @@ export default function setupErrorMessages() {
|
|||
ErrorTypeEnum.BACKEND_TIMEOUT_ERROR,
|
||||
TimeoutErrorMessage,
|
||||
);
|
||||
errorMessageComponentRegistry.registerValue(
|
||||
ErrorTypeEnum.GENERIC_DB_ENGINE_ERROR,
|
||||
DatabaseErrorMessage,
|
||||
);
|
||||
setupErrorMessagesExtra();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ from superset.utils import core as utils
|
|||
|
||||
class AthenaEngineSpec(BaseEngineSpec):
|
||||
engine = "awsathena"
|
||||
engine_name = "Amazon Athena"
|
||||
|
||||
_time_grain_expressions = {
|
||||
None: "{col}",
|
||||
|
|
|
|||
|
|
@ -137,6 +137,9 @@ class BaseEngineSpec: # pylint: disable=too-many-public-methods
|
|||
"""Abstract class for database engine specific configurations"""
|
||||
|
||||
engine = "base" # str as defined in sqlalchemy.engine.engine
|
||||
engine_name: Optional[
|
||||
str
|
||||
] = None # used for user messages, overridden in child classes
|
||||
_date_trunc_functions: Dict[str, str] = {}
|
||||
_time_grain_expressions: Dict[Optional[str], str] = {}
|
||||
time_groupby_inline = False
|
||||
|
|
@ -569,7 +572,7 @@ class BaseEngineSpec: # pylint: disable=too-many-public-methods
|
|||
return f"{cls.engine} error: {cls._extract_error_message(ex)}"
|
||||
|
||||
@classmethod
|
||||
def _extract_error_message(cls, ex: Exception) -> Optional[str]:
|
||||
def _extract_error_message(cls, ex: Exception) -> str:
|
||||
"""Extract error message for queries"""
|
||||
return utils.error_msg_from_exception(ex)
|
||||
|
||||
|
|
@ -579,9 +582,9 @@ class BaseEngineSpec: # pylint: disable=too-many-public-methods
|
|||
dataclasses.asdict(
|
||||
SupersetError(
|
||||
error_type=SupersetErrorType.GENERIC_DB_ENGINE_ERROR,
|
||||
message=cls.extract_error_message(ex),
|
||||
message=cls._extract_error_message(ex),
|
||||
level=ErrorLevel.ERROR,
|
||||
extra={"engine": cls.engine},
|
||||
extra={"engine_name": cls.engine_name},
|
||||
)
|
||||
)
|
||||
]
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ class BigQueryEngineSpec(BaseEngineSpec):
|
|||
As contributed by @mxmzdlv on issue #945"""
|
||||
|
||||
engine = "bigquery"
|
||||
engine_name = "Google BigQuery"
|
||||
max_column_name_length = 128
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ class ClickHouseEngineSpec(BaseEngineSpec): # pylint: disable=abstract-method
|
|||
"""Dialect for ClickHouse analytical DB."""
|
||||
|
||||
engine = "clickhouse"
|
||||
engine_name = "ClickHouse"
|
||||
|
||||
time_secondary_columns = True
|
||||
time_groupby_inline = True
|
||||
|
|
|
|||
|
|
@ -19,3 +19,4 @@ from superset.db_engine_specs.postgres import PostgresEngineSpec
|
|||
|
||||
class CockroachDbEngineSpec(PostgresEngineSpec):
|
||||
engine = "cockroachdb"
|
||||
engine_name = "CockroachDB"
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ from superset.db_engine_specs.base import BaseEngineSpec, LimitMethod
|
|||
|
||||
class Db2EngineSpec(BaseEngineSpec):
|
||||
engine = "ibm_db_sa"
|
||||
engine_name = "IBM Db2"
|
||||
limit_method = LimitMethod.WRAP_SQL
|
||||
force_column_alias_quotes = True
|
||||
max_column_name_length = 30
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ from superset.db_engine_specs.base import BaseEngineSpec
|
|||
class DremioBaseEngineSpec(BaseEngineSpec):
|
||||
|
||||
engine = "dremio"
|
||||
engine_name = "Dremio"
|
||||
|
||||
_time_grain_expressions = {
|
||||
None: "{col}",
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ class DrillEngineSpec(BaseEngineSpec):
|
|||
"""Engine spec for Apache Drill"""
|
||||
|
||||
engine = "drill"
|
||||
engine_name = "Apache Drill"
|
||||
|
||||
_time_grain_expressions = {
|
||||
None: "{col}",
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ class DruidEngineSpec(BaseEngineSpec): # pylint: disable=abstract-method
|
|||
"""Engine spec for Druid.io"""
|
||||
|
||||
engine = "druid"
|
||||
engine_name = "Apache Druid"
|
||||
allows_joins = False
|
||||
allows_subqueries = True
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ from superset.utils import core as utils
|
|||
|
||||
class ElasticSearchEngineSpec(BaseEngineSpec): # pylint: disable=abstract-method
|
||||
engine = "elasticsearch"
|
||||
engine_name = "ElasticSearch"
|
||||
time_groupby_inline = True
|
||||
time_secondary_columns = True
|
||||
allows_joins = False
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class ExasolEngineSpec(BaseEngineSpec): # pylint: disable=abstract-method
|
|||
"""Engine spec for Exasol"""
|
||||
|
||||
engine = "exa"
|
||||
engine_name = "Exasol"
|
||||
max_column_name_length = 128
|
||||
|
||||
# Exasol's DATE_TRUNC function is PostgresSQL compatible
|
||||
|
|
|
|||
|
|
@ -21,5 +21,6 @@ class GSheetsEngineSpec(SqliteEngineSpec):
|
|||
"""Engine for Google spreadsheets"""
|
||||
|
||||
engine = "gsheets"
|
||||
engine_name = "Google Sheets"
|
||||
allows_joins = False
|
||||
allows_subqueries = False
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ from superset.utils import core as utils
|
|||
|
||||
class HanaEngineSpec(PostgresBaseEngineSpec):
|
||||
engine = "hana"
|
||||
engine_name = "SAP HANA"
|
||||
limit_method = LimitMethod.WRAP_SQL
|
||||
force_column_alias_quotes = True
|
||||
max_column_name_length = 30
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ class HiveEngineSpec(PrestoEngineSpec):
|
|||
"""Reuses PrestoEngineSpec functionality."""
|
||||
|
||||
engine = "hive"
|
||||
engine_name = "Apache Hive"
|
||||
max_column_name_length = 767
|
||||
# pylint: disable=line-too-long
|
||||
_time_grain_expressions = {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ class ImpalaEngineSpec(BaseEngineSpec):
|
|||
"""Engine spec for Cloudera's Impala"""
|
||||
|
||||
engine = "impala"
|
||||
engine_name = "Apache Impala"
|
||||
|
||||
_time_grain_expressions = {
|
||||
None: "{col}",
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ class KylinEngineSpec(BaseEngineSpec): # pylint: disable=abstract-method
|
|||
"""Dialect for Apache Kylin"""
|
||||
|
||||
engine = "kylin"
|
||||
engine_name = "Apache Kylin"
|
||||
|
||||
_time_grain_expressions = {
|
||||
None: "{col}",
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
class MssqlEngineSpec(BaseEngineSpec):
|
||||
engine = "mssql"
|
||||
engine_name = "Microsoft SQL"
|
||||
limit_method = LimitMethod.WRAP_SQL
|
||||
max_column_name_length = 128
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ from superset.utils import core as utils
|
|||
|
||||
class MySQLEngineSpec(BaseEngineSpec):
|
||||
engine = "mysql"
|
||||
engine_name = "MySQL"
|
||||
max_column_name_length = 64
|
||||
|
||||
_time_grain_expressions = {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ from superset.utils import core as utils
|
|||
|
||||
class OracleEngineSpec(BaseEngineSpec):
|
||||
engine = "oracle"
|
||||
engine_name = "Oracle"
|
||||
limit_method = LimitMethod.WRAP_SQL
|
||||
force_column_alias_quotes = True
|
||||
max_column_name_length = 30
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ from superset.db_engine_specs.base import BaseEngineSpec, TimestampExpression
|
|||
|
||||
class PinotEngineSpec(BaseEngineSpec): # pylint: disable=abstract-method
|
||||
engine = "pinot"
|
||||
engine_name = "Apache Pinot"
|
||||
allows_subqueries = False
|
||||
allows_joins = False
|
||||
allows_column_aliases = False
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ class PostgresBaseEngineSpec(BaseEngineSpec):
|
|||
""" Abstract class for Postgres 'like' databases """
|
||||
|
||||
engine = ""
|
||||
engine_name = "PostgreSQL"
|
||||
|
||||
_time_grain_expressions = {
|
||||
None: "{col}",
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ from urllib import parse
|
|||
|
||||
import pandas as pd
|
||||
import simplejson as json
|
||||
from flask_babel import lazy_gettext as _
|
||||
from sqlalchemy import Column, literal_column
|
||||
from sqlalchemy.engine.base import Engine
|
||||
from sqlalchemy.engine.reflection import Inspector
|
||||
|
|
@ -104,6 +105,7 @@ def get_children(column: Dict[str, str]) -> List[Dict[str, str]]:
|
|||
|
||||
class PrestoEngineSpec(BaseEngineSpec):
|
||||
engine = "presto"
|
||||
engine_name = "Presto"
|
||||
|
||||
_time_grain_expressions = {
|
||||
None: "{col}",
|
||||
|
|
@ -780,7 +782,7 @@ class PrestoEngineSpec(BaseEngineSpec):
|
|||
polled = cursor.poll()
|
||||
|
||||
@classmethod
|
||||
def _extract_error_message(cls, ex: Exception) -> Optional[str]:
|
||||
def _extract_error_message(cls, ex: Exception) -> str:
|
||||
if (
|
||||
hasattr(ex, "orig")
|
||||
and type(ex.orig).__name__ == "DatabaseError" # type: ignore
|
||||
|
|
@ -794,7 +796,7 @@ class PrestoEngineSpec(BaseEngineSpec):
|
|||
)
|
||||
if type(ex).__name__ == "DatabaseError" and hasattr(ex, "args") and ex.args:
|
||||
error_dict = ex.args[0]
|
||||
return error_dict.get("message")
|
||||
return error_dict.get("message", _("Unknown Presto Error"))
|
||||
return utils.error_msg_from_exception(ex)
|
||||
|
||||
@classmethod
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ from superset.db_engine_specs.postgres import PostgresBaseEngineSpec
|
|||
|
||||
class RedshiftEngineSpec(PostgresBaseEngineSpec):
|
||||
engine = "redshift"
|
||||
engine_name = "Amazon Redshift"
|
||||
max_column_name_length = 127
|
||||
|
||||
@staticmethod
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ if TYPE_CHECKING:
|
|||
|
||||
class SnowflakeEngineSpec(PostgresBaseEngineSpec):
|
||||
engine = "snowflake"
|
||||
engine_name = "Snowflake"
|
||||
force_column_alias_quotes = True
|
||||
max_column_name_length = 256
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ if TYPE_CHECKING:
|
|||
|
||||
class SqliteEngineSpec(BaseEngineSpec):
|
||||
engine = "sqlite"
|
||||
engine_name = "SQLite"
|
||||
|
||||
_time_grain_expressions = {
|
||||
None: "{col}",
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ class TeradataEngineSpec(BaseEngineSpec):
|
|||
"""Dialect for Teradata DB."""
|
||||
|
||||
engine = "teradata"
|
||||
engine_name = "Teradata"
|
||||
limit_method = LimitMethod.WRAP_SQL
|
||||
max_column_name_length = 30 # since 14.10 this is 128
|
||||
|
||||
|
|
|
|||
|
|
@ -19,3 +19,4 @@ from superset.db_engine_specs.postgres import PostgresBaseEngineSpec
|
|||
|
||||
class VerticaEngineSpec(PostgresBaseEngineSpec):
|
||||
engine = "vertica"
|
||||
engine_name = "Vertica"
|
||||
|
|
|
|||
|
|
@ -61,7 +61,13 @@ ERROR_TYPES_TO_ISSUE_CODES_MAPPING = {
|
|||
"code": 1001,
|
||||
"message": _("Issue 1001 - The database is under an unusual load."),
|
||||
},
|
||||
]
|
||||
],
|
||||
SupersetErrorType.GENERIC_DB_ENGINE_ERROR: [
|
||||
{
|
||||
"code": 1002,
|
||||
"message": _("Issue 1002 - The database returned an unexpected error."),
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue