From f2a371fc2d7416cf44870e9a4f0c17db73bafecc Mon Sep 17 00:00:00 2001 From: Igor Octaviano Date: Mon, 13 Sep 2021 10:06:30 -0300 Subject: [PATCH] Display warning messages if fails to parse sr report (#2543) --- .../src/DICOMSR/parseDicomStructuredReport.js | 21 +++++- platform/core/src/classes/LogManager.js | 18 +++++ platform/core/src/classes/PubSub.js | 75 +++++++++++++++++++ platform/core/src/classes/index.js | 4 + .../src/contextProviders/SnackbarProvider.js | 17 +++++ 5 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 platform/core/src/classes/LogManager.js create mode 100644 platform/core/src/classes/PubSub.js diff --git a/platform/core/src/DICOMSR/parseDicomStructuredReport.js b/platform/core/src/DICOMSR/parseDicomStructuredReport.js index 87e42c6d3..d887d3158 100644 --- a/platform/core/src/DICOMSR/parseDicomStructuredReport.js +++ b/platform/core/src/DICOMSR/parseDicomStructuredReport.js @@ -1,7 +1,10 @@ import dcmjs from 'dcmjs'; +import classes from '../classes'; import findInstanceMetadataBySopInstanceUID from './utils/findInstanceMetadataBySopInstanceUid'; +const { LogManager } = classes; + /** * Function to parse the part10 array buffer that comes from a DICOM Structured report into measurementData * measurementData format is a viewer specific format to be stored into the redux and consumed by other components @@ -20,9 +23,21 @@ const parseDicomStructuredReport = (part10SRArrayBuffer, displaySets) => { ); const { MeasurementReport } = dcmjs.adapters.Cornerstone; - const storedMeasurementByToolType = MeasurementReport.generateToolState( - dataset - ); + + let storedMeasurementByToolType; + try { + storedMeasurementByToolType = MeasurementReport.generateToolState(dataset); + } catch (error) { + const seriesDescription = dataset.SeriesDescription || ''; + LogManager.publish(LogManager.EVENTS.OnLog, { + title: `Failed to parse ${seriesDescription} measurement report`, + type: 'warning', + message: error.message || '', + notify: true, + }); + return; + } + const measurementData = {}; let measurementNumber = 0; diff --git a/platform/core/src/classes/LogManager.js b/platform/core/src/classes/LogManager.js new file mode 100644 index 000000000..dc6293374 --- /dev/null +++ b/platform/core/src/classes/LogManager.js @@ -0,0 +1,18 @@ +import PubSub from './PubSub'; + +/** Log Events */ +export const LogEvents = Object.freeze({ + OnLog: 'onLog', +}); + +/** + * Log manager that implements pub/sub. + * This manager can be used to send logs across different packages + * using previously registered events. + */ +class LogManager extends PubSub { + EVENTS = LogEvents; +} + +/** Singleton */ +export default new LogManager(); diff --git a/platform/core/src/classes/PubSub.js b/platform/core/src/classes/PubSub.js new file mode 100644 index 000000000..e82026407 --- /dev/null +++ b/platform/core/src/classes/PubSub.js @@ -0,0 +1,75 @@ +const _subscriptions = Symbol('subscriptions'); +const _lastSubscriptionId = Symbol('lastSubscriptionId'); + +/** + * Class to implement publish/subscribe pattern + * + * @class + * @classdesc Pub/sub mechanism + */ +export default class PubSub { + constructor() { + this[_subscriptions] = {}; + this[_lastSubscriptionId] = 0; + } + + /** + * Subscribe to event + * + * @param {string} eventName Event name + * @param {Function} callback Callback function + * @returns {void} + */ + subscribe(eventName, callback) { + if (eventName === undefined) { + throw new Error('Event name is required'); + } + + if (typeof callback !== 'function') { + throw new Error('Callback must be a function'); + } + + if (!this[_subscriptions].hasOwnProperty(eventName)) { + this[_subscriptions][eventName] = {}; + } + + const subscriptionId = `sub${this[_lastSubscriptionId]++}`; + this[_subscriptions][eventName][subscriptionId] = callback; + } + + /** + * Removes a subscription + * + * @param {string} eventName Event name + * @param {Function} [callback] Callback function + * @returns {void} + */ + unsubscribe(eventName, callback) { + const callbacks = this[_subscriptions][eventName] || {}; + for (let subscriptionId in callbacks) { + if (!callback) { + delete callbacks[subscriptionId]; + } else if (callbacks[subscriptionId] === callback) { + delete callbacks[subscriptionId]; + } + } + } + + /** + * Publish event to all subscriptions + * + * @param {String} eventName Event name + * @param {any} [payload] Data that will be published + * @returns {void} + */ + publish(eventName, ...payload) { + if (eventName === undefined) { + throw new Error('Event name is required'); + } + + const callbacks = this[_subscriptions][eventName] || {}; + for (let subscriptionId in callbacks) { + callbacks[subscriptionId](...payload); + } + } +} diff --git a/platform/core/src/classes/index.js b/platform/core/src/classes/index.js index 9fc2f1bfe..29f4888b4 100644 --- a/platform/core/src/classes/index.js +++ b/platform/core/src/classes/index.js @@ -4,6 +4,8 @@ import CommandsManager from './CommandsManager.js'; import { DICOMFileLoadingListener } from './StudyLoadingListener'; import HotkeysManager from './HotkeysManager.js'; import ImageSet from './ImageSet'; +import LogManager from './LogManager'; +import PubSub from './PubSub'; import MetadataProvider from './MetadataProvider'; import OHIFError from './OHIFError.js'; import { OHIFStudyMetadataSource } from './OHIFStudyMetadataSource'; @@ -36,7 +38,9 @@ const classes = { MetadataProvider, CommandsManager, HotkeysManager, + LogManager, ImageSet, + PubSub, StudyPrefetcher, StudyLoadingListener, StackLoadingListener, diff --git a/platform/ui/src/contextProviders/SnackbarProvider.js b/platform/ui/src/contextProviders/SnackbarProvider.js index 86709fedc..6e54f2334 100644 --- a/platform/ui/src/contextProviders/SnackbarProvider.js +++ b/platform/ui/src/contextProviders/SnackbarProvider.js @@ -6,10 +6,13 @@ import React, { useEffect, } from 'react'; import PropTypes from 'prop-types'; +import { classes } from '@ohif/core'; import SnackbarContainer from '../components/snackbar/SnackbarContainer'; import SnackbarTypes from '../components/snackbar/SnackbarTypes'; +const { LogManager } = classes; + const SnackbarContext = createContext(null); export const useSnackbarContext = () => useContext(SnackbarContext); @@ -28,6 +31,20 @@ const SnackbarProvider = ({ children, service }) => { const [count, setCount] = useState(1); const [snackbarItems, setSnackbarItems] = useState([]); + useEffect(() => { + const onLogHandler = ({ type, notify, title, message }) => { + if (notify) { + show({ type, title, message }); + } + }; + + LogManager.subscribe(LogManager.EVENTS.OnLog, onLogHandler); + + return () => { + LogManager.subscribe(LogManager.EVENTS.OnLog, onLogHandler); + }; + }, [show]); + const show = useCallback( options => { if (!options || (!options.title && !options.message)) {