Updates
This tutorials describes components needed for downloading updates to submissions from the IRB Exchange.
What's Involved
- Adding an attribute to flag whether a submission can receive updates from the IRB Exchange.
- Adding a method to download updates from the IRB Exchange.
- Adding an activity that triggers downloading updates from the IRB Exchange that is called by code for automatic updates.
- Adding a Site Designer page to handle client side logic involved in downloading updates from the IRB Exchange.
- Adding an activity that triggers downloading updates from the IRB Exchange.
- Creating or modifying the Correspond with sIRB and Correspond with pSite activities to upload most recent data to IRB Exchange.
- Modifying the Assign PI Proxy activity to upload updates to IRB Exchange.
- Modifying the Assign Primary Contact activity to upload updates to the IRB Exchange.
- Modifying the Change Tracking activity to upload updates to the IRB Exchange for sites and site modifications on the participating site side.
- Modifying the Send Letter activity to upload updates to the IRB Exchange for studies and reportable new information action plans.
- Modifying the Submit Response activity to upload updates to the IRB Exchange for studies and site modifications.
- Modifying the Send Letter state transitions to upload updates to the IRB Exchange for studies and site modifications.
- Modifying the Pre-Review -> Pending sIRB Review state transition to upload updates to the IRB Exchange for sites and reportable new information submissions.
Adding the Attribute
Create a Boolean attribute on the IRB Submission
project type called Can Download Exchange Updates
. The default value should be false
.
Download Updates Method
This method should download the updates from the IRB Exchange and return JSON metadata about documents to download.
_IRBSubmission.downloadExchangeUpdates Method Example
/** Download updates for submissions from the IRB Exchange.
*
* @param sch (Scripting Context Helper)
* @param activity (_IRBSubmission_UpdateFromIRBExchangeAdmin) Update from IRB Exchange (Admin) activity used for auto-updates
*/
function downloadExchangeUpdates(sch, activity)
{
var isExchangeEnabled = _IRBSettings.getIRBSettings().getQualifiedAttribute("customAttributes.isIRBExchangeEnabled");
if(isExchangeEnabled == true) {
var accountName = this.getQualifiedAttribute("customAttributes.IRB.customAttributes.iRBExchangeAccountName");
if(accountName != null) {
var exchange = ClickIRBUtils.getExchangeClient(accountName);
// Set a session variable that controls auto-updates to every 30 minutes
var now = new Date().getTime();
var sessionContext = wom.getSessionContext();
sessionContext.putContextObject(this.ID + "-LastExchangeUpdate", now, true);
var submissionTypeId = this.getQualifiedAttribute("customAttributes.submissionType.ID");
var submissionId = this.getQualifiedAttribute("ID");
if (submissionTypeId == null) {
throw new Error("Submission type not set for study with ID " + submissionId);
}
var isExternal = this.getQualifiedAttribute("customAttributes.externalIRBInvolved");
if(isExternal == true) {
submissionTypeId = "STUDY";
}
var updatedJson, study, documentsToDownload;
var isSubscribedToProfileData = ClickIntegrationUtils.IsStoreSubscribedToPublication("ProfileData");
switch (submissionTypeId) {
case "STUDY":
var exchangeRef = IrbExchangeReference.GetEntityForPoRef(this + "", exchange + "");
if (exchangeRef != null) {
updatedJson = exchange.GetStudy(this, null);
documentsToDownload = this.setFromMssJson(JSON.parse(updatedJson, null), exchange, null, null, isSubscribedToProfileData, activity);
study = this;
// Set the version increment activity to null to suppress storing changes for study for now until we finalize new design for external IRB
if(documentsToDownload.length > 0) {
documentsToDownload[0].incrementMinorVersionActivity = activity + "";
}
}
break;
case "IRBSITE":
study = this.getSiteMss();
if (sch.currentEntity.getType() == "_IRBSubmission_DownloadModFromExchange") {
documentsToDownload = this.createSiteModFromExchange(sch);
} else {
if (this.status.ID != "Invitation Pending") {
var pSiteIp = this.getQualifiedAttribute("customAttributes.mSSPSiteInstitutionalProfile");
var sIrbSideParticipantId = pSiteIp.getExchangeId();
if (sIrbSideParticipantId != null) {
updatedJson = exchange.GetSite(study, sIrbSideParticipantId);
if (updatedJson != null) {
documentsToDownload = this.setFromSiteJson(JSON.parse(updatedJson, null), exchange, activity);
// Stuff the Update from IRB Exchange activity instance poRef into the JSON so that the remote methods that download the documents can update the changeTrackingInfo (XML)
if(documentsToDownload.length > 0) {
documentsToDownload[0].incrementMinorVersionActivity = activity + "";
}
}
}
}
}
break;
case "RNI":
// If pSite RNI, then check for updates to action plan and responsible party
var relatedStudies = this.getQualifiedAttribute("customAttributes.reportableNewInformation.customAttributes.relatedStudies");
if (relatedStudies == null) {
return;
}
relatedStudies = relatedStudies.elements();
if (relatedStudies.count == 0) {
return;
}
var study = this.getQualifiedAttribute("customAttributes.reportableNewInformation.customAttributes.relatedStudies").elements().item(1);
var isSirbExternal = this.getQualifiedAttribute("customAttributes.reportableNewInformation.customAttributes.isSIRBExternal");
var containerRef = IrbExchangeReference.GetEntityForPoRef(study + "", exchange + "");
var rniRef = IrbExchangeReference.GetEntityForPoRef(this + "", exchange + "");
if (containerRef != null && rniRef != null && isSirbExternal == true) {
// Get RNI action plan item from Exchange
this.setFromRniActionPlanJson(study, exchange, activity);
}
// If sIRB RNI downloaded from Exchange, check for updates to it
var createdByPsite = this.getQualifiedAttribute("customAttributes.reportableNewInformation.customAttributes.createdByPSite");
if (createdByPsite == true) {
var rniJsonStr = exchange.GetRni(study, null, this, null);
var rniJson = JSON.parse(rniJsonStr, null);
documentsToDownload = this.setFromRniJson(rniJson, study, exchange, activity);
// If new action response, log the activity
documentsToDownload = this.setFromRniActionResponseJson(study, rniJson, exchange, sch, documentsToDownload);
}
break;
case "MOD":
var externalStatusId = this.getQualifiedAttribute("customAttributes.externalStudyStatus");
var statusId = this.status.ID;
var isStudyExternal = this.getQualifiedAttribute("customAttributes.parentStudy.customAttributes.mSSStudy.customAttributes.externalIRBInvolved");
if (isStudyExternal == false && externalStatusId != "Discarded" && externalStatusId != "Approved" && statusId != "Discarded" && statusId != "Approved" && IrbExchangeReference.GetExchangeId(this + "", exchange + "", null) != null) {
study = this.getQualifiedAttribute("customAttributes.parentStudy.customAttributes.mSSStudy");
if (study == null) {
throw new Error("MSS unexpectedly null for site mod with ID " + this.ID);
}
var pSiteIp = this.getQualifiedAttribute("customAttributes.parentStudy.customAttributes.mSSPSiteInstitutionalProfile");
var participantId = pSiteIp.getExchangeId();
updatedJson = exchange.GetSiteMod(study, participantId, this);
if (updatedJson != null) {
var draft = this.getQualifiedAttribute("customAttributes.draftStudy");
if (draft == null) {
throw new Error("Draft unexpectedly null for site mod with ID " + this.ID);
}
var activityToPassIn = null;
if(activity.getType() != "_IRBSubmission_DownloadModFromExchange") {
activityToPassIn = activity;
}
documentsToDownload = draft.setFromSiteModJson(JSON.parse(updatedJson, null), this, exchange, activityToPassIn);
// Increment minor version on draft
var incrementMinorVersionActivity = this.incrementVersionOnDraftStudy(sch, sch.user, "minor", this.ID + ": Updated from IRB Exchange");
// Stuff the Increment Minor Version activity instance poRef into the JSON so that the remote methods that download the documents can update the changeTrackingInfo (XML)
if(documentsToDownload.length > 0) {
documentsToDownload[0].incrementMinorVersionActivity = incrementMinorVersionActivity + "";
}
}
}
break;
default:
throw new Error("Unexpected submission type encountered for submission with ID " + submissionId + "; submission type ID = " + submissionTypeId);
break;
}
var htmlArray = [];
htmlArray.push('<script type="text/JavaScript" language="JavaScript">');
htmlArray.push('var documentsToDownloadArray = \'' + JSON.stringify(documentsToDownload, null, null) + '\';');
htmlArray.push('var study = "' + study + '";');
htmlArray.push('jQuery(function(){');
htmlArray.push('if(typeof downloadDocumentsScope == "undefined") downloadDocumentsScope = {};});');
htmlArray.push('</script>');
sessionContext.putContextObject(this.ID + "-DocUpdatesData", htmlArray.join("\n"), true);
if(activity.getType() != "_IRBSubmission_DownloadModFromExchange") {
if (activity.getQualifiedAttribute("customAttributes.hasDataChanged") == true) {
// If the data was changed by the update, then increment the SmartForm version and add a version description
var incrementVersionActivity = ActivityType.getActivityType("_IRBSubmission_IncrementMinorVersion", "_IRBSubmission");
var versionIncrement = this.logActivity(sch, incrementVersionActivity, Person.getCurrentUser());
versionIncrement.versionDescription = "Updated from IRB Exchange";
} else {
// If the data was not changed by the update, then change the completed activity name and add a note to the notesAsStr to indicate such
activity.name = "Checked for updates from IRB Exchange";
activity.notesAsStr = "No changes found";
}
}
}
}
}
Technical notes about this method example:
- The logic is broken up by submission type.
- For studies, the
IrbExchange.GetStudy
method is called to get the study data from the IRB Exchange. It calls the_IRBSubmission.setFromMssJson
method to parse the JSON and set the data. It only runs if the study was downloaded from the IRB Exchange. - For sites, it checks to see if it's on the sIRB or pSite side. For the sIRB side, it calls the
IrbExchange.GetSite
method to get the site data and then calls the_IRBSubmission.setFromSiteJson
method to parse the JSON and set the data. For the pSite side, it calls theIrbExchange.GetSiteSirbReviewDates
method to get the sIRB review dates and calls the_IRBSubmission.setFromSiteSirbReviewDatesJson
method to parse the JSON and set the data. If theDownload Mod from IRB Exchange
activity triggered this call, then it's downloading a site modification for the first time. For more information see the Site Modifications tutorial. - For reportable new information submissions, it checks to see if it's on the sIRB or pSite side. For the pSite side, it will call the
_IRBSubmission.setFromRniActionPlanJson
method to get the action plan JSON from the IRB Exchange and then parse and set it. For the sIRB side, it will call theIrbExchange.GetRni
method to get the reportable new information data from the IRB Exchange. The_IRBSubmission.setFromRniJson
method is called to parse and set the data on the reportable new information submission. The_IRBSubmission.setFromRniActionResponseJson
method is called to check for and download any action response for the reportable new information submission. - For site modifications on the sIRB side, it calls the
IrbExchange.GetSiteMod
method to get the site modification data from the IRB Exchange. Then it calls the_IRBSubmission.setFromSiteModJson
method to parse and set the data. This happens only if both site modifications on both sides are not in the Discarded or Approved states. - This method finishes by pushing a client side script into a session variable that contains metadata that is later used to download documents.
Create the Automatic Update Activity
Create an activity on the IRB Submission
project type called Update from IRB Exchange (Admin)
. It needs to have execute security open to all Registered Users. It needs a post-processing script to download the updates from the IRB Exchange
Update from IRB Exchange (Admin) Post-Processing Script Example
targetEntity.downloadExchangeUpdates(sch, activity);
sch.redirectClientBrowser(sch.urlWithQueryString(null));
Technical notes about this script example:
- The
_IRBSubmission.downloadExchangeUpdates
method is called to start the work of pulling the updates from the IRB Exchange. - When finished, it refreshes the page to update any data displays whose sources might have changed as part of the update.
Download Update Site Designer Page
This Site Designer page implements logic to run the overall update that is downloaded from the IRB Exchange. It is located at Applications:\IRB\Exchange\DownloadUpdate
.
It consists of the following controls in the following order:
- Page.preRender script (see example below)
- Server Side Script (see example below)
- Client Side Script (see example below)
Page.preRender Script Example
function prerender(sch){
var isExchangeEnabled = _IRBSettings.getIRBSettings().getQualifiedAttribute("customAttributes.isIRBExchangeEnabled");
var submission = wom.getEntityFromString(wom.getContext("thisResource"));
var canDownloadExchangeUpdates = submission.getQualifiedAttribute("customAttributes.canDownloadExchangeUpdates");
if (isExchangeEnabled && canDownloadExchangeUpdates) {
return true;
}
return false;
}
Technical note about this script example:
- The attribute
customAttributes.canDownloadExchangeUpdates
is used to track whether the submission is eligible to receive updates from the IRB Exchange. The updates will occur if this attribute is set totrue
.
Server Side Script Example
function generateHtml(sch){
var submission = wom.getEntityFromString(wom.getContext("thisResource"));
var docUpdatesData = sch.getSessionVariable(submission.ID + "-DocUpdatesData");
var lastUpdate = sch.getSessionVariable(submission.ID + "-LastExchangeUpdate");
var now = new Date().getTime();
var canEdit = submission.getQualifiedAttribute("status.customAttributes.canEdit");
if (docUpdatesData != null && docUpdatesData != "") {
// Activity to download updates just ran, so download the docs it specified
var sessionContext = wom.getSessionContext();
sessionContext.putContextObject(submission.ID + "-DocUpdatesData", null, true);
return docUpdatesData;
} else if ((lastUpdate == "" || (now - lastUpdate > 1800000))) {
// There hasn't been an auto-update this session or it's been 30 minutes or more, so auto-update again
var updateFromExchangeAdminActivity = ActivityType.getActivityType("_IRBSubmission_UpdateFromIRBExchangeAdmin", "_IRBSubmission");
submission.logActivity(sch, updateFromExchangeAdminActivity, Person.getCurrentUser());
}
return "";
}
Technical notes about this script example:
- Session variables are used to control when automatic updates happen. Generally, automatic updates occur every 30 minutes on visit to the submission workspace, or if the user just logged in and visited the workspace. Also, automatic updates only occur in editable states, however this does not prevent updates from being requested manually in certain states.
- Automatic updates execute the
Update from IRB Exchange (Admin)
activity to start the update process.
Client Side Script Example
//study will be defined only if there was a page refresh following an update by Exchange
if (typeof study !== "undefined"){
var downloadDocumentsDetails = {};
downloadDocumentsDetails["study"] = study;
jQuery(function(){
downloadDocumentsScope.handleFailure = function(jqXHR, data, textStatus){
var errorString = '<p class="Error">Error thrown attempting to download documents from IRB Exchange. Please contact a system administrator. ' + data + "</p>";
jQuery('#ActivityMessageField').append(errorString);
jQuery("body").css("cursor", "default");
}
downloadDocumentsScope.handleChangeTrackingFailure = function(jqXHR, data, textStatus){
var errorString = '<p class="Error">Error thrown attempting to add change tracking info. Please contact a system administrator. ' + data + "</p>";
jQuery('#ActivityMessageField').append(errorString);
jQuery("body").css("cursor", "default");
}
downloadDocumentsScope.downloadDocument = function(index){
downloadDocumentsDetails["documentPoRef"] = downloadDocumentsScope.documentsToDownload[index].document;
downloadDocumentsDetails["entity"] = downloadDocumentsScope.documentsToDownload[index].entity;
downloadDocumentsDetails["attributePath"] = downloadDocumentsScope.documentsToDownload[index].attributePath;
downloadDocumentsDetails["exchangeRef"] = downloadDocumentsScope.documentsToDownload[index].exchangeRef;
downloadDocumentsDetails["updateFromExchangeActivity"] = downloadDocumentsScope.documentsToDownload[index].updateFromExchangeActivity;
PortalTools.callRemoteMethod("ClickIRBRemoteMethods.downloadDocument", JSON.stringify(downloadDocumentsDetails)).done(function(data, textStatus, jqXHR){
if(data != "success"){
downloadDocumentsScope.handleFailure(jqXHR, data, textStatus);
return;
}
downloadDocumentsScope.downloadedCount++;
jQuery('#CurrentDocument').html('downloaded ' + downloadDocumentsScope.downloadedCount + ' of ' + downloadDocumentsScope.documentsToDownload.length + ' ... ');
// Downloaded all documents, add change tracking info
if(downloadDocumentsScope.downloadedCount == downloadDocumentsScope.documentsToDownload.length){
PortalTools.callRemoteMethod("ClickIRBRemoteMethods.addChangeTrackingInfo", JSON.stringify(downloadDocumentsScope.documentsToDownload)).done(function(data, textStatus, jqXHR){
// Handle failure
if(data != "success"){
downloadDocumentsScope.handleFailure(jqXHR, data, textStatus);
return;
}
// Return cursor to normal and close dialog
jQuery("body").css("cursor", "default");
window.location = window.location;
}).fail(downloadDocumentsScope.handleChangeTrackingFailure);
}
}).fail(downloadDocumentsScope.handleFailure);
}
downloadDocumentsScope.documentsToDownload = JSON.parse(documentsToDownloadArray, null);
var documentsToDownloadCount = 0;
if(downloadDocumentsScope.documentsToDownload != null) {
documentsToDownloadCount = downloadDocumentsScope.documentsToDownload.length;
};
if(documentsToDownloadCount > 0){
var overlay = jQuery('<div id="ActivityOverlay"></div>');
overlay.appendTo(document.body);
var messageText = '<strong>' + documentsToDownloadCount + ' documents still to be downloaded</strong>';
var messageOverlay = jQuery('<div id="ActivityMessageOverlay"><div id="ActivityMessageField">' + messageText + '<p id="CurrentDocument"></p></div></div>');
messageOverlay.appendTo(document.body);
jQuery("body").css("cursor", "progress");
downloadDocumentsScope.downloadedCount = 0;
for(var i = 0; i < documentsToDownloadCount; i++) {
downloadDocumentsScope.downloadDocument(i);
}
}
});
}
Technical notes about this script example:
- This is essentially the same document download code as on the download page.
Update from IRB Exchange Activity
Create an activity on IRB Submission
project type called Update from IRB Exchange
. Create a Boolean attribute called Ready to Update
. The post-processing script should initiate the operations to download the updates from the IRB Exchange.
Update from IRB Exchange Post-Processing Script Example
// Affirmation to download
var readyToUpdate = activity.getQualifiedAttribute("customAttributes.readyToUpdate");
if(readyToUpdate != true){
targetEntity.reportFieldError("_IRBSubmission_UpdateFromIRBExchange.customAttributes.readyToUpdate","You must confirm the downloading of the latest site details from the IRB Exchange");
throw new Error(-1, "There were errors...see below.");
}
targetEntity.downloadExchangeUpdates(sch, activity);
Technical notes about this script example:
- We use the
Ready to Update
attribute, that we have also exposed on the activity form, to control whether the update occurs. - The
_IRBSubmission.downloadExchangeUpdates
method is called to start the update operations.
Uploading Updates with Correspondence
In our workflow, submissions are uploaded to the IRB Exchange at discrete points in the workflow, but when it is desired to upload updates on demand from the participating site side, the standard approach is to execute Correspond with sIRB.
For this, the Correspond with sIRB activity should have a Boolean attribute called Should Update
and it should be bound to the activity form. The post-processing script should check for this value and if set to true
, upload the latest data to the IRB Exchange.
Correspond with sIRB Post-Processing Script Example
activity.onCorrespondWithSirb(targetEntity);
Correspond with sIRB onCorrespondWithSirb Method Example
/** Handle post-processing logic for the activity.
*
* @param targetEntity (_IRBSubmission) The pSite or external RNI being run from
*/
function onCorrespondWithSirb(targetEntity) {
var shouldUpdate = this.getQualifiedAttribute("customAttributes.shouldUpdate");
var irbExchangePoRef = targetEntity.getIrbExchangePoRef();
if(shouldUpdate == true && irbExchangePoRef != null) {
targetEntity.uploadToExchange(null);
}
}
Technical notes about this script example:
- Updates are uploaded to the IRB Exchange only if the submission has previously been uploaded to the IRB Exchange.
- The
_IRBSubmission.uploadToExchange
method is called to start the update upload process.
Assign PI Proxy
PI proxies data are transferred over the IRB Exchange, so the Assign PI Proxy activity that manages them must push updates to the IRB Exchange.
Assign PI Proxy Post-Processing Script Example
activity.onAssignPiProxy(targetEntity);
Assign PI Proxy onAssignPiProxy Method Example
/** Handle post-processing logic for the activity, which involves potential security updates and uploading to Exchange.
*
* @param targetEntity (_IRBSubmission) The submission the PI proxy is being assigned to
*/
function onAssignPiProxy(targetEntity) {
var submissionType = targetEntity.getQualifiedAttribute("customAttributes.submissionType.ID");
var isExternal = targetEntity.getQualifiedAttribute("customAttributes.externalIRBInvolved");
var isMSS = targetEntity.getQualifiedAttribute("customAttributes.isMSS");
// Upload to exchange if MSS study or pSite
if((submissionType == "STUDY" && isMSS == true) || isExternal == true) {
targetEntity.uploadToExchange(null);
}
// If a sIRB Site, anyone could be PI Proxy so need to add them to readers and editors sets and to study's readers set
var mSSStudy = targetEntity.getQualifiedAttribute("customAttributes.mSSStudy");
if(submissionType == "IRBSITE" && mSSStudy != null){
mSSStudy.updateReadersAndEditors(targetEntity);
}
}
Technical notes about this script snippet example:
- Updates are uploaded if the submission is a multi-site study or a site on the participating site side.
- The
_IRBSubmission.uploadToExchange
method is called to start the upload operation.
Assign Primary Contact
Primary contact data is transferred over the IRB Exchange, so the Assign Primary Contact activity that manages them must push updates to the IRB Exchange.
Assign Primary Contact Post-Processing Script Snippet Example
targetEntity.handlePrimaryContact(activity);
IRB Submission handlePrimaryContact Method Example
/** handles the setting of a primary contact for a submission
@param activity (Activity) - the activity this was called from
**/
function handlePrimaryContact(activity)
{
var primaryContactName = this.getQualifiedAttribute("customAttributes.primaryContact.customAttributes.studyTeamMember");
activity.notesAsStr = "Assigned primary contact " + primaryContactName.fullName();
// If this is a sIRB site, the primary contact needs to go into the MSS editors set too
var mssStudy = this.getQualifiedAttribute("customAttributes.mSSStudy");
if(mssStudy != null){
mssStudy.updateReadersAndEditors(this);
} else { // All other submission types
this.updateReadersAndEditors(null);
}
// If external MSS or MSS, then see if we need to upload to Exchange
var studyTypeId = this.getQualifiedAttribute("customAttributes.studyType.ID");
var isExternal = this.getQualifiedAttribute("customAttributes.externalIRBInvolved");
var submissionType = this.getQualifiedAttribute("customAttributes.submissionType.ID");
var isMSS = this.getQualifiedAttribute("customAttributes.isMSS");
if((studyTypeId == "00000001" && isExternal == true) || (submissionType == "STUDY" && isMSS == true)) {
this.uploadToExchange(null);
}
// Primary contact can be changed after approval, so update read rights on any RNIs this submission is related to
var reviewStage = this.status.getQualifiedAttribute("customAttributes.reviewStage");
if (submissionType == "STUDY" && reviewStage != "review"){
this.updateRelatedRNIReaders();
}
}
Technical notes about this script snippet example:
- Updates are uploaded for multi-site studies and sites on the participating site side.
- The
_IRBSubmission.uploadToExchange
method is called to start the upload operation.
Change Tracking Activity
The Change Tracking activity is leveraged for uploading changes to sites and site modifications on the participating site side as they occur in the Pending sIRB Review
state. The post-processing should be set to facilitate this.
Change Tracking Activity Post-Processing Script Example
activity.onChangeTracking(targetEntity);
Change Tracking Activity onChangeTracking Method Example
/** Called by post-processing script of Change Tracking activity to upload changes to IRB Exchange for submissions in Pending sIRB Review that are connected to Exchange.
*
* @param targetEntity (_IRBSubmission) The submission changes were made on
*/
function onChangeTracking(targetEntity)
{
var stateId = targetEntity.getQualifiedAttribute("status.ID");
var isParentMSS = targetEntity.getQualifiedAttribute("customAttributes.parentStudy.customAttributes.isMSS");
var submissionTypeId = targetEntity.getQualifiedAttribute("customAttributes.submissionType.ID");
// Initial pSite
if(stateId == "Pending sIRB Review" && submissionTypeId == "IRBSITE") {
targetEntity.uploadToExchange(null);
}
// pSite mod draft
if(isParentMSS == true && (submissionTypeId == "DRAFT" || submissionTypeId == "MOD")) {
var siteMod = null;
if(submissionTypeId == "DRAFT") {
var siteMods = ApplicationEntity.getResultSet("_IRBSubmission").query("customAttributes.draftStudy = " + targetEntity + " and status.ID != 'Approved' and status.ID != 'Discarded' and customAttributes.submissionType.ID = 'MOD' and customAttributes.modificationDetails.customAttributes.modificationTypes.*.ID = 'FULL_SITE_NO_STAFF'");
if(siteMods.elements().count() == 1) {
siteMod = siteMods.elements().item(1);
}
} else {
siteMod = targetEntity;
}
if(siteMod != null) {
var siteModStateId = siteMod.getQualifiedAttribute("status.ID");
if (siteModStateId == "Pending sIRB Review") {
// Upload the site to the Exchange if connected
siteMod.uploadToExchange(null);
}
}
}
}
Technical note about this script example:
- The
_IRBSubmission.uploadToExchange
method is called to start the upload operation.
Send Letter Activity
The Send Letter activity post-processing needs to be modified to upload updates to the IRB Exchange for certain scenarios.
Send Letter Activity Post-Processing Script Snippet Example
targetEntity.sendLetter(activity);
IRB Submission sendLetter Method Example
/** Set letter on activity, calculate performance metrics, and handle exchange actions
@param activity (Activity) - the calling activity
**/
function sendLetter(activity)
{
var letter = this.getQualifiedAttribute("customAttributes.irbCorrespondenceLetter.customAttributes.finalVersion");
if(letter != null){
var historicalLetter = EntityCloner.quickClone(letter);
activity.setQualifiedAttribute("documents", historicalLetter, "add");
}
var submissionType = this.getQualifiedAttribute("customAttributes.submissionType.ID");
var determination = this.getQualifiedAttribute("customAttributes.irbDetermination.ID");
var determinationName = this.getQualifiedAttribute("customAttributes.irbDetermination.customAttributes.name");
if(submissionType != "RNI" && submissionType != "IRBSITE"){
var isFinalDetermination = (determination == "APPROVED" ||
determination == "HR_NOT_ENGAGED" ||
determination == "NOT_HR" ||
determination == "DISAPPROVED");
// Calculate performance metrics
var millisecondsFromPreReviewToInitialDetermination = this.getQualifiedAttribute("customAttributes.performanceMetrics.customAttributes.millisecondsFromPreReviewToInitialDetermination");
if(isFinalDetermination){
var millisecondsFromPreReviewToFinalDetermination = this.getQualifiedAttribute("customAttributes.performanceMetrics.customAttributes.millisecondsFromPreReviewToFinalDetermination");
if(millisecondsFromPreReviewToFinalDetermination == null || millisecondsFromPreReviewToFinalDetermination == 0){
var dateEnteredIRB = this.getQualifiedAttribute("customAttributes.dateEnteredIRB");
if(dateEnteredIRB != null){
var nowMilliseconds = (new Date()).getTime();
var enteredIRBMilliseconds = dateEnteredIRB.getTime();
timeToFinalDetermination = nowMilliseconds - enteredIRBMilliseconds;
this.setQualifiedAttribute("customAttributes.performanceMetrics.customAttributes.millisecondsFromPreReviewToFinalDetermination", timeToFinalDetermination);
if(millisecondsFromPreReviewToInitialDetermination == null || millisecondsFromPreReviewToInitialDetermination == 0){
this.setQualifiedAttribute("customAttributes.performanceMetrics.customAttributes.millisecondsFromPreReviewToInitialDetermination", timeToFinalDetermination);
}
}
}
} else if(millisecondsFromPreReviewToInitialDetermination == null || millisecondsFromPreReviewToInitialDetermination == 0){
var dateEnteredIRB = this.getQualifiedAttribute("customAttributes.dateEnteredIRB");
if(dateEnteredIRB != null){
var nowMilliseconds = (new Date()).getTime();
var enteredIRBMilliseconds = dateEnteredIRB.getTime();
timeToInitialDetermination = nowMilliseconds - enteredIRBMilliseconds;
this.setQualifiedAttribute("customAttributes.performanceMetrics.customAttributes.millisecondsFromPreReviewToInitialDetermination", timeToInitialDetermination);
}
}
}
// If RNI downloaded from Exchange and there is an action plan, then upload that to Exchange
var createdByPsite = this.getQualifiedAttribute("customAttributes.reportableNewInformation.customAttributes.createdByPSite");
var actionPlan = this.getQualifiedAttribute("customAttributes.reportableNewInformation.customAttributes.requiredAction");
if(createdByPsite == true && actionPlan != "" && actionPlan != null){
this.uploadRniActionPlanToExchange();
}
var isMSS = this.getQualifiedAttribute("customAttributes.isMSS");
if(isMSS == true && submissionType == "STUDY"){
var state = this.status.ID;
switch(state){
case "Approved":
case "Deferred":
case "Not Human Research":
case "Human Research, Not Engaged":
case "Disapproved":
case "Terminated":
case "Suspended":
case "Closed":
case "Lapsed":
this.uploadToExchange(null);
default:
// no-op
}
}
}
Technical notes about this script snippet example:
- The updates are uploaded if the submission is a reportable new information submission downloaded from the IRB Exchange and there is an action plan set or the submission is a multi-site study on the sIRB side in one of a number of post-approval or terminal states.
- The
_IRBSubmission.uploadRniActionPlanToExchange
method is called to start the upload process for reportable new information action plan. - The
_IRBSubmission.uploadToExchange
method is called to start the upload process for studies.
Submit Response Activity
The Submit Response activity post-processing should be modified to upload updates to the IRB Exchange for studies and site modifications in certain scenarios.
Submit Response Activity Post-Processing Script Example
targetEntity.handleSubmitResponse();
IRB Submission handleSubmitResponse Method Example
/** Create a snapshot and send to exchange if necessary
**/
function handleSubmitResponse()
{
var submissionType = this.getQualifiedAttribute("customAttributes.submissionType.ID");
var shouldUpload = false;
switch(submissionType){
case "STUDY":
var isMSS = this.getQualifiedAttribute("customAttributes.isMSS");
if(isMSS == true){
shouldUpload = true;
}
break;
case "IRBSITE":
shouldUpload = true;
break;
case "MOD":
case "MODCR":
case "CR":
wom.putContext("MODCRSnapshot", true, true);
// If pSite mod, upload to Exchange
var isMssExternal = this.getQualifiedAttribute("customAttributes.parentStudy.customAttributes.externalIRBInvolved");
var state = this.getQualifiedAttribute("status.ID");
if(isMssExternal == true && state != "Clarification Requested (Pre-Review)") {
shouldUpload = true;
}
break;
default:
// Do nothing
}
if(shouldUpload == true){
this.uploadToExchange(null);
}
this.generateIRBSnapshot(false);
}
Technical notes about this script snippet example:
- Updates are uploaded for multi-site studies and site modifications on the participating site side where the state is not
Clarification Requested (Pre-Review)
.
Send Letter State Transitions
The second post-processing script needs to be modified to upload updates for studies and site modifications under certain scenarios for the following state transitions:
- Post-Review -> Approved
- Post-Review -> Deferred
- Post-Review -> Disapproved
- Post-Review -> Human Research, Not Engaged
- Post-Review -> Modifications Required
- Post-Review -> Not Human Research
State Transition Second Post-Processing Script Example
// If pSite mod or MSS, upload to Exchange
var submissionType = targetEntity.getQualifiedAttribute("customAttributes.submissionType.ID");
var isPSiteMod = targetEntity.getQualifiedAttribute("customAttributes.parentStudy.customAttributes.isMSS");
var isMSS = targetEntity.getQualifiedAttribute("customAttributes.isMSS");
if((isPSiteMod == true && submissionType == "MOD") || (isMSS == true && submissionType == "STUDY")) {
targetEntity.uploadToExchange(null);
}
Technical note about this script example:
- Updates are uploaded for multi-site studies and site modifications on the participating site side.
Pre-Review -> Pending sIRB Review State Transition
The post-processing script needs to be modified to upload updates to the IRB Exchange.
Pre-Review -> Pending sIRB Review State Transition Post-Processing Script Example
targetEntity.onTransitionToPendingSirbReview();
IRB Submission onTransitionToPendingSirbReview Method Example
/** performs post-processing for state transition
called from the pre-review to pending sIRB Review state transition
*/
function onTransitionToPendingSirbReview(){
// If RNI, set the flag for external RNI review
var submissionTypeID = this.getQualifiedAttribute("customAttributes.submissionType.ID");
var studyTypeID = this.getQualifiedAttribute("customAttributes.parentStudy.customAttributes.studyType.ID");
if (submissionTypeID == "RNI"){
this.setQualifiedAttribute("customAttributes.reportableNewInformation.customAttributes.wasReviewedExternally", true);
this.uploadToExchange(null);
} else if (studyTypeID == "00000001"){ //00000001 is MSS submission
this.uploadToExchange(null);
}
}