Sites
Site data originates from participating sites and is uploaded to the IRB Exchange after the participating site downloads the external study.
What's Involved
- Adding a method to add a participant to a study in the IRB Exchange from the sIRB side.
- Modifying the state transition Invitation Pending -> Awaiting Site Materials to trigger the add participant operation.
- Adding a method to create JSON representing a site's data on the pSite side for upload to the IRB Exchange.
- Adding a method to parse JSON representing a site's data that is downloaded from the IRB Exchange and set site data.
Add Participant
When the sIRB approves the site invitation, this method will upload metadata about the chosen site Principal Investigator to the IRB Exchange and grant access to the study in the IRB Exchange to the participating site.
_IRBSubmission.getSiteMss Method Example
/** Gets the MSS for this site.
*
* @returns (_IRBSubmission)
**/
function getSiteMss()
{
return this.getQualifiedAttribute("customAttributes.mSSStudy");
}
_IRBSubmission.onApproveSiteInvite Method Example
/** Handles logic for when a site invitation is approved.
**/
function onApproveSiteInvite() {
var isExchangeEnabled = _IRBSettings.getIRBSettings().getQualifiedAttribute("customAttributes.isIRBExchangeEnabled");
if(isExchangeEnabled == true) {
var accountName = this.getQualifiedAttribute("customAttributes.IRB.customAttributes.iRBExchangeAccountName");
if(accountName != null) {
var pSiteIp = this.getQualifiedAttribute("customAttributes.mSSPSiteInstitutionalProfile");
var pSiteExchangeID = pSiteIp.getExchangeId();
if(pSiteExchangeID != null) {
var exchangeClient = ClickIRBUtils.getExchangeClient(accountName);
// Validate the PI has email set
var mss = this.getSiteMss();
var piEmail = mss.getQualifiedAttribute("customAttributes.investigator.customAttributes.studyTeamMember.contactInformation.emailPreferred.emailAddress");
if (piEmail == null) {
throw new Error("Study PI must have email address set to upload study to IRB Exchange");
}
// Validate the site's PI has email set
var sitePiPerson = this.getQualifiedAttribute("customAttributes.investigator.customAttributes.studyTeamMember");
var sitePiEmail = sitePiPerson.getQualifiedAttribute("contactInformation.emailPreferred.emailAddress");
if (sitePiEmail == null) {
throw new Error("Site PI must have email address set to upload study to IRB Exchange and give access to the site");
}
// Create a JSON object to store the site PI info to be later downloaded by the pSite
var jsonObj = {};
jsonObj.firstName = sitePiPerson.getQualifiedAttribute("firstName");
jsonObj.lastName = sitePiPerson.getQualifiedAttribute("lastName");
jsonObj.email = sitePiEmail;
if(exchangeClient.AddParticipant(mss, pSiteExchangeID, JSON.stringify(jsonObj, null, null)) == true) {
this.setQualifiedAttribute("customAttributes.canDownloadExchangeUpdates", true);
this.setQualifiedAttribute("customAttributes.exchangeInfo.customAttributes.hasPSiteBeenApproved", false);
}
}
}
}
}
Technical notes about these method examples:
- The
_IRBSubmission.getSiteMss
method is used to get the multi-site study for the site. - Validation ensures the study and site Principal Investigators have preferred email set for matching on the participating site side.
- The
customAttributes.canDownloadExchangeUpdates
attribute is set totrue
to denote that it can now download updates for the site from the IRB Exchange. - The method
IrbExchange.AddParticipant
is called to perform the operations on the IRB Exchange.
Invitation Pending -> Awaiting Site Materials State Transition
The post-processing script needs to be modified to trigger the add participant operation for the site.
Invitation Pending -> Awaiting Site Materials Post-Processing Script Example
// Upload pSite to the exchange if it's being used
targetEntity.onApproveSiteInvite();
Technical note about this script example:
- The
_IRBSubmission.onApproveSiteInvite
method is called to do the work.
Create Site JSON
_IRBSubmission.toSiteJson Method Example
/** Converts the relevant data for a site into JSON for upload to the IRB Exchange.
*
* @returns {{JSON}} Metadata representation of the site's data
*/
function toSiteJson() {
// Set most of site level data
var jsonObj = {};
jsonObj.poRef = this + "";
jsonObj.siteId = this.getQualifiedAttribute("ID");
jsonObj.status = this.getQualifiedAttribute("status.ID");
jsonObj.description = this.getQualifiedAttribute("customAttributes.siteDescription");
// Set PI
var piObj = {};
piObj.firstName = this.getQualifiedAttribute("customAttributes.investigator.customAttributes.studyTeamMember.firstName");
piObj.lastName = this.getQualifiedAttribute("customAttributes.investigator.customAttributes.studyTeamMember.lastName");
piObj.email = this.getQualifiedAttribute("customAttributes.investigator.customAttributes.studyTeamMember.contactInformation.emailPreferred.emailAddress");
piObj.hasFinancialInterest = this.getQualifiedAttribute("customAttributes.investigator.customAttributes.hasFinancialInterest");
jsonObj.principalInvestigator = piObj;
// Set primary contact
var primaryContactObj = {};
primaryContactObj.firstName = this.getQualifiedAttribute("customAttributes.primaryContact.customAttributes.studyTeamMember.firstName");
primaryContactObj.lastName = this.getQualifiedAttribute("customAttributes.primaryContact.customAttributes.studyTeamMember.lastName");
primaryContactObj.email = this.getQualifiedAttribute("customAttributes.primaryContact.customAttributes.studyTeamMember.contactInformation.emailPreferred.emailAddress");
primaryContactObj.poRef = this.getQualifiedAttribute("customAttributes.primaryContact.customAttributes.studyTeamMember") + "";
jsonObj.primaryContact = primaryContactObj;
// Set PI proxy
var piProxies = this.getQualifiedAttribute("customAttributes.piProxiesPerson");
if(piProxies != null) {
var piProxyObj = {};
var piProxiesObj = [];
piProxies = piProxies.elements();
var piProxiesCount = piProxies.count();
var piProxy;
for(var i = 1; i <= piProxiesCount; i++) {
piProxy = piProxies.item(i);
piProxyObj.firstName = piProxy.firstName;
piProxyObj.lastName = piProxy.lastName;
piProxyObj.email = piProxy.getQualifiedAttribute("contactInformation.emailPreferred.emailAddress");
piProxiesObj.push(piProxyObj);
//reset the piProxy object for assigning new values.
piProxyObj = {};
}
jsonObj.piProxies = piProxiesObj;
}
// 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"});
jsonObj.fundingSources = toEsetJson(this, "customAttributes.localFundingSources", jsonDataMap);
jsonObj.consentForms = getDocSetPoRefs(this.getQualifiedAttribute("customAttributes.recruitmentDetails.customAttributes.consentForms"));
jsonObj.recruitmentMaterials = getDocSetPoRefs(this.getQualifiedAttribute("customAttributes.recruitmentDetails.customAttributes.recruitmentAttachments"));
jsonObj.attachments = getDocSetPoRefs(this.getQualifiedAttribute("customAttributes.attachments"));
// Set a flag in the JSON to indicate if pSite has been approved yet or not
jsonObj.hasPSiteBeenApproved = this.getQualifiedAttribute("customAttributes.exchangeInfo.customAttributes.hasPSiteBeenApproved");
return JSON.stringify(jsonObj, null, null);
}
Parsing Site JSON
_IRBSubmission.setFromSiteJson Method Example
/** Takes JSON from the IRB Exchange for a site and sets the data onto the appropriate site.
*
* @param siteJson (JSON) JSON of the site containing its data
* @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
*/
function setFromSiteJson(siteJson, exchangeClient, activity) {
var ipOrg = this.getQualifiedAttribute("customAttributes.mSSPSiteInstitutionalProfile.customAttributes.institution");
var isSubscribedToProfileData = ClickIntegrationUtils.IsStoreSubscribedToPublication("ProfileData");
var draft = this.getQualifiedAttribute("customAttributes.draftStudy");
var docsToDownload = [];
var mss = this.getSiteMss();
// If the draft has not yet been created, then directly update SmartForm data
if(draft == null) {
// Get the site PI and create the Person for them if needed
var pi = getMatchedPersonFromExchangeData(siteJson.principalInvestigator.email, siteJson.principalInvestigator.firstName, siteJson.principalInvestigator.lastName, ipOrg, null, isSubscribedToProfileData, this, "customAttributes.investigator.customAttributes.studyTeamMember");
if(pi != null) {
setAttributeAndChangeCheck(this, pi, "customAttributes.investigator.customAttributes.studyTeamMember", activity, false);
}
// Set the scalar data
setAttributeAndChangeCheck(this, siteJson.principalInvestigator.hasFinancialInterest, "customAttributes.investigator.customAttributes.hasFinancialInterest", activity, false);
var description = siteJson.description;
if(description != null) {
setAttributeAndChangeCheck(this, description, "customAttributes.siteDescription", activity, false);
}
// Set the eSet data
docsToDownload = updateEsetFromExchange(mss, "customAttributes.localFundingSources", siteJson.fundingSources, "_FundingSource", exchangeClient, docsToDownload, this, activity);
// Set the site level documents
docsToDownload = downloadDocSet(exchangeClient, mss, this, "customAttributes.recruitmentDetails.customAttributes.consentForms", siteJson.consentForms, true, docsToDownload, this, "Recruitment Details.Consent Forms", null, null, null, activity);
docsToDownload = downloadDocSet(exchangeClient, mss, this, "customAttributes.recruitmentDetails.customAttributes.recruitmentAttachments", siteJson.recruitmentMaterials, true, docsToDownload, this, "Recruitment Details.Recruitment Attachments", null, null, null, activity);
docsToDownload = downloadDocSet(exchangeClient, mss, this, "customAttributes.attachments", siteJson.attachments, true, docsToDownload, this, "Site Supporting Documents", null, null, null, activity);
// If the pSite has been approved (so its draft exists), then set a flag here and create the draft
this.setQualifiedAttribute("customAttributes.exchangeInfo.customAttributes.hasPSiteBeenApproved", siteJson.hasPSiteBeenApproved);
var hasSirbSiteBeenApproved = this.getQualifiedAttribute("customAttributes.exchangeInfo.customAttributes.hasSIRBSiteBeenApproved");
// If the flag indicating pSite approval is true or null/undefined due to older data from Exchange and sIRB site has been approved, then create draft
if(siteJson.hasPSiteBeenApproved != false && hasSirbSiteBeenApproved == true) {
//if there are no docs create the snapshot now
//if there are docs we need to wait until all docs are downloaded (see ClickIRBRemoteMethods.addChangeTrackingInfo)
if (docsToDownload.length == 0){
this.createSiteSnapshotUsingWorkaround();
}
//create Draft
this.cloneIRBSubmission(activity);
}
}
// Get the site primary contact and create the Person for them if needed
var primaryContact = getMatchedPersonFromExchangeData(siteJson.primaryContact.email, siteJson.primaryContact.firstName, siteJson.primaryContact.lastName, ipOrg, null, isSubscribedToProfileData, this, "customAttributes.primaryContact.customAttributes.studyTeamMember");
if(primaryContact != null) {
setAttributeAndChangeCheck(this, primaryContact, "customAttributes.primaryContact.customAttributes.studyTeamMember", activity, false);
}
// Set PI proxies
setSelectionSetFromJson("Person", this, "customAttributes.piProxiesPerson", siteJson.piProxies, "contactInformation.emailPreferred.emailAddress", activity);
// Non-SmartForm scalar data
this.setQualifiedAttribute("customAttributes.cededStudyID", siteJson.siteId);
setAttributeAndChangeCheck(this, siteJson.status, "customAttributes.externalStudyStatus", activity, false);
// Set readers and editors
mss.updateReadersAndEditors(this);
return docsToDownload;
}