diff --git a/mcc/src/org/labkey/mcc/MccController.java b/mcc/src/org/labkey/mcc/MccController.java
index 2fc49a80f..ef8bbe26b 100644
--- a/mcc/src/org/labkey/mcc/MccController.java
+++ b/mcc/src/org/labkey/mcc/MccController.java
@@ -24,6 +24,8 @@
import org.labkey.api.action.ConfirmAction;
import org.labkey.api.action.MutatingApiAction;
import org.labkey.api.action.SpringActionController;
+import org.labkey.api.collections.CaseInsensitiveHashMap;
+import org.labkey.api.collections.CaseInsensitiveHashSet;
import org.labkey.api.data.CompareType;
import org.labkey.api.data.Container;
import org.labkey.api.data.ContainerManager;
@@ -38,8 +40,12 @@
import org.labkey.api.module.ModuleLoader;
import org.labkey.api.module.ModuleProperty;
import org.labkey.api.pipeline.PipelineUrls;
+import org.labkey.api.query.BatchValidationException;
import org.labkey.api.query.DetailsURL;
import org.labkey.api.query.FieldKey;
+import org.labkey.api.query.InvalidKeyException;
+import org.labkey.api.query.QueryService;
+import org.labkey.api.query.QueryUpdateServiceException;
import org.labkey.api.security.AuthenticationManager;
import org.labkey.api.security.Group;
import org.labkey.api.security.GroupManager;
@@ -57,8 +63,12 @@
import org.labkey.api.settings.AppProps;
import org.labkey.api.settings.LookAndFeelProperties;
import org.labkey.api.studies.StudiesService;
+import org.labkey.api.study.Dataset;
+import org.labkey.api.study.Study;
+import org.labkey.api.study.StudyService;
import org.labkey.api.util.ConfigurationException;
import org.labkey.api.util.ExceptionUtil;
+import org.labkey.api.util.GUID;
import org.labkey.api.util.HtmlString;
import org.labkey.api.util.MailHelper;
import org.labkey.api.util.PageFlowUtil;
@@ -66,6 +76,7 @@
import org.labkey.api.util.URLHelper;
import org.labkey.api.view.HtmlView;
import org.labkey.mcc.etl.ZimsImportTask;
+import org.labkey.mcc.security.MccDataAdminPermission;
import org.labkey.mcc.security.MccDataAdminRole;
import org.labkey.mcc.security.MccFinalReviewerRole;
import org.labkey.mcc.security.MccRabReviewerRole;
@@ -78,6 +89,7 @@
import jakarta.mail.Address;
import jakarta.mail.Message;
+import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -85,7 +97,9 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
+import java.util.stream.IntStream;
public class MccController extends SpringActionController
{
@@ -690,6 +704,9 @@ public boolean handlePost(Object o, BindException errors) throws Exception
ModuleProperty mp = ehr.getModuleProperties().get("EHRStudyContainer");
mp.saveValue(getUser(), getContainer(), getContainer().getPath());
+ ModuleProperty mp2 = ehr.getModuleProperties().get("DefaultAnimalHistoryReport");
+ mp2.saveValue(getUser(), getContainer(), "demographics");
+
StudiesService.get().importFolderDefinition(getContainer(), getUser(), ModuleLoader.getInstance().getModule(MccModule.NAME), new Path("referenceStudy"));
return true;
@@ -781,4 +798,169 @@ public void setRowIds(Integer[] rowIds)
this.rowIds = rowIds;
}
}
+
+ @RequiresPermission(MccDataAdminPermission.class)
+ public class RenameIdsAction extends MutatingApiAction
+ {
+ @Override
+ public Object execute(RenameIdsForm form, BindException errors) throws Exception
+ {
+ if (form.getOriginalIds() == null || form.getOriginalIds().length == 0)
+ {
+ errors.reject(ERROR_MSG, "Must provide a list of IDs to change");
+ return null;
+ }
+
+ if (form.getNewIds() == null || form.getOriginalIds().length != form.getNewIds().length)
+ {
+ errors.reject(ERROR_MSG, "Differing number of Original/New IDs were provided");
+ return null;
+ }
+
+ Study s = StudyService.get().getStudy(getContainer());
+ if (s == null)
+ {
+ errors.reject(ERROR_MSG, "There is no study in this container");
+ return null;
+ }
+
+ final Map oldToNew = new CaseInsensitiveHashMap<>();
+ IntStream.range(0, form.getOriginalIds().length).forEach(i -> oldToNew.put(form.getOriginalIds()[i], form.getNewIds()[i]));
+
+ final Set idsUpdated = new CaseInsensitiveHashSet();
+ final AtomicInteger totalRecordsUpdated = new AtomicInteger();
+ final TableInfo mccAliases = QueryService.get().getUserSchema(getUser(), getContainer(), MccSchema.NAME).getTable(MccSchema.TABLE_ANIMAL_MAPPING);
+
+ try (DbScope.Transaction transaction = DbScope.getLabKeyScope().ensureTransaction())
+ {
+ // NOTE: include this value so it will get added to the audit trail. This is a loose way to connect changes made in this transaction
+ final String batchId = "MCC.Rename." + new GUID();
+ Set messages = new HashSet<>();
+ for (Dataset ds : s.getDatasets())
+ {
+ TableInfo ti = ds.getTableInfo(getUser());
+ TableSelector ts = new TableSelector(ti, PageFlowUtil.set("Id", "lsid"), new SimpleFilter(FieldKey.fromString("Id"), oldToNew.keySet(), CompareType.IN), null);
+ if (!ts.exists())
+ {
+ continue;
+ }
+
+ List> toUpdate = new ArrayList<>();
+ List> oldKeys = new ArrayList<>();
+ ts.forEachResults(rs -> {
+ if (ds.isDemographicData())
+ {
+ // test if a record exists with the new ID
+ if (new TableSelector(ti, new SimpleFilter(FieldKey.fromString("Id"), oldToNew.get(rs.getString(FieldKey.fromString("Id")))), null).exists())
+ {
+ messages.add("Existing record for ID: " + oldToNew.get(rs.getString(FieldKey.fromString("Id"))) + " for dataset: " + ds.getLabel() + " in container: " + getContainer().getPath() + ", skipping rename");
+ return;
+ }
+ }
+
+ toUpdate.add(Map.of("lsid", rs.getString(FieldKey.fromString("lsid")), "Id", oldToNew.get(rs.getString(FieldKey.fromString("Id"))), "_batchId_", batchId));
+ oldKeys.add(Map.of("lsid", rs.getString(FieldKey.fromString("lsid"))));
+ idsUpdated.add(rs.getString(FieldKey.fromString("Id")));
+ totalRecordsUpdated.getAndIncrement();
+ });
+
+ if (!toUpdate.isEmpty())
+ {
+ try
+ {
+ ti.getUpdateService().updateRows(getUser(), getContainer(), toUpdate, oldKeys, null, null);
+ }
+ catch (InvalidKeyException | BatchValidationException | QueryUpdateServiceException | SQLException e)
+ {
+ _log.error("Error updating MCC dataset rows", e);
+ errors.reject(ERROR_MSG, "Error updating MCC dataset rows: " + e.getMessage());
+ return null;
+ }
+ }
+ }
+
+ for (String oldId : oldToNew.keySet())
+ {
+ // Find the MCC ID of the new ID:
+ String mccIdForOldId = new TableSelector(mccAliases, PageFlowUtil.set("externalAlias"), new SimpleFilter(FieldKey.fromString("subjectname"), oldId), null).getObject(String.class);
+ String mccIdForNewId = new TableSelector(mccAliases, PageFlowUtil.set("externalAlias"), new SimpleFilter(FieldKey.fromString("subjectname"), oldToNew.get(oldId)), null).getObject(String.class);
+
+ if (mccIdForOldId == null)
+ {
+ // This should not really happen...
+ _log.error("An MCC rename was performed where the original ID lacked an MCC alias: " + oldId);
+ messages.add("Missing MCC alias: " + oldId);
+ }
+
+ if (mccIdForNewId == null)
+ {
+ if (mccIdForOldId != null)
+ {
+ // Create record for the new ID pointing to the MCC ID of the original
+ List> toInsert = Arrays.asList(Map.of(
+ "subjectname", mccIdForOldId,
+ "_batchId_", batchId
+ ));
+ BatchValidationException bve = new BatchValidationException();
+ mccAliases.getUpdateService().insertRows(getUser(), getContainer(), toInsert, bve, null, null);
+ if (bve.hasErrors())
+ {
+ throw bve;
+ }
+ }
+ }
+ else if (mccIdForOldId != null)
+ {
+ messages.add("Both IDs have existing MCC aliases, no changes were made: " + oldId + " / " + oldToNew.get(oldId));
+ }
+ }
+
+ transaction.commit();
+
+ return new ApiSimpleResponse(Map.of(
+ "success", true,
+ "totalIdsUpdated", idsUpdated.size(),
+ "totalRecordsUpdated", totalRecordsUpdated.get(),
+ "messages", StringUtils.join(messages, " ")
+ ));
+ }
+ catch (Exception e)
+ {
+ _log.error("Error renaming MCC IDs", e);
+
+ return new ApiSimpleResponse(Map.of(
+ "success", false,
+ "error", e.getMessage()
+ ));
+ }
+
+
+ }
+ }
+
+ public static class RenameIdsForm
+ {
+ private String[] _originalIds;
+ private String[] _newIds;
+
+ public String[] getOriginalIds()
+ {
+ return _originalIds;
+ }
+
+ public void setOriginalIds(String[] originalIds)
+ {
+ _originalIds = originalIds;
+ }
+
+ public String[] getNewIds()
+ {
+ return _newIds;
+ }
+
+ public void setNewIds(String[] newIds)
+ {
+ _newIds = newIds;
+ }
+ }
}
diff --git a/mcc/src/org/labkey/mcc/MccMaintenanceTask.java b/mcc/src/org/labkey/mcc/MccMaintenanceTask.java
index 1d000685c..c9800058a 100644
--- a/mcc/src/org/labkey/mcc/MccMaintenanceTask.java
+++ b/mcc/src/org/labkey/mcc/MccMaintenanceTask.java
@@ -1,14 +1,18 @@
package org.labkey.mcc;
import org.apache.logging.log4j.Logger;
+import org.labkey.api.data.CompareType;
import org.labkey.api.data.Container;
import org.labkey.api.data.ContainerManager;
+import org.labkey.api.data.SimpleFilter;
import org.labkey.api.data.TableInfo;
import org.labkey.api.data.TableSelector;
import org.labkey.api.ldk.LDKService;
import org.labkey.api.module.ModuleLoader;
+import org.labkey.api.query.FieldKey;
import org.labkey.api.query.QueryService;
import org.labkey.api.security.User;
+import org.labkey.api.study.Dataset;
import org.labkey.api.study.Study;
import org.labkey.api.study.StudyService;
import org.labkey.api.util.SystemMaintenance;
@@ -81,6 +85,16 @@ private void checkForDuplicateAliases(Logger log)
{
log.error("Duplicate MCC aliases found in the folder: " + s.getContainer().getPath() + ". Please load the query mcc/duplicateAliases for more detail");
}
+
+ for (Dataset ds : s.getDatasets())
+ {
+ TableInfo dsTableInfo = ds.getTableInfo(u);
+ long missingRecords = new TableSelector(dsTableInfo, new SimpleFilter(FieldKey.fromString("Id/Demographics/Id"), null, CompareType.ISBLANK), null).getRowCount();
+ if (missingRecords > 0)
+ {
+ log.warn("Found " + missingRecords + " dataset records with an ID not found in demographics for: " + ds.getLabel() + " / " + ds.getContainer().getPath());
+ }
+ }
}
}
}
diff --git a/mcc/src/org/labkey/mcc/MccModule.java b/mcc/src/org/labkey/mcc/MccModule.java
index b2b969cdb..c7c4fe169 100644
--- a/mcc/src/org/labkey/mcc/MccModule.java
+++ b/mcc/src/org/labkey/mcc/MccModule.java
@@ -23,6 +23,7 @@
import org.labkey.api.ehr.EHRService;
import org.labkey.api.ldk.ExtendedSimpleModule;
import org.labkey.api.ldk.LDKService;
+import org.labkey.api.ldk.buttons.ShowEditUIButton;
import org.labkey.api.module.Module;
import org.labkey.api.module.ModuleContext;
import org.labkey.api.query.DefaultSchema;
@@ -33,6 +34,7 @@
import org.labkey.api.writer.ContainerUser;
import org.labkey.mcc.query.MarkShippedButton;
import org.labkey.mcc.query.MccEhrCustomizer;
+import org.labkey.mcc.query.RenameIdButton;
import org.labkey.mcc.query.ReviewerNotifyButton;
import org.labkey.mcc.security.MccDataAdminRole;
import org.labkey.mcc.security.MccFinalReviewPermission;
@@ -59,7 +61,7 @@ public String getName()
@Override
public @Nullable Double getSchemaVersion()
{
- return 20.015;
+ return 20.016;
}
@Override
@@ -121,6 +123,8 @@ private void registerEHRResources()
EHRService.get().registerModule(this);
EHRService.get().registerTableCustomizer(this, MccEhrCustomizer.class);
EHRService.get().registerMoreActionsButton(new MarkShippedButton(), "study", "demographics");
+ EHRService.get().registerMoreActionsButton(new RenameIdButton(), "study", "demographics");
+ LDKService.get().registerQueryButton(new ShowEditUIButton(this, MccSchema.NAME, MccSchema.TABLE_CENSUS), MccSchema.NAME, MccSchema.TABLE_CENSUS);
}
@Override
diff --git a/mcc/src/org/labkey/mcc/MccSchema.java b/mcc/src/org/labkey/mcc/MccSchema.java
index 1745b67bb..03e4cd3ed 100644
--- a/mcc/src/org/labkey/mcc/MccSchema.java
+++ b/mcc/src/org/labkey/mcc/MccSchema.java
@@ -31,6 +31,7 @@ public class MccSchema
public static final String TABLE_ANIMAL_REQUESTS = "animalRequests";
public static final String TABLE_REQUEST_REVIEWS = "requestReviews";
public static final String TABLE_REQUEST_SCORE = "requestScores";
+ public static final String TABLE_CENSUS = "census";
public static MccSchema getInstance()
{
diff --git a/mcc/src/org/labkey/mcc/MccUserSchema.java b/mcc/src/org/labkey/mcc/MccUserSchema.java
index 6b0a6d35a..8a503f291 100644
--- a/mcc/src/org/labkey/mcc/MccUserSchema.java
+++ b/mcc/src/org/labkey/mcc/MccUserSchema.java
@@ -1,7 +1,9 @@
package org.labkey.mcc;
+import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.labkey.api.collections.CaseInsensitiveTreeSet;
import org.labkey.api.data.Container;
import org.labkey.api.data.ContainerFilter;
import org.labkey.api.data.DataColumn;
@@ -13,26 +15,70 @@
import org.labkey.api.ldk.table.CustomPermissionsTable;
import org.labkey.api.query.ExprColumn;
import org.labkey.api.query.FieldKey;
+import org.labkey.api.query.QueryDefinition;
+import org.labkey.api.query.QueryException;
+import org.labkey.api.query.QueryService;
import org.labkey.api.query.SimpleUserSchema;
import org.labkey.api.security.User;
import org.labkey.api.security.permissions.DeletePermission;
import org.labkey.api.security.permissions.InsertPermission;
import org.labkey.api.security.permissions.ReadPermission;
import org.labkey.api.security.permissions.UpdatePermission;
+import org.labkey.api.util.logging.LogHelper;
+import org.labkey.mcc.security.MccDataAdminPermission;
import org.labkey.mcc.security.MccRabReviewPermission;
import org.labkey.mcc.security.MccRequestAdminPermission;
import org.labkey.mcc.security.MccRequestorPermission;
import org.labkey.mcc.security.MccViewRequestsPermission;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
+import java.util.Set;
public class MccUserSchema extends SimpleUserSchema
{
+ private static final Logger _log = LogHelper.getLogger(MccUserSchema.class, "MCC UserSchema messages");
+
public MccUserSchema(User user, Container container, DbSchema dbschema)
{
super(MccSchema.NAME, "MCC-specific tables, such as requests", user, container, dbschema);
}
+ @Override
+ public TableInfo createTable(String name, ContainerFilter cf)
+ {
+ if (supportsAggregatedTables())
+ {
+ if (TABLE_AGGREGATED_WEIGHT.equalsIgnoreCase(name))
+ {
+ return getWeightQuery();
+ }
+ else if (TABLE_AGGREGATED_KINSHIP.equalsIgnoreCase(name))
+ {
+ return getKinshipQuery();
+ }
+ else if (TABLE_AGGREGATED_DEPARTURES.equalsIgnoreCase(name))
+ {
+ return getDepartureQuery();
+ }
+ else if (TABLE_AGGREGATED_GENOMICS.equalsIgnoreCase(name))
+ {
+ return getGenomicsQuery();
+ }
+ else if (TABLE_AGGREGATED_OBS.equalsIgnoreCase(name))
+ {
+ return getObsQuery();
+ }
+ else if (TABLE_AGGREGATED_CENSUS.equalsIgnoreCase(name))
+ {
+ return getCensusQuery();
+ }
+ }
+
+ return super.createTable(name, cf);
+ }
+
@Override
@Nullable
protected TableInfo createWrappedTable(String name, @NotNull TableInfo schemaTable, ContainerFilter cf)
@@ -74,11 +120,201 @@ else if (MccSchema.TABLE_REQUEST_SCORE.equalsIgnoreCase(name))
return addScoreColumns(ret);
}
+ else if (MccSchema.TABLE_CENSUS.equalsIgnoreCase(name))
+ {
+ CustomPermissionsTable> ret = new CustomPermissionsTable<>(this, schemaTable, cf);
+ ret.addPermissionMapping(InsertPermission.class, MccDataAdminPermission.class);
+ ret.addPermissionMapping(UpdatePermission.class, MccDataAdminPermission.class);
+ ret.addPermissionMapping(DeletePermission.class, MccDataAdminPermission.class);
+
+ return ret.init();
+ }
return super.createWrappedTable(name, schemaTable, cf);
}
- // TODO: make automatic aggregation queries for ETLs
+ @Override
+ public Set getTableNames()
+ {
+ Set available = new CaseInsensitiveTreeSet();
+ available.addAll(super.getTableNames());
+ addAggregatedTableNames(available);
+
+ return available;
+ }
+
+ private static final String TABLE_AGGREGATED_WEIGHT = "aggregatedWeight";
+ private static final String TABLE_AGGREGATED_KINSHIP = "aggregatedKinship";
+ private static final String TABLE_AGGREGATED_DEPARTURES = "aggregatedDepartures";
+ private static final String TABLE_AGGREGATED_OBS = "aggregatedObservations";
+ private static final String TABLE_AGGREGATED_GENOMICS = "aggregatedGenomicDatasets";
+ private static final String TABLE_AGGREGATED_CENSUS = "aggregatedCensus";
+
+ @Override
+ public Set getVisibleTableNames()
+ {
+ Set available = new CaseInsensitiveTreeSet();
+ available.addAll(super.getVisibleTableNames());
+ addAggregatedTableNames(available);
+
+ return Collections.unmodifiableSet(available);
+ }
+
+ private boolean supportsAggregatedTables()
+ {
+ return getContainer().hasPermission(getUser(), MccDataAdminPermission.class) && MccManager.get().getMCCInternalDataContainer(getContainer()) != null;
+ }
+
+ private void addAggregatedTableNames(Set available)
+ {
+ if (supportsAggregatedTables())
+ {
+ available.add(TABLE_AGGREGATED_KINSHIP);
+ available.add(TABLE_AGGREGATED_WEIGHT);
+ available.add(TABLE_AGGREGATED_DEPARTURES);
+ available.add(TABLE_AGGREGATED_OBS);
+ available.add(TABLE_AGGREGATED_GENOMICS);
+ available.add(TABLE_AGGREGATED_CENSUS);
+ }
+ }
+
+ private TableInfo getKinshipQuery()
+ {
+ String template = "SELECT\n" +
+ " d.Id.mccAlias.externalAlias as Id,\n" +
+ " d.Id as originalId,\n" +
+ " d.date,\n" +
+ " d.Id2MccAlias.externalAlias as Id2,\n" +
+ " d.Id2 as originalId2,\n" +
+ " d.kinship,\n" +
+ " d.relationship,\n" +
+ " d.objectid,\n" +
+ " d.container\n" +
+ "\n" +
+ "FROM \"\".study.kinship d WHERE d.qcstate.publicdata = true\n";
+
+ return makeAggregatedQuery(TABLE_AGGREGATED_KINSHIP, template);
+ }
+
+ private TableInfo getWeightQuery()
+ {
+ String template = "SELECT\n" +
+ " d.Id.mccAlias.externalAlias as Id,\n" +
+ " d.Id as originalId,\n" +
+ " d.date,\n" +
+ " d.weight,\n" +
+ " d.objectid,\n" +
+ " d.container\n" +
+ "\n" +
+ "FROM \"\".study.weight d WHERE d.qcstate.publicdata = true\n";
+
+ return makeAggregatedQuery(TABLE_AGGREGATED_WEIGHT, template);
+ }
+
+ private TableInfo getCensusQuery()
+ {
+ String template = "SELECT\n" +
+ " d.yearNo,\n" +
+ " d.startdate,\n" +
+ " d.enddate,\n" +
+ " d.centerName,\n" +
+ " d.totalBreedingPairs,\n" +
+ " d.totalLivingOffspring,\n" +
+ " d.survivalRates,\n" +
+ " d.marmosetsShipped,\n" +
+ " d.container\n" +
+ "\n" +
+ "FROM \"\".mcc.census d\n";
+
+ return makeAggregatedQuery(TABLE_AGGREGATED_CENSUS, template);
+ }
+
+ private TableInfo getDepartureQuery()
+ {
+ String template = "SELECT\n" +
+ " d.Id.mccAlias.externalAlias as Id,\n" +
+ " d.Id as originalId,\n" +
+ " d.date,\n" +
+ " d.destination,\n" +
+ " d.objectid,\n" +
+ " d.container\n" +
+ "\n" +
+ "FROM \"\".study.departure d WHERE d.qcstate.publicdata = true\n";
+
+ return makeAggregatedQuery(TABLE_AGGREGATED_DEPARTURES, template);
+ }
+
+ private TableInfo getObsQuery()
+ {
+ String template = "SELECT\n" +
+ " d.Id.mccAlias.externalAlias as Id,\n" +
+ " d.Id as originalId,\n" +
+ " d.date,\n" +
+ " d.category,\n" +
+ " d.observation,\n" +
+ " d.objectid,\n" +
+ " d.container\n" +
+ "\n" +
+ "FROM \"\".study.clinical_observations d WHERE d.qcstate.publicdata = true\n";
+
+ return makeAggregatedQuery(TABLE_AGGREGATED_OBS, template);
+ }
+
+ private TableInfo getGenomicsQuery()
+ {
+ String template = "SELECT\n" +
+ " d.Id.mccAlias.externalAlias as Id,\n" +
+ " d.Id as originalId,\n" +
+ " d.date,\n" +
+ " d.datatype,\n" +
+ " d.sra_accession,\n" +
+ " d.objectid,\n" +
+ " d.container\n" +
+ "\n" +
+ "FROM \"\".study.genomicDatasets d WHERE d.qcstate.publicdata = true\n";
+
+ return makeAggregatedQuery(TABLE_AGGREGATED_GENOMICS, template);
+ }
+
+ private TableInfo makeAggregatedQuery(String queryName, String sqlTemplate)
+ {
+ if (!getContainer().hasPermission(getUser(), MccDataAdminPermission.class))
+ {
+ return null;
+ }
+
+ QueryDefinition qd = QueryService.get().createQueryDef(getUser(), getContainer(), this, queryName);
+ StringBuilder sql = new StringBuilder();
+
+ Container parent = MccManager.get().getMCCInternalDataContainer(getContainer());
+ if (parent == null)
+ {
+ return null;
+ }
+
+ String unionClause = "";
+ for (Container c : parent.getChildren())
+ {
+ sql.append(unionClause);
+ unionClause = " UNION ALL ";
+ sql.append(sqlTemplate.replaceAll("", c.getPath()));
+ }
+
+ qd.setSql(sql.toString());
+
+ List errors = new ArrayList();
+ TableInfo ti = qd.getTable(errors, true);
+ if (!errors.isEmpty())
+ {
+ _log.error("Problem with aggregated query: " + queryName);
+ for (QueryException e : errors)
+ {
+ _log.error(e.getMessage());
+ }
+ }
+
+ return ti;
+ }
private CustomPermissionsTable> addScoreColumns(CustomPermissionsTable> ti)
{
diff --git a/mcc/src/org/labkey/mcc/etl/PopulateGeneticDataStep.java b/mcc/src/org/labkey/mcc/etl/PopulateGeneticDataStep.java
new file mode 100644
index 000000000..a71c552d0
--- /dev/null
+++ b/mcc/src/org/labkey/mcc/etl/PopulateGeneticDataStep.java
@@ -0,0 +1,162 @@
+package org.labkey.mcc.etl;
+
+import org.apache.xmlbeans.XmlException;
+import org.jetbrains.annotations.NotNull;
+import org.labkey.api.collections.CaseInsensitiveHashMap;
+import org.labkey.api.data.Container;
+import org.labkey.api.data.ContainerManager;
+import org.labkey.api.data.SimpleFilter;
+import org.labkey.api.data.TableInfo;
+import org.labkey.api.data.TableSelector;
+import org.labkey.api.di.DataIntegrationService;
+import org.labkey.api.di.TaskRefTask;
+import org.labkey.api.pipeline.PipelineJob;
+import org.labkey.api.pipeline.PipelineJobException;
+import org.labkey.api.pipeline.RecordedActionSet;
+import org.labkey.api.query.BatchValidationException;
+import org.labkey.api.query.DuplicateKeyException;
+import org.labkey.api.query.FieldKey;
+import org.labkey.api.query.QueryService;
+import org.labkey.api.query.QueryUpdateServiceException;
+import org.labkey.api.util.PageFlowUtil;
+import org.labkey.api.writer.ContainerUser;
+import org.labkey.mcc.MccManager;
+import org.labkey.mcc.MccSchema;
+import org.labkey.remoteapi.CommandException;
+import org.labkey.remoteapi.query.SelectRowsCommand;
+import org.labkey.remoteapi.query.SelectRowsResponse;
+
+import java.io.IOException;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class PopulateGeneticDataStep implements TaskRefTask
+{
+ protected final Map _settings = new CaseInsensitiveHashMap<>();
+
+ protected ContainerUser _containerUser;
+
+ private enum Settings
+ {
+ remoteSource()
+ }
+
+ @Override
+ public RecordedActionSet run(@NotNull PipelineJob job) throws PipelineJobException
+ {
+ populateGeneticData(job);
+
+ return new RecordedActionSet();
+ }
+
+ private void populateGeneticData(PipelineJob job) throws PipelineJobException
+ {
+ DataIntegrationService.RemoteConnection rc = DataIntegrationService.get().getRemoteConnection(_settings.get(Settings.remoteSource.name()), _containerUser.getContainer(), job.getLogger());
+ if (rc == null)
+ {
+ throw new PipelineJobException("Unable to find remote connection: " + _settings.get(Settings.remoteSource.name()));
+ }
+
+ try
+ {
+ //first select all rows from remote table
+ SelectRowsCommand sr = new SelectRowsCommand(MccSchema.NAME, "genomicDatasetsSource");
+ sr.setColumns(Arrays.asList("Id", "date", "datatype", "sra_accession"));
+
+ TableInfo aggregatedDemographics = QueryService.get().getUserSchema(job.getUser(), job.getContainer(), MccSchema.NAME).getTable("aggregatedDemographics");
+
+ //and select all rows from our source table
+ SelectRowsResponse srr = sr.execute(rc.connection, rc.remoteContainer);
+ Map>> toInsert = new HashMap<>();
+ srr.getRows().forEach(x -> {
+ String mccId = (String)x.get("Id");
+
+ Collection> rows = new TableSelector(aggregatedDemographics, PageFlowUtil.set("originalId", "container"), new SimpleFilter(FieldKey.fromString("Id"), mccId), null).getMapCollection();
+ if (rows.isEmpty())
+ {
+ job.getLogger().error("Unable to find ID: " + mccId);
+ return;
+ }
+
+ if (rows.size() > 1)
+ {
+ job.getLogger().error("MCC ID linked to multiple containers: " + mccId);
+ return;
+ }
+
+ Map row = rows.iterator().next();
+
+ Container target = ContainerManager.getForId(String.valueOf(row.get("container")));
+ if (!toInsert.containsKey(target))
+ {
+ toInsert.put(target, new ArrayList<>());
+ }
+
+ Map newRow = new CaseInsensitiveHashMap<>();
+ newRow.put("Id", row.get("originalId"));
+ newRow.put("date", x.get("date"));
+ newRow.put("datatype", x.get("datatype"));
+ newRow.put("sra_accession", x.get("sra_accession"));
+
+ toInsert.get(target).add(newRow);
+ });
+
+ Container c = MccManager.get().getMCCInternalDataContainer(job.getContainer());
+ if (c == null)
+ {
+ throw new IllegalStateException("MCCInternalDataContainer not set");
+ }
+
+ for (Container child : c.getChildren())
+ {
+ TableInfo gd = QueryService.get().getUserSchema(job.getUser(), child, "study").getTable("genomicDatasets");
+ try
+ {
+ gd.getUpdateService().truncateRows(job.getUser(), child, null, null);
+
+ if (toInsert.containsKey(child))
+ {
+ BatchValidationException bve = new BatchValidationException();
+ gd.getUpdateService().insertRows(job.getUser(), child, toInsert.get(child), bve, null, null);
+ if (bve.hasErrors())
+ {
+ throw bve;
+ }
+ }
+ }
+ catch (BatchValidationException | SQLException | DuplicateKeyException | QueryUpdateServiceException e)
+ {
+ throw new PipelineJobException(e);
+ }
+ }
+ }
+ catch (CommandException | IOException e)
+ {
+ throw new PipelineJobException(e);
+ }
+ }
+
+ @Override
+ public List getRequiredSettings()
+ {
+ return Collections.unmodifiableList(Arrays.asList(Settings.remoteSource.name()));
+ }
+
+ @Override
+ public void setSettings(Map settings) throws XmlException
+ {
+ _settings.putAll(settings);
+ }
+
+ @Override
+ public void setContainerUser(ContainerUser containerUser)
+ {
+ _containerUser = containerUser;
+ }
+}
\ No newline at end of file
diff --git a/mcc/src/org/labkey/mcc/query/MccEhrCustomizer.java b/mcc/src/org/labkey/mcc/query/MccEhrCustomizer.java
index 817d86a39..ff6a0d31c 100644
--- a/mcc/src/org/labkey/mcc/query/MccEhrCustomizer.java
+++ b/mcc/src/org/labkey/mcc/query/MccEhrCustomizer.java
@@ -3,8 +3,10 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.labkey.api.data.AbstractTableInfo;
+import org.labkey.api.data.BaseColumnInfo;
import org.labkey.api.data.ColumnInfo;
import org.labkey.api.data.JdbcType;
+import org.labkey.api.data.MutableColumnInfo;
import org.labkey.api.data.SQLFragment;
import org.labkey.api.data.TableInfo;
import org.labkey.api.data.WrappedColumn;
@@ -12,6 +14,7 @@
import org.labkey.api.query.ExprColumn;
import org.labkey.api.query.FieldKey;
import org.labkey.api.query.LookupForeignKey;
+import org.labkey.api.query.QueryForeignKey;
import org.labkey.api.query.UserSchema;
import org.labkey.mcc.MccSchema;
@@ -74,7 +77,7 @@ private void customizeWeight(AbstractTableInfo ti)
private void customizeAnimalTable(AbstractTableInfo ti)
{
- for (String colName : new String[]{"MostRecentArrival", "numRoommates", "curLocation", "lastHousing", "weightChange", "CageClass", "MhcStatus"})
+ for (String colName : new String[]{"MostRecentArrival", "numRoommates", "curLocation", "lastHousing", "weightChange", "CageClass", "MhcStatus", "history"})
{
ColumnInfo ci = ti.getColumn(colName);
if (ci != null)
@@ -84,6 +87,30 @@ private void customizeAnimalTable(AbstractTableInfo ti)
}
addMccAlias(ti, "Id", "mccAlias", "MCC Alias");
+
+ if (ti.getColumn("mostRecentObservations") == null)
+ {
+ MutableColumnInfo col = getWrappedIdCol(ti.getUserSchema(), ti, "mostRecentObservations", "mostRecentObservationsPivoted");
+ col.setLabel("Most Recent Observations");
+ col.setDescription("Displays the most recent observation of each category");
+ ti.addColumn(col);
+ }
+
+ }
+
+ private BaseColumnInfo getWrappedIdCol(UserSchema us, AbstractTableInfo ds, String name, String queryName)
+ {
+
+ String colName = "Id";
+ String targetCol = "Id";
+
+ WrappedColumn col = new WrappedColumn(ds.getColumn(colName), name);
+ col.setReadOnly(true);
+ col.setIsUnselectable(true);
+ col.setUserEditable(false);
+ col.setFk(new QueryForeignKey(us, null, queryName, targetCol, targetCol));
+
+ return col;
}
private void addMccAlias(AbstractTableInfo ti, String sourceCol, String name, String label)
diff --git a/mcc/src/org/labkey/mcc/query/RenameIdButton.java b/mcc/src/org/labkey/mcc/query/RenameIdButton.java
new file mode 100644
index 000000000..a5fc2ea9a
--- /dev/null
+++ b/mcc/src/org/labkey/mcc/query/RenameIdButton.java
@@ -0,0 +1,18 @@
+package org.labkey.mcc.query;
+
+import org.labkey.api.ldk.table.SimpleButtonConfigFactory;
+import org.labkey.api.module.ModuleLoader;
+import org.labkey.api.view.template.ClientDependency;
+import org.labkey.mcc.MccModule;
+import org.labkey.mcc.security.MccDataAdminPermission;
+
+import java.util.List;
+
+public class RenameIdButton extends SimpleButtonConfigFactory
+{
+ public RenameIdButton()
+ {
+ super(ModuleLoader.getInstance().getModule(MccModule.class), "Rename ID", "MCC.window.RenameIdWindow.buttonHandler(dataRegionName);", List.of(ClientDependency.supplierFromPath("mcc/window/RenameIdWindow.js")));
+ setPermission(MccDataAdminPermission.class);
+ }
+}
diff --git a/mcc/src/org/labkey/mcc/query/RequestScoreActionsDisplayColumnFactory.java b/mcc/src/org/labkey/mcc/query/RequestScoreActionsDisplayColumnFactory.java
index f6100a7d3..3ca12b80e 100644
--- a/mcc/src/org/labkey/mcc/query/RequestScoreActionsDisplayColumnFactory.java
+++ b/mcc/src/org/labkey/mcc/query/RequestScoreActionsDisplayColumnFactory.java
@@ -13,6 +13,7 @@
import org.labkey.api.security.User;
import org.labkey.api.security.UserManager;
import org.labkey.api.util.PageFlowUtil;
+import org.labkey.api.view.HttpView;
import org.labkey.api.view.template.ClientDependency;
import org.labkey.mcc.MccManager;
import org.labkey.mcc.security.MccFinalReviewPermission;
@@ -32,6 +33,8 @@ public DisplayColumn createRenderer(ColumnInfo colInfo)
{
return new AbstractMccDisplayColumn(colInfo)
{
+ private boolean _hasRegisteredApprovedHandler = false;
+
@Override
public void renderGridCellContents(RenderContext ctx, Writer out) throws IOException
{
@@ -94,7 +97,14 @@ else if (st == MccManager.RequestStatus.PendingDecision)
}
else if (st == MccManager.RequestStatus.Approved)
{
- out.write("Mark Fulfilled ");
+ out.write("Mark Fulfilled ");
+
+ if (!_hasRegisteredApprovedHandler)
+ {
+ HttpView.currentPageConfig().addHandlerForQuerySelector("a.rsadc-approved", "click", "MCC.window.ChangeStatusWindow.buttonHandler(" + PageFlowUtil.jsString(ctx.getCurrentRegion().getName()) + ", this.attributes.getNamedItem('data-requestrowid').value, 'Fulfilled'); return false;");
+
+ _hasRegisteredApprovedHandler = true;
+ }
}
}
catch (IllegalArgumentException e)
@@ -128,9 +138,4 @@ public void addQueryFieldKeys(Set keys)
}
};
}
-
- private String getWithdrawnLine(RenderContext ctx, int requestRowId)
- {
- return "Withdraw Request ";
- }
}
diff --git a/mcc/src/org/labkey/mcc/query/TriggerHelper.java b/mcc/src/org/labkey/mcc/query/TriggerHelper.java
index 957339003..02aed50cb 100644
--- a/mcc/src/org/labkey/mcc/query/TriggerHelper.java
+++ b/mcc/src/org/labkey/mcc/query/TriggerHelper.java
@@ -279,7 +279,7 @@ public int ensureMccAliasExists(Collection rawIds, Map e
if (ciExistingAliases.containsKey(rs.getString(FieldKey.fromString("subjectname")))) {
if (!ciExistingAliases.get(rs.getString(FieldKey.fromString("subjectname"))).equalsIgnoreCase(rs.getString(FieldKey.fromString("externalAlias"))))
{
- _log.error("Incoming MCC alias for: " + rs.getString(FieldKey.fromString("subjectname")) + " does not match existing: " + rs.getString(FieldKey.fromString("externalAlias")));
+ _log.error("Incoming MCC alias for: " + rs.getString(FieldKey.fromString("subjectname")) + "(" + ciExistingAliases.get(rs.getString(FieldKey.fromString("subjectname"))) + ") does not match existing: " + rs.getString(FieldKey.fromString("externalAlias")));
}
}
});
diff --git a/mcc/test/src/org/labkey/test/tests/mcc/MccTest.java b/mcc/test/src/org/labkey/test/tests/mcc/MccTest.java
index fdf94d2aa..b1da3960d 100644
--- a/mcc/test/src/org/labkey/test/tests/mcc/MccTest.java
+++ b/mcc/test/src/org/labkey/test/tests/mcc/MccTest.java
@@ -68,9 +68,9 @@ public void testMccModule() throws Exception
testAnimalImportAndTransfer();
}
- private static final String ANIMAL_DATA_HEADER = "animal ID\tprevious IDs\tsource\t\"DOB\n(MM/DD/YYYY)\"\tsex\tmaternal ID\tpaternal ID\t\"weight(grams)\"\t\"date of weight\n(MM/DD/YY)\"\tU24 status\tavailalble to transfer\tcurrent housing status\tinfant history\tfertility status\tmedical history\n";
+ private static final String ANIMAL_DATA_HEADER = "animal ID\tprevious IDs\tsource\t\"DOB\n(MM/DD/YYYY)\"\tsex\tmaternal ID\tpaternal ID\t\"weight(grams)\"\t\"date of weight\n(MM/DD/YY)\"\tU24 status\tavailable to transfer\tcurrent housing status\tinfant history\tfertility status\tmedical history\n";
- private static final String ANIMAL_DATA1 = "12345\t\t\t7/10/2011\t0 - male\t23456\t23453\t382.8\t5/19/2021\t0 - not assigned to U24 breeding colony\t0 - not available for transfer\t1 - natal family group\t3 - successful rearing of offspring\t2 - successful offspring produced\t0 - naive animal\n";
+ private static final String ANIMAL_DATA1 = "12345\t\t\t7/10/2011\t0 - male\t23456\t23453\t382.8\t5/19/2021\t0 - not assigned to U24 breeding colony\t0 - not available for transfer\t1 - natal family unit\t3 - successful rearing of offspring\t2 - successful offspring produced\t0 - naive animal\n";
private static final String ANIMAL_DATA2 = "Animal2\t\t\t6/3/2015\t1 - female\tDam2\tSire2\t361.2\t1/28/2021\t0 - not assigned to U24 breeding colony\t0 - not available for transfer\t2 - active breeding\t3 - successful rearing of offspring\t2 - successful offspring produced\t0 - naive animal\n";
diff --git a/primeseq/src/org/labkey/primeseq/PrimeseqController.java b/primeseq/src/org/labkey/primeseq/PrimeseqController.java
index 855cbf6bb..d93d71258 100644
--- a/primeseq/src/org/labkey/primeseq/PrimeseqController.java
+++ b/primeseq/src/org/labkey/primeseq/PrimeseqController.java
@@ -311,7 +311,7 @@ private SQLFragment getSql(UpdateFilePathsForm form)
sql.append("UPDATE Exp.Data SET DataFileUrl = replace(DataFileUrl, ").appendValue("file://" + sourcePrefix).append(", ").appendValue("file://" + replacementPrefix).append(") ");
sql.append("WHERE DataFileUrl like ").appendValue("file://" + sourcePrefix + "%").append("\n");
- sql.append("UPDATE pipeline.StatusFiles SET FilePath = replace(FilePath, ").appendValue(sourcePrefix).append(", ").appendValue(replacementPrefix).append(" ");
+ sql.append("UPDATE pipeline.StatusFiles SET FilePath = replace(FilePath, ").appendValue(sourcePrefix).append(", ").appendValue(replacementPrefix).append(") ");
sql.append("WHERE FilePath like ").appendValue(sourcePrefix + "%");
return sql;
diff --git a/primeseq/src/org/labkey/primeseq/PrimeseqModule.java b/primeseq/src/org/labkey/primeseq/PrimeseqModule.java
index a924e18fd..c8c3539ae 100644
--- a/primeseq/src/org/labkey/primeseq/PrimeseqModule.java
+++ b/primeseq/src/org/labkey/primeseq/PrimeseqModule.java
@@ -39,7 +39,6 @@
import org.labkey.primeseq.pipeline.ClusterMaintenanceTask;
import org.labkey.primeseq.pipeline.DeleteJobCheckpointButton;
import org.labkey.primeseq.pipeline.ExacloudResourceSettings;
-import org.labkey.primeseq.pipeline.GeographicOriginStep;
import org.labkey.primeseq.pipeline.MhcCleanupPipelineJob;
import org.labkey.primeseq.pipeline.SequenceJobResourceAllocator;
import org.labkey.primeseq.query.PerformMhcCleanupButton;
@@ -121,8 +120,6 @@ public PipelineStartup()
SequenceAnalysisService.get().registerFileHandler(new MethylationRateComparisonHandler());
SequenceAnalysisService.get().registerFileHandler(new CombineMethylationRatesHandler());
- SequencePipelineService.get().registerPipelineStep(new GeographicOriginStep.Provider());
-
_hasRegistered = true;
}
}
diff --git a/primeseq/src/org/labkey/primeseq/pipeline/SequenceJobResourceAllocator.java b/primeseq/src/org/labkey/primeseq/pipeline/SequenceJobResourceAllocator.java
index fb92cabdc..9dba77ced 100644
--- a/primeseq/src/org/labkey/primeseq/pipeline/SequenceJobResourceAllocator.java
+++ b/primeseq/src/org/labkey/primeseq/pipeline/SequenceJobResourceAllocator.java
@@ -43,7 +43,8 @@ public Integer getPriority(TaskId taskId)
{
return (taskId.getNamespaceClass() != null && (
(taskId.getNamespaceClass().getName().startsWith("org.labkey.sequenceanalysis.pipeline") ||
- taskId.getNamespaceClass().getName().startsWith("org.labkey.jbrowse.pipeline"))
+ taskId.getNamespaceClass().getName().startsWith("org.labkey.jbrowse.pipeline") ||
+ taskId.getNamespaceClass().getName().endsWith("GeneticCalculationsRTask"))
)) ? 50 : null;
}
}
@@ -53,6 +54,11 @@ private boolean isSequenceNormalizationTask(PipelineJob job)
return (job.getActiveTaskId() != null && job.getActiveTaskId().getNamespaceClass().getName().endsWith("SequenceNormalizationTask"));
}
+ private boolean isGeneticsTask(PipelineJob job)
+ {
+ return (job.getActiveTaskId() != null && job.getActiveTaskId().getNamespaceClass().getName().endsWith("GeneticCalculationsRTask"));
+ }
+
private boolean isLuceneIndexJob(PipelineJob job)
{
return (job.getActiveTaskId() != null && job.getActiveTaskId().getNamespaceClass().getName().endsWith("JBrowseLuceneTask"));
@@ -95,6 +101,7 @@ public Integer getMaxRequestCpus(PipelineJob job)
job.getLogger().debug("setting max CPUs to 8");
return 8;
}
+
if (isLuceneIndexJob(job))
{
job.getLogger().debug("setting max CPUs to 24");
@@ -152,6 +159,12 @@ public Integer getMaxRequestMemory(PipelineJob job)
return 48;
}
+ if (isGeneticsTask(job))
+ {
+ job.getLogger().debug("setting memory to 72");
+ return 72;
+ }
+
if (isCacheAlignerIndexesTask(job))
{
job.getLogger().debug("setting memory to 48");
diff --git a/tcrdb/src/org/labkey/tcrdb/pipeline/CellRangerVDJUtils.java b/tcrdb/src/org/labkey/tcrdb/pipeline/CellRangerVDJUtils.java
index a9c200f6f..acf5d44e8 100644
--- a/tcrdb/src/org/labkey/tcrdb/pipeline/CellRangerVDJUtils.java
+++ b/tcrdb/src/org/labkey/tcrdb/pipeline/CellRangerVDJUtils.java
@@ -53,6 +53,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.TreeMap;
public class CellRangerVDJUtils
{
@@ -296,6 +297,8 @@ else if ("Low Counts".equals(hto))
int fullLengthNoClonotype = 0;
int multiChainConverted = 0;
int discordantLoci = 0;
+ Map chainMap = new TreeMap<>();
+
Set knownBarcodes = new HashSet<>();
Map headerToIdx = null;
@@ -398,6 +401,8 @@ else if (discordantBarcodes.contains(barcode))
discordantLoci++;
}
+ chainMap.put(locus, chainMap.getOrDefault(locus, 0) + 1);
+
// Aggregate by: cDNA_ID, cdr3, chain, raw_clonotype_id, coalescedContigName, vHit, dHit, jHit, cHit, cdr3_nt
String key = StringUtils.join(new String[]{cDNA.toString(), line[headerToIdx.get(HEADER_FIELD.CDR3)], locus, rawClonotypeId, coalescedContigName, removeNone(line[headerToIdx.get(HEADER_FIELD.V_GENE)]), removeNone(line[headerToIdx.get(HEADER_FIELD.D_GENE)]), removeNone(line[headerToIdx.get(HEADER_FIELD.J_GENE)]), cGene, removeNone(line[headerToIdx.get(HEADER_FIELD.CDR3_NT)])}, "<>");
AssayModel am;
@@ -447,6 +452,10 @@ else if (discordantBarcodes.contains(barcode))
_log.info("total rows lacking clonotype, but marked full-length: " + fullLengthNoClonotype);
_log.info("total rows converted from Multi to TRA: " + multiChainConverted);
_log.info("total rows with discordant chain/c-gene calls: " + discordantLoci);
+ for (String chain : chainMap.keySet())
+ {
+ _log.info("total rows for " + chain + ":" + chainMap.get(chain));
+ }
}
catch (IOException e)