From 2da841bab02e2aa5399ea9dcb33f231792adf845 Mon Sep 17 00:00:00 2001 From: Aysel Afsar Date: Mon, 4 Apr 2016 00:00:33 -0400 Subject: [PATCH] LT-117: Add warning icon in study list row and give tip about user when warning icon is hovered if study is in-use by another user --- LesionTracker/defaultSettings.js | 21 ++++-- Packages/lesiontracker/both/collections.js | 3 +- .../client/collections/subscriptions.js | 1 + .../lesionLocationDialog.js | 8 +++ .../lesionTrackerWorklistStudy.html | 4 ++ .../lesionTrackerWorklistStudy.js | 68 +++++++++++++++++++ .../lesionTrackerWorklistStudy.styl | 5 +- Packages/lesiontracker/lib/clearTools.js | 8 +++ .../lib/handleMeasurementAdded.js | 6 +- .../lib/handleMeasurementModified.js | 4 ++ .../lib/handleMeasurementRemoved.js | 4 ++ .../lib/syncMeasurementAndToolData.js | 5 ++ Packages/lesiontracker/package.js | 6 ++ Packages/lesiontracker/server/collections.js | 5 ++ Packages/lesiontracker/server/reviewers.js | 66 ++++++++++++++++++ 15 files changed, 206 insertions(+), 8 deletions(-) create mode 100644 Packages/lesiontracker/client/collections/subscriptions.js create mode 100644 Packages/lesiontracker/server/reviewers.js diff --git a/LesionTracker/defaultSettings.js b/LesionTracker/defaultSettings.js index 3d4ad2e10..2b1f48637 100644 --- a/LesionTracker/defaultSettings.js +++ b/LesionTracker/defaultSettings.js @@ -16,11 +16,11 @@ Meteor.startup(function() { qidoSupportsIncludeField: false, imageRendering: 'wadouri', requestOptions: { - auth: 'orthanc:orthanc', - logRequests: true, - logResponses: false, - logTiming: true - } + auth: 'orthanc:orthanc', + logRequests: true, + logResponses: false, + logTiming: true + } }] }, dimse: [{ @@ -37,4 +37,15 @@ Meteor.startup(function() { }; console.log('Using default LesionTracker settings with service: ' + Meteor.settings.defaultServiceType); + + // Bind events if window is closed + $(window).bind('beforeunload', function (e) { + closingWindow(); + // have to return null, unless you want a chrome popup alert + //return 'If you leave this page then any unsaved changes will be lost.'; + }); }); + +closingWindow = function(){ + Meteor.call('removeUserFromReviewers', Meteor.userId()); +}; diff --git a/Packages/lesiontracker/both/collections.js b/Packages/lesiontracker/both/collections.js index ee3101cd0..12cd17f08 100644 --- a/Packages/lesiontracker/both/collections.js +++ b/Packages/lesiontracker/both/collections.js @@ -3,4 +3,5 @@ Studies = new Meteor.Collection('studies'); Measurements = new Meteor.Collection('measurements'); ImageMeasurements = new Meteor.Collection('imageMeasurements'); -WorklistSubscriptions = ['studies', 'timepoints']; \ No newline at end of file +WorklistSubscriptions = ['studies', 'timepoints']; +Reviewers = new Meteor.Collection('reviewers'); diff --git a/Packages/lesiontracker/client/collections/subscriptions.js b/Packages/lesiontracker/client/collections/subscriptions.js new file mode 100644 index 000000000..bc19ceff2 --- /dev/null +++ b/Packages/lesiontracker/client/collections/subscriptions.js @@ -0,0 +1 @@ +Meteor.subscribe('reviewers'); diff --git a/Packages/lesiontracker/client/components/lesionLocationDialog/lesionLocationDialog.js b/Packages/lesiontracker/client/components/lesionLocationDialog/lesionLocationDialog.js index 78ea49ccb..e4c91368e 100644 --- a/Packages/lesiontracker/client/components/lesionLocationDialog/lesionLocationDialog.js +++ b/Packages/lesiontracker/client/components/lesionLocationDialog/lesionLocationDialog.js @@ -183,6 +183,14 @@ changeLesionLocationCallback = function(measurementData, eventData, doneCallback selected: true } }); + + // Set reviewer for this timepoint + Object.keys(measurement.timepoints).forEach(function(key) { + if (measurement.timepoints[key].studyInstanceUid) { + Meteor.call('setReviewer',measurement.timepoints[key].studyInstanceUid); + } + }); + }; var config = { diff --git a/Packages/lesiontracker/client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.html b/Packages/lesiontracker/client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.html index d3c8f8744..0fd4359e4 100644 --- a/Packages/lesiontracker/client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.html +++ b/Packages/lesiontracker/client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.html @@ -1,6 +1,9 @@ \ No newline at end of file diff --git a/Packages/lesiontracker/client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.js b/Packages/lesiontracker/client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.js index 0f3eae242..2d2336639 100644 --- a/Packages/lesiontracker/client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.js +++ b/Packages/lesiontracker/client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.js @@ -26,5 +26,73 @@ Template[defaultTemplate].helpers({ } return getTimepointName(timepoint); + }, + reviewerTip: function() { + var data = this; + var study = Studies.findOne({ + studyInstanceUid: data.studyInstanceUid + }); + + if (!study) { + return; + } + + var timepoint = Timepoints.findOne({ + timepointId: study.timepointId + }); + + if (!timepoint) { + return; + } + + var timepointReviewers = Reviewers.findOne({timepointId: timepoint.timepointId}); + if (!timepointReviewers) { + return; + } + + return getReviewerTipText(timepointReviewers.reviewers); + } +}); + +function getReviewerTipText(reviewers) { + if (!reviewers || reviewers.length === 0) { + return; + } + + var newReviewers = reviewers.filter(function ( reviewer ) { + return reviewer.userId !== Meteor.userId(); + }); + + if (newReviewers.length === 0) { + return; + } + + var tipText = 'The study is being reviewed by '; + newReviewers.forEach(function(reviewer, index) { + if (reviewer.userId === Meteor.userId()) { + return; + } + + if (index > 0) { + tipText += ','; + } + tipText += reviewer.userName; + + }); + + return tipText; +} + +// Observe changes on WorklistTabs +WorklistTabs.find().observe({ + removed: function(tab) { + var timepointId = tab.timepointId; + if (!timepointId) { + return; + } + + // Remove the current user from Reviewers + Meteor.call('removeReviewer', timepointId); + } }); \ No newline at end of file diff --git a/Packages/lesiontracker/client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.styl b/Packages/lesiontracker/client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.styl index 024cbeb81..5c99989b8 100644 --- a/Packages/lesiontracker/client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.styl +++ b/Packages/lesiontracker/client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.styl @@ -1,2 +1,5 @@ .worklistStudy - cursor: pointer \ No newline at end of file + cursor: pointer + + .reviewerTip + color: darkred \ No newline at end of file diff --git a/Packages/lesiontracker/lib/clearTools.js b/Packages/lesiontracker/lib/clearTools.js index 31268f8ba..70c0787c5 100644 --- a/Packages/lesiontracker/lib/clearTools.js +++ b/Packages/lesiontracker/lib/clearTools.js @@ -8,6 +8,14 @@ clearTools = function() { var seriesInstanceUids = []; // Holds seriesInstanceUid of imageViewerViewport elements viewportElements.each(function(index, element) { var enabledElement = cornerstone.getEnabledElement(element); + var study = cornerstoneTools.metaData.get('study', enabledElement.image.imageId); + if (!study) { + return; + } + // Set reviewer for this timepoint + if (study.studyInstanceUid) { + Meteor.call('setReviewer',study.studyInstanceUid); + } var series = cornerstoneTools.metaData.get('series', enabledElement.image.imageId); if (!series) { return; diff --git a/Packages/lesiontracker/lib/handleMeasurementAdded.js b/Packages/lesiontracker/lib/handleMeasurementAdded.js index f99f599cb..8dcebd688 100644 --- a/Packages/lesiontracker/lib/handleMeasurementAdded.js +++ b/Packages/lesiontracker/lib/handleMeasurementAdded.js @@ -1,6 +1,5 @@ handleMeasurementAdded = function(e, eventData) { var measurementData = eventData.measurementData; - switch (measurementData.toolType) { case 'nonTarget': case 'bidirectional': @@ -10,6 +9,10 @@ handleMeasurementAdded = function(e, eventData) { log.info('CornerstoneToolsMeasurementAdded'); LesionManager.updateLesionData(measurementData); TrialResponseCriteria.validateDelayed(measurementData); + // Set reviewer for this timepoint + if (measurementData.studyInstanceUid) { + Meteor.call('setReviewer',measurementData.studyInstanceUid); + } break; case 'ellipticalRoi': case 'length': @@ -33,4 +36,5 @@ handleMeasurementAdded = function(e, eventData) { break; } + }; diff --git a/Packages/lesiontracker/lib/handleMeasurementModified.js b/Packages/lesiontracker/lib/handleMeasurementModified.js index 8a1b84c18..9e3092f95 100644 --- a/Packages/lesiontracker/lib/handleMeasurementModified.js +++ b/Packages/lesiontracker/lib/handleMeasurementModified.js @@ -9,6 +9,10 @@ handleMeasurementModified = function(e, eventData) { log.info('CornerstoneToolsMeasurementModified'); LesionManager.updateLesionData(measurementData); TrialResponseCriteria.validateDelayed(measurementData); + // Set reviewer for this timepoint + if (measurementData.studyInstanceUid) { + Meteor.call('setReviewer',measurementData.studyInstanceUid); + } break; case 'ellipticalRoi': case 'length': diff --git a/Packages/lesiontracker/lib/handleMeasurementRemoved.js b/Packages/lesiontracker/lib/handleMeasurementRemoved.js index 340087b9e..eefd5f8c8 100644 --- a/Packages/lesiontracker/lib/handleMeasurementRemoved.js +++ b/Packages/lesiontracker/lib/handleMeasurementRemoved.js @@ -16,6 +16,10 @@ handleMeasurementRemoved = function(e, eventData) { if (!measurement) { return; } + // Set reviewer for this timepoint + if (measurementData.studyInstanceUid) { + Meteor.call('setReviewer',measurementData.studyInstanceUid); + } clearMeasurementTimepointData(measurement._id, measurementData.timepointId); break; diff --git a/Packages/lesiontracker/lib/syncMeasurementAndToolData.js b/Packages/lesiontracker/lib/syncMeasurementAndToolData.js index f1b5686a9..c5e58a867 100644 --- a/Packages/lesiontracker/lib/syncMeasurementAndToolData.js +++ b/Packages/lesiontracker/lib/syncMeasurementAndToolData.js @@ -12,6 +12,11 @@ syncMeasurementAndToolData = function(measurement) { // Sync the Cornerstone ToolData with this Measurement's timepoint-specific data syncTimepointDataWithToolData(measurement, timepointData, imageId, toolType); + + // Set reviewer for this timepoint + if (measurement.timepoints[key].studyInstanceUid) { + Meteor.call('setReviewer',measurement.timepoints[key].studyInstanceUid); + } }); }; diff --git a/Packages/lesiontracker/package.js b/Packages/lesiontracker/package.js index 6b1efe236..d1647ae86 100644 --- a/Packages/lesiontracker/package.js +++ b/Packages/lesiontracker/package.js @@ -29,6 +29,8 @@ Package.onUse(function(api) { // Client-side collections api.addFiles('client/collections/LesionLocations.js', 'client'); api.addFiles('client/collections/LocationResponses.js', 'client'); + api.addFiles('client/collections/subscriptions.js', 'client'); + // Additional Custom Cornerstone Tools for Lesion Tracker api.addFiles('client/compatibility/bidirectionalTool.js', 'client', { @@ -158,6 +160,7 @@ Package.onUse(function(api) { // Server functions api.addFiles('server/collections.js', 'server'); api.addFiles('server/removeCollections.js', [ 'server' ]); + api.addFiles('server/reviewers.js', [ 'server' ]); // Both client and server functions api.addFiles('both/collections.js', [ 'client', 'server' ]); @@ -233,4 +236,7 @@ Package.onUse(function(api) { api.export('Measurements', [ 'client', 'server' ]); api.export('Studies', [ 'client', 'server' ]); api.export('Timepoints', [ 'client', 'server' ]); + + api.export('Reviewers', [ 'client', 'server' ]); + }); diff --git a/Packages/lesiontracker/server/collections.js b/Packages/lesiontracker/server/collections.js index b3f6ccf4d..8cd221556 100644 --- a/Packages/lesiontracker/server/collections.js +++ b/Packages/lesiontracker/server/collections.js @@ -30,6 +30,11 @@ Meteor.publish('singlePatientImageMeasurements', function(patientId) { }); }); +Meteor.publish('reviewers', function() { + return Reviewers.find(); +}); + + // Temporary fix to drop all Collections on server restart // http://stackoverflow.com/questions/23891631/meteor-how-can-i-drop-all-mongo-collections-and-clear-all-data-on-startup Meteor.startup(function() { diff --git a/Packages/lesiontracker/server/reviewers.js b/Packages/lesiontracker/server/reviewers.js new file mode 100644 index 000000000..18a7420ff --- /dev/null +++ b/Packages/lesiontracker/server/reviewers.js @@ -0,0 +1,66 @@ +Meteor.methods({ + setReviewer: function (studyInstanceUid) { + var study = Studies.findOne({ + studyInstanceUid: studyInstanceUid + }); + + if (!study) { + return; + } + + var timepoint = Timepoints.findOne({ + timepointId: study.timepointId + }); + + if (!timepoint) { + return; + } + + var reviewerTimepoint = Reviewers.findOne({timepointId: timepoint.timepointId}); + var user = Meteor.users.findOne(Meteor.userId()); + if (reviewerTimepoint) { + if (reviewerTimepoint.reviewers) { + + // Check whether the user exists + var isReviewerFound = reviewerTimepoint.reviewers.filter(function ( reviewer ) { + return reviewer.userId === user._id; + })[0]; + + // Return if user eixistes for related timepoint + if (isReviewerFound) { + return; + } + var existedReviewers = reviewerTimepoint.reviewers; + existedReviewers.push({userId: user._id, userName: user.profile.fullName}); + + // Update reviewers array after pushing the user + Reviewers.update(reviewerTimepoint._id, {$set: {reviewers: existedReviewers}}); + + } else { + Reviewers.update(reviewerTimepoint._id, {$set: {reviewers: [{userId: user._id, userName: user.profile.fullName}]}}); + } + } else { + Reviewers.insert({timepointId: timepoint.timepointId, reviewers: [{userId: user._id, userName: user.profile.fullName}]}); + } + }, + + removeReviewer: function (timepointId) { + var reviewerTimepoint = Reviewers.findOne({timepointId: timepointId}); + if (!reviewerTimepoint || !reviewerTimepoint.reviewers) { + return; + } + + // Initalize a new array without the current user + var reviewers = reviewerTimepoint.reviewers.filter(function ( reviewer ) { + return reviewer.userId !== Meteor.userId(); + }); + Reviewers.update(reviewerTimepoint._id, {$set: {reviewers: reviewers}}); + }, + + removeUserFromReviewers: function(userId) { + Reviewers.find().map(function (timepoint) { + Reviewers.update({_id: timepoint._id}, {"$pull": {"reviewers": {"userId": userId}}}); + }); + } +}); +