Site Modifications
When the participating site creates a site modification, if the IRB staff accept the site updates, then they need to be uploaded to the IRB Exchange and downloaded by the sIRB for review.
What's Involved
- Add a method to create JSON representing a site modification's data for upload to the IRB Exchange.
- Add a method to parse JSON representing a site modification as downloaded from the IRB Exchange and set the data on the site modification.
- Add a method to download site modifications from the IRB Exchange that will be called by the activity.
- Add an activity to download site modifications from the IRB Exchange.
- Modifying the state transition Pending sIRB Review -> Approved to upload updates.
Create Site Modification JSON
_IRBSubmission.toSiteModJson Method Example
/** Converts the relevant data for a site into JSON for upload to the IRB Exchange.
*
* @returns {{JSON}}
*/
function toSiteModJson() {
var draftSite = this.getQualifiedAttribute("customAttributes.draftStudy");
// Set most of site mod level data
var jsonObj = {};
jsonObj.poRef = this + "";
jsonObj.studyPoRef = this.getQualifiedAttribute("customAttributes.parentStudy") + "";
jsonObj.siteModId = this.getQualifiedAttribute("ID");
jsonObj.status = this.getQualifiedAttribute("status.ID");
jsonObj.summary = this.getQualifiedAttribute("customAttributes.modificationDetails.customAttributes.modificationSummary");
jsonObj.dateCreated = this.getQualifiedAttribute("dateCreated");
// Set most of site mod draft level data
var draftObj = {};
draftObj.description = draftSite.getQualifiedAttribute("customAttributes.siteDescription");
// Set PI
var piObj = {};
piObj.firstName = draftSite.getQualifiedAttribute("customAttributes.investigator.customAttributes.studyTeamMember.firstName");
piObj.lastName = draftSite.getQualifiedAttribute("customAttributes.investigator.customAttributes.studyTeamMember.lastName");
piObj.email = draftSite.getQualifiedAttribute("customAttributes.investigator.customAttributes.studyTeamMember.contactInformation.emailPreferred.emailAddress");
piObj.hasFinancialInterest = draftSite.getQualifiedAttribute("customAttributes.investigator.customAttributes.hasFinancialInterest");
draftObj.principalInvestigator = piObj;
// Funding sources
var jsonDataMap = [];
jsonDataMap.push({"jsonName":"name", "attrPath":"customAttributes.organization.name"});
jsonDataMap.push({"jsonName":"sponsorId", "attrPath":"customAttributes.fundingSourceID"});
jsonDataMap.push({"jsonName":"grantsOfficeId", "attrPath":"customAttributes.grantOfficeID"});
jsonDataMap.push({"jsonName":"documents", "attrPath":"customAttributes.attachments", "isDocs":"true"});
draftObj.fundingSources = toEsetJson(draftSite, "customAttributes.localFundingSources", jsonDataMap);
// Selection sets
jsonObj.enrollmentStatus = getSelectionSetIds(this, "customAttributes.modificationDetails.customAttributes.enrollmentStatus", "ID");
jsonObj.subjectNotifications = getSelectionSetIds(this, "customAttributes.modificationDetails.customAttributes.subjectNotifications", "ID");
// Document sets on the draft level
draftObj.consentForms = getDocSetPoRefs(draftSite.getQualifiedAttribute("customAttributes.recruitmentDetails.customAttributes.consentForms"));
draftObj.recruitmentMaterials = getDocSetPoRefs(draftSite.getQualifiedAttribute("customAttributes.recruitmentDetails.customAttributes.recruitmentAttachments"));
draftObj.attachments = getDocSetPoRefs(draftSite.getQualifiedAttribute("customAttributes.attachments"));
jsonObj.draftSite = draftObj;
return JSON.stringify(jsonObj, null, null);
}
Technical note about this method example:
- This method is similar to the
_IRBSubmission.toSiteJson
method used for create site JSON, but the SmartForm data in this case comes from the draft.
Parse the Site Modification JSON
_IRBSubmission.setFromSiteModJson Method Example
/** Takes JSON from the IRB Exchange for a site mod and sets the data onto the draft.
*
* @param siteModJson (JSON) JSON of the site containing its data
* @param siteMod (_IRBSubmission) The site mod
* @param exchangeClient (IrbExchange) Handle to the etype to run operations on against the Exchange
* @param activity (Activity) The activity being executed as part of the update from Exchange
*
* @returns {JSON} JSON array of metadata about documents to download
**/
function setFromSiteModJson(siteModJson, siteMod, exchangeClient, activity) {
// Set the mod level data
var description = siteModJson.draftSite.description;
if(description != null) {
setAttributeAndChangeCheck(this, description, "customAttributes.siteDescription", activity, false);
}
setAttributeAndChangeCheck(siteMod, siteModJson.status, "customAttributes.externalStudyStatus", activity, false);
siteMod.setQualifiedAttribute("customAttributes.cededStudyID", siteModJson.siteModId);
setAttributeAndChangeCheck(siteMod, siteModJson.summary, "customAttributes.modificationDetails.customAttributes.modificationSummary", activity, false);
// Set the eSet data on the draft
var parentStudy = this.getQualifiedAttribute("customAttributes.parentStudy");
var mss = parentStudy.getSiteMss();
var documentsToDownloadJson = [];
documentsToDownloadJson = updateEsetFromExchange(mss, "customAttributes.localFundingSources", siteModJson.draftSite.fundingSources, "_FundingSource", exchangeClient, documentsToDownloadJson, this, activity);
// Set the PI on the draft
var ipOrg = parentStudy.getQualifiedAttribute("customAttributes.mSSPSiteInstitutionalProfile.customAttributes.institution");
var isSubscribedToProfileData = ClickIntegrationUtils.IsStoreSubscribedToPublication("ProfileData");
var pi = getMatchedPersonFromExchangeData(siteModJson.draftSite.principalInvestigator.email, siteModJson.draftSite.principalInvestigator.firstName, siteModJson.draftSite.principalInvestigator.lastName, ipOrg, null, isSubscribedToProfileData, this, "customAttributes.investigator.customAttributes.studyTeamMember");
if(pi != null) {
setAttributeAndChangeCheck(this, pi, "customAttributes.investigator.customAttributes.studyTeamMember", activity, false);
}
setAttributeAndChangeCheck(this, siteModJson.draftSite.principalInvestigator.hasFinancialInterest, "customAttributes.investigator.customAttributes.hasFinancialInterest", activity, false);
// Set mod level selection sets
setSelectionSetFromJson("_EnrollmentStatus", siteMod, "customAttributes.modificationDetails.customAttributes.enrollmentStatus", siteModJson.enrollmentStatus, "ID", activity);
setSelectionSetFromJson("_SubjectNotification", siteMod, "customAttributes.modificationDetails.customAttributes.subjectNotifications", siteModJson.subjectNotifications, "ID", activity);
// Set the draft level documents
documentsToDownloadJson = downloadDocSet(exchangeClient, mss, this, "customAttributes.recruitmentDetails.customAttributes.consentForms", siteModJson.draftSite.consentForms, true, documentsToDownloadJson, this, "Recruitment Details.Consent Forms", null, null, null, activity);
documentsToDownloadJson = downloadDocSet(exchangeClient, mss, this, "customAttributes.recruitmentDetails.customAttributes.recruitmentAttachments", siteModJson.draftSite.recruitmentMaterials, true, documentsToDownloadJson, this, "Recruitment Details.Recruitment Attachments", null, null, null, activity);
documentsToDownloadJson = downloadDocSet(exchangeClient, mss, this, "customAttributes.attachments", siteModJson.draftSite.attachments, true, documentsToDownloadJson, this, "Site Supporting Documents", null, null, null, activity);
return documentsToDownloadJson;
}
Download Site Modification from IRB Exchange
_IRBSubmission.createSiteModFromExchange Method Example
/** Creates a site mod when downloading it from the IRB Exchange.
@param sch (Scripting Context Helper)
@return documentsToDownload (JSON) - JSON array of metadata about documents to download
**/
function createSiteModFromExchange(sch) {
var activity = sch.currentEntity;
var documentsToDownload = [];
// Check if there is an already active mod for this site and if so just report this in activity history
var activeMods = this.getQualifiedAttribute("customAttributes.modificationDetails.customAttributes.activeModifications");
if(activeMods != null) {
activeMods = activeMods.elements();
if(activeMods.count() > 0) {
activity.setQualifiedAttribute("notesAsStr", "There is already an active modification for this site");
activity.setQualifiedAttribute("name", "Checked for Mods from IRB Exchange");
return documentsToDownload;
}
}
// Get site mod item from Exchange; validate admin office IRB Exchange account name and IP IRB Exchange ID; if no mods available for download just report this in activity history
var adminOffice = this.getQualifiedAttribute("customAttributes.IRB");
var accountName = adminOffice.getQualifiedAttribute("customAttributes.iRBExchangeAccountName");
if(accountName == null) {
throw new Error("Account name not set for admin office with ID " + adminOffice.ID);
}
var exchangeClient = ClickIRBUtils.getExchangeClient(accountName);
var siteParticipantId = this.getQualifiedAttribute("customAttributes.mSSPSiteInstitutionalProfile.customAttributes.exchangeId");
if(siteParticipantId == null) {
throw new Error("Exchange ID unexpectedly null for site with ID " + this.ID);
}
var siteModJsonStr = exchangeClient.GetSiteMod(this.getSiteMss(), siteParticipantId, null);
if(siteModJsonStr == null) {
activity.setQualifiedAttribute("notesAsStr", "There are no modifications available for download from the IRB Exchange for this site");
activity.setQualifiedAttribute("name", "Checked for Mods from IRB Exchange");
return documentsToDownload;
}
var siteModJson = JSON.parse(siteModJsonStr, null);
// Check if this mod was previously downloaded
var siteModRef = IrbExchangeReference.GetPoRef(siteModJson.exchangeRef, exchangeClient + "", exchangeClient.GetEndpoint() + "");
if(siteModRef != null) {
activity.setQualifiedAttribute("notesAsStr", "The active modification for this site has already been downloaded from the IRB Exchange");
activity.setQualifiedAttribute("name", "Checked for Mods from IRB Exchange");
return documentsToDownload;
}
// Create the site mod
var siteMod = this.createAndSetUpSirbSiteMod();
siteMod.setQualifiedAttribute("customAttributes.canDownloadExchangeUpdates", true);
siteMod.setQualifiedAttribute("dateCreated", siteModJson.dateCreated);
// Add a reference to the site mod to the Exchange
IrbExchangeReference.LinkExchangeIdToPoRef(siteModJson.exchangeRef, siteMod + "", exchangeClient + "", exchangeClient.GetEndpoint() + "", true, false, false, false);
// Fill in rest of SmartForm data
var draft = siteMod.getQualifiedAttribute("customAttributes.draftStudy");
if(draft == null) {
throw new Error("Draft unexpectedly null for site mod with ID " + siteMod.ID);
}
documentsToDownload = draft.setFromSiteModJson(siteModJson, siteMod, exchangeClient, null);
// Set site inbox contacts (study staff)
siteMod.updateContacts("StudyStaff");
// Increment version on draft
var incrementMinorVersionActivity = draft.incrementVersionOnDraftStudy(sch, Person.getCurrentUser(), "minor", siteMod.ID + ": Modification downloaded 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 + "";
}
// Report the details of successful site mod download in activity history on site
activity.setQualifiedAttribute("notesAsStr", "Modification: <a href='" + siteMod.getRelativeUrl() + "'>" + siteMod.ID + "</a>");
activity.setQualifiedAttribute("name", "Modification " + siteMod.ID + " Downloaded from IRB Exchange");
// Store current date and time as last IRB Exchange update date and time for automatic update downloads timing
var sessionContext = wom.getSessionContext();
sessionContext.putContextObject(siteMod.ID + "-LastExchangeUpdate", new Date(), true);
return documentsToDownload;
}
Technical notes about this method example:
- The
IrbExchange.GetSiteMod
method is called to get the site modification JSON from the IRB Exchange. - The
_IRBSubmission.createAndSetUpSirbSiteMod
method is called to create the site modification and set standard data on it. - The
_IRBSubmission.setFromSiteModJson
method is called to parse the JSON from the IRB Exchange into the data on the site modification. - This method updates the name and notes on the Download Mod from IRB Exchange activity to provide feedback on the result of the activity execution.
- After processing of the site modification download is complete, a session variable is set for the last IRB Exchange update for the site modification. For more information on how session variables are used in updates, see the Updates tutorial.
Download Mod from IRB Exchange Activity
The activity simply needs to call a method to do its work.
Download Mod from IRB Exchange Activity Post-Processing Script Example
targetEntity.downloadExchangeUpdates(sch, activity);
Technical note about this script example:
- This calls the
_IRBSubmission.downloadExchangeUpdates
that calls the_IRBSubmission.createSiteModFromExchange
method to do its work. The_IRBSubmission.downloadExchangeUpdates
method is used mostly for downloading updates, but it was technically convenient on a site workspace to implement it this way for downloading site modification documents consistently. For more information on the_IRBSubmission.downloadExchangeUpdates
method, see the Updates tutorial.
Pending sIRB Review -> Approved State Transition
The post-processing script needs to be modified to upload updates to the IRB Exchange when on the participating site side.
Pending sIRB Review -> Approved State Transition Post-Processing Script Snippet Example
// If pSite mod, upload study to Exchange
var isMSS = targetEntity.getQualifiedAttribute("customAttributes.parentStudy.customAttributes.isMSS");
if(isMSS == true){
targetEntity.uploadToExchange(null);
}