From 4d587eaaeb900f97b432e408649a396c0c80a9c9 Mon Sep 17 00:00:00 2001 From: David Blader Date: Wed, 18 Oct 2017 15:25:35 -0400 Subject: [PATCH] [Imaging Uploader] Prevent upload duplication and handle other mriFile statuses (#3150) When retrieving the list of MRI's, _getFileList will only append files which already exist in the incoming data directory. However these files get deleted when the MRI pipeline runs successfully, allowing for duplicate uploads for previously successful files. It can be assumed that the insertion was successful based on the number of MINCs inserted being > 0. Use this in addition to file existence to generate the mriList which gets checked when an upload form is submitted. --- modules/imaging_uploader/js/index.js | 4 +-- modules/imaging_uploader/jsx/UploadForm.js | 33 ++++++++++++++++++- ...NDB_Menu_Filter_imaging_uploader.class.inc | 13 +++++--- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/modules/imaging_uploader/js/index.js b/modules/imaging_uploader/js/index.js index 2bb05e5bcaa..6a3be07bfc8 100644 --- a/modules/imaging_uploader/js/index.js +++ b/modules/imaging_uploader/js/index.js @@ -1,3 +1,3 @@ -!function(modules){function __webpack_require__(moduleId){if(installedModules[moduleId])return installedModules[moduleId].exports;var module=installedModules[moduleId]={exports:{},id:moduleId,loaded:!1};return modules[moduleId].call(module.exports,module,module.exports,__webpack_require__),module.loaded=!0,module.exports}var installedModules={};return __webpack_require__.m=modules,__webpack_require__.c=installedModules,__webpack_require__.p="",__webpack_require__(0)}([function(module,exports,__webpack_require__){"use strict";function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}var _ImagingUploader=__webpack_require__(14),_ImagingUploader2=_interopRequireDefault(_ImagingUploader);$(function(){var imagingUploader=React.createElement("div",{className:"page-imaging-uploader"},React.createElement(_ImagingUploader2.default,{Module:"imaging_uploader",DataURL:loris.BaseURL+"/imaging_uploader/?format=json"}));ReactDOM.render(imagingUploader,document.getElementById("lorisworkspace"))})},,,,,,,function(module,exports){"use strict";function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(self,call){if(!self)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!call||"object"!=typeof call&&"function"!=typeof call?self:call}function _inherits(subClass,superClass){if("function"!=typeof superClass&&null!==superClass)throw new TypeError("Super expression must either be null or a function, not "+typeof superClass);subClass.prototype=Object.create(superClass&&superClass.prototype,{constructor:{value:subClass,enumerable:!1,writable:!0,configurable:!0}}),superClass&&(Object.setPrototypeOf?Object.setPrototypeOf(subClass,superClass):subClass.__proto__=superClass)}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function defineProperties(target,props){for(var i=0;i0&&(activeTab=_this.props.tabs[0].id),_this.state={activeTab:activeTab},_this.handleClick=_this.handleClick.bind(_this),_this.getTabs=_this.getTabs.bind(_this),_this.getTabPanes=_this.getTabPanes.bind(_this),_this}return _inherits(Tabs,_React$Component),_createClass(Tabs,[{key:"handleClick",value:function(tabId,e){if(this.setState({activeTab:tabId}),this.props.onTabChange(tabId),this.props.updateURL){var scrollDistance=$("body").scrollTop()||$("html").scrollTop();window.location.hash=e.target.hash,$("html,body").scrollTop(scrollDistance)}}},{key:"getTabs",value:function(){var tabs=this.props.tabs.map(function(tab){var tabClass=this.state.activeTab===tab.id?"active":null,href="#"+tab.id,tabID="tab-"+tab.id;return React.createElement("li",{role:"presentation",className:tabClass,key:tab.id},React.createElement("a",{id:tabID,href:href,role:"tab","data-toggle":"tab",onClick:this.handleClick.bind(null,tab.id)},tab.label))}.bind(this));return tabs}},{key:"getTabPanes",value:function(){var tabPanes=React.Children.map(this.props.children,function(child,key){if(child)return React.cloneElement(child,{activeTab:this.state.activeTab,key:key})}.bind(this));return tabPanes}},{key:"render",value:function(){var tabs=this.getTabs(),tabPanes=this.getTabPanes(),tabStyle={marginLeft:0,marginBottom:"5px"};return React.createElement("div",null,React.createElement("ul",{className:"nav nav-tabs",role:"tablist",style:tabStyle},tabs),React.createElement("div",{className:"tab-content"},tabPanes))}}]),Tabs}(React.Component);Tabs.propTypes={tabs:React.PropTypes.array.isRequired,defaultTab:React.PropTypes.string,updateURL:React.PropTypes.bool},Tabs.defaultProps={onTabChange:function(){},updateURL:!1};var TabPane=function(_React$Component2){function TabPane(){return _classCallCheck(this,TabPane),_possibleConstructorReturn(this,(TabPane.__proto__||Object.getPrototypeOf(TabPane)).apply(this,arguments))}return _inherits(TabPane,_React$Component2),_createClass(TabPane,[{key:"render",value:function(){var classList="tab-pane",title=void 0;return this.props.TabId===this.props.activeTab&&(classList+=" active"),this.props.Title&&(title=React.createElement("h1",null,this.props.Title)),React.createElement("div",{role:"tabpanel",className:classList,id:this.props.TabId},title,this.props.children)}}]),TabPane}(React.Component);TabPane.propTypes={TabId:React.PropTypes.string.isRequired,Title:React.PropTypes.string,activeTab:React.PropTypes.string},exports.Tabs=Tabs,exports.TabPane=TabPane},,,,function(module,exports,__webpack_require__){"use strict";function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(self,call){if(!self)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!call||"object"!=typeof call&&"function"!=typeof call?self:call}function _inherits(subClass,superClass){if("function"!=typeof superClass&&null!==superClass)throw new TypeError("Super expression must either be null or a function, not "+typeof superClass);subClass.prototype=Object.create(superClass&&superClass.prototype,{constructor:{value:subClass,enumerable:!1,writable:!0,configurable:!0}}),superClass&&(Object.setPrototypeOf?Object.setPrototypeOf(subClass,superClass):subClass.__proto__=superClass)}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function defineProperties(target,props){for(var i=0;i",logType:"summary"},_this.initHelper=_this.initHelper.bind(_this),_this.onLogTypeChange=_this.onLogTypeChange.bind(_this),_this.setServerPolling=_this.setServerPolling.bind(_this),_this.monitorProgress=_this.monitorProgress.bind(_this),_this}return _inherits(LogPanel,_React$Component),_createClass(LogPanel,[{key:"componentDidMount",value:function(){this.initHelper()}},{key:"initHelper",value:function(){var uploadProgress=new UploadProgress;this.uploadProgress=uploadProgress,$("#mri_upload_table").on("click","tbody tr",function(event){return null!==uploadProgress.getUploadRow()&&($(uploadProgress.getUploadRow()).css("background-color","white"),this.setServerPolling(!1)),event.currentTarget===uploadProgress.getUploadRow()?(uploadProgress.setUploadRow(null),uploadProgress.setProgressFromServer(null),void this.setState({logText:""})):(uploadProgress.setUploadRow(event.currentTarget),$(event.currentTarget).css("background-color","#EFEFFB"),void this.monitorProgress(this.state.logType))}.bind(this))}},{key:"monitorProgress",value:function(logType){var summary="summary"===logType,uploadProgress=this.uploadProgress,uploadId=uploadProgress.getUploadId();uploadId&&$.post(loris.BaseURL+"/imaging_uploader/ajax/getUploadSummary.php",{uploadId:uploadId,summary:summary},function(data){uploadProgress.setProgressFromServer(data),this.setState({logText:uploadProgress.getProgressText()}),this.setServerPolling(uploadProgress.getPipelineStatus()===UploadProgress.PIPELINE_STATUS_RUNNING)}.bind(this))}},{key:"setServerPolling",value:function(poll){var uploadProgress=this.uploadProgress;poll?(this.setServerPolling.getSummaryInterval||(this.setServerPolling.getSummaryInterval=setInterval(this.monitorProgress,5e3)),this.setServerPolling.dotUpdateInterval||(this.setServerPolling.dotUpdateInterval=setInterval(function(){uploadProgress.updateDots(),this.setState({logText:uploadProgress.getProgressText()})},3e3)),this.setServerPolling.animatedCharInterval||(this.setServerPolling.animatedCharInterval=setInterval(function(){uploadProgress.updateAnimatedCharIndex(),this.setState({logText:uploadProgress.getProgressText()})},250))):(this.setServerPolling.getSummaryInterval&&(clearInterval(this.setServerPolling.getSummaryInterval),this.setServerPolling.getSummaryInterval=null),this.setServerPolling.dotUpdateInterval&&(clearInterval(this.setServerPolling.dotUpdateInterval),this.setServerPolling.dotUpdateInterval=null),this.setServerPolling.animatedCharInterval&&(clearInterval(this.setServerPolling.animatedCharInterval),this.setServerPolling.animatedCharInterval=null))}},{key:"onLogTypeChange",value:function(name,value){this.monitorProgress(value),this.setState({logType:value})}},{key:"render",value:function(){var logTypes={summary:"Summary",detailed:"Detailed"};return React.createElement(_Panel2.default,{id:"log_panel",title:"Log Viewer"},React.createElement(FormElement,{name:"log_form"},React.createElement(SelectElement,{name:"LogType",label:"Logs to display",options:logTypes,onUserInput:this.onLogTypeChange,value:this.state.logType,emptyOption:!1}),React.createElement(TextareaElement,{name:"UploadLogs",disabled:!0,id:"mri_upload_logs",value:this.state.logText,rows:6})))}}]),LogPanel}(React.Component);LogPanel.propTypes={},LogPanel.defaultProps={},exports.default=LogPanel},function(module,exports,__webpack_require__){"use strict";function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(self,call){if(!self)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!call||"object"!=typeof call&&"function"!=typeof call?self:call}function _inherits(subClass,superClass){if("function"!=typeof superClass&&null!==superClass)throw new TypeError("Super expression must either be null or a function, not "+typeof superClass);subClass.prototype=Object.create(superClass&&superClass.prototype,{constructor:{value:subClass,enumerable:!1,writable:!0,configurable:!0}}),superClass&&(Object.setPrototypeOf?Object.setPrototypeOf(subClass,superClass):subClass.__proto__=superClass)}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function defineProperties(target,props){for(var i=0;i-1});if(!mriFile)return void this.uploadFile();if("Success"===mriFile.status)return void swal({title:"File already exists!",text:"A file with this name has already successfully passed the MRI pipeline!\n",type:"error",confirmButtonText:"OK"});if("In Progress..."===mriFile.status)return void swal({title:"File is currently processing!",text:"A file with this name is currently going through the MRI pipeline!\n",type:"error",confirmButtonText:"OK"});"Failure"===mriFile.status&&swal({title:"Are you sure?",text:"A file with this name already exists!\n Would you like to override existing file?",type:"warning",showCancelButton:!0,confirmButtonText:"Yes, I am sure!",cancelButtonText:"No, cancel it!"},function(isConfirm){isConfirm?this.uploadFile(!0):swal("Cancelled","Your imaginary file is safe :)","error")}.bind(this)),"Not Started"===mriFile.status&&swal({title:"Are you sure?",text:"A file with this name has been uploaded but has not yet started the MRI pipeline.\n Would you like to override the existing file?",type:"warning",showCancelButton:!0,confirmButtonText:"Yes, I am sure!",cancelButtonText:"No, cancel it!"},function(isConfirm){isConfirm?this.uploadFile(!0):swal("Cancelled","Your upload has been cancelled.","error")}.bind(this))}}},{key:"uploadFile",value:function(overwriteFile){var formData=this.state.formData,formObj=new FormData;for(var key in formData)""!==formData[key]&&formObj.append(key,formData[key]);formObj.append("fire_away","Upload"),overwriteFile&&formObj.append("overwrite",!0),$.ajax({type:"POST",url:loris.BaseURL+"/imaging_uploader/",data:formObj,cache:!1,contentType:!1,processData:!1,xhr:function(){var xhr=new window.XMLHttpRequest;return xhr.upload.addEventListener("progress",function(evt){if(evt.lengthComputable){var percentage=Math.round(evt.loaded/evt.total*100);this.setState({uploadProgress:percentage})}}.bind(this),!1),xhr}.bind(this),success:function(data){var errMessage="The following errors occured while attempting to display this page:";data.indexOf(errMessage)>-1?(data=data.replace("history.back()","location.reload()"),document.open(),document.write(data),document.close()):swal({title:"Upload Successful!",type:"success"},function(){window.location.assign(loris.BaseURL+"/imaging_uploader/")})},error:function(err){console.error(err),this.setState({uploadProgress:-1})}.bind(this)})}},{key:"render",value:function(){var form=this.state.form;form.IsPhantom.value=this.state.formData.IsPhantom,form.candID.value=this.state.formData.candID,form.pSCID.value=this.state.formData.pSCID,form.visitLabel.value=this.state.formData.visitLabel,form.mri_file.value=this.state.formData.mri_file;var btnClass=this.state.uploadProgress>-1?"btn btn-primary hide":void 0;return React.createElement("div",{className:"row"},React.createElement("div",{className:"col-md-7"},React.createElement("h3",null,"Upload an imaging scan"),React.createElement("br",null),React.createElement(FormElement,{name:"upload_form",formElements:form,fileUpload:!0,onUserInput:this.onFormChange},React.createElement(StaticElement,{label:"Notes",text:"File name should be of type .tgz or tar.gz or .zip"}),React.createElement("div",{className:"row"},React.createElement("div",{className:"col-sm-9 col-sm-offset-3"},React.createElement(_ProgressBar2.default,{value:this.state.uploadProgress}))),React.createElement(ButtonElement,{onUserInput:this.submitForm,buttonClass:btnClass}))))}}]),UploadForm}(React.Component);UploadForm.propTypes={},UploadForm.defaultProps={},exports.default=UploadForm},function(module,exports){"use strict";function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(self,call){if(!self)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!call||"object"!=typeof call&&"function"!=typeof call?self:call}function _inherits(subClass,superClass){if("function"!=typeof superClass&&null!==superClass)throw new TypeError("Super expression must either be null or a function, not "+typeof superClass);subClass.prototype=Object.create(superClass&&superClass.prototype,{constructor:{value:subClass,enumerable:!1,writable:!0,configurable:!0}}),superClass&&(Object.setPrototypeOf?Object.setPrototypeOf(subClass,superClass):subClass.__proto__=superClass)}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function defineProperties(target,props){for(var i=0;i-1)return null;var row={};rowHeaders.forEach(function(header,index){row[header]=rowData[index]},this);var cellStyle={whiteSpace:"nowrap"};if("Progress"===column){if("Failure"===cell)return cellStyle.color="#fff",React.createElement("td",{className:"label-danger",style:cellStyle},cell);if("In Progress..."===cell)return cellStyle.color="#fff",React.createElement("td",{className:"label-warning",style:cellStyle},cell);var created=row["Number Of MincCreated"],inserted=row["Number Of MincInserted"];return React.createElement("td",{style:cellStyle},cell," (",inserted," out of ",created,")")}if("Tarchive Info"===column){if(!cell||"0"===cell)return React.createElement("td",null);var url=loris.BaseURL+"/dicom_archive/viewDetails/?tarchiveID="+cell;return React.createElement("td",{style:cellStyle},React.createElement("a",{href:url},"View Details"))}if("Number Of MincInserted"===column&&cell>0)return React.createElement("td",{style:cellStyle},React.createElement("a",{onClick:handleClick.bind(null,row.CandID)},cell));if("Number Of MincCreated"===column){var violatedScans=void 0;if(row["Number Of MincCreated"]-row["Number Of MincInserted"]>0){var numViolatedScans=row["Number Of MincCreated"]-row["Number Of MincInserted"],patientName=row.PatientName;violatedScans=React.createElement("a",{onClick:openViolatedScans.bind(null,patientName)},"(",numViolatedScans," violated scans)")}return React.createElement("td",{style:cellStyle},cell," ",violatedScans)}return React.createElement("td",{style:cellStyle},cell)}Object.defineProperty(exports,"__esModule",{value:!0}),loris.hiddenHeaders=["PatientName"],exports.default=formatColumn}]); //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/modules/imaging_uploader/jsx/UploadForm.js b/modules/imaging_uploader/jsx/UploadForm.js index 757d325e4c5..d82c79f9899 100644 --- a/modules/imaging_uploader/jsx/UploadForm.js +++ b/modules/imaging_uploader/jsx/UploadForm.js @@ -101,6 +101,17 @@ class UploadForm extends React.Component { return; } + // File in the middle of insertion pipeline + if (mriFile.status === "In Progress...") { + swal({ + title: "File is currently processing!", + text: "A file with this name is currently going through the MRI pipeline!\n", + type: "error", + confirmButtonText: 'OK' + }); + return; + } + // File uploaded but failed during mri pipeline if (mriFile.status === "Failure") { swal({ @@ -117,8 +128,28 @@ class UploadForm extends React.Component { swal("Cancelled", "Your imaginary file is safe :)", "error"); } }.bind(this)); - return; } + + // Pipeline has not been triggered yet + if (mriFile.status === "Not Started") { + swal({ + title: "Are you sure?", + text: "A file with this name has been uploaded but has not yet started the MRI pipeline." + + "\n Would you like to override the existing file?", + type: "warning", + showCancelButton: true, + confirmButtonText: 'Yes, I am sure!', + cancelButtonText: 'No, cancel it!' + }, function(isConfirm) { + if (isConfirm) { + this.uploadFile(true); + } else { + swal("Cancelled", "Your upload has been cancelled.", "error"); + } + }.bind(this)); + } + + return; } /* diff --git a/modules/imaging_uploader/php/NDB_Menu_Filter_imaging_uploader.class.inc b/modules/imaging_uploader/php/NDB_Menu_Filter_imaging_uploader.class.inc index 623920953c8..814dfb8f93c 100644 --- a/modules/imaging_uploader/php/NDB_Menu_Filter_imaging_uploader.class.inc +++ b/modules/imaging_uploader/php/NDB_Menu_Filter_imaging_uploader.class.inc @@ -596,14 +596,12 @@ class NDB_Menu_Filter_Imaging_Uploader extends NDB_Menu_Filter_Form */ function _getFileList($data) { - $files = array(); - foreach ($data as $row) { + $uploadStatus = $row[1]; $filePath = $row[5]; + $mincInserted = $row[10]; $fileName = basename($filePath); - $uploadStatus = $row[1]; - // Checks if a file is already in files array $isDuplicate = ( array_search( @@ -612,7 +610,12 @@ class NDB_Menu_Filter_Imaging_Uploader extends NDB_Menu_Filter_Form ) > -1 ); - if (file_exists($filePath) && !$isDuplicate) { + // If file successfully completed pipeline, then filePath is no longer + // valid as the file would have been deleted. + // Check that mincInserted > 0 in mri_upload to handle Successful uploads + if ((file_exists($filePath) || $mincInserted) + && !$isDuplicate + ) { array_push( $files, [