From 83e067b701b0babf5c53b6cedb4c7b0e440ea048 Mon Sep 17 00:00:00 2001 From: Aysel Afsar Date: Tue, 5 Apr 2016 12:16:50 -0400 Subject: [PATCH 1/2] LT-61: Record actions that create, modify, delete, associate timepoints, remove associations and login - Show Last Login Date popup after login successful (WIP) --- LesionTracker/.meteor/packages | 2 + LesionTracker/.meteor/versions | 2 + LesionTracker/client/routes.js | 3 +- LesionTracker/defaultSettings.js | 6 +- Packages/active-entry/server/methods.js | 7 +- .../associationModal/associationModal.js | 34 +++++++ .../lastLoginModal/lastLoginModal.html | 16 ++++ .../lastLoginModal/lastLoginModal.js | 9 ++ .../lesionTrackerLayout.js | 88 +++++++++++-------- .../lesionTrackerWorklistContextMenu.js | 54 ++++++++++-- Packages/lesiontracker/package.js | 2 + Packages/lesiontracker/server/reviewers.js | 16 ++++ 12 files changed, 187 insertions(+), 52 deletions(-) create mode 100644 Packages/lesiontracker/client/components/lastLoginModal/lastLoginModal.html create mode 100644 Packages/lesiontracker/client/components/lastLoginModal/lastLoginModal.js diff --git a/LesionTracker/.meteor/packages b/LesionTracker/.meteor/packages index 91343c364..a17d120ed 100644 --- a/LesionTracker/.meteor/packages +++ b/LesionTracker/.meteor/packages @@ -52,3 +52,5 @@ aldeed:template-extension@4.0.0 zuuk:stale-session gilbertwat:bootstrap3-daterangepicker email +peppelg:bootstrap-3-modal +simple:reactive-method diff --git a/LesionTracker/.meteor/versions b/LesionTracker/.meteor/versions index 8ff293c77..37f81bc2d 100644 --- a/LesionTracker/.meteor/versions +++ b/LesionTracker/.meteor/versions @@ -83,6 +83,7 @@ npm-bcrypt@0.7.8_2 npm-mongo@1.4.39_1 observe-sequence@1.0.7 ordered-dict@1.0.4 +peppelg:bootstrap-3-modal@1.0.4 practicalmeteor:chai@2.1.0_1 practicalmeteor:loglevel@1.2.0_2 promise@0.5.1 @@ -98,6 +99,7 @@ service-configuration@1.0.5 session@1.1.1 sha@1.0.4 silentcicero:jszip@0.0.4 +simple:reactive-method@1.0.2 spacebars@1.0.7 spacebars-compiler@1.0.7 srp@1.0.4 diff --git a/LesionTracker/client/routes.js b/LesionTracker/client/routes.js index 6beedb3f6..f983af0c1 100644 --- a/LesionTracker/client/routes.js +++ b/LesionTracker/client/routes.js @@ -26,7 +26,8 @@ var data = { 'associationModal', 'optionsModal', 'serverInformationModal', - 'confirmRemoveTimepointAssociation' + 'confirmRemoveTimepointAssociation', + 'lastLoginModal' ] }; diff --git a/LesionTracker/defaultSettings.js b/LesionTracker/defaultSettings.js index 2b1f48637..f7cb4245a 100644 --- a/LesionTracker/defaultSettings.js +++ b/LesionTracker/defaultSettings.js @@ -40,12 +40,8 @@ Meteor.startup(function() { // 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.'; + // return 'If you leave this page then any unsaved changes will be lost.'; }); }); -closingWindow = function(){ - Meteor.call('removeUserFromReviewers', Meteor.userId()); -}; diff --git a/Packages/active-entry/server/methods.js b/Packages/active-entry/server/methods.js index 5b6cbb1c1..5225148e9 100644 --- a/Packages/active-entry/server/methods.js +++ b/Packages/active-entry/server/methods.js @@ -123,7 +123,12 @@ Meteor.methods({ }, updateLastLoginDate: function () { - Meteor.users.update({_id: Meteor.userId()}, {$set: {lastLoginDate: new Date()}}); + var user = Meteor.users.findOne(Meteor.userId()); + if (!user) { + return; + } + // Update priorLoginDate and lastLoginDate + Meteor.users.update({_id: Meteor.userId()}, {$set: {priorLoginDate: user.lastLoginDate, lastLoginDate: new Date()}}); }, isAccountInactive: function (inactivityParameters) { diff --git a/Packages/lesiontracker/client/components/associationModal/associationModal.js b/Packages/lesiontracker/client/components/associationModal/associationModal.js index 57dbed08f..5cc60b76e 100644 --- a/Packages/lesiontracker/client/components/associationModal/associationModal.js +++ b/Packages/lesiontracker/client/components/associationModal/associationModal.js @@ -59,6 +59,10 @@ Template.associationModal.events({ // Sort the study dates, so we can get a range for these values studyDates = studyDates.sort(); + // HipaaEventType to log changes in collections + var hipaaEventType; + var hipaaEvent; + // Check if these studies are already associated with an existing Timepoint var existingTimepoint; if (timepointType === 'baseline') { @@ -91,6 +95,7 @@ Template.associationModal.events({ } }); timepointId = existingTimepoint.timepointId; + hipaaEventType = 'modify'; } else { // Create a new timepoint to represent the (baseline or follow-up) studies var timepoint = { @@ -105,8 +110,20 @@ Template.associationModal.events({ // Insert this timepoint into the Timepoints Collection Timepoints.insert(timepoint); timepointId = timepoint.timepointId; + hipaaEventType = 'create'; } + // Log + hipaaEvent = { + eventType: hipaaEventType, + userId: Meteor.userId(), + userName: Meteor.user().profile.fullName, + collectionName: "Timepoints", + recordId: timepointId, + patientId: relatedStudies[0].patientId, + patientName: relatedStudies[0].patientName + }; + // Loop through these studies to associate them with the newly created timepoint relatedStudies.forEach(function(study) { // Check if a study already exists in the Studies collection @@ -121,6 +138,8 @@ Template.associationModal.events({ timepointId: timepointId } }); + // Log modify + hipaaEventType = 'modify'; } else { // If no such study exists, update the entry with the new timepointId @@ -130,7 +149,22 @@ Template.associationModal.events({ // Attach the timepointId and insert it into the Studies Collection study.timepointId = timepointId; Studies.insert(study); + + // Log create + hipaaEventType = 'create'; } + + // Log + hipaaEvent = { + eventType: hipaaEventType, + userId: Meteor.userId(), + userName: Meteor.user().profile.fullName, + collectionName: "Studies", + recordId: study.studyInstanceUid, + patientId: study.patientId, + patientName: study.patientName + }; + HipaaLogger.logEvent(hipaaEvent); }); }); diff --git a/Packages/lesiontracker/client/components/lastLoginModal/lastLoginModal.html b/Packages/lesiontracker/client/components/lastLoginModal/lastLoginModal.html new file mode 100644 index 000000000..4b15781fb --- /dev/null +++ b/Packages/lesiontracker/client/components/lastLoginModal/lastLoginModal.html @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/Packages/lesiontracker/client/components/lastLoginModal/lastLoginModal.js b/Packages/lesiontracker/client/components/lastLoginModal/lastLoginModal.js new file mode 100644 index 000000000..388677bf0 --- /dev/null +++ b/Packages/lesiontracker/client/components/lastLoginModal/lastLoginModal.js @@ -0,0 +1,9 @@ +Template.lastLoginModal.helpers({ + lastLoginDate: function() { + var userLoginDate = ReactiveMethod.call('getPriorLoginDate'); + if (userLoginDate && userLoginDate.priorLoginDate) { + return moment(userLoginDate.priorLoginDate).format("MMMM Do YYYY, HH:mm:ss A"); + } + return; + } +}); \ No newline at end of file diff --git a/Packages/lesiontracker/client/components/lesionTrackerLayout/lesionTrackerLayout.js b/Packages/lesiontracker/client/components/lesionTrackerLayout/lesionTrackerLayout.js index c90d9b4c1..7ba31f20d 100644 --- a/Packages/lesiontracker/client/components/lesionTrackerLayout/lesionTrackerLayout.js +++ b/Packages/lesiontracker/client/components/lesionTrackerLayout/lesionTrackerLayout.js @@ -1,42 +1,5 @@ Session.set('defaultSignInMessage', 'Tumor tracking in your browser.'); - -Template.lesionTrackerLayout.helpers({ - fullName: function() { - return Meteor.user().profile.fullName; - }, - - showWorklistMenu: function() { - return Template.instance().showWorklistMenu.get(); - }, - - currentUser: function() { - var verifyEmail = Meteor.settings && Meteor.settings.public && Meteor.settings.public.verifyEmail || false; - - if (!Meteor.user() || !Meteor.userId()) { - return false; - } - - if (!verifyEmail) { - return true; - } - - if (Meteor.user().emails[0].verified) { - return true; - } - - return false; - } -}); - Template.lesionTrackerLayout.onCreated(function() { - // showViewer to go to viewer from audit - this.showWorklistMenu = new ReactiveVar(true); - // Get url and check worklist - var currentPath = Router.current().route.path(this); - if (currentPath === '/' || currentPath === '/worklist') { - this.showWorklistMenu.set(false); - } - // Show countdown dialog var handle; $(document).on('TriggerOpenTimeoutCountdownDialog', function(e, leftTime) { @@ -68,3 +31,54 @@ Template.lesionTrackerLayout.onCreated(function() { } }); }); + +Template.lesionTrackerLayout.onRendered(function() { + var oldUserId = undefined; + var userName; + var lastLoginModalInterval; + + Tracker.autorun(function() { + // Hook login/logout + var newUserId = Meteor.userId(); + if (oldUserId === null && newUserId) { + Session.set('showLastLoginModal', true); + userName = Meteor.user().profile.fullName; + // Log + HipaaLogger.logEvent({ + eventType: 'init', + userId: Meteor.userId(), + userName: userName + }); + } else if (newUserId === null && oldUserId) { + // Set showLastLoginModal as null + Session.set('showLastLoginModal', null); + // Destroy interval for last login modal + Meteor.clearInterval(lastLoginModalInterval); + console.log('The user logged out'); + + // Log + // TODO: eventype is not defined for logout in hipaa-audit-log + /*HipaaLogger.logEvent({ + eventType: 'logout', + userId: oldUserId, + userName: userName + });*/ + + // Remove the user from Reviewers + Meteor.call('removeUserFromReviewers', oldUserId); + } + oldUserId = Meteor.userId(); + + // Trigger last login date popup + if (Session.get('showLastLoginModal')) { + Modal.show('lastLoginModal'); + lastLoginModalInterval = Meteor.setInterval( function() { + Modal.hide('lastLoginModal'); + Session.set("showLastLoginModal", null); + }, 3000); + return true; + } + }); +}); + + diff --git a/Packages/lesiontracker/client/components/lesionTrackerWorklistContextMenu/lesionTrackerWorklistContextMenu.js b/Packages/lesiontracker/client/components/lesionTrackerWorklistContextMenu/lesionTrackerWorklistContextMenu.js index 555b5135b..ca43814b6 100644 --- a/Packages/lesiontracker/client/components/lesionTrackerWorklistContextMenu/lesionTrackerWorklistContextMenu.js +++ b/Packages/lesiontracker/client/components/lesionTrackerWorklistContextMenu/lesionTrackerWorklistContextMenu.js @@ -37,7 +37,20 @@ function removeTimepointAssociations() { Meteor.call('removeAssociatedStudy', study._id, function(error) { if (error) { log.warn(error); + return; } + + // Log + var hipaaEvent = { + eventType: 'delete', + userId: Meteor.userId(), + userName: Meteor.user().profile.fullName, + collectionName: "Studies", + recordId: study.studyInstanceUid, + patientId: study.patientId, + patientName: study.patientName + }; + HipaaLogger.logEvent(hipaaEvent); }); // Find the Timepoint that was previously referenced @@ -64,13 +77,38 @@ function removeTimepointAssociations() { studyInstanceUids: timepoint.studyInstanceUids } }); + + // Log + var hipaaEvent = { + eventType: 'modify', + userId: Meteor.userId(), + userName: Meteor.user().profile.fullName, + collectionName: "Timepoints", + recordId: study.timepointId, + patientId: study.patientId, + patientName: study.patientName + }; + HipaaLogger.logEvent(hipaaEvent); } else { // If no more Studies are associated with this Timepoint, we should remove it // from the Timepoints Collection via a server call Meteor.call('removeTimepoint', timepoint._id, function(error) { if (error) { log.warn(error); + return; } + + // Log + var hipaaEvent = { + eventType: 'delete', + userId: Meteor.userId(), + userName: Meteor.user().profile.fullName, + collectionName: "Timepoints", + recordId: study.timepointId, + patientId: study.patientId, + patientName: study.patientName + }; + HipaaLogger.logEvent(hipaaEvent); }); } }); @@ -81,10 +119,10 @@ function removeTimepointAssociations() { */ function exportSelectedStudies() { var selectedStudies = WorklistSelectedStudies.find({}, { - sort: { - studyDate: 1 - } - }).fetch(); + sort: { + studyDate: 1 + } + }).fetch(); if (!selectedStudies) { return; @@ -98,10 +136,10 @@ function exportSelectedStudies() { */ function viewStudies() { var selectedStudies = WorklistSelectedStudies.find({}, { - sort: { - studyDate: 1 - } - }).fetch(); + sort: { + studyDate: 1 + } + }).fetch(); if (!selectedStudies) { return; diff --git a/Packages/lesiontracker/package.js b/Packages/lesiontracker/package.js index d1647ae86..cc905a869 100644 --- a/Packages/lesiontracker/package.js +++ b/Packages/lesiontracker/package.js @@ -156,6 +156,8 @@ Package.onUse(function(api) { api.addFiles('client/components/confirmRemoveTimepointAssociation/confirmRemoveTimepointAssociation.html', 'client'); api.addFiles('client/components/confirmRemoveTimepointAssociation/confirmRemoveTimepointAssociation.js', 'client'); + api.addFiles('client/components/lastLoginModal/lastLoginModal.html', 'client'); + api.addFiles('client/components/lastLoginModal/lastLoginModal.js', 'client'); // Server functions api.addFiles('server/collections.js', 'server'); diff --git a/Packages/lesiontracker/server/reviewers.js b/Packages/lesiontracker/server/reviewers.js index 2ed801c47..27aa79d84 100644 --- a/Packages/lesiontracker/server/reviewers.js +++ b/Packages/lesiontracker/server/reviewers.js @@ -45,6 +45,18 @@ Meteor.methods({ } else { Reviewers.insert({timepointId: timepoint.timepointId, reviewers: [{userId: user._id, userName: user.profile.fullName}]}); } + + // Log + var hipaaEvent = { + eventType: 'modify', + userId: Meteor.userId(), + userName: Meteor.user().profile.fullName, + collectionName: "Measurements", + recordId: study.studyInstanceUid, + patientId: study.patientId, + patientName: study.patientName + }; + HipaaLogger.logEvent(hipaaEvent); }, removeReviewer: function (timepointId) { @@ -70,6 +82,10 @@ Meteor.methods({ Reviewers.find().map(function (timepoint) { Reviewers.update({_id: timepoint._id}, {"$pull": {"reviewers": {"userId": userId}}}); }); + }, + + getPriorLoginDate: function() { + return Meteor.users.findOne({_id: Meteor.userId()}, {fields: {priorLoginDate: 1}}); } }); From 2a89e5c7e29398826f150ced0161accd5c885a23 Mon Sep 17 00:00:00 2001 From: Aysel Afsar Date: Wed, 6 Apr 2016 14:48:21 -0400 Subject: [PATCH 2/2] LT-61: Search by User Name, Patient Name, Study InstanceUid and Action Type --- .../hipaaAuditLog/hipaaAuditLog.html | 20 ++++++------ .../components/hipaaAuditLog/hipaaAuditLog.js | 31 ++++++++++++++++++- .../hipaaAuditLog/hipaaAuditLog.less | 4 +++ .../components/hipaaRibbon/hipaaRibbon.html | 14 ++++++--- .../components/hipaaRibbon/hipaaRibbon.js | 8 ++++- .../components/hipaaRibbon/hipaaRibbon.less | 11 +++++++ 6 files changed, 71 insertions(+), 17 deletions(-) diff --git a/Packages/hipaa-audit-log/components/hipaaAuditLog/hipaaAuditLog.html b/Packages/hipaa-audit-log/components/hipaaAuditLog/hipaaAuditLog.html index 9ea39789f..e24036ac0 100644 --- a/Packages/hipaa-audit-log/components/hipaaAuditLog/hipaaAuditLog.html +++ b/Packages/hipaa-audit-log/components/hipaaAuditLog/hipaaAuditLog.html @@ -16,7 +16,7 @@
{{entryTimestamp}}
- {{userName}} initialized HIPAA Audit Log. + {{userName}} initialized HIPAA Audit Log.
@@ -60,7 +60,7 @@
{{entryTimestamp}}
{{#if hasPatientInfo}} - {{userName}} modified record {{getRecordId}} in the {{getCollectionName}} collection, regarding patient {{getPatientName}}. + {{userName}} modified record {{getRecordId}} in the {{getCollectionName}} collection, regarding patient {{getPatientName}}. {{else}} {{userName}} modified record {{getRecordId}} in the {{getCollectionName}} collection. {{/if}} @@ -77,7 +77,7 @@
{{entryTimestamp}}
- {{userName}} cloned record {{getRecordId}} in the {{getCollectionName}} collection. + {{userName}} cloned record {{getRecordId}} in the {{getCollectionName}} collection.
@@ -90,7 +90,7 @@
{{entryTimestamp}}
- {{userName}} deleted record {{getRecordId}} in the {{getCollectionName}} collection. + {{userName}} deleted record {{getRecordId}} in the {{getCollectionName}} collection.
@@ -103,7 +103,7 @@
{{entryTimestamp}}
- {{userName}} was denied access to record {{getRecordId}} in the {{getCollectionName}} collection. + {{userName}} was denied access to record {{getRecordId}} in the {{getCollectionName}} collection.
@@ -117,9 +117,9 @@
{{entryTimestamp}}
{{#if hasPatientInfo}} - {{userName}} viewed record {{getRecordId}} in the {{getCollectionName}} collection, regarding patient {{getPatientName}}. + {{userName}} viewed record {{getRecordId}} in the {{getCollectionName}} collection, regarding patient {{getPatientName}}. {{else}} - {{userName}} viewed record {{getRecordId}} in the {{getCollectionName}} collection. + {{userName}} viewed record {{getRecordId}} in the {{getCollectionName}} collection. {{/if}}
@@ -133,7 +133,7 @@
{{entryTimestamp}}
- {{userName}} published record {{getRecordId}} in the {{getCollectionName}} collection. + {{userName}} published record {{getRecordId}} in the {{getCollectionName}} collection.
@@ -146,7 +146,7 @@
{{entryTimestamp}}
- {{userName}} unpublished record {{getRecordId}} in the {{getCollectionName}} collection. + {{userName}} unpublished record {{getRecordId}} in the {{getCollectionName}} collection.
@@ -159,7 +159,7 @@
{{entryTimestamp}}
- {{userName}} received a data access error while performing an action!
+ {{userName}} received a data access error while performing an action!
{{getErrorMessage}}
diff --git a/Packages/hipaa-audit-log/components/hipaaAuditLog/hipaaAuditLog.js b/Packages/hipaa-audit-log/components/hipaaAuditLog/hipaaAuditLog.js index acd458a9b..ffe8fc177 100644 --- a/Packages/hipaa-audit-log/components/hipaaAuditLog/hipaaAuditLog.js +++ b/Packages/hipaa-audit-log/components/hipaaAuditLog/hipaaAuditLog.js @@ -20,7 +20,7 @@ Template.hipaaAuditLog.helpers({ }, hipaaAudit: function () { // return HipaaLog.find(); - return HipaaLog.find({ + return HipaaLog.find({ $or: [ { userName: { @@ -33,6 +33,18 @@ Template.hipaaAuditLog.helpers({ $regex: Session.get('hipaaSearchFilter'), $options: 'i' } + }, + { + recordId: { + $regex: Session.get('hipaaSearchFilter'), + $options: 'i' + } + }, + { + collectionName: { + $regex: Session.get('hipaaSearchFilter'), + $options: 'i' + } } ], eventType: { @@ -54,6 +66,23 @@ Template.hipaaAuditLog.helpers({ Template.hipaaAuditLog.events({ "keyup #hipaaSearchFilter": function (event, template) { Session.set("hipaaSearchFilter", $('#hipaaSearchFilter').val()); + }, + "click .userName": function(event, template) { + var userName = $(event.currentTarget).text(); + Session.set('hipaaSearchFilter', userName); + }, + "click .patientName": function(event, template) { + var patientName = $(event.currentTarget).text(); + var patientNameRegex = patientName.replace(/[!@#$%^&*()+=\-[\]\\';,./{}|":<>?~_]/g, "\\$&"); + Session.set('hipaaSearchFilter', patientNameRegex); + }, + "click .mongoRecordId": function(event, template) { + var mongoRecordId = $(event.currentTarget).text(); + Session.set('hipaaSearchFilter', mongoRecordId); + }, + "click .collectionName": function(event, template) { + var collectionName = $(event.currentTarget).text(); + Session.set('hipaaSearchFilter', collectionName); } }); diff --git a/Packages/hipaa-audit-log/components/hipaaAuditLog/hipaaAuditLog.less b/Packages/hipaa-audit-log/components/hipaaAuditLog/hipaaAuditLog.less index 0d902457e..e600a3850 100644 --- a/Packages/hipaa-audit-log/components/hipaaAuditLog/hipaaAuditLog.less +++ b/Packages/hipaa-audit-log/components/hipaaAuditLog/hipaaAuditLog.less @@ -70,5 +70,9 @@ width: 100%; } + .userName, .patientName, .mongoRecordId, .collectionName{ + cursor: pointer; + } + } diff --git a/Packages/hipaa-audit-log/components/hipaaRibbon/hipaaRibbon.html b/Packages/hipaa-audit-log/components/hipaaRibbon/hipaaRibbon.html index c0a38264a..076b5e387 100644 --- a/Packages/hipaa-audit-log/components/hipaaRibbon/hipaaRibbon.html +++ b/Packages/hipaa-audit-log/components/hipaaRibbon/hipaaRibbon.html @@ -1,13 +1,17 @@