feat(errors): add client scaffolding for custom error messages (#9677)

This commit is contained in:
Erik Ritter
2020-04-29 17:20:31 -07:00
committed by GitHub
parent 62a15f027d
commit 5d7b13507e
9 changed files with 219 additions and 5 deletions

View File

@@ -0,0 +1,64 @@
/**
* 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 getErrorMessageComponentRegistry from 'src/components/ErrorMessage/getErrorMessageComponentRegistry';
import { ErrorMessageComponentProps } from 'src/components/ErrorMessage/types';
const ERROR_MESSAGE_COMPONENT = (_: ErrorMessageComponentProps) => (
<div>Test error</div>
);
const OVERRIDE_ERROR_MESSAGE_COMPONENT = (_: ErrorMessageComponentProps) => (
<div>Custom error</div>
);
describe('getErrorMessageComponentRegistry', () => {
it('returns undefined for a non existent key', () => {
expect(getErrorMessageComponentRegistry().get('INVALID_KEY')).toEqual(
undefined,
);
});
it('returns a component for a set key', () => {
getErrorMessageComponentRegistry().registerValue(
'VALID_KEY',
ERROR_MESSAGE_COMPONENT,
);
expect(getErrorMessageComponentRegistry().get('VALID_KEY')).toEqual(
ERROR_MESSAGE_COMPONENT,
);
});
it('returns the correct component for an overridden key', () => {
getErrorMessageComponentRegistry().registerValue(
'OVERRIDE_KEY',
ERROR_MESSAGE_COMPONENT,
);
getErrorMessageComponentRegistry().registerValue(
'OVERRIDE_KEY',
OVERRIDE_ERROR_MESSAGE_COMPONENT,
);
expect(getErrorMessageComponentRegistry().get('OVERRIDE_KEY')).toEqual(
OVERRIDE_ERROR_MESSAGE_COMPONENT,
);
});
});

View File

@@ -24,7 +24,7 @@ import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
import { Logger, LOG_ACTIONS_RENDER_CHART_CONTAINER } from '../logger/LogUtils';
import Loading from '../components/Loading';
import RefreshChartOverlay from '../components/RefreshChartOverlay';
import ErrorMessageWithStackTrace from '../components/ErrorMessageWithStackTrace';
import ErrorMessageWithStackTrace from '../components/ErrorMessage/ErrorMessageWithStackTrace';
import ErrorBoundary from '../components/ErrorBoundary';
import ChartRenderer from './ChartRenderer';
import './chart.less';

View File

@@ -19,7 +19,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { t } from '@superset-ui/translation';
import ErrorMessageWithStackTrace from './ErrorMessageWithStackTrace';
import ErrorMessageWithStackTrace from './ErrorMessage/ErrorMessageWithStackTrace';
const propTypes = {
children: PropTypes.node.isRequired,

View File

@@ -19,19 +19,35 @@
import React, { useState } from 'react';
// @ts-ignore
import { Alert, Collapse } from 'react-bootstrap';
import getErrorMessageComponentRegistry from './getErrorMessageComponentRegistry';
import { SupersetError } from './types';
interface Props {
message: string;
type Props = {
error?: SupersetError;
link?: string;
message: string;
stackTrace?: string;
}
};
export default function ErrorMessageWithStackTrace({
error,
message,
link,
stackTrace,
}: Props) {
const [showStackTrace, setShowStackTrace] = useState(false);
// Check if a custom error message component was registered for this message
if (error) {
const ErrorMessageComponent = getErrorMessageComponentRegistry().get(
error.errorType,
);
if (ErrorMessageComponent) {
return <ErrorMessageComponent error={error} />;
}
}
// Fallback to the default error message renderer
return (
<div className={`stack-trace-container${stackTrace ? ' has-trace' : ''}`}>
<Alert

View File

@@ -0,0 +1,38 @@
/**
* 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 { Registry, makeSingleton, OverwritePolicy } from '@superset-ui/core';
import { ErrorMessageComponent } from './types';
class ErrorMessageComponentRegistry extends Registry<
ErrorMessageComponent,
ErrorMessageComponent
> {
constructor() {
super({
name: 'ErrorMessageComponent',
overwritePolicy: OverwritePolicy.ALLOW,
});
}
}
const getErrorMessageComponentRegistry = makeSingleton(
ErrorMessageComponentRegistry,
);
export default getErrorMessageComponentRegistry;

View File

@@ -0,0 +1,47 @@
/**
* 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.
*/
// TODO: Add more error types as we classify more errors
export const ErrorTypeEnum = {
// Generic errors created on the frontend
FRONTEND_CSRF_ERROR: 'FRONTEND_CSRF_ERROR',
FRONTEND_NETWORK_ERROR: 'FRONTEND_NETWORK_ERROR',
FRONTEND_TIMEOUT_ERROR: 'FRONTEND_TIMEOUT_ERROR',
} as const;
type ValueOf<T> = T[keyof T];
export type ErrorType = ValueOf<typeof ErrorTypeEnum>;
export type ErrorLevel = 'info' | 'warning' | 'error';
export type SupersetError = {
errorType: ErrorType;
extra: Record<string, any>;
level: ErrorLevel;
message: string;
};
export type ErrorMessageComponentProps = {
error: SupersetError;
};
export type ErrorMessageComponent = React.ComponentType<
ErrorMessageComponentProps
>;

View File

@@ -20,6 +20,7 @@
import $ from 'jquery';
import { SupersetClient } from '@superset-ui/connection';
import getClientErrorObject from '../utils/getClientErrorObject';
import setupErrorMessages from './setupErrorMessages';
function showApiMessage(resp: { severity?: string; message: string }) {
const template =
@@ -84,4 +85,7 @@ export default function setupApp() {
// @ts-ignore
window.jQuery = $;
require('bootstrap');
// setup appwide custom error messages
setupErrorMessages();
}

View File

@@ -0,0 +1,24 @@
/**
* 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 setupErrorMessagesExtra from './setupErrorMessagesExtra';
export default function setupErrorMessages() {
// TODO: Register error messages to the ErrorMessageComponentRegistry once implemented
setupErrorMessagesExtra();
}

View File

@@ -0,0 +1,21 @@
/**
* 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.
*/
// For individual deployments to add custom error messages
export default function setupErrorMessagesExtra() {}