Skip to content

Commit

Permalink
[UnifiedFieldList] Handle exceptions in FieldPopover better (elastic#…
Browse files Browse the repository at this point in the history
…155866)

## Summary

This PR wraps rendering of FieldPopover with additional try/catch and
error boundary so we can look into exceptions if they happen.
  • Loading branch information
jughosta authored Apr 27, 2023
1 parent 5422d08 commit c571a38
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';

/**
* Renders nothing instead of a component which triggered an exception.
*/
export class ErrorBoundary extends React.Component<{}, { hasError: boolean }> {
constructor(props: {}) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError() {
return { hasError: true };
}

render() {
if (this.state.hasError) {
return null;
}

return this.props.children;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

export { ErrorBoundary } from './error_boundary';
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,24 @@ export const FieldPopover: React.FC<FieldPopoverProps> = ({
renderContent,
...otherPopoverProps
}) => {
const header = (isOpen && renderHeader?.()) || null;
const content = (isOpen && renderContent?.()) || null;
let header = null;
let content = null;

if (isOpen) {
try {
header = renderHeader?.() || null;
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}

try {
content = renderContent?.() || null;
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}
}

return (
<EuiPopover
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,27 @@ import React, { useEffect, useState } from 'react';
import { EuiPopoverFooter, EuiSpacer } from '@elastic/eui';
import { type FieldVisualizeButtonProps, getFieldVisualizeButton } from '../field_visualize_button';
import { FieldCategorizeButtonProps, getFieldCategorizeButton } from '../field_categorize_button';
import { ErrorBoundary } from '../error_boundary';

export type FieldPopoverFooterProps = FieldVisualizeButtonProps | FieldCategorizeButtonProps;

export const FieldPopoverFooter: React.FC<FieldPopoverFooterProps> = (props) => {
const FieldPopoverFooterComponent: React.FC<FieldPopoverFooterProps> = (props) => {
const [visualizeButton, setVisualizeButton] = useState<JSX.Element | null>(null);
const [categorizeButton, setCategorizeButton] = useState<JSX.Element | null>(null);

useEffect(() => {
getFieldVisualizeButton(props).then(setVisualizeButton);
getFieldCategorizeButton(props).then(setCategorizeButton);
getFieldVisualizeButton(props)
.then(setVisualizeButton)
.catch((error) => {
// eslint-disable-next-line no-console
console.error(error);
});
getFieldCategorizeButton(props)
.then(setCategorizeButton)
.catch((error) => {
// eslint-disable-next-line no-console
console.error(error);
});
}, [props]);

return visualizeButton || categorizeButton ? (
Expand All @@ -30,3 +41,11 @@ export const FieldPopoverFooter: React.FC<FieldPopoverFooterProps> = (props) =>
</EuiPopoverFooter>
) : null;
};

export const FieldPopoverFooter: React.FC<FieldPopoverFooterProps> = (props) => {
return (
<ErrorBoundary>
<FieldPopoverFooterComponent {...props} />
</ErrorBoundary>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
getDefaultColor,
} from './field_top_values';
import { FieldSummaryMessage } from './field_summary_message';
import { ErrorBoundary } from '../error_boundary';

export interface FieldStatsState {
isLoading: boolean;
Expand Down Expand Up @@ -540,25 +541,6 @@ const FieldStatsComponent: React.FC<FieldStatsProps> = ({
return null;
};

class ErrorBoundary extends React.Component<{}, { hasError: boolean }> {
constructor(props: FieldStatsProps) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError() {
return { hasError: true };
}

render() {
if (this.state.hasError) {
return null;
}

return this.props.children;
}
}

/**
* Component which fetches and renders stats for a data view field
* @param props
Expand Down

0 comments on commit c571a38

Please sign in to comment.