feat: configuration to hook into XHR Error handling
* Error handling. * Fix error handling, add redirect example. * remove debugger * docs: whitelabeling and httpErrorHandler * bump dicomweb-client version * bump cornerstone-wado-image-loader version * Add more documentation around httpErrorHandler callback * test: add errorHandler as an expected top level export Co-authored-by: dannyrb <danny.ri.brown@gmail.com>
This commit is contained in:
1 parent
273f31ad56
commit
e96205de35
23 files changed
+227
-76
No files matched your search
@@ -33,14 +33,14 @@
|
||||
"peerDependencies": {
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-tools": "4.12.5",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
"cornerstone-wado-image-loader": "^3.1.0",
|
||||
"dicom-parser": "^1.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"ajv": "^6.10.0",
|
||||
"dcmjs": "^0.12.0",
|
||||
"dicomweb-client": "^0.5.2",
|
||||
"dicomweb-client": "^0.6.0",
|
||||
"immer": "6.0.2",
|
||||
"isomorphic-base64": "^1.0.2",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
|
||||
@@ -5,6 +5,7 @@ import DICOMWeb from '../DICOMWeb';
|
||||
import parseDicomStructuredReport from './parseDicomStructuredReport';
|
||||
import parseMeasurementsData from './parseMeasurementsData';
|
||||
import getAllDisplaySets from './utils/getAllDisplaySets';
|
||||
import errorHandler from '../errorHandler';
|
||||
|
||||
const VERSION_NAME = 'dcmjs-0.0';
|
||||
const TRANSFER_SYNTAX_UID = '1.2.840.10008.1.2.1';
|
||||
@@ -21,6 +22,7 @@ const retrieveMeasurementFromSR = async (series, studies, serverUrl) => {
|
||||
const config = {
|
||||
url: serverUrl,
|
||||
headers: DICOMWeb.getAuthorizationHeader(),
|
||||
errorInterceptor: errorHandler.getHTTPErrorHandler(),
|
||||
};
|
||||
|
||||
const dicomWeb = new api.DICOMwebClient(config);
|
||||
@@ -71,6 +73,7 @@ const stowSRFromMeasurements = async (measurements, serverUrl) => {
|
||||
const config = {
|
||||
url: serverUrl,
|
||||
headers: DICOMWeb.getAuthorizationHeader(),
|
||||
errorInterceptor: errorHandler.getHTTPErrorHandler(),
|
||||
};
|
||||
|
||||
const dicomWeb = new api.DICOMwebClient(config);
|
||||
|
||||
@@ -11,6 +11,7 @@ import { api } from 'dicomweb-client';
|
||||
import { isImage } from '../../utils/isImage';
|
||||
import isDisplaySetReconstructable from '../../utils/isDisplaySetReconstructable';
|
||||
import isLowPriorityModality from '../../utils/isLowPriorityModality';
|
||||
import errorHandler from '../../errorHandler';
|
||||
|
||||
export class StudyMetadata extends Metadata {
|
||||
constructor(data, uid) {
|
||||
@@ -102,10 +103,7 @@ export class StudyMetadata extends Metadata {
|
||||
* @param {SeriesMetadata} series The series metadata object from which the display sets will be created
|
||||
* @returns {Array} The list of display sets created for the given series object
|
||||
*/
|
||||
_createDisplaySetsForSeries(
|
||||
sopClassHandlerModules,
|
||||
series,
|
||||
) {
|
||||
_createDisplaySetsForSeries(sopClassHandlerModules, series) {
|
||||
const study = this;
|
||||
const displaySets = [];
|
||||
|
||||
@@ -293,14 +291,13 @@ export class StudyMetadata extends Metadata {
|
||||
}
|
||||
|
||||
// Loop through the series (SeriesMetadata)
|
||||
this.forEachSeries(
|
||||
series => {
|
||||
const displaySetsForSeries = this._createDisplaySetsForSeries(
|
||||
sopClassHandlerModules,
|
||||
series,
|
||||
);
|
||||
this.forEachSeries(series => {
|
||||
const displaySetsForSeries = this._createDisplaySetsForSeries(
|
||||
sopClassHandlerModules,
|
||||
series
|
||||
);
|
||||
|
||||
displaySets.push(...displaySetsForSeries);
|
||||
displaySets.push(...displaySetsForSeries);
|
||||
});
|
||||
|
||||
return sortDisplaySetList(displaySets);
|
||||
@@ -321,7 +318,10 @@ export class StudyMetadata extends Metadata {
|
||||
return false;
|
||||
}
|
||||
|
||||
const displaySets = this._createDisplaySetsForSeries(sopClassHandlerModules, series)
|
||||
const displaySets = this._createDisplaySetsForSeries(
|
||||
sopClassHandlerModules,
|
||||
series
|
||||
);
|
||||
|
||||
// Note: filtering in place because this._displaySets has writable: false
|
||||
for (let i = this._displaySets.length - 1; i >= 0; i--) {
|
||||
@@ -835,9 +835,11 @@ function _getDisplaySetFromSopClassModule(
|
||||
|
||||
const plugin = handlersForSopClassUID[0];
|
||||
const headers = DICOMWeb.getAuthorizationHeader();
|
||||
const errorInterceptor = errorHandler.getHTTPErrorHandler();
|
||||
const dicomWebClient = new dwc({
|
||||
url: study.getData().wadoRoot,
|
||||
headers,
|
||||
errorInterceptor,
|
||||
});
|
||||
|
||||
let displaySet = plugin.getDisplaySetFromSeries(
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// These should be overridden by the implementation
|
||||
const errorHandler = {
|
||||
getHTTPErrorHandler: () => null,
|
||||
};
|
||||
|
||||
export default errorHandler;
|
||||
@@ -18,6 +18,7 @@ import string from './string.js';
|
||||
import studies from './studies/';
|
||||
import ui from './ui';
|
||||
import user from './user.js';
|
||||
import errorHandler from './errorHandler.js';
|
||||
import utils, { hotkeys } from './utils/';
|
||||
|
||||
import {
|
||||
@@ -46,6 +47,7 @@ const OHIF = {
|
||||
string,
|
||||
ui,
|
||||
user,
|
||||
errorHandler,
|
||||
object,
|
||||
log,
|
||||
DICOMWeb,
|
||||
@@ -79,6 +81,7 @@ export {
|
||||
string,
|
||||
ui,
|
||||
user,
|
||||
errorHandler,
|
||||
object,
|
||||
log,
|
||||
DICOMWeb,
|
||||
|
||||
@@ -24,6 +24,7 @@ describe('Top level exports', () => {
|
||||
'header',
|
||||
'cornerstone',
|
||||
'default', //
|
||||
'errorHandler',
|
||||
'string',
|
||||
'ui',
|
||||
'user',
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import DICOMWeb from '../../../DICOMWeb/';
|
||||
import { api } from 'dicomweb-client';
|
||||
|
||||
import errorHandler from '../../../errorHandler';
|
||||
|
||||
/**
|
||||
* Parses data returned from a QIDO search and transforms it into
|
||||
* an array of series that are present in the study
|
||||
@@ -79,6 +81,7 @@ export default function Instances(server, StudyInstanceUID) {
|
||||
const config = {
|
||||
url: server.qidoRoot,
|
||||
headers: DICOMWeb.getAuthorizationHeader(server),
|
||||
errorInterceptor: errorHandler.getHTTPErrorHandler(),
|
||||
};
|
||||
const dicomWeb = new api.DICOMwebClient(config);
|
||||
const queryParams = getQIDOQueryParams(
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { api } from 'dicomweb-client';
|
||||
import DICOMWeb from '../../../DICOMWeb/';
|
||||
|
||||
import errorHandler from '../../../errorHandler';
|
||||
|
||||
/**
|
||||
* Creates a QIDO date string for a date range query
|
||||
* Assumes the year is positive, at most 4 digits long.
|
||||
@@ -115,6 +117,7 @@ export default function Studies(server, filter) {
|
||||
const config = {
|
||||
url: server.qidoRoot,
|
||||
headers: DICOMWeb.getAuthorizationHeader(server),
|
||||
errorInterceptor: errorHandler.getHTTPErrorHandler(),
|
||||
};
|
||||
|
||||
const dicomWeb = new api.DICOMwebClient(config);
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
addInstancesToStudy,
|
||||
} from './studyInstanceHelpers';
|
||||
|
||||
import errorHandler from '../../../errorHandler';
|
||||
|
||||
const { naturalizeDataset } = dcmjs.data.DicomMetaDictionary;
|
||||
|
||||
/**
|
||||
@@ -73,6 +75,7 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader
|
||||
const client = new api.DICOMwebClient({
|
||||
url: server.qidoRoot,
|
||||
headers: DICOMWeb.getAuthorizationHeader(server),
|
||||
errorInterceptor: errorHandler.getHTTPErrorHandler(),
|
||||
});
|
||||
|
||||
this.client = client;
|
||||
@@ -117,7 +120,7 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader
|
||||
|
||||
return {
|
||||
seriesInstanceUIDsMap,
|
||||
seriesData
|
||||
seriesData,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -155,11 +158,14 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader
|
||||
SeriesDescription: series.SeriesDescription,
|
||||
SeriesNumber: series.SeriesNumber,
|
||||
Modality: series.Modality,
|
||||
instances: []
|
||||
instances: [],
|
||||
};
|
||||
|
||||
if (study.series[idx]) {
|
||||
study.series[idx] = Object.assign(seriesDataFromQIDO, study.series[idx]);
|
||||
study.series[idx] = Object.assign(
|
||||
seriesDataFromQIDO,
|
||||
study.series[idx]
|
||||
);
|
||||
} else {
|
||||
study.series[idx] = seriesDataFromQIDO;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ import DICOMWeb from '../../../DICOMWeb/';
|
||||
import { createStudyFromSOPInstanceList } from './studyInstanceHelpers';
|
||||
import RetrieveMetadataLoader from './retrieveMetadataLoader';
|
||||
|
||||
import errorHandler from '../../../errorHandler';
|
||||
|
||||
/**
|
||||
* Class for sync load of study metadata.
|
||||
* It inherits from RetrieveMetadataLoader
|
||||
@@ -58,6 +60,7 @@ export default class RetrieveMetadataLoaderSync extends RetrieveMetadataLoader {
|
||||
const client = new api.DICOMwebClient({
|
||||
url: server.wadoRoot,
|
||||
headers: DICOMWeb.getAuthorizationHeader(server),
|
||||
errorInterceptor: errorHandler.getHTTPErrorHandler(),
|
||||
});
|
||||
|
||||
this.client = client;
|
||||
|
||||
@@ -3,6 +3,8 @@ import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
|
||||
import { api } from 'dicomweb-client';
|
||||
import DICOMWeb from '../DICOMWeb';
|
||||
|
||||
import errorHandler from '../errorHandler';
|
||||
|
||||
const getImageId = imageObj => {
|
||||
if (!imageObj) {
|
||||
return;
|
||||
@@ -57,11 +59,13 @@ const wadorsRetriever = (
|
||||
studyInstanceUID,
|
||||
seriesInstanceUID,
|
||||
sopInstanceUID,
|
||||
headers = DICOMWeb.getAuthorizationHeader()
|
||||
headers = DICOMWeb.getAuthorizationHeader(),
|
||||
errorInterceptor = errorHandler.getHTTPErrorHandler()
|
||||
) => {
|
||||
const config = {
|
||||
url,
|
||||
headers,
|
||||
errorInterceptor,
|
||||
};
|
||||
const dicomWeb = new api.DICOMwebClient(config);
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import DICOMWeb from '../../DICOMWeb';
|
||||
import str2ab from '../str2ab';
|
||||
import unpackOverlay from './unpackOverlay';
|
||||
|
||||
import errorHandler from '../../errorHandler';
|
||||
|
||||
export default async function fetchOverlayData(instance, server) {
|
||||
const OverlayDataPromises = [];
|
||||
const OverlayDataTags = [];
|
||||
@@ -21,14 +23,20 @@ export default async function fetchOverlayData(instance, server) {
|
||||
const inlineBinaryData = atob(instance[OverlayDataTag].InlineBinary);
|
||||
const arraybuffer = str2ab(inlineBinaryData);
|
||||
|
||||
instance[OverlayDataTag] = unpackOverlay(arraybuffer)
|
||||
} else if (instance[OverlayDataTag] && instance[OverlayDataTag].BulkDataURI) {
|
||||
instance[OverlayDataTag] = unpackOverlay(arraybuffer);
|
||||
} else if (
|
||||
instance[OverlayDataTag] &&
|
||||
instance[OverlayDataTag].BulkDataURI
|
||||
) {
|
||||
OverlayDataPromises.push(
|
||||
_getOverlayData(instance[OverlayDataTag], server)
|
||||
);
|
||||
OverlayDataTags.push(OverlayDataTag);
|
||||
} else if (instance[OverlayDataTag] && instance[OverlayDataTag] instanceof ArrayBuffer) {
|
||||
instance[OverlayDataTag] = unpackOverlay(instance[OverlayDataTag])
|
||||
} else if (
|
||||
instance[OverlayDataTag] &&
|
||||
instance[OverlayDataTag] instanceof ArrayBuffer
|
||||
) {
|
||||
instance[OverlayDataTag] = unpackOverlay(instance[OverlayDataTag]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +68,7 @@ async function _getOverlayData(tag, server) {
|
||||
const config = {
|
||||
url: server.wadoRoot, //BulkDataURI is absolute, so this isn't used
|
||||
headers: DICOMWeb.getAuthorizationHeader(server),
|
||||
errorInterceptor: errorHandler.getHTTPErrorHandler(),
|
||||
};
|
||||
const dicomWeb = new api.DICOMwebClient(config);
|
||||
const options = {
|
||||
@@ -71,5 +80,3 @@ async function _getOverlayData(tag, server) {
|
||||
.then(result => result[0])
|
||||
.then(unpackOverlay);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ import { api } from 'dicomweb-client';
|
||||
import DICOMWeb from '../../DICOMWeb';
|
||||
import str2ab from '../str2ab';
|
||||
|
||||
import errorHandler from '../../errorHandler';
|
||||
|
||||
export default async function fetchPaletteColorLookupTableData(
|
||||
instance,
|
||||
server
|
||||
@@ -146,6 +148,7 @@ function _getPaletteColor(server, paletteColorLookupTableData, lutDescriptor) {
|
||||
const config = {
|
||||
url: server.wadoRoot, //BulkDataURI is absolute, so this isn't used
|
||||
headers: DICOMWeb.getAuthorizationHeader(server),
|
||||
errorInterceptor: errorHandler.getHTTPErrorHandler(),
|
||||
};
|
||||
const dicomWeb = new api.DICOMwebClient(config);
|
||||
const options = {
|
||||
@@ -164,6 +167,8 @@ function _getPaletteColor(server, paletteColorLookupTableData, lutDescriptor) {
|
||||
resolve(arrayBufferToPaletteColorLUT(arraybuffer));
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve(arrayBufferToPaletteColorLUT(paletteColorLookupTableData));
|
||||
return Promise.resolve(
|
||||
arrayBufferToPaletteColorLUT(paletteColorLookupTableData)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-math": "^0.1.8",
|
||||
"cornerstone-tools": "4.12.5",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
"cornerstone-wado-image-loader": "^3.1.0",
|
||||
"dcmjs": "^0.12.0",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"dicomweb-client": "^0.4.4",
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
window.config = function(props) {
|
||||
var servicesManager = props.servicesManager;
|
||||
|
||||
return {
|
||||
routerBasename: '/',
|
||||
enableGoogleCloudAdapter: true,
|
||||
enableGoogleCloudAdapterUI: false,
|
||||
showStudyList: true,
|
||||
httpErrorHandler: error => {
|
||||
// This is 429 when rejected from the public idc sandbox too often.
|
||||
console.warn(error.status);
|
||||
|
||||
// Could use services manager here to bring up a dialog/modal if needed.
|
||||
console.warn('test, navigate to https://ohif.org/');
|
||||
window.location = 'https://ohif.org/';
|
||||
},
|
||||
healthcareApiEndpoint: 'https://idc-sandbox-002.appspot.com/v1beta1',
|
||||
};
|
||||
};
|
||||
@@ -31,7 +31,8 @@ import {
|
||||
import i18n from '@ohif/i18n';
|
||||
|
||||
// TODO: This should not be here
|
||||
import './config';
|
||||
//import './config';
|
||||
import { setConfiguration } from './config';
|
||||
|
||||
/** Utils */
|
||||
import {
|
||||
@@ -123,6 +124,8 @@ class App extends Component {
|
||||
oidc,
|
||||
} = this._appConfig;
|
||||
|
||||
setConfiguration(this._appConfig);
|
||||
|
||||
this.initUserManager(oidc);
|
||||
_initServices([
|
||||
UINotificationService,
|
||||
|
||||
@@ -3,43 +3,59 @@ import cornerstone from 'cornerstone-core';
|
||||
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
|
||||
import dicomParser from 'dicom-parser';
|
||||
import version from './version.js';
|
||||
import AppContext from './context/AppContext';
|
||||
|
||||
let homepage;
|
||||
const { process } = window;
|
||||
if (process && process.env && process.env.PUBLIC_URL) {
|
||||
homepage = process.env.PUBLIC_URL;
|
||||
}
|
||||
|
||||
window.info = {
|
||||
version,
|
||||
homepage,
|
||||
};
|
||||
|
||||
// For debugging
|
||||
//if (process.env.node_env === 'development') {
|
||||
window.cornerstone = cornerstone;
|
||||
window.cornerstoneWADOImageLoader = cornerstoneWADOImageLoader;
|
||||
//}
|
||||
|
||||
cornerstoneWADOImageLoader.external.cornerstone = cornerstone;
|
||||
cornerstoneWADOImageLoader.external.dicomParser = dicomParser;
|
||||
|
||||
OHIF.user.getAccessToken = () => {
|
||||
// TODO: Get the Redux store from somewhere else
|
||||
const state = window.store.getState();
|
||||
if (!state.oidc || !state.oidc.user) {
|
||||
return;
|
||||
export function setConfiguration(appConfig) {
|
||||
let homepage;
|
||||
const { process } = window;
|
||||
if (process && process.env && process.env.PUBLIC_URL) {
|
||||
homepage = process.env.PUBLIC_URL;
|
||||
}
|
||||
|
||||
return state.oidc.user.access_token;
|
||||
};
|
||||
window.info = {
|
||||
version,
|
||||
homepage,
|
||||
};
|
||||
|
||||
cornerstoneWADOImageLoader.configure({
|
||||
beforeSend: function(xhr) {
|
||||
const headers = OHIF.DICOMWeb.getAuthorizationHeader();
|
||||
// For debugging
|
||||
//if (process.env.node_env === 'development') {
|
||||
window.cornerstone = cornerstone;
|
||||
window.cornerstoneWADOImageLoader = cornerstoneWADOImageLoader;
|
||||
//}
|
||||
|
||||
if (headers.Authorization) {
|
||||
xhr.setRequestHeader('Authorization', headers.Authorization);
|
||||
cornerstoneWADOImageLoader.external.cornerstone = cornerstone;
|
||||
cornerstoneWADOImageLoader.external.dicomParser = dicomParser;
|
||||
|
||||
OHIF.user.getAccessToken = () => {
|
||||
// TODO: Get the Redux store from somewhere else
|
||||
const state = window.store.getState();
|
||||
if (!state.oidc || !state.oidc.user) {
|
||||
return;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return state.oidc.user.access_token;
|
||||
};
|
||||
|
||||
OHIF.errorHandler.getHTTPErrorHandler = () => {
|
||||
// const { appConfig = {} } = AppContext;
|
||||
|
||||
return appConfig.httpErrorHandler;
|
||||
};
|
||||
|
||||
cornerstoneWADOImageLoader.configure({
|
||||
beforeSend: function(xhr) {
|
||||
const headers = OHIF.DICOMWeb.getAuthorizationHeader();
|
||||
|
||||
if (headers.Authorization) {
|
||||
xhr.setRequestHeader('Authorization', headers.Authorization);
|
||||
}
|
||||
},
|
||||
errorInterceptor: error => {
|
||||
// const { appConfig = {} } = AppContext;
|
||||
|
||||
if (typeof appConfig.httpErrorHandler === 'function') {
|
||||
appConfig.httpErrorHandler(errorHandler);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { httpErrorToStr, checkDicomFile } from '../utils/helpers';
|
||||
import { api } from 'dicomweb-client';
|
||||
import { errorHandler } from '@ohif/core';
|
||||
|
||||
class DicomUploadService {
|
||||
async smartUpload(files, url, uploadCallback, cancellationToken) {
|
||||
@@ -22,8 +23,7 @@ class DicomUploadService {
|
||||
let error = null;
|
||||
try {
|
||||
if (chunk.length > 1) throw new Error('Not implemented');
|
||||
if (chunk.length === 1)
|
||||
await this.simpleUpload(chunk[0], url);
|
||||
if (chunk.length === 1) await this.simpleUpload(chunk[0], url);
|
||||
} catch (err) {
|
||||
// It looks like a stupid bug of Babel that err is not an actual Exception object
|
||||
error = httpErrorToStr(err);
|
||||
@@ -76,11 +76,12 @@ class DicomUploadService {
|
||||
|
||||
getClient(url) {
|
||||
const headers = this.retrieveAuthHeaderFunc();
|
||||
const errorInterceptor = errorHandler.getHTTPErrorHandler();
|
||||
|
||||
// TODO: a bit weird we are creating a new dicomweb client instance for every upload
|
||||
return new api.DICOMwebClient({
|
||||
url,
|
||||
headers
|
||||
headers,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user