chore(native-filters): Ensure consistent error handling (#24206)

Co-authored-by: Michael S. Molina <michael.s.molina@gmail.com>
This commit is contained in:
John Bodley 2023-05-30 09:08:38 -07:00 committed by GitHub
parent 6e7b93eb48
commit 674da1b209
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 75 additions and 31 deletions

View File

@ -37,6 +37,7 @@ const StyledContent = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-left: ${({ theme }) => theme.gridUnit * 2}px; margin-left: ${({ theme }) => theme.gridUnit * 2}px;
overflow: hidden;
`; `;
const StyledTitle = styled.span` const StyledTitle = styled.span`

View File

@ -21,7 +21,8 @@ import React from 'react';
import { render, screen } from 'spec/helpers/testing-library'; import { render, screen } from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import ErrorMessageWithStackTrace from './ErrorMessageWithStackTrace'; import ErrorMessageWithStackTrace from './ErrorMessageWithStackTrace';
import { ErrorLevel, ErrorSource } from './types'; import BasicErrorAlert from './BasicErrorAlert';
import { ErrorLevel, ErrorSource, ErrorTypeEnum } from './types';
jest.mock( jest.mock(
'src/components/Icons/Icon', 'src/components/Icons/Icon',
@ -57,3 +58,21 @@ test('should render the link', () => {
expect(link).toHaveTextContent('(Request Access)'); expect(link).toHaveTextContent('(Request Access)');
expect(link).toHaveAttribute('href', mockedProps.link); expect(link).toHaveAttribute('href', mockedProps.link);
}); });
test('should render the fallback', () => {
const body = 'Blahblah';
render(
<ErrorMessageWithStackTrace
error={{
error_type: ErrorTypeEnum.FRONTEND_NETWORK_ERROR,
message: body,
extra: {},
level: 'error',
}}
fallback={<BasicErrorAlert title="Blah" body={body} level="error" />}
{...mockedProps}
/>,
{ useRedux: true },
);
expect(screen.getByText(body)).toBeInTheDocument();
});

View File

@ -34,6 +34,7 @@ type Props = {
source?: ErrorSource; source?: ErrorSource;
description?: string; description?: string;
errorMitigationFunction?: () => void; errorMitigationFunction?: () => void;
fallback?: React.ReactNode;
}; };
export default function ErrorMessageWithStackTrace({ export default function ErrorMessageWithStackTrace({
@ -45,6 +46,7 @@ export default function ErrorMessageWithStackTrace({
stackTrace, stackTrace,
source, source,
description, description,
fallback,
}: Props) { }: Props) {
// Check if a custom error message component was registered for this message // Check if a custom error message component was registered for this message
if (error) { if (error) {
@ -62,6 +64,10 @@ export default function ErrorMessageWithStackTrace({
} }
} }
if (fallback) {
return <>{fallback}</>;
}
return ( return (
<ErrorAlert <ErrorAlert
level="warning" level="warning"

View File

@ -40,9 +40,13 @@ import { isEqual, isEqualWith } from 'lodash';
import { getChartDataRequest } from 'src/components/Chart/chartAction'; import { getChartDataRequest } from 'src/components/Chart/chartAction';
import Loading from 'src/components/Loading'; import Loading from 'src/components/Loading';
import BasicErrorAlert from 'src/components/ErrorMessage/BasicErrorAlert'; import BasicErrorAlert from 'src/components/ErrorMessage/BasicErrorAlert';
import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace';
import { isFeatureEnabled } from 'src/featureFlags'; import { isFeatureEnabled } from 'src/featureFlags';
import { waitForAsyncData } from 'src/middleware/asyncEvent'; import { waitForAsyncData } from 'src/middleware/asyncEvent';
import { ClientErrorObject } from 'src/utils/getClientErrorObject'; import {
ClientErrorObject,
getClientErrorObject,
} from 'src/utils/getClientErrorObject';
import { FilterBarOrientation, RootState } from 'src/dashboard/types'; import { FilterBarOrientation, RootState } from 'src/dashboard/types';
import { import {
onFiltersRefreshSuccess, onFiltersRefreshSuccess,
@ -97,7 +101,7 @@ const FilterValue: React.FC<FilterControlProps> = ({
const dependencies = useFilterDependencies(id, dataMaskSelected); const dependencies = useFilterDependencies(id, dataMaskSelected);
const shouldRefresh = useShouldFilterRefresh(); const shouldRefresh = useShouldFilterRefresh();
const [state, setState] = useState<ChartDataResponseResult[]>([]); const [state, setState] = useState<ChartDataResponseResult[]>([]);
const [error, setError] = useState<string>(''); const [error, setError] = useState<ClientErrorObject>();
const [formData, setFormData] = useState<Partial<QueryFormData>>({ const [formData, setFormData] = useState<Partial<QueryFormData>>({
inView: false, inView: false,
}); });
@ -183,11 +187,11 @@ const FilterValue: React.FC<FilterControlProps> = ({
setState(asyncResult); setState(asyncResult);
handleFilterLoadFinish(); handleFilterLoadFinish();
}) })
.catch((error: ClientErrorObject) => { .catch((error: Response) => {
setError( getClientErrorObject(error).then(clientErrorObject => {
error.message || error.error || t('Check configuration'), setError(clientErrorObject);
); handleFilterLoadFinish();
handleFilterLoadFinish(); });
}); });
} else { } else {
throw new Error( throw new Error(
@ -196,13 +200,15 @@ const FilterValue: React.FC<FilterControlProps> = ({
} }
} else { } else {
setState(json.result); setState(json.result);
setError(''); setError(undefined);
handleFilterLoadFinish(); handleFilterLoadFinish();
} }
}) })
.catch((error: Response) => { .catch((error: Response) => {
setError(error.statusText); getClientErrorObject(error).then(clientErrorObject => {
handleFilterLoadFinish(); setError(clientErrorObject);
handleFilterLoadFinish();
});
}); });
} }
}, [ }, [
@ -298,10 +304,15 @@ const FilterValue: React.FC<FilterControlProps> = ({
if (error) { if (error) {
return ( return (
<BasicErrorAlert <ErrorMessageWithStackTrace
title={t('Cannot load filter')} error={error.errors?.[0]}
body={error} fallback={
level="error" <BasicErrorAlert
title={t('Cannot load filter')}
body={error.error}
level="error"
/>
}
/> />
); );
} }

View File

@ -54,6 +54,7 @@ import { Input, TextArea } from 'src/components/Input';
import { Select, FormInstance } from 'src/components'; import { Select, FormInstance } from 'src/components';
import Collapse from 'src/components/Collapse'; import Collapse from 'src/components/Collapse';
import BasicErrorAlert from 'src/components/ErrorMessage/BasicErrorAlert'; import BasicErrorAlert from 'src/components/ErrorMessage/BasicErrorAlert';
import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace';
import { FormItem } from 'src/components/Form'; import { FormItem } from 'src/components/Form';
import Icons from 'src/components/Icons'; import Icons from 'src/components/Icons';
import Loading from 'src/components/Loading'; import Loading from 'src/components/Loading';
@ -72,7 +73,10 @@ import DateFilterControl from 'src/explore/components/controls/DateFilterControl
import AdhocFilterControl from 'src/explore/components/controls/FilterControl/AdhocFilterControl'; import AdhocFilterControl from 'src/explore/components/controls/FilterControl/AdhocFilterControl';
import { isFeatureEnabled } from 'src/featureFlags'; import { isFeatureEnabled } from 'src/featureFlags';
import { waitForAsyncData } from 'src/middleware/asyncEvent'; import { waitForAsyncData } from 'src/middleware/asyncEvent';
import { ClientErrorObject } from 'src/utils/getClientErrorObject'; import {
ClientErrorObject,
getClientErrorObject,
} from 'src/utils/getClientErrorObject';
import { SingleValueType } from 'src/filters/components/Range/SingleValueType'; import { SingleValueType } from 'src/filters/components/Range/SingleValueType';
import { import {
getFormData, getFormData,
@ -345,7 +349,7 @@ const FiltersConfigForm = (
ref: React.RefObject<any>, ref: React.RefObject<any>,
) => { ) => {
const isRemoved = !!removedFilters[filterId]; const isRemoved = !!removedFilters[filterId];
const [error, setError] = useState<string>(''); const [error, setError] = useState<ClientErrorObject>();
const [metrics, setMetrics] = useState<Metric[]>([]); const [metrics, setMetrics] = useState<Metric[]>([]);
const [activeTabKey, setActiveTabKey] = useState<string>( const [activeTabKey, setActiveTabKey] = useState<string>(
FilterTabs.configuration.key, FilterTabs.configuration.key,
@ -440,11 +444,11 @@ const FiltersConfigForm = (
const setNativeFilterFieldValuesWrapper = (values: object) => { const setNativeFilterFieldValuesWrapper = (values: object) => {
setNativeFilterFieldValues(form, filterId, values); setNativeFilterFieldValues(form, filterId, values);
setError(''); setError(undefined);
forceUpdate(); forceUpdate();
}; };
const setErrorWrapper = (error: string) => { const setErrorWrapper = (error: ClientErrorObject) => {
setNativeFilterFieldValues(form, filterId, { setNativeFilterFieldValues(form, filterId, {
defaultValueQueriesData: null, defaultValueQueriesData: null,
}); });
@ -506,10 +510,10 @@ const FiltersConfigForm = (
defaultValueQueriesData: asyncResult, defaultValueQueriesData: asyncResult,
}); });
}) })
.catch((error: ClientErrorObject) => { .catch((error: Response) => {
setError( getClientErrorObject(error).then(clientErrorObject => {
error.message || error.error || t('Check configuration'), setErrorWrapper(clientErrorObject);
); });
}); });
} else { } else {
throw new Error( throw new Error(
@ -523,10 +527,8 @@ const FiltersConfigForm = (
} }
}) })
.catch((error: Response) => { .catch((error: Response) => {
error.json().then(body => { getClientErrorObject(error).then(clientErrorObject => {
setErrorWrapper( setError(clientErrorObject);
body.message || error.statusText || t('Check configuration'),
);
}); });
}); });
}, },
@ -1224,10 +1226,15 @@ const FiltersConfigForm = (
{error || showDefaultValue ? ( {error || showDefaultValue ? (
<DefaultValueContainer> <DefaultValueContainer>
{error ? ( {error ? (
<BasicErrorAlert <ErrorMessageWithStackTrace
title={t('Cannot load filter')} error={error.errors?.[0]}
body={error} fallback={
level="error" <BasicErrorAlert
title={t('Cannot load filter')}
body={error.error}
level="error"
/>
}
/> />
) : ( ) : (
<DefaultValue <DefaultValue