Merge pull request #2283 from OHIF/IDC-2223-update
feat(IDC-2223): add new log service to be used by debugger extension
This commit is contained in:
commit
5a970056ae
@ -12,3 +12,15 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.debug-report-modal-container .errors {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.debug-report-modal-container .errors-container {
|
||||
border: 1px solid var(--active-color);
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
overflow: scroll;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ const DubugReportModal = ({
|
||||
extensionManager,
|
||||
mailTo,
|
||||
debugModalMessage,
|
||||
errors = [],
|
||||
}) => {
|
||||
const copyDebugDataToClipboard = () => {
|
||||
const body = getEmailBody();
|
||||
@ -131,6 +132,19 @@ const DubugReportModal = ({
|
||||
{getLayout(viewports)}
|
||||
</table>
|
||||
</div>
|
||||
<div className="errors">
|
||||
<h3>Errors ({errors.length})</h3>
|
||||
<div className="errors-container">
|
||||
{errors.map(error => {
|
||||
return (
|
||||
<div>
|
||||
<pre>Message: {error.message}</pre>
|
||||
{error.error && <pre>Stack: {error.error.stack}</pre>}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import OHIF from '@ohif/core';
|
||||
import { ToolbarButton, useLogger } from '@ohif/ui';
|
||||
|
||||
import {
|
||||
save,
|
||||
upload,
|
||||
@ -95,6 +97,7 @@ export function getCommands(context, servicesManager, extensionManager) {
|
||||
const { UIModalService } = servicesManager.services;
|
||||
|
||||
const WrappedDebugReportModal = function() {
|
||||
const { state } = useLogger();
|
||||
return (
|
||||
<DebugReportModal
|
||||
viewports={viewports}
|
||||
@ -103,6 +106,7 @@ export function getCommands(context, servicesManager, extensionManager) {
|
||||
extensionManager={extensionManager}
|
||||
mailTo={state.mailTo}
|
||||
debugModalMessage={state.debugModalMessage}
|
||||
errors={state.errors}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -70,12 +70,18 @@ class DicomMicroscopyViewport extends Component {
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Microscopy Viewer] Failed to load:', error);
|
||||
const { UINotificationService } = this.props.servicesManager.services;
|
||||
const {
|
||||
UINotificationService,
|
||||
LoggerService,
|
||||
} = this.props.servicesManager.services;
|
||||
if (UINotificationService) {
|
||||
const message =
|
||||
'Failed to load viewport. Please check that you have hardware acceleration enabled.';
|
||||
LoggerService.error({ error, message });
|
||||
UINotificationService.show({
|
||||
autoClose: false,
|
||||
title: 'Microscopy Viewport',
|
||||
message:
|
||||
'Failed to load viewport. Please check that you have hardware acceleration enabled.',
|
||||
message,
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
|
||||
@ -25,12 +25,13 @@ export default {
|
||||
return toolbarModule;
|
||||
},
|
||||
getPanelModule({ commandsManager, api, servicesManager }) {
|
||||
const { UINotificationService } = servicesManager.services;
|
||||
const { UINotificationService, LoggerService } = servicesManager.services;
|
||||
|
||||
const ExtendedSegmentationPanel = props => {
|
||||
const { activeContexts } = api.hooks.useAppContext();
|
||||
|
||||
const onDisplaySetLoadFailureHandler = error => {
|
||||
LoggerService.error({ error, message: error.message });
|
||||
UINotificationService.show({
|
||||
title: 'DICOM Segmentation Loader',
|
||||
message: error.message,
|
||||
|
||||
@ -360,12 +360,15 @@ class OHIFVTKViewport extends Component {
|
||||
} catch (error) {
|
||||
const errorTitle = 'Failed to load 2D MPR';
|
||||
console.error(errorTitle, error);
|
||||
const { UINotificationService } = this.props.servicesManager.services;
|
||||
const {
|
||||
UINotificationService,
|
||||
LoggerService,
|
||||
} = this.props.servicesManager.services;
|
||||
if (this.props.viewportIndex === 0) {
|
||||
const message = error.message.includes('buffer')
|
||||
? 'Dataset is too big to display in MPR'
|
||||
: error.message;
|
||||
console.error(errorTitle, error);
|
||||
LoggerService.error({ error, message });
|
||||
UINotificationService.show({
|
||||
title: errorTitle,
|
||||
message,
|
||||
@ -428,11 +431,15 @@ class OHIFVTKViewport extends Component {
|
||||
};
|
||||
|
||||
const onPixelDataInsertedErrorCallback = error => {
|
||||
const { UINotificationService } = this.props.servicesManager.services;
|
||||
const {
|
||||
UINotificationService,
|
||||
LoggerService,
|
||||
} = this.props.servicesManager.services;
|
||||
|
||||
if (!this.hasError) {
|
||||
if (this.props.viewportIndex === 0) {
|
||||
// Only show the notification from one viewport 1 in MPR2D.
|
||||
LoggerService.error({ error, message: error.message });
|
||||
UINotificationService.show({
|
||||
title: 'MPR Load Error',
|
||||
message: error.message,
|
||||
|
||||
@ -14,7 +14,9 @@ import OHIFVTKViewport from './OHIFVTKViewport';
|
||||
|
||||
const { BlendMode } = Constants;
|
||||
|
||||
const commandsModule = ({ commandsManager, UINotificationService }) => {
|
||||
const commandsModule = ({ commandsManager, servicesManager }) => {
|
||||
const { UINotificationService, LoggerService } = servicesManager.services;
|
||||
|
||||
// TODO: Put this somewhere else
|
||||
let apis = {};
|
||||
let defaultVOI;
|
||||
@ -175,7 +177,7 @@ const commandsModule = ({ commandsManager, UINotificationService }) => {
|
||||
segmentNumber,
|
||||
frameIndex,
|
||||
frame,
|
||||
done = () => { },
|
||||
done = () => {},
|
||||
}) => {
|
||||
let api = apis[viewports.activeViewportIndex];
|
||||
|
||||
@ -473,10 +475,12 @@ const commandsModule = ({ commandsManager, UINotificationService }) => {
|
||||
const volumeLength = dimensions[0] * dimensions[1] * dimensions[2];
|
||||
|
||||
if (volumeLength > maxBufferLengthFloat32) {
|
||||
const message =
|
||||
'This volume is too large to fit in WebGL 1 textures and will display incorrectly. Please use a different browser to view this data';
|
||||
LoggerService.error({ message });
|
||||
UINotificationService.show({
|
||||
title: 'Browser does not support WebGL 2',
|
||||
message:
|
||||
'This volume is too large to fit in WebGL 1 textures and will display incorrectly. Please use a different browser to view this data',
|
||||
message,
|
||||
type: 'error',
|
||||
autoClose: false,
|
||||
});
|
||||
|
||||
@ -35,8 +35,7 @@ const vtkExtension = {
|
||||
return toolbarModule;
|
||||
},
|
||||
getCommandsModule({ commandsManager, servicesManager }) {
|
||||
const { UINotificationService } = servicesManager.services;
|
||||
return commandsModule({ commandsManager, UINotificationService });
|
||||
return commandsModule({ commandsManager, servicesManager });
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -63,10 +63,15 @@ export class HotkeysManager {
|
||||
|
||||
definitions.forEach(definition => this.registerHotkeys(definition));
|
||||
} catch (error) {
|
||||
const { UINotificationService } = this._servicesManager.services;
|
||||
const {
|
||||
UINotificationService,
|
||||
LoggerService,
|
||||
} = this._servicesManager.services;
|
||||
const message = 'Erro while setting hotkeys';
|
||||
LoggerService.error({ error, message });
|
||||
UINotificationService.show({
|
||||
title: 'Hotkeys Manager',
|
||||
message: 'Erro while setting hotkeys',
|
||||
message,
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@ import {
|
||||
UIModalService,
|
||||
UIDialogService,
|
||||
MeasurementService,
|
||||
LoggerService,
|
||||
} from './services';
|
||||
|
||||
const OHIF = {
|
||||
@ -60,6 +61,7 @@ const OHIF = {
|
||||
UIModalService,
|
||||
UIDialogService,
|
||||
MeasurementService,
|
||||
LoggerService,
|
||||
};
|
||||
|
||||
export {
|
||||
@ -93,6 +95,7 @@ export {
|
||||
UIModalService,
|
||||
UIDialogService,
|
||||
MeasurementService,
|
||||
LoggerService,
|
||||
};
|
||||
|
||||
export { OHIF };
|
||||
|
||||
@ -14,6 +14,7 @@ describe('Top level exports', () => {
|
||||
'UIModalService',
|
||||
'UIDialogService',
|
||||
'MeasurementService',
|
||||
'LoggerService',
|
||||
//
|
||||
'utils',
|
||||
'hotkeys',
|
||||
|
||||
67
platform/core/src/services/LoggerService/index.js
Normal file
67
platform/core/src/services/LoggerService/index.js
Normal file
@ -0,0 +1,67 @@
|
||||
const name = 'LoggerService';
|
||||
|
||||
const publicAPI = {
|
||||
name,
|
||||
info: _info,
|
||||
error: _error,
|
||||
setServiceImplementation,
|
||||
};
|
||||
|
||||
const serviceImplementation = {
|
||||
_info: () => console.warn('info() NOT IMPLEMENTED'),
|
||||
_error: () => console.warn('error() NOT IMPLEMENTED'),
|
||||
};
|
||||
|
||||
/**
|
||||
* Logs an info
|
||||
*
|
||||
* @param {object} props { message, displayOnConsole }
|
||||
*/
|
||||
function _info({ message, displayOnConsole }) {
|
||||
return serviceImplementation._info({
|
||||
message,
|
||||
displayOnConsole,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an error
|
||||
*
|
||||
* @param {object} props { error, stack, message, displayOnConsole }
|
||||
* @returns void
|
||||
*/
|
||||
function _error({ error, stack, message, displayOnConsole }) {
|
||||
return serviceImplementation._error({
|
||||
error,
|
||||
stack,
|
||||
message,
|
||||
displayOnConsole,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {*} {
|
||||
* info: infoImplementation,
|
||||
* error: errorImplementation,
|
||||
* }
|
||||
*/
|
||||
function setServiceImplementation({
|
||||
info: infoImplementation,
|
||||
error: errorImplementation,
|
||||
}) {
|
||||
if (infoImplementation) {
|
||||
serviceImplementation._info = infoImplementation;
|
||||
}
|
||||
if (errorImplementation) {
|
||||
serviceImplementation._error = errorImplementation;
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name,
|
||||
create: ({ configuration = {} }) => {
|
||||
return publicAPI;
|
||||
},
|
||||
};
|
||||
@ -3,6 +3,7 @@ import UINotificationService from './UINotificationService';
|
||||
import UIModalService from './UIModalService';
|
||||
import UIDialogService from './UIDialogService';
|
||||
import MeasurementService from './MeasurementService';
|
||||
import LoggerService from './LoggerService';
|
||||
|
||||
export {
|
||||
UINotificationService,
|
||||
@ -10,4 +11,5 @@ export {
|
||||
UIDialogService,
|
||||
ServicesManager,
|
||||
MeasurementService,
|
||||
LoggerService,
|
||||
};
|
||||
|
||||
104
platform/ui/src/contextProviders/LoggerProvider.js
Normal file
104
platform/ui/src/contextProviders/LoggerProvider.js
Normal file
@ -0,0 +1,104 @@
|
||||
import React, { useState, createContext, useContext, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const LoggerContext = createContext(null);
|
||||
const { Provider } = LoggerContext;
|
||||
|
||||
export const useLogger = () => useContext(LoggerContext);
|
||||
|
||||
const LoggerProvider = ({ children, service }) => {
|
||||
const [state, setState] = useState({
|
||||
errors: [],
|
||||
infos: [],
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const onErrorHandler = ({ error: errorObject, message }) => {
|
||||
error({ error: errorObject, message });
|
||||
};
|
||||
window.addEventListener('error', onErrorHandler);
|
||||
return () => {
|
||||
window.removeEventListener('error', onErrorHandler);
|
||||
};
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Logs an error
|
||||
*
|
||||
* @param {object} props { error, stack, message, displayOnConsole }
|
||||
* @returns void
|
||||
*/
|
||||
const error = ({
|
||||
error = {},
|
||||
stack = '',
|
||||
message = '',
|
||||
displayOnConsole = true,
|
||||
}) => {
|
||||
const errorObject = { error, stack, message, displayOnConsole };
|
||||
setState(state => ({ ...state, errors: [...state.errors, errorObject] }));
|
||||
|
||||
if (displayOnConsole) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Logs an info
|
||||
*
|
||||
* @param {object} props { message, displayOnConsole }
|
||||
* @returns void
|
||||
*/
|
||||
const info = ({ message = '', displayOnConsole = true }) => {
|
||||
setState(state => ({
|
||||
...state,
|
||||
infos: state.infos.push({ message, displayOnConsole }),
|
||||
}));
|
||||
|
||||
if (displayOnConsole) {
|
||||
console.info(message);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the implementation of a log service that can be used by extensions
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (service) {
|
||||
service.setServiceImplementation({ error, info });
|
||||
}
|
||||
}, [error, service, info]);
|
||||
|
||||
return <Provider value={{ info, error, state }}>{children}</Provider>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Higher Order Component to use the log methods through a Class Component
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
export const withLogger = Component => {
|
||||
return function WrappedComponent(props) {
|
||||
const { error, info, state } = useLogger();
|
||||
return <Component {...props} logger={{ error, info, state }} />;
|
||||
};
|
||||
};
|
||||
|
||||
LoggerProvider.defaultProps = {
|
||||
service: null,
|
||||
};
|
||||
|
||||
LoggerProvider.propTypes = {
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
]).isRequired,
|
||||
service: PropTypes.shape({
|
||||
setServiceImplementation: PropTypes.func,
|
||||
}),
|
||||
};
|
||||
|
||||
export default LoggerProvider;
|
||||
|
||||
export const LogConsumer = LoggerContext.Consumer;
|
||||
@ -18,3 +18,8 @@ export {
|
||||
withDialog,
|
||||
useDialog,
|
||||
} from './DialogProvider.js';
|
||||
export {
|
||||
default as LoggerProvider,
|
||||
withLogger,
|
||||
useLogger,
|
||||
} from './LoggerProvider.js';
|
||||
|
||||
@ -30,7 +30,7 @@ import {
|
||||
AboutContent,
|
||||
OHIFModal,
|
||||
ErrorBoundary,
|
||||
ErrorPage
|
||||
ErrorPage,
|
||||
} from './components';
|
||||
import { useDebounce, useMedia } from './hooks';
|
||||
|
||||
@ -66,6 +66,9 @@ import {
|
||||
ModalConsumer,
|
||||
useModal,
|
||||
withModal,
|
||||
LoggerProvider,
|
||||
withLogger,
|
||||
useLogger,
|
||||
} from './contextProviders';
|
||||
|
||||
export {
|
||||
@ -127,11 +130,14 @@ export {
|
||||
useDialog,
|
||||
ErrorBoundary,
|
||||
ErrorPage,
|
||||
LoggerProvider,
|
||||
withLogger,
|
||||
useLogger,
|
||||
// Hooks
|
||||
useDebounce,
|
||||
useMedia,
|
||||
// Utils
|
||||
ViewerbaseDragDropContext,
|
||||
asyncComponent,
|
||||
retryImport
|
||||
retryImport,
|
||||
};
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
ModalProvider,
|
||||
DialogProvider,
|
||||
OHIFModal,
|
||||
LoggerProvider,
|
||||
ErrorBoundary,
|
||||
} from '@ohif/ui';
|
||||
|
||||
@ -24,6 +25,7 @@ import {
|
||||
UINotificationService,
|
||||
UIModalService,
|
||||
UIDialogService,
|
||||
LoggerService,
|
||||
MeasurementService,
|
||||
utils,
|
||||
redux as reduxOHIF,
|
||||
@ -141,6 +143,7 @@ class App extends Component {
|
||||
UIModalService,
|
||||
UIDialogService,
|
||||
MeasurementService,
|
||||
LoggerService,
|
||||
]);
|
||||
_initExtensions(
|
||||
[...defaultExtensions, ...extensions],
|
||||
@ -164,6 +167,7 @@ class App extends Component {
|
||||
UIDialogService,
|
||||
UIModalService,
|
||||
MeasurementService,
|
||||
LoggerService,
|
||||
} = servicesManager.services;
|
||||
|
||||
if (this._userManager) {
|
||||
@ -176,18 +180,20 @@ class App extends Component {
|
||||
<UserManagerContext.Provider value={this._userManager}>
|
||||
<Router basename={routerBasename}>
|
||||
<WhiteLabelingContext.Provider value={whiteLabeling}>
|
||||
<SnackbarProvider service={UINotificationService}>
|
||||
<DialogProvider service={UIDialogService}>
|
||||
<ModalProvider
|
||||
modal={OHIFModal}
|
||||
service={UIModalService}
|
||||
>
|
||||
<OHIFStandaloneViewer
|
||||
userManager={this._userManager}
|
||||
/>
|
||||
</ModalProvider>
|
||||
</DialogProvider>
|
||||
</SnackbarProvider>
|
||||
<LoggerProvider service={LoggerService}>
|
||||
<SnackbarProvider service={UINotificationService}>
|
||||
<DialogProvider service={UIDialogService}>
|
||||
<ModalProvider
|
||||
modal={OHIFModal}
|
||||
service={UIModalService}
|
||||
>
|
||||
<OHIFStandaloneViewer
|
||||
userManager={this._userManager}
|
||||
/>
|
||||
</ModalProvider>
|
||||
</DialogProvider>
|
||||
</SnackbarProvider>
|
||||
</LoggerProvider>
|
||||
</WhiteLabelingContext.Provider>
|
||||
</Router>
|
||||
</UserManagerContext.Provider>
|
||||
@ -206,13 +212,18 @@ class App extends Component {
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<Router basename={routerBasename}>
|
||||
<WhiteLabelingContext.Provider value={whiteLabeling}>
|
||||
<SnackbarProvider service={UINotificationService}>
|
||||
<DialogProvider service={UIDialogService}>
|
||||
<ModalProvider modal={OHIFModal} service={UIModalService}>
|
||||
<OHIFStandaloneViewer />
|
||||
</ModalProvider>
|
||||
</DialogProvider>
|
||||
</SnackbarProvider>
|
||||
<LoggerProvider service={LoggerService}>
|
||||
<SnackbarProvider service={UINotificationService}>
|
||||
<DialogProvider service={UIDialogService}>
|
||||
<ModalProvider
|
||||
modal={OHIFModal}
|
||||
service={UIModalService}
|
||||
>
|
||||
<OHIFStandaloneViewer />
|
||||
</ModalProvider>
|
||||
</DialogProvider>
|
||||
</SnackbarProvider>
|
||||
</LoggerProvider>
|
||||
</WhiteLabelingContext.Provider>
|
||||
</Router>
|
||||
</I18nextProvider>
|
||||
|
||||
@ -4,7 +4,7 @@ import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { utils } from '@ohif/core';
|
||||
import { useSnackbarContext } from '@ohif/ui';
|
||||
import { useSnackbarContext, useLogger } from '@ohif/ui';
|
||||
//
|
||||
import ViewportPane from './ViewportPane.js';
|
||||
import DefaultViewport from './DefaultViewport.js';
|
||||
@ -12,7 +12,7 @@ import EmptyViewport from './EmptyViewport.js';
|
||||
|
||||
const { loadAndCacheDerivedDisplaySets } = utils;
|
||||
|
||||
const ViewportGrid = function (props) {
|
||||
const ViewportGrid = function(props) {
|
||||
const {
|
||||
activeViewportIndex,
|
||||
availablePlugins,
|
||||
@ -36,14 +36,15 @@ const ViewportGrid = function (props) {
|
||||
}
|
||||
|
||||
const snackbar = useSnackbarContext();
|
||||
const logger = useLogger();
|
||||
|
||||
useEffect(() => {
|
||||
if (isStudyLoaded) {
|
||||
viewportData.forEach(displaySet => {
|
||||
const promises = loadAndCacheDerivedDisplaySets(displaySet, studies);
|
||||
|
||||
promises.forEach(promise => {
|
||||
promise.catch(error => {
|
||||
logger.error({ error, message: error.message });
|
||||
snackbar.show({
|
||||
title: 'Error loading derived display set:',
|
||||
message: error.message,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user