IRB Exchange

Show / Hide Table of Contents

Updates

This tutorials describes components needed for downloading updates to submissions from the IRB Exchange.

What's Involved

  1. Adding an attribute to flag whether a submission can receive updates from the IRB Exchange.
  2. Adding a method to download updates from the IRB Exchange.
  3. Adding an activity that triggers downloading updates from the IRB Exchange that is called by code for automatic updates.
  4. Adding a Site Designer page to handle client side logic involved in downloading updates from the IRB Exchange.
  5. Adding an activity that triggers downloading updates from the IRB Exchange.
  6. Creating or modifying the Correspond with sIRB and Correspond with pSite activities to upload most recent data to IRB Exchange.
  7. Modifying the Assign PI Proxy activity to upload updates to IRB Exchange.
  8. Modifying the Assign Primary Contact activity to upload updates to the IRB Exchange.
  9. Modifying the Change Tracking activity to upload updates to the IRB Exchange for sites and site modifications on the participating site side.
  10. Modifying the Send Letter activity to upload updates to the IRB Exchange for studies and reportable new information action plans.
  11. Modifying the Submit Response activity to upload updates to the IRB Exchange for studies and site modifications.
  12. Modifying the Send Letter state transitions to upload updates to the IRB Exchange for studies and site modifications.
  13. 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 the IrbExchange.GetSiteSirbReviewDates method to get the sIRB review dates and calls the _IRBSubmission.setFromSiteSirbReviewDatesJson method to parse the JSON and set the data. If the Download 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 the IrbExchange.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 to true.

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);
    }
}
Back to top © 2017 Huron Consulting Group Inc. and affiliates.
Use and distribution prohibited except through written agreement with Huron. Trademarks used in this website are registered or unregistered trademarks of Huron or its licensors.