Data Entry Sets
Data entry sets are critical components to most submissions' data. Sets are represented in the JSON as JSON arrays.
Handling data entry sets involves two parts:
- Adding a method to create a JSON representation of a data entry set for upload to the IRB Exchange
- Adding a method to parse a JSON representation of a data entry set into a data entry set upon download from the IRB Exchange
Creating JSON for a Data Entry Set
_IRBSubmission.toEsetJson Method Example
/** Converts the relevant data for a data entry set into JSON for upload to the IRB Exchange.
*
* @param submission (_IRBSubmission) Submission the eSet lives on
* @param attributePath (string) Attribute path to the eSet
* @param jsonDataMap (JSON) Info on the eSet's data structure so we can copy it into JSON
*
* @returns JSON - JSON representation of the eSet data
*/
function toEsetJson(submission, attributePath, jsonDataMap) {
var eSet = submission.getQualifiedAttribute(attributePath);
var eSetObj = [], memberObj;
if (eSet != null) {
var eSetElements = eSet.elements();
var eSetCount = eSetElements.count();
var memberData, memberType;
for (var i = 1; i <= eSetCount; i++) {
var member = eSetElements.item(i);
if(memberType == null) {
memberType = member.getType();
}
memberObj = {};
for(var j = 0; j < jsonDataMap.length; j++) {
memberData = member.getQualifiedAttribute(jsonDataMap[j].attrPath);
if(jsonDataMap[j].isDocs) {
// Document set
memberObj[jsonDataMap[j].jsonName] = getDocSetPoRefs(memberData);
} else if(jsonDataMap[j].altPath != null && (memberData == null || memberData == "")) {
// IND or IDE holder
memberObj[jsonDataMap[j].jsonName] = member.getQualifiedAttribute(jsonDataMap[j].altPath);
} else {
// Regular data
memberObj[jsonDataMap[j].jsonName] = member.getQualifiedAttribute(jsonDataMap[j].attrPath);
}
}
// If this is a site mod, we'll need the poRef of the initial site version of this funding source
if(memberType == "_FundingSource") {
var metaCloneId = member.getQualifiedAttribute("metaCloneId");
var owningEntity = member.getQualifiedAttribute("owningEntity");
var memberOwningEntitySubmissionType = owningEntity.getQualifiedAttribute("customAttributes.submissionType.ID");
if (metaCloneId != null && memberOwningEntitySubmissionType == "DRAFT") {
var initialSubmission = owningEntity.getQualifiedAttribute("customAttributes.parentStudy");
var initialSiteFundingSourceMatches = ApplicationEntity.getResultSet("_FundingSource").query("ID != '" + member.ID + "' and metaCloneId = '" + metaCloneId + "' and dateCreated is not null and owningEntity = " + initialSubmission).elements();
var initialSiteFundingSourceMatchesCount = initialSiteFundingSourceMatches.count();
if (initialSiteFundingSourceMatchesCount != 1) {
throw new Error("Unexpected number of matches for _FundingSource with metaCloneId " + metaCloneId + "; count: " + initialSiteFundingSourceMatchesCount);
}
memberObj.fundingSourceInitialSitePoRef = initialSiteFundingSourceMatches.item(1) + "";
}
}
memberObj.poRef = member + "";
eSetObj.push(memberObj);
}
}
return eSetObj;
}
Technical notes about this method example:
- This method uses a JSON argument that specifies metadata about the data entry set for creating the JSON. The top for loop iterates the set members while the inner for loop iterates the specification of the entity's attributes to set them.
- Document sets are handled specially by calling the
_IRBSubmission.getDocSetPoRefs
method (see the Documents tutorial for more information). - Data is included for funding sources on site modifications that identifies which funding source on the initial site it corresponds to. This aids in keeping View Differences accurate.
Parsing JSON into Data Entry Sets
_IRBSubmission.updateEsetFromExchange Method Example
/** Add, update and remove changed entities from the IRB Exchange.
*
* @param study (_IRBSubmission) Study the container is related to
* @param eSetPath (string) Attribute path to the eSet
* @param eSetObj (JSON) JSON array of the entities' data from the IRB Exchange
* @param eTypeStr (string) eType of the set members
* @param exchangeClient (IrbExchange) eType that acts as a handler to call Portal IRB Client Library methods
* @param documentsToDownloadJson (JSON) Running JSON array of metadata about documents to download from IRB Exchange
* @param submission (_IRBSubmission) Submission eset lives on
* @param activity (Activity) The Update from IRB Exchange or Update from IRB Exchange (Admin) activity that triggered the call here, or null if initial download
**/
function updateEsetFromExchange(study, eSetPath, eSetObj, eTypeStr, exchangeClient, documentsToDownloadJson, submission, activity) {
// Eset from Exchange exists, so set the data locally
if(eSetObj != null) {
var eSet = this.getQualifiedAttribute(eSetPath);
var eSetObjCount = eSetObj.length;
var entityObj, entity, poRef, eSetType, docs, fundingSourceAttrDisplay, fundingSourceAttrInternal;
// If there's data to set, but the local set doesn't exist, then create it and set on submission
if (eSetObjCount > 0 && eSet == null) {
eSetType = ApplicationEntity.getTypeNamed(eTypeStr);
eSet = eSetType.createEntitySet();
this.setQualifiedAttribute(eSetPath, eSet);
}
// Create a set of entities to keep for aiding on removed element handling
var entityType = ApplicationEntity.getTypeNamed("CustomDataType");
var entitiesToKeep = entityType.createEntitySet();
// Special handling for the two different funding sources sets for View Differences
if(eSetPath == "customAttributes.fundingSources") {
fundingSourceAttrDisplay = "Funding Sources";
fundingSourceAttrInternal = "fundingSources";
} else {
fundingSourceAttrDisplay = "Local Funding Sources";
fundingSourceAttrInternal = "localFundingSources";
}
// Loop the set elements from the Exchange and set locally
for (var i = 0; i < eSetObjCount; i++) {
entityObj = eSetObj[i];
// Attempt to get the entity to see if it exists already
poRef = entityObj.poRef;
if (poRef != null) {
try {
entity = wom.getEntityFromString(poRef);
} catch(e) {
entity = null;
}
} else {
entity = null;
}
// Check if this is a site mod funding source, if so it may already exist on the draft
if(entity == null && entityObj.fundingSourceInitialSitePoRef != null) {
var fundingSourceInitialSite = null;
try {
fundingSourceInitialSite = wom.getEntityFromString(entityObj.fundingSourceInitialSitePoRef);
} catch(e) {
// Intentionally eat the exception; for _FundingSource type, if wom.getEntityFromString is called for an entity that no longer exists, it will throw an exception, but this scenario is expected
}
if(fundingSourceInitialSite != null) {
var initialFundingSourceMetaCloneId = fundingSourceInitialSite.getQualifiedAttribute("metaCloneId");
var draftSiteFundingSourceMatches = ApplicationEntity.getResultSet("_FundingSource").query("ID != '" + fundingSourceInitialSite.ID + "' and metaCloneId = '" + initialFundingSourceMetaCloneId + "'").elements();
if (draftSiteFundingSourceMatches.count() == 1) {
entity = draftSiteFundingSourceMatches.item(1);
IrbExchangeReference.LinkExchangeIdToPoRef(entityObj.exchangeRef, entity + "", exchangeClient + "", null, true, false, true, false);
}
}
}
// Entity doesn't exist in store, create it
if (entity == null) {
entity = wom.createEntity(eTypeStr);
eSet.addElement(entity);
if (poRef == null) {
IrbExchangeReference.LinkExchangeIdToPoRef(entityObj.exchangeRef, entity + "", exchangeClient + "", null, true, false, false, false);
}
}
entitiesToKeep.addElement(entity);
// Set type specific attributes' values
switch (eTypeStr) {
case "_FundingSource":
var org = getEntityForAttribute(entityObj, "name = '" + entityObj.name.replace("'", "''") + "'", "Company", submission, entity);
setAttributeAndChangeCheck(entity, org, "customAttributes.organization", activity, false);
setAttributeAndChangeCheck(entity, entityObj.sponsorId, "customAttributes.fundingSourceID", activity, false);
setAttributeAndChangeCheck(entity, entityObj.grantsOfficeId, "customAttributes.grantOfficeID", activity, false);
documentsToDownloadJson = downloadDocSet(exchangeClient, study, entity, "customAttributes.attachments", entityObj.documents, true, documentsToDownloadJson, submission, "Attachments", fundingSourceAttrDisplay, fundingSourceAttrInternal, "customAttributes.organization.name", activity);
break;
case "_Drug":
var drug = getEntityForAttribute(entityObj, "customAttributes.brandName = '" + entityObj.brandName.replace("'", "''") + "'", "_DrugSelection", this, null);
setAttributeAndChangeCheck(entity, drug, "customAttributes.drug", activity, false);
documentsToDownloadJson = downloadDocSet(exchangeClient, study, entity, "customAttributes.drugAttachments", entityObj.documents, true, documentsToDownloadJson, this, "Drug Attachments", "Drugs", "drugs", "customAttributes.drug", activity);
break;
case "_INDInformation":
setAttributeAndChangeCheck(entity, entityObj.number, "customAttributes.INDNumber", activity, false);
var indHolder = getEntityForAttribute(entityObj, "customAttributes.name = '" + entityObj.holder.replace("'", "''") + "'", "_IDE-INDHolder", null, null);
if(indHolder == null) {
setAttributeAndChangeCheck(entity, entityObj.holder, "customAttributes.INDHolderOther", activity, false);
indHolder = getEntityForAttribute(entityObj, "customAttributes.name = 'Other'", "_IDE-INDHolder", null, null);
}
setAttributeAndChangeCheck(entity, indHolder, "customAttributes.INDHolder", activity, false);
break;
case "_Device":
var device = getEntityForAttribute(entityObj, "customAttributes.name = '" + entityObj.name.replace("'", "''") + "'", "_DeviceSelection", this, null);
setAttributeAndChangeCheck(entity, device, "customAttributes.device", activity, false);
documentsToDownloadJson = downloadDocSet(exchangeClient, study, entity, "customAttributes.deviceAttachments", entityObj.documents, true, documentsToDownloadJson, this, "Device Attachments", "Devices", "devices", "customAttributes.device", activity);
break;
case "_IDEInformation":
setAttributeAndChangeCheck(entity, entityObj.number, "customAttributes.IDENumber", activity, false);
var ideHolder = getEntityForAttribute(entityObj, "customAttributes.name = '" + entityObj.holder.replace("'", "''") + "'", "_IDE-INDHolder", null, null);
if(ideHolder == null) {
setAttributeAndChangeCheck(entity, entityObj.holder, "customAttributes.IDEHolderOther", activity, false);
ideHolder = getEntityForAttribute(entityObj, "customAttributes.name = 'Other'", "_IDE-INDHolder", null, null);
}
setAttributeAndChangeCheck(entity, ideHolder, "customAttributes.IDEHolder", activity, false);
break;
default:
throw new Error("Unexpected eType encountered: " + eTypeStr);
break;
}
// Add hint for change tracking for existing set entity
ChangeTrackingUtils.addHintForModifiedSetMember(eSet, entity);
}
}
// Remove entities that have been deleted on the Exchange
if(eSet != null) {
var eSetElements = eSet.elements();
var eSetCount = eSetElements.count();
for(var i = 1; i <= eSetCount; i++) {
entity = eSetElements.item(i);
if(!entitiesToKeep.contains(entity)) {
eSet.removeElement(entity);
if (activity){
activity.setQualifiedAttribute("customAttributes.hasDataChanged", true);
}
}
}
}
return documentsToDownloadJson;
}
Technical notes about this method example:
- Since
wom.getEntityFromString
throws an exception for some types, we surround it with a try/catch block, since the entity not existing (needs to be created) is an expected scenario. - Entities are linked to the IRB Exchange counterparts using a call to
IrbExchangeReference.LinkExchangeIdToPoRef
. This is done for efficient updates. - A switch statement is used for setting type specific attributes for entities.
- For selection entities,
_IRBSubmission.getEntityForAttribute
is called to get the entity. See the Selections tutorial for more information. - For document sets,
_IRBSubmission.downloadDocSet
is called to set the document set on the entity. See the Documents tutorial for more information. - Entities deleted from IRB Exchange will be deleted from the set.
- Funding sources for site modifications have some special handling that considers the funding source being downloaded for the site modification may already exist on the local draft submission. This is to keep View Differences accurate and not unnecessarily delete and re-create entities.