From eddd510a3538620738ba073218cf9f5487f2f505 Mon Sep 17 00:00:00 2001 From: Alireza Date: Wed, 16 Nov 2022 20:27:24 -0500 Subject: [PATCH] feat: enhanced error handling ui (#3021) * feat: better error handling * better error handling * try to fix netlify build * remove new line --- .../src/Panels/PanelMeasurementTable.tsx | 14 +---- extensions/default/src/ViewerLayout/index.tsx | 27 ++++++++-- .../MeasurementService/MeasurementService.js | 3 +- .../ErrorBoundary/ErrorBoundary.tsx | 51 +++++++++++++------ platform/ui/src/components/Icon/getIcon.js | 10 +++- platform/ui/src/components/index.js | 3 +- platform/ui/src/index.js | 2 +- runtime.txt | 2 +- 8 files changed, 73 insertions(+), 39 deletions(-) diff --git a/extensions/default/src/Panels/PanelMeasurementTable.tsx b/extensions/default/src/Panels/PanelMeasurementTable.tsx index fb890117828..78c6a905d8c 100644 --- a/extensions/default/src/Panels/PanelMeasurementTable.tsx +++ b/extensions/default/src/Panels/PanelMeasurementTable.tsx @@ -8,14 +8,6 @@ import { utils } from '@ohif/core'; const { downloadCSVReport } = utils; -// tools with measurements to display inside the panel -const MEASUREMENT_TOOLS = [ - 'EllipticalROI', - 'RectangleROI', - 'Length', - 'Bidirectional', -]; - export default function PanelMeasurementTable({ servicesManager, commandsManager, @@ -185,12 +177,8 @@ PanelMeasurementTable.propTypes = { function _getMappedMeasurements(MeasurementService) { const measurements = MeasurementService.getMeasurements(); - // filter out measurements whose toolName is not in MEASUREMENT_TOOLS - const measurementTools = measurements.filter(measurement => - MEASUREMENT_TOOLS.includes(measurement.toolName) - ); - const mappedMeasurements = measurementTools.map((m, index) => + const mappedMeasurements = measurements.map((m, index) => _mapMeasurementToDisplay(m, index, MeasurementService.VALUE_TYPES) ); diff --git a/extensions/default/src/ViewerLayout/index.tsx b/extensions/default/src/ViewerLayout/index.tsx index 562760b0435..fb5fb5503e4 100644 --- a/extensions/default/src/ViewerLayout/index.tsx +++ b/extensions/default/src/ViewerLayout/index.tsx @@ -122,10 +122,29 @@ function ViewerLayout({ }; }, []); - const getPanelData = id => { + const getComponent = id => { const entry = extensionManager.getModuleEntry(id); - // TODO, not sure why sidepanel content has to be JSX, and not a children prop? - const content = entry.component; + + if (!entry) { + throw new Error( + `${id} is not a valid entry for an extension module, please check your configuration or make sure the extension is registered.` + ); + } + + let content; + if (entry && entry.component) { + content = entry.component; + } else { + throw new Error( + `No component found from extension ${id}. Check the reference string to the extension in your Mode configuration` + ); + } + + return { entry, content }; + }; + + const getPanelData = id => { + const { content, entry } = getComponent(id); return { iconName: entry.iconName, @@ -156,7 +175,7 @@ function ViewerLayout({ }, [HangingProtocolService]); const getViewportComponentData = viewportComponent => { - const entry = extensionManager.getModuleEntry(viewportComponent.namespace); + const { entry } = getComponent(viewportComponent.namespace); return { component: entry.component, diff --git a/platform/core/src/services/MeasurementService/MeasurementService.js b/platform/core/src/services/MeasurementService/MeasurementService.js index 789218acf1e..f7258941d03 100644 --- a/platform/core/src/services/MeasurementService/MeasurementService.js +++ b/platform/core/src/services/MeasurementService/MeasurementService.js @@ -498,8 +498,7 @@ class MeasurementService { measurement.source = source; } catch (error) { throw new Error( - `Failed to map '${sourceInfo}' measurement for annotationType ${annotationType}:`, - error.message + `Failed to map '${sourceInfo}' measurement for annotationType ${annotationType}: ${error.message}` ); } diff --git a/platform/ui/src/components/ErrorBoundary/ErrorBoundary.tsx b/platform/ui/src/components/ErrorBoundary/ErrorBoundary.tsx index b101e695cea..3f89f7991f0 100644 --- a/platform/ui/src/components/ErrorBoundary/ErrorBoundary.tsx +++ b/platform/ui/src/components/ErrorBoundary/ErrorBoundary.tsx @@ -1,11 +1,18 @@ import React, { useState } from 'react'; import { ErrorBoundary as ReactErrorBoundary } from 'react-error-boundary'; +import { Icon, IconButton } from '@ohif/ui'; import PropTypes from 'prop-types'; import Modal from '../Modal'; const isProduction = process.env.NODE_ENV === 'production'; -const DefaultFallback = ({ error, componentStack, context, resetErrorBoundary, fallbackRoute }) => { +const DefaultFallback = ({ + error, + context, + resetErrorBoundary, + fallbackRoute, +}) => { + const [showDetails, setShowDetails] = useState(false); const title = `Something went wrong${!isProduction && ` in ${context}`}.`; const subtitle = `Sorry, something went wrong there. Try again.`; return ( @@ -13,17 +20,33 @@ const DefaultFallback = ({ error, componentStack, context, resetErrorBoundary, f

{title}

{subtitle}

{!isProduction && ( -
-
Context: {context}
-
Error Message: {error.message}
-
Stack: {componentStack}
+
+

Context: {context}

+

Error Message: {error.message}

+ + setShowDetails(!showDetails)} + > + +
{'Stack Trace'}
+ +
+
+ + {showDetails && ( +

Stack: {error.stack}

+ )}
)}
); }; -const noop = () => { }; +const noop = () => {}; DefaultFallback.propTypes = { error: PropTypes.object.isRequired, @@ -32,7 +55,7 @@ DefaultFallback.propTypes = { }; DefaultFallback.defaultProps = { - resetErrorBoundary: noop + resetErrorBoundary: noop, }; const ErrorBoundary = ({ @@ -42,7 +65,7 @@ const ErrorBoundary = ({ fallbackComponent: FallbackComponent, children, fallbackRoute, - isPage + isPage, }) => { const [isOpen, setIsOpen] = useState(true); @@ -53,7 +76,7 @@ const ErrorBoundary = ({ const onResetHandler = (...args) => onReset(...args); - const withModal = (Component) => props => ( + const withModal = Component => props => ( ( - + )} onReset={onResetHandler} onError={onErrorHandler} @@ -95,7 +114,7 @@ ErrorBoundary.propTypes = { onError: PropTypes.func, fallbackComponent: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), children: PropTypes.node.isRequired, - fallbackRoute: PropTypes.string + fallbackRoute: PropTypes.string, }; ErrorBoundary.defaultProps = { @@ -103,7 +122,7 @@ ErrorBoundary.defaultProps = { onReset: noop, onError: noop, fallbackComponent: DefaultFallback, - fallbackRoute: null + fallbackRoute: null, }; export default ErrorBoundary; diff --git a/platform/ui/src/components/Icon/getIcon.js b/platform/ui/src/components/Icon/getIcon.js index 1cf7bfe88a2..42c5fd6e801 100644 --- a/platform/ui/src/components/Icon/getIcon.js +++ b/platform/ui/src/components/Icon/getIcon.js @@ -201,6 +201,14 @@ const ICONS = { 'old-stop': oldStop, }; +function addIcon(iconName, iconSVG) { + if (ICONS[iconName]) { + console.warn(`Icon ${iconName} already exists.`); + } + + ICONS[iconName] = iconSVG; +} + /** * Return the matching SVG Icon as a React Component. * Results in an inlined SVG Element. If there's no match, @@ -214,4 +222,4 @@ export default function getIcon(key, props) { return React.createElement(ICONS[key], props); } -export { getIcon, ICONS }; +export { getIcon, ICONS, addIcon }; diff --git a/platform/ui/src/components/index.js b/platform/ui/src/components/index.js index 66aeeb87415..b88bcefb15b 100644 --- a/platform/ui/src/components/index.js +++ b/platform/ui/src/components/index.js @@ -8,7 +8,7 @@ import Dialog from './Dialog'; import Dropdown from './Dropdown'; import EmptyStudies from './EmptyStudies'; import ErrorBoundary from './ErrorBoundary'; -import Icon from './Icon'; +import Icon, { addIcon } from './Icon'; import IconButton from './IconButton'; import Input from './Input'; import InputDateRange from './InputDateRange'; @@ -89,6 +89,7 @@ export { ExpandableToolbarButton, ListMenu, Icon, + addIcon, IconButton, Input, InputDateRange, diff --git a/platform/ui/src/index.js b/platform/ui/src/index.js index 9dd09344c19..e5dca106c5f 100644 --- a/platform/ui/src/index.js +++ b/platform/ui/src/index.js @@ -107,7 +107,7 @@ export { } from './components'; /** These are mostly used in the docs */ -export { getIcon, ICONS } from './components/Icon/getIcon'; +export { getIcon, ICONS, addIcon } from './components/Icon/getIcon'; export { BackgroundColor } from './pages/Colors/BackgroundColor'; export { ModalComponent } from './contextProviders/ModalComponent'; export { Types }; diff --git a/runtime.txt b/runtime.txt index 475ba515c04..98fccd6d025 100644 --- a/runtime.txt +++ b/runtime.txt @@ -1 +1 @@ -3.7 +3.8 \ No newline at end of file