Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ [open-formulieren/open-forms#5033] User friendly error in case of 503 #783

Merged
merged 4 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# ViteJS based
VITE_VERSION=latest
VITE_MUTE_ERROR_BOUNDARY_LOG=false
VITE_BASE_API_URL=http://localhost:8000/api/v2/
VITE_FORM_ID=93c09209-5fb9-4105-b6bb-9d9f0aa6782c
VITE_USE_HASH_ROUTING=false
32 changes: 17 additions & 15 deletions src/components/Errors/ErrorBoundary.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import * as Sentry from '@sentry/react';
import {getEnv} from 'env.mjs';
import PropTypes from 'prop-types';
import React from 'react';
import {FormattedMessage, useIntl} from 'react-intl';

import Body from 'components/Body';
import Card from 'components/Card';
import FormUnavailable from 'components/Errors/FormUnavailable';
import FormMaximumSubmissions from 'components/FormMaximumSubmissions';
import Link from 'components/Link';
import MaintenanceMode from 'components/MaintenanceMode';
Expand All @@ -13,7 +16,12 @@
import ErrorMessage from './ErrorMessage';

const logError = (error, errorInfo) => {
if (DEBUG) console.error(error, errorInfo);
if (DEBUG) {
const muteConsole = getEnv('MUTE_ERROR_BOUNDARY_LOG');

Check warning on line 20 in src/components/Errors/ErrorBoundary.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/Errors/ErrorBoundary.jsx#L20

Added line #L20 was not covered by tests
if (!muteConsole) console.error(error, errorInfo);
} else {
Sentry.captureException(error);
}
};

class ErrorBoundary extends React.Component {
Expand All @@ -30,7 +38,6 @@
}

componentDidCatch(error, errorInfo) {
// TODO: depending on the error type, send to sentry?
logError(error, errorInfo);
}

Expand Down Expand Up @@ -139,13 +146,9 @@
UnprocessableEntityError.propTypes = GenericError.propTypes;

const ServiceUnavailableError = ({wrapper: Wrapper, error}) => {
if (!['form-maintenance', 'form-maximum-submissions'].includes(error.code)) {
return <GenericError wrapper={Wrapper} error={error} />;
}

// handle maintenance mode forms
if (error.code === 'form-maintenance') {
return (
const defaultComponent = <GenericError wrapper={Wrapper} error={error} />;
const componentMapping = {
'form-maintenance': (
<MaintenanceMode
title={
<FormattedMessage
Expand All @@ -154,13 +157,12 @@
/>
}
/>
);
}
),
'form-maximum-submissions': <FormMaximumSubmissions />,
service_unavailable: <FormUnavailable wrapper={Wrapper} />,
};

// handle submission limit forms
if (error.code === 'form-maximum-submissions') {
return <FormMaximumSubmissions />;
}
return componentMapping[error.code] || defaultComponent;
};

// map the error class to the component to render it
Expand Down
103 changes: 103 additions & 0 deletions src/components/Errors/ErrorBoundary.stories.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import {MemoryRouter} from 'react-router';

import {PermissionDenied, ServiceUnavailable, UnprocessableEntity} from 'errors';

import ErrorBoundary from './ErrorBoundary';

const Nested = ({error}) => {
throw error;
};

const render = ({useCard, errorType, errorCode}) => {
const error = new errorType('some error', 500, 'some error', errorCode);
return (
<ErrorBoundary useCard={useCard}>
<Nested error={error} />
</ErrorBoundary>
);
};

export default {
title: 'Private API / ErrorBoundary',
component: ErrorBoundary,
render,
argTypes: {
useCard: {control: {type: 'boolean'}},
errorType: {
table: {
options: [PermissionDenied, ServiceUnavailable, UnprocessableEntity],
control: {type: 'radio'},
},
},
},
};

export const GenericError = {
args: {
useCard: true,
errorType: Error,
errorCode: 'generic',
},
};

export const PermissionDeniedError = {
decorators: [
Story => (
<MemoryRouter initialEntries={['/']}>
<Story />
</MemoryRouter>
),
],
args: {
useCard: true,
errorType: PermissionDenied,
},
};

export const UnprocessableEntityErrorInactive = {
args: {
useCard: true,
errorType: UnprocessableEntity,
errorCode: 'form-inactive',
},
};

export const UnprocessableEntityErrorGeneric = {
args: {
useCard: true,
errorType: UnprocessableEntity,
errorCode: 'generic',
},
};

export const ServiceUnavailableErrorMaintenance = {
args: {
useCard: true,
errorType: ServiceUnavailable,
errorCode: 'form-maintenance',
},
};

export const ServiceUnavailableErrorMaxSubmissions = {
args: {
useCard: true,
errorType: ServiceUnavailable,
errorCode: 'form-maximum-submissions',
},
};

export const ServiceUnavailableError = {
args: {
useCard: true,
errorType: ServiceUnavailable,
errorCode: 'service_unavailable',
},
};

export const ServiceUnavailableErrorGeneric = {
args: {
useCard: true,
errorType: ServiceUnavailable,
errorCode: 'generic',
},
};
24 changes: 24 additions & 0 deletions src/components/Errors/FormUnavailable.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {FormattedMessage, useIntl} from 'react-intl';

import ErrorMessage from 'components/Errors/ErrorMessage';

const FormUnavailable = ({wrapper: Wrapper}) => {
const intl = useIntl();
sergei-maertens marked this conversation as resolved.
Show resolved Hide resolved
// Wrapper may be a DOM element, which can't handle <FormattedMessage />
const title = intl.formatMessage({
description: 'Open Forms service unavailable error title',
defaultMessage: 'Form unavailable',
});
return (
<Wrapper title={title}>
<ErrorMessage modifier="error">
<FormattedMessage
description="Open Forms service unavailable error message"
defaultMessage="Unfortunately, this form is currently unavailable due to an outage. Please try again later."
/>
</ErrorMessage>
</Wrapper>
);
};

export default FormUnavailable;