LT-240: Add studyListFunctionsEnabled property (false as default) in public.ui object in settings file to enable/disable import / export / anonymize functions

LT-239: Error handling on the import process (WIP)
This commit is contained in:
Aysel Afsar 2016-04-21 22:42:06 -04:00
parent 1e1c4033a6
commit 2a17760ff8
15 changed files with 267 additions and 179 deletions

View File

@ -31,7 +31,10 @@ Meteor.startup(function() {
}], }],
"defaultServiceType": 'dicomWeb', "defaultServiceType": 'dicomWeb',
"public": { "public": {
"verifyEmail": false "verifyEmail": false,
"ui": {
"studyListFunctionsEnabled": false
}
} }
//defaultServiceType: 'dimse' //defaultServiceType: 'dimse'
}; };

View File

@ -28,7 +28,12 @@ Meteor.startup(function() {
port: 4242, port: 4242,
hostAE: 'ORTHANC' hostAE: 'ORTHANC'
}, },
defaultServiceType: 'dicomWeb' defaultServiceType: 'dicomWeb',
public: {
ui: {
studyListFunctionsEnabled: false
}
}
//defaultServiceType: 'dimse' //defaultServiceType: 'dimse'
}; };

View File

@ -24,16 +24,20 @@
title="Remove Timepoint Association"> title="Remove Timepoint Association">
<i class="fa fa-eraser fa-lg"></i> Remove Association <i class="fa fa-eraser fa-lg"></i> Remove Association
</a> </a>
<a class="disabled"><span class="fa-stack fa-lg">
<i class="fa fa-user fa-stack-1x"></i> {{#if studyListFunctionsEnabled}}
<i class="fa fa-ban fa-stack-2x text-danger"></i> <a class="disabled"><span class="fa-stack fa-lg">
</span> Anonymize <i class="fa fa-user fa-stack-1x"></i>
</a> <i class="fa fa-ban fa-stack-2x text-danger"></i>
</span> Anonymize
</a>
<a id="exportSelectedStudies" type="button"
title="Export Selected Studies">
<i class="fa fa-exchange fa-lg"></i> Export
</a>
{{/if}}
<a class="disabled"><i class="fa fa-trash fa-lg"></i> Delete</a> <a class="disabled"><i class="fa fa-trash fa-lg"></i> Delete</a>
<a class="disabled"><i class="fa fa-send-o fa-lg"></i> Send</a> <a class="disabled"><i class="fa fa-send-o fa-lg"></i> Send</a>
<a id="exportSelectedStudies" type="button"
title="Export Selected Studies">
<i class="fa fa-exchange fa-lg"></i> Export</a>
<a class="disabled"><i class="fa fa-download fa-lg"></i> Download</a> <a class="disabled"><i class="fa fa-download fa-lg"></i> Download</a>
<a class="disabled"><i class="fa fa-photo fa-lg"></i> View Series Details</a> <a class="disabled"><i class="fa fa-photo fa-lg"></i> View Series Details</a>
</li> </li>

View File

@ -171,4 +171,12 @@ function viewStudies() {
// Switch to the new tab // Switch to the new tab
switchToTab(contentid); switchToTab(contentid);
} }
Template.studyContextMenu.helpers({
'studyListFunctionsEnabled': function() {
var studyListFunctionsEnabled = Meteor.settings && Meteor.settings.public && Meteor.settings.public.ui &&
Meteor.settings.public.ui.studyListFunctionsEnabled || false;
return studyListFunctionsEnabled;
}
});

View File

@ -9,6 +9,7 @@
{{progressStatus}} {{progressStatus}}
</div> </div>
</div> </div>
<div class="message">{{progressMessage}}</div>
</div> </div>
</div> </div>
</template> </template>

View File

@ -18,6 +18,11 @@ progressDialog = {
Session.set("progressDialogSettings", { title: "", numberOfCompleted: 0, numberOfTotal: 1 }); Session.set("progressDialogSettings", { title: "", numberOfCompleted: 0, numberOfTotal: 1 });
$('#progressDialog').css('display', 'none'); $('#progressDialog').css('display', 'none');
}, },
'setMessage': function(message) {
var progressDialogSettings = Session.get("progressDialogSettings");
progressDialogSettings.message = message;
Session.set("progressDialogSettings", progressDialogSettings);
}
}; };
Template.progressDialog.helpers({ Template.progressDialog.helpers({
@ -40,5 +45,12 @@ Template.progressDialog.helpers({
} }
return parseInt((numberOfCompleted / numberofTotal) * 100) + "%"; return parseInt((numberOfCompleted / numberofTotal) * 100) + "%";
},
'progressMessage': function() {
var progressDialogSettings = Session.get("progressDialogSettings");
if (progressDialogSettings && progressDialogSettings.message) {
return progressDialogSettings.message;
}
return;
} }
}); });

View File

@ -6,7 +6,10 @@ importStudies = function(filesToImport, importCallback) {
if (filesToImport.length < 1) { if (filesToImport.length < 1) {
return; return;
} }
var fileUploadStatus = { numberOfFilesUploaded: 0, numberOfFilesFailed: 0 }; var fileUploadStatus = {
numberOfFilesUploaded: 0,
numberOfFilesFailed: 0
};
var numberOfFilesToUpload = filesToImport.length; var numberOfFilesToUpload = filesToImport.length;
var studiesToImport = []; var studiesToImport = [];
@ -60,8 +63,11 @@ function updateFileUploadStatus(fileUploadStatus, isSuccess) {
fileUploadStatus.numberOfFilesUploaded++; fileUploadStatus.numberOfFilesUploaded++;
} }
} }
function importStudiesInternal(studiesToImport, importCallback) { function importStudiesInternal(studiesToImport, importCallback) {
if (!studiesToImport) {
return;
}
var numberOfStudiesToImport = studiesToImport.length; var numberOfStudiesToImport = studiesToImport.length;
progressDialog.show("Importing Studies...", numberOfStudiesToImport); progressDialog.show("Importing Studies...", numberOfStudiesToImport);
@ -69,7 +75,9 @@ function importStudiesInternal(studiesToImport, importCallback) {
// Create/Insert a new study import status item // Create/Insert a new study import status item
Meteor.call("createStudyImportStatus", function(err, studyImportStatusId) { Meteor.call("createStudyImportStatus", function(err, studyImportStatusId) {
if (err) { if (err) {
console.log(err); // Hide dialog
progressDialog.close();
console.log(err.message);
return; return;
} }
@ -82,6 +90,12 @@ function importStudiesInternal(studiesToImport, importCallback) {
var numberOfStudiesProcessedToImport = studyImportStatus.numberOfStudiesImported + studyImportStatus.numberOfStudiesFailed; var numberOfStudiesProcessedToImport = studyImportStatus.numberOfStudiesImported + studyImportStatus.numberOfStudiesFailed;
// Show failed message in the dialog
if (studyImportStatus.numberOfStudiesFailed > 0) {
var failMessage = "Failed to import " + studyImportStatus.numberOfStudiesFailed + " of " + numberOfStudiesToImport + " files";
progressDialog.setMessage(failMessage);
}
progressDialog.update(numberOfStudiesProcessedToImport); progressDialog.update(numberOfStudiesProcessedToImport);
if (numberOfStudiesProcessedToImport == numberOfStudiesToImport) { if (numberOfStudiesProcessedToImport == numberOfStudiesToImport) {
@ -90,7 +104,10 @@ function importStudiesInternal(studiesToImport, importCallback) {
if (studyImportStatus.numberOfStudiesFailed > 0) { if (studyImportStatus.numberOfStudiesFailed > 0) {
//TODO: Some files failed to import, so let user know //TODO: Some files failed to import, so let user know
console.log("Failed to import " + studyImportStatus.numberOfStudiesFailed + " of " + numberOfStudiesToImport + " files"); // Update progress dialog message
var failMessage = "Failed to import " + studyImportStatus.numberOfStudiesFailed + " of " + numberOfStudiesToImport + " files";
progressDialog.setMessage(failMessage);
console.log(failMessage);
} }
// Let the caller know that import operation is completed // Let the caller know that import operation is completed

View File

@ -49,6 +49,10 @@ Meteor.methods({
* @param studyImportStatusId Study import status collection id to track import status * @param studyImportStatusId Study import status collection id to track import status
*/ */
importStudies: function(studiesToImport, studyImportStatusId) { importStudies: function(studiesToImport, studyImportStatusId) {
if (!studiesToImport || !studyImportStatusId) {
return;
}
if (Meteor.settings.dicomWeb && Meteor.settings.defaultServiceType === 'dicomWeb') { if (Meteor.settings.dicomWeb && Meteor.settings.defaultServiceType === 'dicomWeb') {
//TODO: Support importing studies into dicomWeb //TODO: Support importing studies into dicomWeb
console.log('Importing studies into dicomWeb is currently not supported.'); console.log('Importing studies into dicomWeb is currently not supported.');
@ -76,24 +80,41 @@ Meteor.methods({
}); });
function importStudiesDIMSE(studiesToImport, studyImportStatusId) { function importStudiesDIMSE(studiesToImport, studyImportStatusId) {
if (!studiesToImport || !studyImportStatusId) {
return;
}
// Perform C-Store to import studies and handle the callbacks to update import status // Perform C-Store to import studies and handle the callbacks to update import status
DIMSE.storeInstances(studiesToImport, function(err, file) { DIMSE.storeInstances(studiesToImport, function(err, file) {
// Use fiber to be able to modify meteor collection in callback try {
fiber(function() { // Use fiber to be able to modify meteor collection in callback
// Update the import status fiber(function() {
if (err) { try {
StudyImportStatus.update({_id: studyImportStatusId}, {$inc: {'numberOfStudiesFailed': 1}}); // Update the import status
console.log("Failed to import study via DIMSE: ", file, err); if (err) {
} else { StudyImportStatus.update({_id: studyImportStatusId}, {$inc: {'numberOfStudiesFailed': 1}});
StudyImportStatus.update({_id: studyImportStatusId}, {$inc: {'numberOfStudiesImported': 1}}); console.log("Failed to import study via DIMSE: ", file, err);
console.log("Study successfully imported via DIMSE: ", file); } else {
} StudyImportStatus.update({_id: studyImportStatusId}, {$inc: {'numberOfStudiesImported': 1}});
console.log("Study successfully imported via DIMSE: ", file);
}
} catch(error) {
StudyImportStatus.update({_id: studyImportStatusId}, {$inc: {'numberOfStudiesFailed': 1}});
console.log("Failed to import study via DIMSE: ", file, error);
} finally {
// The import operation of this file is completed, so delete it if still exists
if (fileExists(file)) {
fs.unlink(file);
}
}
}).run();
} catch(error) {
StudyImportStatus.update({_id: studyImportStatusId}, {$inc: {'numberOfStudiesFailed': 1}});
console.log("Failed to import study via DIMSE: ", file, error);
}
// The import operation of this file is completed, so delete it if still exists
if (fileExists(file)) {
fs.unlink(file);
}
}).run();
}); });
} }

View File

@ -5,16 +5,18 @@
onselectstart='return false;'> onselectstart='return false;'>
<ul> <ul>
<li> <li>
<a class="disabled"><span class="fa-stack fa-lg"> {{#if studyListFunctionsEnabled}}
<a class="disabled"><span class="fa-stack fa-lg">
<i class="fa fa-user fa-stack-1x"></i> <i class="fa fa-user fa-stack-1x"></i>
<i class="fa fa-ban fa-stack-2x text-danger"></i> <i class="fa fa-ban fa-stack-2x text-danger"></i>
</span> Anonymize </span> Anonymize
</a> </a>
<a id="exportSelectedStudies" type="button" title="Export Selected Studies">
<i class="fa fa-exchange fa-lg"></i> Export
</a>
{{/if}}
<a class="disabled"><i class="fa fa-trash fa-lg"></i> Delete</a> <a class="disabled"><i class="fa fa-trash fa-lg"></i> Delete</a>
<a class="disabled"><i class="fa fa-send-o fa-lg"></i> Send</a> <a class="disabled"><i class="fa fa-send-o fa-lg"></i> Send</a>
<a id="exportSelectedStudies" type="button"
title="Export Selected Studies">
<i class="fa fa-exchange fa-lg"></i> Export</a>
<a class="disabled"><i class="fa fa-download fa-lg"></i> Download</a> <a class="disabled"><i class="fa fa-download fa-lg"></i> Download</a>
<a class="disabled"><i class="fa fa-photo fa-lg"></i> View Series Details</a> <a class="disabled"><i class="fa fa-photo fa-lg"></i> View Series Details</a>
</li> </li>

View File

@ -82,3 +82,10 @@ Template.studyContextMenu.events({
closeHandler(dialog); closeHandler(dialog);
} }
}); });
Template.studyContextMenu.helpers({
'studyListFunctionsEnabled': function() {
var studyListFunctionsEnabled = Meteor.settings && Meteor.settings.public && Meteor.settings.public.ui &&
Meteor.settings.public.ui.studyListFunctionsEnabled || false; return studyListFunctionsEnabled;
}
});

View File

@ -1,7 +1,8 @@
<template name="worklistResult"> <template name="worklistResult">
{{>worklistToolbar}} <div id="studyListContainer">
<table id="tblStudyList" class="worklistResult table noselect"> {{>worklistToolbar}}
<thead> <table id="tblStudyList" class="worklistResult table noselect">
<thead>
<tr> <tr>
<th> <th>
<input type="text" <input type="text"
@ -25,16 +26,16 @@
</th> </th>
{{#unless isTouchDevice}} {{#unless isTouchDevice}}
<th> <th>
<input type="text" <input type="text"
class="form-control worklist-search" class="form-control worklist-search"
id="accessionNumber" id="accessionNumber"
placeholder="Search Accession #"> placeholder="Search Accession #">
<div id="_accessionNumber" class="sortingCell"> <div id="_accessionNumber" class="sortingCell">
<span>Accession #</span> <span>Accession #</span>
<i class="{{sortingColumnsIcons.accessionNumber}}">&nbsp;</i> <i class="{{sortingColumnsIcons.accessionNumber}}">&nbsp;</i>
</div> </div>
</th> </th>
{{/unless}} {{/unless}}
<th> <th>
@ -50,16 +51,16 @@
</th> </th>
{{#unless isTouchDevice}} {{#unless isTouchDevice}}
<th> <th>
<input type="text" <input type="text"
class="form-control worklist-search" class="form-control worklist-search"
id="modality" id="modality"
placeholder="Search Modality"> placeholder="Search Modality">
<div id="_modalities" class="sortingCell"> <div id="_modalities" class="sortingCell">
<span>Modality</span> <span>Modality</span>
<i class="{{sortingColumnsIcons.modalities}}">&nbsp;</i> <i class="{{sortingColumnsIcons.modalities}}">&nbsp;</i>
</div> </div>
</th> </th>
{{/unless}} {{/unless}}
<th> <th>
<input type="text" <input type="text"
@ -73,26 +74,28 @@
</th> </th>
{{#unless isTouchDevice}} {{#unless isTouchDevice}}
<th> <th>
<div id="_numberOfStudyRelatedInstances" class="sortingCell"> <div id="_numberOfStudyRelatedInstances" class="sortingCell">
<span># Images</span> <span># Images</span>
<i class="{{sortingColumnsIcons.numberOfStudyRelatedInstances}}">&nbsp;</i> <i class="{{sortingColumnsIcons.numberOfStudyRelatedInstances}}">&nbsp;</i>
</div> </div>
</th> </th>
{{/unless}} {{/unless}}
</tr> </tr>
</thead> </thead>
<tbody id="studyListData"> <tbody id="studyListData">
{{#each studies}} {{#each studies}}
{{>worklistStudy }} {{>worklistStudy }}
{{/each}} {{/each}}
</tbody> </tbody>
</table> </table>
{{#if showLoadingText}} {{#if showLoadingText}}
{{>loadingText}} {{>loadingText}}
{{/if}} {{/if}}
{{#if showNotFoundMessage}}
{{>studyNotFound}}
{{/if}}
</div>
{{#if showNotFoundMessage}}
{{>studyNotFound}}
{{/if}}
</template> </template>

View File

@ -1,105 +1,107 @@
table#tblStudyList #studyListContainer
color: #888888 position: relative
border-collapse: collapse table#tblStudyList
border-color: gray color: #888888
border-collapse: collapse
border-color: gray
thead thead
background-color: black
white-space: nowrap
tr
th:first-child
div.sortingCell
border-right: 0.5px dashed #555555
border-left: none
th
padding: 0
div.sortingCell
display: inline-block
cursor: pointer
width: 100%
min-width: 95px
margin: 0 auto
color: white
padding: 10px
font-weight: 500
background: #171717
border-left: 0.5px dashed #555555
span
font-size: 15px
float: left
i
margin: 0 5px
&:hover, &.active
background: #252525
input.worklist-search
height: 34px
margin: 0 5px 20px 5px
cursor: pointer
padding: 10px
border: none
background-color: #707070
color: #434343
font-size: 10pt
font-weight: normal
width: calc(100% - 10px)
box-sizing: border-box
-webkit-transition: all 0.15s ease-in-out;
-moz-transition: all 0.15s ease-in-out;
-o-transition: all 0.15s ease-in-out;
transition: all 0.15s ease-in-out;
&:active, &:hover
color: black
background-color: #d3d3d9
&::-webkit-input-placeholder /* WebKit, Blink, Edge */
color: #434343
&:-moz-placeholder /* Mozilla Firefox 4 to 18 */
color: #434343
opacity: 1
&::-moz-placeholder /* Mozilla Firefox 19+ */
color: #434343
opacity: 1
&:-ms-input-placeholder /* Internet Explorer 10-11 */
color: #434343
::-webkit-datetime-edit-year-field:not([aria-valuenow]),
::-webkit-datetime-edit-month-field:not([aria-valuenow]),
::-webkit-datetime-edit-day-field:not([aria-valuenow])
color: transparent
tbody
tr
height: 22px
padding: 5px
background-color: black background-color: black
white-space: nowrap
td tr
color: #ffffff th:first-child
border-top: 0.5px dashed #555555 div.sortingCell
white-space: nowrap border-right: 0.5px dashed #555555
border-left: none
-webkit-transition: all 0.1s ease th
transition: all 0.1s ease padding: 0
&:hover, &:active, &.active div.sortingCell
background-color: #009BD2 display: inline-block
color: white cursor: pointer
width: 100%
min-width: 95px
margin: 0 auto
color: white
padding: 10px
font-weight: 500
background: #171717
border-left: 0.5px dashed #555555
span
font-size: 15px
float: left
i
margin: 0 5px
&:hover, &.active
background: #252525
input.worklist-search
height: 34px
margin: 0 5px 20px 5px
cursor: pointer
padding: 10px
border: none
background-color: #707070
color: #434343
font-size: 10pt
font-weight: normal
width: calc(100% - 10px)
box-sizing: border-box
-webkit-transition: all 0.15s ease-in-out;
-moz-transition: all 0.15s ease-in-out;
-o-transition: all 0.15s ease-in-out;
transition: all 0.15s ease-in-out;
&:active, &:hover
color: black
background-color: #d3d3d9
&::-webkit-input-placeholder /* WebKit, Blink, Edge */
color: #434343
&:-moz-placeholder /* Mozilla Firefox 4 to 18 */
color: #434343
opacity: 1
&::-moz-placeholder /* Mozilla Firefox 19+ */
color: #434343
opacity: 1
&:-ms-input-placeholder /* Internet Explorer 10-11 */
color: #434343
::-webkit-datetime-edit-year-field:not([aria-valuenow]),
::-webkit-datetime-edit-month-field:not([aria-valuenow]),
::-webkit-datetime-edit-day-field:not([aria-valuenow])
color: transparent
tbody
tr
height: 22px
padding: 5px
background-color: black
td td
// This selector is necessary to override bootstrap's 'table' class color: #ffffff
border-top: 0.5px dashed #dddddd border-top: 0.5px dashed #555555
border-bottom: 0.5px dashed #dddddd white-space: nowrap
color: white
-webkit-transition: all 0.1s ease
transition: all 0.1s ease
&:hover, &:active, &.active
background-color: #009BD2 background-color: #009BD2
color: white
td
// This selector is necessary to override bootstrap's 'table' class
border-top: 0.5px dashed #dddddd
border-bottom: 0.5px dashed #dddddd
color: white
background-color: #009BD2

View File

@ -4,7 +4,7 @@
{{#if importSupported }} {{#if importSupported }}
<span class="btn btn-default btn-file"> <span class="btn btn-default btn-file">
<i class="fa fa-upload"></i> <i class="fa fa-upload"></i>
<input id="btnImport" title="Import" type="file" webkitdirectory directory multiple> <input id="btnImport" title="Import Study" type="file" webkitdirectory directory multiple>
</span> </span>
{{/if }} {{/if }}
</div> </div>

View File

@ -12,7 +12,10 @@ Template.worklistToolbar.events({
Template.worklistToolbar.helpers({ Template.worklistToolbar.helpers({
importSupported: function() { importSupported: function() {
var importSupported = Session.get('importSupported'); var importSupported = Session.get('importSupported');
if (importSupported) { var studyListFunctionsEnabled = Meteor.settings && Meteor.settings.public && Meteor.settings.public.ui &&
Meteor.settings.public.ui.studyListFunctionsEnabled || false;
if (importSupported && studyListFunctionsEnabled) {
return true; return true;
} }
return false; return false;

View File

@ -1,8 +1,8 @@
#worklistToolbar #worklistToolbar
background-color: black background-color: black
text-align: right position: absolute
width: 100% top: 0
height: auto right: 0
.btn-file .btn-file
position: relative position: relative