From da318c9c8c1e5ce734a8466198df0173f1e44bf2 Mon Sep 17 00:00:00 2001 From: Erik Ziegler Date: Fri, 26 Jul 2019 15:04:28 +0200 Subject: [PATCH] feat(DicomUploader): Make DICOM Uploader work with all DICOMWeb servers (#717) * feat(DicomUploader): Make DICOM Uploader work with all DICOMWeb servers * Bump viewerbase version --- package.json | 2 +- public/config/google.js | 26 +------------ public/config/local_dcm4chee.js | 1 + .../ConnectedDicomFilesUploader.js | 9 +++-- src/googleCloud/DicomFileUploaderModal.js | 6 +-- src/googleCloud/DicomUploader.css | 20 ++++++++++ src/googleCloud/DicomUploader.js | 18 ++++----- src/googleCloud/api/DicomUploadService.js | 37 +++++++++--------- src/googleCloud/googleCloud.css | 11 ------ src/googleCloud/utils/helpers.js | 5 --- src/studylist/StudyListRouting.js | 9 ++++- src/studylist/StudyListWithData.js | 38 ++++++++----------- yarn.lock | 8 ++-- 13 files changed, 85 insertions(+), 105 deletions(-) create mode 100644 src/googleCloud/DicomUploader.css diff --git a/package.json b/package.json index b508527c1..65ba11e2f 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "react-router": "^5.0.1", "react-router-dom": "^5.0.1", "react-transition-group": "^4.1.1", - "react-viewerbase": "0.17.0", + "react-viewerbase": "0.17.6", "redux": "^4.0.1", "redux-logger": "^3.0.6", "redux-oidc": "3.1.x", diff --git a/public/config/google.js b/public/config/google.js index 48d64d3c9..ffde232c3 100644 --- a/public/config/google.js +++ b/public/config/google.js @@ -21,31 +21,7 @@ window.config = { revoke_uri: 'https://accounts.google.com/o/oauth2/revoke?token=', automaticSilentRenew: true, revokeAccessTokenOnSignout: true, - metadata: { - issuer: "https://accounts.google.com", - authorization_endpoint: "https://accounts.google.com/o/oauth2/v2/auth", - token_endpoint: "https://www.googleapis.com/oauth2/v4/token", - userinfo_endpoint: "https://www.googleapis.com/oauth2/v3/userinfo", - jwks_uri: "https://www.googleapis.com/oauth2/v3/certs", - }, - signingKeys: [ - { - "kid": "6e5508d27965ad7907c232212defa48ed763727e", - "e": "AQAB", - "kty": "RSA", - "alg": "RS256", - "n": "vOsmJlsBscTxCcOLa6IfCuUnvXI5cBC-o-NIDC1R5O782U_BC67p9dtMLL2UHpQ1wj_b6I4R8cHzddPrPDZ7eTKJw5q18pZU4B5hCmFe9A0JvzyQe3VFPYhKSI5LV_6UIwWdGWjJic8tCJ05AJaVOogSUCn17ss_8KQHSbs66zVbwls9p_ObdHGuzLQE-Y-fkxO5aD9S09DB5dNKuZNL76wgZAhc-HEyo1HkTbDaGAPCk-EpoqfjjoPY2FZGHg5QRCUMdxnYoebjzyO6oaJ8yVniVpOkf-MNF6HltRBbOzE3u6Y2VGjWoj_W0AUidrLC_KQ57URkcAfk1BhMUVJekQ", - "use": "sig" - }, - { - "use": "sig", - "kid": "84f294c45160088d079fee68138f52133d3e228c", - "e": "AQAB", - "kty": "RSA", - "alg": "RS256", - "n": "iyzj9wpDDZLCbgbr2zKv3bs8zqjflcVEd7PYMjKGYpoaY2LdqfjFxrwTqd9Ea4m3NIR2giOx9JLQhtqqSSpBJpBBpHmaEd2FCPwd4GQTKJurEP6Ho9HWAuRTMhs8W04pd__HQ0Bc22AEamieGLtzcYfIaAc9g5RCxZdRVbGK0Z0vSOAwN1PC_S76nWGphouHukU40EiwjqC-D9G2xYFbKNb0_NJMxJ5UCenN85FjEii5-oW0wCBmt_1Sr76Q_e0INxfGu6dRf0vGXPvqxkINz2knjl9ec2SvOK2hnmRN4O9zToKH70_DBrsZE0ePDScTOWPHJU2wOyE6gzkL6FdaFQ" - } - ] }, ], + studyListFunctionsEnabled: true, } diff --git a/public/config/local_dcm4chee.js b/public/config/local_dcm4chee.js index 7abddc0cf..4416fca0c 100644 --- a/public/config/local_dcm4chee.js +++ b/public/config/local_dcm4chee.js @@ -21,4 +21,5 @@ window.config = { }, ], }, + studyListFunctionsEnabled: true, } diff --git a/src/googleCloud/ConnectedDicomFilesUploader.js b/src/googleCloud/ConnectedDicomFilesUploader.js index 1edf639f0..46719749a 100644 --- a/src/googleCloud/ConnectedDicomFilesUploader.js +++ b/src/googleCloud/ConnectedDicomFilesUploader.js @@ -1,15 +1,18 @@ import { connect } from 'react-redux'; import DicomFileUploaderModal from './DicomFileUploaderModal.js'; +import OHIF from 'ohif-core'; const isActive = a => a.active === true; const mapStateToProps = state => { const activeServer = state.servers.servers.find(isActive); - const { authority, client_id } = window.config.oidc[0]; - const oidcStorageKey = `oidc.user:${authority}:${client_id}`; + + // TODO: Not sure I like this approach since it means we are recreating + // this function every time redux changes + const retrieveAuthHeaderFunction = () => OHIF.DICOMWeb.getAuthorizationHeader(activeServer); return { - oidcStorageKey, + retrieveAuthHeaderFunction, url: activeServer && activeServer.qidoRoot, }; }; diff --git a/src/googleCloud/DicomFileUploaderModal.js b/src/googleCloud/DicomFileUploaderModal.js index f0e38fe4a..1f113c683 100644 --- a/src/googleCloud/DicomFileUploaderModal.js +++ b/src/googleCloud/DicomFileUploaderModal.js @@ -7,7 +7,7 @@ import { withTranslation } from 'react-i18next'; class DicomFileUploaderModal extends Component { static propTypes = { url: PropTypes.string, - oidcStorageKey: PropTypes.string.isRequired, + retrieveAuthHeaderFunction: PropTypes.func, onClose: PropTypes.func, }; @@ -32,13 +32,13 @@ class DicomFileUploaderModal extends Component { > - {this.props.t('Google Cloud Healthcare API - DICOM Upload')} + {this.props.t('Upload DICOM Files')} diff --git a/src/googleCloud/DicomUploader.css b/src/googleCloud/DicomUploader.css new file mode 100644 index 000000000..80f0b0920 --- /dev/null +++ b/src/googleCloud/DicomUploader.css @@ -0,0 +1,20 @@ +.dicom-uploader .button { + float: left; + margin: 5px; +} + +.invisible-input { + position: absolute; + display: none; + z-index: -1000; + max-width: 0 !important; + max-height: 0 !important; +} + +.dicom-uploader .table-header { + color: #ffffff; + font-size: 16px; + font-weight: 28px; + text-align: left; + margin: 20px auto; +} diff --git a/src/googleCloud/DicomUploader.js b/src/googleCloud/DicomUploader.js index 1f03dd06a..fb17f4ab5 100644 --- a/src/googleCloud/DicomUploader.js +++ b/src/googleCloud/DicomUploader.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { formatFileSize } from './utils/helpers'; import CancellationToken from './utils/CancellationToken'; import dicomUploader from './api/DicomUploadService'; -import './googleCloud.css'; +import './DicomUploader.css'; export default class DicomUploader extends Component { state = { @@ -26,7 +26,7 @@ export default class DicomUploader extends Component { id: PropTypes.string, event: PropTypes.string, url: PropTypes.string, - oidcKey: PropTypes.string, + retrieveAuthHeaderFunction: PropTypes.func, }; filesLeft() { @@ -94,11 +94,12 @@ export default class DicomUploader extends Component { const uploadCallback = (fileId, error) => uploadContext === this.state.uploadContext && this.uploadCallback.call(this, fileId, error); - dicomUploader.setOidcStorageKey(this.props.oidcKey); + + dicomUploader.setRetrieveAuthHeaderFunction(this.props.retrieveAuthHeaderFunction); + dicomUploader.smartUpload( files.target.files, this.props.url, - this.props.oidcKey, uploadCallback, cancellationToken ); @@ -137,14 +138,13 @@ export default class DicomUploader extends Component { render() { if (this.state.files === null) { return ( -
+
- + {this.percents()}% {this.filesLeft()} diff --git a/src/googleCloud/api/DicomUploadService.js b/src/googleCloud/api/DicomUploadService.js index 6bd6a7d83..6db849ab3 100644 --- a/src/googleCloud/api/DicomUploadService.js +++ b/src/googleCloud/api/DicomUploadService.js @@ -1,23 +1,16 @@ -import { httpErrorToStr, getOidcToken, checkDicomFile } from '../utils/helpers'; +import { httpErrorToStr, checkDicomFile } from '../utils/helpers'; import { api } from 'dicomweb-client'; class DicomUploadService { - setOidcStorageKey(oidcStorageKey) { - /* eslint-disable */ - if (!oidcStorageKey) console.error('OIDC storage key is empty'); - this.oidcStorageKey = oidcStorageKey; - } - - async smartUpload(files, url, authToken, uploadCallback, cancellationToken) { - /* eslint-disable */ + async smartUpload(files, url, uploadCallback, cancellationToken) { const CHUNK_SIZE = 1; // Only one file per request is supported so far const MAX_PARALLEL_JOBS = 50; // FIXME: tune MAX_PARALLEL_JOBS number - // + let filesArray = Array.from(files); if (filesArray.length === 0) { - console.warn('No files are supplied for uploading'); - return; + throw new Error('No files were provided.'); } + let parallelJobsCount = Math.min(filesArray.length, MAX_PARALLEL_JOBS); let completed = false; @@ -30,7 +23,7 @@ class DicomUploadService { try { if (chunk.length > 1) throw new Error('Not implemented'); if (chunk.length === 1) - await this.simpleUpload(chunk[0], url, authToken); + 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); @@ -51,13 +44,13 @@ class DicomUploadService { }); } - async simpleUpload(file, url, authToken) { - /* eslint-disable */ + async simpleUpload(file, url) { const client = this.getClient(url); const loadedFile = await this.readFile(file); const content = loadedFile.content; if (!checkDicomFile(content)) - throw new Error('The file has a wrong DICOM header'); + throw new Error('This is not a valid DICOM file.'); + await client.storeInstances({ datasets: [content] }); } @@ -77,13 +70,17 @@ class DicomUploadService { }); } + setRetrieveAuthHeaderFunction(func) { + this.retrieveAuthHeaderFunc = func; + } + getClient(url) { - if (!this.oidcStorageKey) throw new Error('OIDC storage key is not set'); - const accessToken = getOidcToken(this.oidcStorageKey); - if (!accessToken) throw new Error('OIDC access_token is not set'); + const headers = this.retrieveAuthHeaderFunc(); + + // TODO: a bit weird we are creating a new dicomweb client instance for every upload return new api.DICOMwebClient({ url, - headers: { Authorization: 'Bearer ' + accessToken }, + headers }); } } diff --git a/src/googleCloud/googleCloud.css b/src/googleCloud/googleCloud.css index b83d718cd..9315165aa 100644 --- a/src/googleCloud/googleCloud.css +++ b/src/googleCloud/googleCloud.css @@ -94,13 +94,6 @@ background-color: #000000; } -.gcp-invisible-input { - position: absolute; - display: none; - z-index: -1000; - max-width: 0 !important; - max-height: 0 !important; -} .gcp-hidden { display: none; } @@ -148,7 +141,3 @@ margin: 20px auto; } -.gcp-dicom-uploader .button { - float: left; - margin: 5px; -} diff --git a/src/googleCloud/utils/helpers.js b/src/googleCloud/utils/helpers.js index fda924c39..ba27ce5ed 100644 --- a/src/googleCloud/utils/helpers.js +++ b/src/googleCloud/utils/helpers.js @@ -26,11 +26,6 @@ export function httpErrorToStr(error) { return error.message || 'Unknown error.'; } -export function getOidcToken(oidcStorageKey) { - const oidcConfigStr = sessionStorage.getItem(oidcStorageKey); - if (oidcConfigStr) return JSON.parse(oidcConfigStr).access_token; -} - /* eslint-disable */ export function checkDicomFile(arrayBuffer) { if (arrayBuffer.length <= 132) return false; diff --git a/src/studylist/StudyListRouting.js b/src/studylist/StudyListRouting.js index e156592fc..8b7ea8a63 100644 --- a/src/studylist/StudyListRouting.js +++ b/src/studylist/StudyListRouting.js @@ -24,7 +24,14 @@ function getFilters({ search }) { function StudyListRouting({ location }) { const filters = location ? getFilters(location) : undefined; - return ; + let studyListFunctionsEnabled = false; + if (window.config && window.config.studyListFunctionsEnabled) { + studyListFunctionsEnabled = window.config.studyListFunctionsEnabled; + } + return ; } StudyListRouting.propTypes = { diff --git a/src/studylist/StudyListWithData.js b/src/studylist/StudyListWithData.js index 5749baf58..2a51709cc 100644 --- a/src/studylist/StudyListWithData.js +++ b/src/studylist/StudyListWithData.js @@ -27,6 +27,11 @@ class StudyListWithData extends Component { server: PropTypes.object, user: PropTypes.object, history: PropTypes.object, + studyListFunctionsEnabled: PropTypes.bool + }; + + static defaultProps = { + studyListFunctionsEnabled: true }; static rowsPerPage = 25; @@ -147,7 +152,7 @@ class StudyListWithData extends Component { }; onImport = () => { - //console.log('onImport'); + this.openModal('DicomFilesUploader') }; openModal = modalComponentId => { @@ -176,9 +181,6 @@ class StudyListWithData extends Component { render() { const onDrop = async acceptedFiles => { - // Do something with the files - console.warn(acceptedFiles); - try { const studies = await filesToStudies(acceptedFiles); @@ -200,16 +202,10 @@ class StudyListWithData extends Component { // TODO: This should probably be a prop if (window.config.enableGoogleCloudAdapter) { healthCareApiWindows = ( - <> - - - + ); healthCareApiButtons = ( @@ -223,24 +219,16 @@ class StudyListWithData extends Component { > {this.props.t('Change DICOM Store')} -
); } - console.warn(this.state.studies); - const studyList = (
{this.state.studies ? ( + {this.props.studyListFunctionsEnabled ? : null} {healthCareApiButtons} {healthCareApiWindows} diff --git a/yarn.lock b/yarn.lock index 7aec97441..599e92790 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12700,10 +12700,10 @@ react-transition-group@^4.1.1: loose-envify "^1.4.0" prop-types "^15.6.2" -react-viewerbase@0.17.0: - version "0.17.0" - resolved "https://registry.yarnpkg.com/react-viewerbase/-/react-viewerbase-0.17.0.tgz#e741f94b24b6cef419fdd32b9bcfcdcfe81783dc" - integrity sha512-fy51pHrdhdXcPg6Pb1l8S5fzxlenFVhRUe80ZFq1aK9omrQXgaBF0uhm537WREyx2CS0/ZOD/wQnnbyjqEvYdQ== +react-viewerbase@0.17.6: + version "0.17.6" + resolved "https://registry.yarnpkg.com/react-viewerbase/-/react-viewerbase-0.17.6.tgz#21dcc3a2a48174d235bda47aad97c339dcf3135a" + integrity sha512-8kbXBbz1sIqlpLBWGjeSyua/axeAxNqy28DLPVVZlBuoTjDwyhdliuZhkDiQOHpv0POuYn3zxZdZcus7jAd5iQ== dependencies: "@babel/runtime" "7.2.0" "@ohif/i18n" "0.2.1"