From 0fdd3ccb85a97301a472bd44803a9efabba99005 Mon Sep 17 00:00:00 2001 From: bbimber Date: Fri, 13 Feb 2026 16:21:15 -0800 Subject: [PATCH 01/23] Add column to view --- .../queries/study/demographics/Expanded Vaccine Detail.qview.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/SivStudies/resources/queries/study/demographics/Expanded Vaccine Detail.qview.xml b/SivStudies/resources/queries/study/demographics/Expanded Vaccine Detail.qview.xml index 5734403d..c1c0512e 100644 --- a/SivStudies/resources/queries/study/demographics/Expanded Vaccine Detail.qview.xml +++ b/SivStudies/resources/queries/study/demographics/Expanded Vaccine Detail.qview.xml @@ -14,6 +14,7 @@ + From 34d527df92f21c6743173cbc36dec11f8ba6d4d4 Mon Sep 17 00:00:00 2001 From: bbimber Date: Fri, 13 Feb 2026 16:25:24 -0800 Subject: [PATCH 02/23] Adjust behavior for non-FL clonotypes --- .../tcrdb/pipeline/CellRangerVDJUtils.java | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/tcrdb/src/org/labkey/tcrdb/pipeline/CellRangerVDJUtils.java b/tcrdb/src/org/labkey/tcrdb/pipeline/CellRangerVDJUtils.java index b9a86e8d..04db1c39 100644 --- a/tcrdb/src/org/labkey/tcrdb/pipeline/CellRangerVDJUtils.java +++ b/tcrdb/src/org/labkey/tcrdb/pipeline/CellRangerVDJUtils.java @@ -310,6 +310,8 @@ else if ("Low Counts".equals(hto)) while ((line = reader.readNext()) != null) { idx++; + Set comments = new HashSet<>(); + if (idx == 1) { _log.debug("parsing header, length: " + line.length); @@ -346,7 +348,7 @@ else if ("Low Counts".equals(hto)) if ("False".equalsIgnoreCase(line[headerToIdx.get(HEADER_FIELD.FULL_LENGTH)])) { notFullLength++; - continue; + comments.add("Not full length"); } //NOTE: 10x appends "-1" to barcode sequences @@ -435,10 +437,26 @@ else if (discordantBarcodes.contains(barcode)) am.jHit = removeNone(line[headerToIdx.get(HEADER_FIELD.J_GENE)]); am.cHit = removeNone(line[headerToIdx.get(HEADER_FIELD.C_GENE)]); am.cdr3Nt = removeNone(line[headerToIdx.get(HEADER_FIELD.CDR3_NT)]); + if (!comments.isEmpty()) + { + am.comment = StringUtils.join(comments, "\n"); + } } else { am = rows.get(key); + if (!comments.isEmpty()) + { + if (am.comment != null) + { + comments.addAll(Arrays.asList(am.comment.split("\n"))); + am.comment = StringUtils.join(comments, "\n"); + } + else + { + am.comment = StringUtils.join(comments, "\n"); + } + } } uniqueContigNames.add(am.coalescedContigName); @@ -456,7 +474,7 @@ else if (discordantBarcodes.contains(barcode)) _log.info("total rows marked as cells: " + totalCells); _log.info("total clonotype rows without CDR3: " + noCDR3); _log.info("total clonotype rows discarded for no C-gene: " + noCGene); - _log.info("total clonotype rows discarded for not full length: " + notFullLength); + _log.info("total clonotype rows not full length (these are imported): " + notFullLength); _log.info("total clonotype rows discarded for lacking consensus clonotype: " + noConsensusClonotype); _log.info("total clonotype rows skipped for unknown barcocdes: " + totalSkipped + " (" + (NumberFormat.getPercentInstance().format(totalSkipped / (double)totalCells)) + ")"); _log.info("total clonotype rows skipped because they are doublets: " + doubletSkipped + " (" + (NumberFormat.getPercentInstance().format(doubletSkipped / (double)totalCells)) + ")"); @@ -614,6 +632,7 @@ private static class AssayModel private String jHit; private String cHit; private int cdna; + private String comment; private final Set barcodes = new HashSet<>(); private String coalescedContigName; @@ -648,6 +667,7 @@ private Map processRow(AssayModel assayModel, AnalysisModel mode row.put("cdr3", assayModel.cdr3); row.put("cdr3_nt", assayModel.cdr3Nt); row.put("count", assayModel.barcodes.size()); + row.put("comment", assayModel.comment); double fraction = (double)assayModel.barcodes.size() / totalCellsBySample.get(assayModel.cdna).size(); row.put("fraction", fraction); @@ -700,7 +720,7 @@ private void saveRun(PipelineJob job, ExpProtocol protocol, AnalysisModel model, JSONObject json = new JSONObject(); json.put("Run", runProps); - File assayTmp = new File(outDir, FileUtil.makeLegalName("10x-assay-upload_" + FileUtil.getTimestamp() + ".txt")); + File assayTmp = FileUtil.appendName(outDir, FileUtil.makeLegalName("10x-assay-upload_" + FileUtil.getTimestamp() + ".txt")); if (assayTmp.exists()) { assayTmp.delete(); From 9a071841b9b721a7593ecfa3207b3f475aa3c5fc Mon Sep 17 00:00:00 2001 From: bbimber Date: Mon, 23 Feb 2026 20:21:14 -0800 Subject: [PATCH 03/23] Update JS46 details --- .../org/labkey/sivstudies/etl/PerformManualIdrStepsTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SivStudies/src/org/labkey/sivstudies/etl/PerformManualIdrStepsTask.java b/SivStudies/src/org/labkey/sivstudies/etl/PerformManualIdrStepsTask.java index f3b976a1..e122d725 100644 --- a/SivStudies/src/org/labkey/sivstudies/etl/PerformManualIdrStepsTask.java +++ b/SivStudies/src/org/labkey/sivstudies/etl/PerformManualIdrStepsTask.java @@ -294,7 +294,7 @@ public void setContainerUser(ContainerUser containerUser) private void updateJS46() throws PipelineJobException { - updateTreatmentRecords("JS46", new SimpleFilter(FieldKey.fromString("treatment"), "SIV - Unknown"), Map.of("treatment", "SIVmac239", "route", "IV")); + updateTreatmentRecords("JS46", new SimpleFilter(FieldKey.fromString("treatment"), "SIV - Unknown"), Map.of("treatment", "SIVmac239", "route", "Rectal", "amount", 1500, "amount_units", "TCID50")); } private void updateTreatmentRecords(String cohortName, SimpleFilter treatmentFilter, final Map additionalProps) throws PipelineJobException From e4242247c0362dac2bcef3d47d0124d653712539 Mon Sep 17 00:00:00 2001 From: bbimber Date: Mon, 23 Feb 2026 20:53:20 -0800 Subject: [PATCH 04/23] Add columns for WPI --- .../src/org/labkey/sivstudies/query/SivStudiesCustomizer.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SivStudies/src/org/labkey/sivstudies/query/SivStudiesCustomizer.java b/SivStudies/src/org/labkey/sivstudies/query/SivStudiesCustomizer.java index 0f089490..d6b4953e 100644 --- a/SivStudies/src/org/labkey/sivstudies/query/SivStudiesCustomizer.java +++ b/SivStudies/src/org/labkey/sivstudies/query/SivStudiesCustomizer.java @@ -464,9 +464,11 @@ public TableInfo getLookupTableInfo() qd.setSql("SELECT\n" + "min(tr.date) as artInitiation,\n" + "CONVERT(TIMESTAMPDIFF('SQL_TSI_DAY', CAST(min(tr.date) AS DATE), CAST(c." + dateColName + " AS DATE)), INTEGER) as daysPostArtInitiation,\n" + + "CONVERT(TIMESTAMPDIFF('SQL_TSI_DAY', CAST(min(tr.date) AS DATE), CAST(c." + dateColName + " AS DATE)) / 7, INTEGER) as weeksPostArtInitiation,\n" + "CONVERT(age_in_months(CAST(min(tr.date) AS DATE), CAST(c." + dateColName + " AS DATE)), FLOAT) as monthsPostArtInitiation,\n" + "max(tr.enddate) as artRelease,\n" + "CONVERT(CASE WHEN max(tr.enddate) IS NULL THEN NULL ELSE TIMESTAMPDIFF('SQL_TSI_DAY', CAST(max(tr.enddate) AS DATE), CAST(c." + dateColName + " AS DATE)) END, INTEGER) as daysPostArtRelease,\n" + + "CONVERT(CASE WHEN max(tr.enddate) IS NULL THEN NULL ELSE TIMESTAMPDIFF('SQL_TSI_DAY', CAST(max(tr.enddate) AS DATE), CAST(c." + dateColName + " AS DATE)) END / 7, INTEGER) as weeksPostArtRelease,\n" + "CONVERT(CASE WHEN max(tr.enddate) IS NULL THEN NULL ELSE age_in_months(CAST(max(tr.enddate) AS DATE), CAST(c." + dateColName + " AS DATE)) END, FLOAT) as monthsPostArtRelease,\n" + "CAST(CASE WHEN CAST(min(tr.date) AS DATE) <= CAST(c." + dateCol.getFieldKey().toString() + " AS DATE) AND CAST(max(coalesce(tr.enddate, now())) AS DATE) >= CAST(c." + dateCol.getFieldKey().toString() + " AS DATE) THEN 'Y' ELSE null END as VARCHAR) as onArt,\n" + "GROUP_CONCAT(DISTINCT tr.treatment) AS artTreatment,\n" + @@ -499,9 +501,11 @@ public TableInfo getLookupTableInfo() ((BaseColumnInfo)ti.getColumn("artRelease")).setLabel("ART Release"); ((BaseColumnInfo)ti.getColumn("daysPostArtInitiation")).setLabel("Days Post-ART Initiation"); + ((BaseColumnInfo)ti.getColumn("weeksPostArtInitiation")).setLabel("Weeks Post-ART Initiation"); ((BaseColumnInfo)ti.getColumn("monthsPostArtInitiation")).setLabel("Months Post-ART Initiation"); ((BaseColumnInfo)ti.getColumn("daysPostArtRelease")).setLabel("Days Post-ART Release"); + ((BaseColumnInfo)ti.getColumn("weeksPostArtRelease")).setLabel("Weeks Post-ART Release"); ((BaseColumnInfo)ti.getColumn("monthsPostArtRelease")).setLabel("Months Post-ART Release"); ((BaseColumnInfo)ti.getColumn("artTreatment")).setLabel("ART Treatment(s)"); From 6994987831172f532e997b0980de42631da5d4da Mon Sep 17 00:00:00 2001 From: bbimber Date: Sat, 28 Feb 2026 09:04:47 -0800 Subject: [PATCH 05/23] Add option to use cluster rhel96 partition --- .../pipeline/ExacloudResourceSettings.java | 3 ++- .../SequenceJobResourceAllocator.java | 27 ++++++++++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/primeseq/src/org/labkey/primeseq/pipeline/ExacloudResourceSettings.java b/primeseq/src/org/labkey/primeseq/pipeline/ExacloudResourceSettings.java index f00327a1..77162b83 100644 --- a/primeseq/src/org/labkey/primeseq/pipeline/ExacloudResourceSettings.java +++ b/primeseq/src/org/labkey/primeseq/pipeline/ExacloudResourceSettings.java @@ -39,7 +39,8 @@ public List getParams() put("minValue", 512); }}, 1028), ToolParameterDescriptor.create("localSSD", "Request Nodes With SSD Scratch", "If selected, -C ssdscratch will be added to the submit script, which limits to node with faster SSD scratch space. This might be important for I/O intense jobs.", "checkbox", null, null), - ToolParameterDescriptor.create("gpus", "GPUs", "The number of GPUs requested for this job. If non-zero, the gpu partition will be used.", "ldk-integerfield", null, null) + ToolParameterDescriptor.create("gpus", "GPUs", "The number of GPUs requested for this job. If non-zero, the gpu partition will be used.", "ldk-integerfield", null, null), + ToolParameterDescriptor.create("useExperimentalPartition", "Use RHEL 9.6 Partition", "If selected, jobs will be submitted to the experimental rhel96TESTING partition.", "checkbox", null, null) ); } diff --git a/primeseq/src/org/labkey/primeseq/pipeline/SequenceJobResourceAllocator.java b/primeseq/src/org/labkey/primeseq/pipeline/SequenceJobResourceAllocator.java index 8f57b6ae..3e0dd231 100644 --- a/primeseq/src/org/labkey/primeseq/pipeline/SequenceJobResourceAllocator.java +++ b/primeseq/src/org/labkey/primeseq/pipeline/SequenceJobResourceAllocator.java @@ -384,7 +384,7 @@ private void possiblyAddHighIO(PipelineJob job, RemoteExecutionEngine engine, } } - private void possiblyAddDisk(PipelineJob job, RemoteExecutionEngine engine, List lines) + private void possiblyAddDisk(PipelineJob job, RemoteExecutionEngine engine, List lines) { Map params = ((HasJobParams) job).getJobParams(); String val = StringUtils.trimToNull(params.get("resourceSettings.resourceSettings.localDisk")); @@ -405,6 +405,12 @@ private boolean needsGPUs(PipelineJob job) return hasCellBender(job) || StringUtils.trimToNull(params.get("resourceSettings.resourceSettings.gpus")) != null; } + private boolean useExperimentalPartition(PipelineJob job) + { + Map params = ((HasJobParams) job).getJobParams(); + return StringUtils.trimToNull(params.get("resourceSettings.resourceSettings.useExperimentalPartition")) != null; + } + private boolean hasCellBender(PipelineJob job) { if (!isSequenceSequenceOutputHandlerTask(job)) @@ -436,7 +442,7 @@ private boolean hasCellBender(PipelineJob job) return false; } - private void possiblyAddGpus(PipelineJob job, RemoteExecutionEngine engine, List lines) + private void possiblyAddGpus(PipelineJob job, RemoteExecutionEngine engine, List lines) { Map params = ((HasJobParams) job).getJobParams(); String val = StringUtils.trimToNull(params.get("resourceSettings.resourceSettings.gpus")); @@ -457,7 +463,7 @@ private void possiblyAddGpus(PipelineJob job, RemoteExecutionEngine engine, List lines.add("#SBATCH --gres=gpu:" + val); } - private void possiblyAddExclusive(PipelineJob job, RemoteExecutionEngine engine, List lines) + private void possiblyAddExclusive(PipelineJob job, RemoteExecutionEngine engine, List lines) { Map params = ((HasJobParams)job).getJobParams(); String val = StringUtils.trimToNull(params.get("resourceSettings.resourceSettings.useExclusive")); @@ -478,7 +484,7 @@ private void possiblyAddExclusive(PipelineJob job, RemoteExecutionEngine engine, } } - private void possiblyAddSSD(PipelineJob job, RemoteExecutionEngine engine, List lines) + private void possiblyAddSSD(PipelineJob job, RemoteExecutionEngine engine, List lines) { Map params = ((HasJobParams)job).getJobParams(); String val = StringUtils.trimToNull(params.get("resourceSettings.resourceSettings.localSSD")); @@ -499,7 +505,7 @@ private void possiblyAddSSD(PipelineJob job, RemoteExecutionEngine engine, List< } } - private void possiblyAddQOS(PipelineJob job, RemoteExecutionEngine engine, List lines) + private void possiblyAddQOS(PipelineJob job, RemoteExecutionEngine engine, List lines) { //first remove existing removeQueueLines(lines); @@ -579,7 +585,16 @@ private void possiblyAddQOS(PipelineJob job, RemoteExecutionEngine engine, List< private String getPartition(PipelineJob job) { - return needsGPUs(job) ? "gpu" : "batch"; + if (needsGPUs(job)) + { + return "gpu"; + } + else if (useExperimentalPartition(job)) + { + return "rhel96TESTING"; + } + + return "batch"; } private Long getFileSize(PipelineJob job) From 2e5e084c0f73b40c9d708a03878faa63dc94b012 Mon Sep 17 00:00:00 2001 From: bbimber Date: Sun, 1 Mar 2026 07:25:13 -0800 Subject: [PATCH 06/23] Bugfix to cluster partition choice --- .../pipeline/SequenceJobResourceAllocator.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/primeseq/src/org/labkey/primeseq/pipeline/SequenceJobResourceAllocator.java b/primeseq/src/org/labkey/primeseq/pipeline/SequenceJobResourceAllocator.java index 3e0dd231..592bacd0 100644 --- a/primeseq/src/org/labkey/primeseq/pipeline/SequenceJobResourceAllocator.java +++ b/primeseq/src/org/labkey/primeseq/pipeline/SequenceJobResourceAllocator.java @@ -214,7 +214,7 @@ public Integer getMaxRequestMemory(PipelineJob job) if (isSequenceSequenceOutputHandlerTask(job)) { - File jobXml = new File(job.getLogFile().getParentFile(), FileUtil.getBaseName(job.getLogFile()) + ".job.json.txt"); + File jobXml = FileUtil.appendName(job.getLogFile().getParentFile(), FileUtil.getBaseName(job.getLogFile()) + ".job.json.txt"); if (jobXml.exists()) { try (BufferedReader reader = Readers.getReader(jobXml)) @@ -408,7 +408,13 @@ private boolean needsGPUs(PipelineJob job) private boolean useExperimentalPartition(PipelineJob job) { Map params = ((HasJobParams) job).getJobParams(); - return StringUtils.trimToNull(params.get("resourceSettings.resourceSettings.useExperimentalPartition")) != null; + String rawVal = StringUtils.trimToNull(params.get("resourceSettings.resourceSettings.useExperimentalPartition")); + if (rawVal == null) + { + return false; + } + + return Boolean.parseBoolean(rawVal); } private boolean hasCellBender(PipelineJob job) @@ -418,7 +424,7 @@ private boolean hasCellBender(PipelineJob job) return false; } - File jobXml = new File(job.getLogFile().getParentFile(), FileUtil.getBaseName(job.getLogFile()) + ".job.json.txt"); + File jobXml = FileUtil.appendName(job.getLogFile().getParentFile(), FileUtil.getBaseName(job.getLogFile()) + ".job.json.txt"); if (jobXml.exists()) { try (BufferedReader reader = Readers.getReader(jobXml)) From 0c2d79ec2db4152e3754dc09d69b1177b07968b6 Mon Sep 17 00:00:00 2001 From: bbimber Date: Sun, 1 Mar 2026 16:51:19 -0800 Subject: [PATCH 07/23] Improvements to MCC census --- .../demographicsMostRecentDeparture.query.xml | 3 + .../study/demographicsMostRecentDeparture.sql | 1 + mcc/resources/web/mcc/panel/MccImportPanel.js | 93 ++++++++++++++++++- 3 files changed, 92 insertions(+), 5 deletions(-) diff --git a/mcc/resources/queries/study/demographicsMostRecentDeparture.query.xml b/mcc/resources/queries/study/demographicsMostRecentDeparture.query.xml index 7136f001..69afb887 100644 --- a/mcc/resources/queries/study/demographicsMostRecentDeparture.query.xml +++ b/mcc/resources/queries/study/demographicsMostRecentDeparture.query.xml @@ -14,6 +14,9 @@ query.sort=-Date& + + Destination(s) + MCC Request Id(s) diff --git a/mcc/resources/queries/study/demographicsMostRecentDeparture.sql b/mcc/resources/queries/study/demographicsMostRecentDeparture.sql index f504e2de..b3a5b12f 100644 --- a/mcc/resources/queries/study/demographicsMostRecentDeparture.sql +++ b/mcc/resources/queries/study/demographicsMostRecentDeparture.sql @@ -2,6 +2,7 @@ select T1.Id, max(T1.date) as MostRecentDeparture, + group_concat(distinct t1.destination) as destination, group_concat(distinct t1.mccRequestId) as mccRequestId FROM study.departure T1 diff --git a/mcc/resources/web/mcc/panel/MccImportPanel.js b/mcc/resources/web/mcc/panel/MccImportPanel.js index e00d01a6..f344f593 100644 --- a/mcc/resources/web/mcc/panel/MccImportPanel.js +++ b/mcc/resources/web/mcc/panel/MccImportPanel.js @@ -29,7 +29,7 @@ Ext4.define('MCC.panel.MccImportPanel', { allowBlank: true },{ name: 'Id', - labels: ['Id', 'animal ID', 'AnimalId', 'MarmId', 'Marm Id'], + labels: ['Id', 'animal ID', 'AnimalId', 'MarmId', 'Marm Id', 'Center Id'], allowRowSpan: false, alwaysShow: true, transform: 'animalId', @@ -37,7 +37,7 @@ Ext4.define('MCC.panel.MccImportPanel', { expectInImport: true },{ name: 'alternateIds', - labels: ['Alternate Ids', 'previous Ids'], + labels: ['Alternate Ids', 'AlternateIds', 'previous Ids'], allowRowSpan: false, alwaysShow: true, transform: 'alternateIds', @@ -121,6 +121,13 @@ Ext4.define('MCC.panel.MccImportPanel', { alwaysShow: true, allowBlank: false, transform: 'date' + },{ + name: 'mccAlias', + labels: ['mccAlias', 'MCC ID', 'mccId', 'MCC_ID'], + alwaysShow: true, + allowRowSpan: false, + allowBlank: true, + expectInImport: false },{ name: 'u24_status', labels: ['U24 status'], @@ -132,7 +139,7 @@ Ext4.define('MCC.panel.MccImportPanel', { },{ name: 'availability', // NOTE: availalble was a typo in one generation of the input templates: - labels: ['Available to Transfer', 'available to transfer', 'availalble to transfer'], + labels: ['Available to Transfer', 'available to transfer', 'available to transfer'], allowRowSpan: false, allowBlank: true, transform: 'available', @@ -395,10 +402,83 @@ Ext4.define('MCC.panel.MccImportPanel', { style: 'margin-bottom: 20px;', items: [{ xtype: 'button', - text: 'Download Template', + text: 'Download Blank Template', border: true, scope: this, href: LABKEY.ActionURL.getContextPath() + '/mcc/exampleData/MCC_Data_Template.xlsx' + },{ + xtype: 'button', + text: 'Download Template', + border: true, + scope: this, + handler: function(btn){ + var colonyName = btn.up('mcc-mccimportpanel').down('#centerName') + if (!colonyName) { + Ext4.Msg.alert('Error', 'Must enter the colony name') + return + } + + Ext4.Msg.wait('Loading...'); + var fieldMap = { + 'mccAlias/externalId': 'MCC_ID', + 'Id': 'Center Id', + 'alternateIds': 'Previous IDs', + 'colony': 'Colony', + 'gender': 'Sex', + 'birth': 'Birth', + 'calculated_status': 'status', + 'Id/MostRecentDeparture/destination': 'Shipping Destination', + 'Id/MostRecentDeparture/MostRecentDeparture': 'Most Recent Departure Date', + 'death': 'Death', + 'deathCause': 'Cause of death', + 'dam': 'Dam', + 'sire': 'Sire', + 'Id/MostRecentWeight/MostRecentWeightGrams': 'Weight (g)', + 'Id/MostRecentWeight/MostRecentWeightDate': 'Date of Weight', + 'u24_status': 'U24 assigned?', + 'Id/mostRecentObservations/availability::observation': 'Availability', + 'Id/mostRecentObservations/current_housing_status::observation': 'Current Housing Status', + 'breeding partner ID': 'Breeding Partner ID', + 'Id/mostRecentObservations/infant_history::observation': 'Infant History', + 'Id/mostRecentObservations/fertility_status::observation': 'Fertility Status', + 'Id/mostRecentObservations/medical_history::observation': 'Medical History', + 'Id/mostRecentObservations/usage_current::observation': 'Usage (Current)', + 'Id/mostRecentObservations/usage_future::observation': 'Usage (Future)' + } + + LABKEY.Query.selectRows({ + schemaName: 'study', + queryName: 'demographics', + columns: 'Id,alternateIds,dam,sire,birth,death,colony,objectid,lsid,,Id/death/date,Id/MostRecentDeparture/MostRecentDeparture', + scope: this, + failure: LDK.Utils.getErrorCallback(), + filterArray: [LABKEY.Filter.create('colony', colonyName)], + success: function (results) { + Ext4.Msg.hide(); + + const rows = results.map(row => { + const newRow = [] + Object.keys(fieldMap).forEach(key => { + if (row[key] !== undefined) { + newRow.push(row[key]) + } else { + newRow.push('') + } + }) + + return newRow + }) + + LABKEY.Utils.convertToExcel({ + fileName : 'MCC_Import_' + colonyName + '.xlsx', + sheets : [{ + name: 'data', + data: Object.values(fieldMap).concat(rows) + }] + }); + } + }); + } }] },{ xtype: 'datefield', @@ -523,13 +603,15 @@ Ext4.define('MCC.panel.MccImportPanel', { row.existingRecord = row.Id && demographicsRecords.allIds.indexOf(row.Id.toLowerCase()) > -1; if (row.existingRecord) { var existingRecord = demographicsRecords.rowMap[row.Id.toLowerCase()]; + existingRecord.mccAlias = existingRecord['mccAlias/externalId'] + if (existingRecord.colony !== row.colony) { row.errors.push('Colony does not match existing row: ' + existingRecord.colony); } else { row.objectId = existingRecord.objectid; - var fields = ['birth', 'dam', 'sire', 'source']; + var fields = ['birth', 'dam', 'sire', 'source', 'mccAlias']; for (var idx in fields) { var fn = fields[idx]; @@ -1110,6 +1192,7 @@ Ext4.define('MCC.panel.MccImportPanel', { Ext4.Array.forEach(rawData, function(row){ if (row.existingRecord) { + // Note: this was merged with the existing values upstream of this demographicsUpdates.push({ Id: row.Id, date: row.date, From be2e0e8f3bd9a608bd20fb221639b7d6fffd2236 Mon Sep 17 00:00:00 2001 From: bbimber Date: Sun, 1 Mar 2026 17:55:37 -0800 Subject: [PATCH 08/23] Improvements to MCC census --- mcc/resources/web/mcc/panel/MccImportPanel.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mcc/resources/web/mcc/panel/MccImportPanel.js b/mcc/resources/web/mcc/panel/MccImportPanel.js index f344f593..fb59ab6b 100644 --- a/mcc/resources/web/mcc/panel/MccImportPanel.js +++ b/mcc/resources/web/mcc/panel/MccImportPanel.js @@ -408,11 +408,11 @@ Ext4.define('MCC.panel.MccImportPanel', { href: LABKEY.ActionURL.getContextPath() + '/mcc/exampleData/MCC_Data_Template.xlsx' },{ xtype: 'button', - text: 'Download Template', + text: 'Download Template Data', border: true, scope: this, handler: function(btn){ - var colonyName = btn.up('mcc-mccimportpanel').down('#centerName') + var colonyName = btn.up('mcc-mccimportpanel').down('#centerName').getValue() if (!colonyName) { Ext4.Msg.alert('Error', 'Must enter the colony name') return @@ -456,7 +456,7 @@ Ext4.define('MCC.panel.MccImportPanel', { success: function (results) { Ext4.Msg.hide(); - const rows = results.map(row => { + const rows = results.rows.map(row => { const newRow = [] Object.keys(fieldMap).forEach(key => { if (row[key] !== undefined) { From 83ef6d4c098f222853556ddbe55832c2d767aef1 Mon Sep 17 00:00:00 2001 From: bbimber Date: Sun, 1 Mar 2026 19:53:42 -0800 Subject: [PATCH 09/23] Improvements to MCC census --- mcc/resources/web/mcc/panel/MccImportPanel.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mcc/resources/web/mcc/panel/MccImportPanel.js b/mcc/resources/web/mcc/panel/MccImportPanel.js index fb59ab6b..e198cc19 100644 --- a/mcc/resources/web/mcc/panel/MccImportPanel.js +++ b/mcc/resources/web/mcc/panel/MccImportPanel.js @@ -409,6 +409,7 @@ Ext4.define('MCC.panel.MccImportPanel', { },{ xtype: 'button', text: 'Download Template Data', + style: 'padding-left: 5px', border: true, scope: this, handler: function(btn){ @@ -473,7 +474,7 @@ Ext4.define('MCC.panel.MccImportPanel', { fileName : 'MCC_Import_' + colonyName + '.xlsx', sheets : [{ name: 'data', - data: Object.values(fieldMap).concat(rows) + data: [Object.values(fieldMap)].concat(rows) }] }); } From da3733fd9e43c50a66709e15f4c4a2180388211c Mon Sep 17 00:00:00 2001 From: bbimber Date: Sun, 1 Mar 2026 20:55:46 -0800 Subject: [PATCH 10/23] Improvements to MCC census and MCC ID --- mcc/resources/web/mcc/panel/MccImportPanel.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mcc/resources/web/mcc/panel/MccImportPanel.js b/mcc/resources/web/mcc/panel/MccImportPanel.js index e198cc19..4fc32a03 100644 --- a/mcc/resources/web/mcc/panel/MccImportPanel.js +++ b/mcc/resources/web/mcc/panel/MccImportPanel.js @@ -421,7 +421,7 @@ Ext4.define('MCC.panel.MccImportPanel', { Ext4.Msg.wait('Loading...'); var fieldMap = { - 'mccAlias/externalId': 'MCC_ID', + 'Id/mccAlias/externalId': 'MCC_ID', 'Id': 'Center Id', 'alternateIds': 'Previous IDs', 'colony': 'Colony', @@ -561,7 +561,7 @@ Ext4.define('MCC.panel.MccImportPanel', { LABKEY.Query.selectRows({ schemaName: 'study', queryName: 'demographics', - columns: 'Id,alternateIds,dam,sire,birth,death,colony,objectid,lsid,mccAlias/externalId,Id/death/date,Id/MostRecentDeparture/MostRecentDeparture', + columns: 'Id,alternateIds,dam,sire,birth,death,colony,objectid,lsid,Id/mccAlias/externalId,Id/death/date,Id/MostRecentDeparture/MostRecentDeparture', scope: this, failure: LDK.Utils.getErrorCallback(), success: function(results) { @@ -604,7 +604,7 @@ Ext4.define('MCC.panel.MccImportPanel', { row.existingRecord = row.Id && demographicsRecords.allIds.indexOf(row.Id.toLowerCase()) > -1; if (row.existingRecord) { var existingRecord = demographicsRecords.rowMap[row.Id.toLowerCase()]; - existingRecord.mccAlias = existingRecord['mccAlias/externalId'] + existingRecord.mccAlias = existingRecord['Id/mccAlias/externalId'] if (existingRecord.colony !== row.colony) { row.errors.push('Colony does not match existing row: ' + existingRecord.colony); From 0109d5d22673ffbfdd0b823e779314dddef413c5 Mon Sep 17 00:00:00 2001 From: bbimber Date: Mon, 2 Mar 2026 06:02:22 -0800 Subject: [PATCH 11/23] Update csp policy --- mGAP/src/org/labkey/mgap/mGAPModule.java | 2 +- mcc/resources/web/mcc/panel/MccImportPanel.js | 2 +- mcc/src/org/labkey/mcc/MccModule.java | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/mGAP/src/org/labkey/mgap/mGAPModule.java b/mGAP/src/org/labkey/mgap/mGAPModule.java index b702a830..f46733b0 100644 --- a/mGAP/src/org/labkey/mgap/mGAPModule.java +++ b/mGAP/src/org/labkey/mgap/mGAPModule.java @@ -110,7 +110,7 @@ public void doStartupAfterSpringConfig(ModuleContext moduleContext) ContentSecurityPolicyFilter.registerAllowedSources(this.getClass().getName(), Directive.Connection, "https://code.jquery.com", "https://*.fontawesome.com"); ContentSecurityPolicyFilter.registerAllowedSources(this.getClass().getName(), Directive.Style, "https://code.jquery.com", "https://www.gstatic.com"); - ContentSecurityPolicyFilter.registerAllowedSources(this.getClass().getName(), Directive.Font, "https://*.fontawesome.com"); + ContentSecurityPolicyFilter.registerAllowedSources(this.getClass().getName(), Directive.Font, "https://*.fontawesome.com", "https://fonts.googleapis.com"); ContentSecurityPolicyFilter.registerAllowedSources(this.getClass().getName(), Directive.Connection, "https://oss.maxcdn.com"); new PipelineStartup(); diff --git a/mcc/resources/web/mcc/panel/MccImportPanel.js b/mcc/resources/web/mcc/panel/MccImportPanel.js index 4fc32a03..d5b134b1 100644 --- a/mcc/resources/web/mcc/panel/MccImportPanel.js +++ b/mcc/resources/web/mcc/panel/MccImportPanel.js @@ -450,7 +450,7 @@ Ext4.define('MCC.panel.MccImportPanel', { LABKEY.Query.selectRows({ schemaName: 'study', queryName: 'demographics', - columns: 'Id,alternateIds,dam,sire,birth,death,colony,objectid,lsid,,Id/death/date,Id/MostRecentDeparture/MostRecentDeparture', + columns: Object.keys(fieldMap).join(','), scope: this, failure: LDK.Utils.getErrorCallback(), filterArray: [LABKEY.Filter.create('colony', colonyName)], diff --git a/mcc/src/org/labkey/mcc/MccModule.java b/mcc/src/org/labkey/mcc/MccModule.java index 11292760..2528fcdc 100644 --- a/mcc/src/org/labkey/mcc/MccModule.java +++ b/mcc/src/org/labkey/mcc/MccModule.java @@ -138,9 +138,10 @@ protected void doStartupAfterSpringConfig(ModuleContext moduleContext) SystemMaintenance.addTask(new MccMaintenanceTask()); - ContentSecurityPolicyFilter.registerAllowedSources(this.getClass().getName(), Directive.Connection, "https://cdn.datatables.net"); - ContentSecurityPolicyFilter.registerAllowedSources(this.getClass().getName(), Directive.Style, "https://cdn.datatables.net"); + ContentSecurityPolicyFilter.registerAllowedSources(this.getClass().getName(), Directive.Connection, "https://cdn.datatables.net", "https://code.jquery.com", "https://*.fontawesome.com", "https://oss.maxcdn.com"); + ContentSecurityPolicyFilter.registerAllowedSources(this.getClass().getName(), Directive.Style, "https://cdn.datatables.net", "https://code.jquery.com", "https://www.gstatic.com"); ContentSecurityPolicyFilter.registerAllowedSources(this.getClass().getName(), Directive.Image, "https://cdn.datatables.net"); + ContentSecurityPolicyFilter.registerAllowedSources(this.getClass().getName(), Directive.Font, "https://*.fontawesome.com", "https://fonts.googleapis.com"); } @Override From 51a2ddfa7d6b8ddf1ef1d7e9b7a2fa4dbe8ca6b9 Mon Sep 17 00:00:00 2001 From: bbimber Date: Mon, 2 Mar 2026 06:52:15 -0800 Subject: [PATCH 12/23] Handle arrays in row values --- mcc/resources/views/mccDataImport.view.xml | 7 ------- mcc/resources/web/mcc/panel/MccImportPanel.js | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/mcc/resources/views/mccDataImport.view.xml b/mcc/resources/views/mccDataImport.view.xml index 8418d6d3..63e81ecc 100644 --- a/mcc/resources/views/mccDataImport.view.xml +++ b/mcc/resources/views/mccDataImport.view.xml @@ -19,12 +19,5 @@ - - - - - - - \ No newline at end of file diff --git a/mcc/resources/web/mcc/panel/MccImportPanel.js b/mcc/resources/web/mcc/panel/MccImportPanel.js index d5b134b1..ca191b27 100644 --- a/mcc/resources/web/mcc/panel/MccImportPanel.js +++ b/mcc/resources/web/mcc/panel/MccImportPanel.js @@ -461,7 +461,7 @@ Ext4.define('MCC.panel.MccImportPanel', { const newRow = [] Object.keys(fieldMap).forEach(key => { if (row[key] !== undefined) { - newRow.push(row[key]) + newRow.push(Ext4.isArray(row[key]) ? row[key].join(',') : '') } else { newRow.push('') } From 25ad31e67d0de31ddb2f3e00cfc6405c67f2085c Mon Sep 17 00:00:00 2001 From: bbimber Date: Mon, 2 Mar 2026 06:52:51 -0800 Subject: [PATCH 13/23] Handle arrays in row values --- mcc/resources/web/mcc/panel/MccImportPanel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mcc/resources/web/mcc/panel/MccImportPanel.js b/mcc/resources/web/mcc/panel/MccImportPanel.js index ca191b27..f5b8ea8b 100644 --- a/mcc/resources/web/mcc/panel/MccImportPanel.js +++ b/mcc/resources/web/mcc/panel/MccImportPanel.js @@ -461,7 +461,7 @@ Ext4.define('MCC.panel.MccImportPanel', { const newRow = [] Object.keys(fieldMap).forEach(key => { if (row[key] !== undefined) { - newRow.push(Ext4.isArray(row[key]) ? row[key].join(',') : '') + newRow.push(Ext4.isArray(row[key]) ? row[key].join(',') : row[key]) } else { newRow.push('') } From 1b3034ae4936f977c977c1aaff1467c4644cc29f Mon Sep 17 00:00:00 2001 From: bbimber Date: Mon, 2 Mar 2026 07:35:38 -0800 Subject: [PATCH 14/23] Another CSP update --- mcc/resources/web/mcc/panel/MccImportPanel.js | 11 +++++++---- mcc/src/org/labkey/mcc/MccModule.java | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/mcc/resources/web/mcc/panel/MccImportPanel.js b/mcc/resources/web/mcc/panel/MccImportPanel.js index f5b8ea8b..32bcd794 100644 --- a/mcc/resources/web/mcc/panel/MccImportPanel.js +++ b/mcc/resources/web/mcc/panel/MccImportPanel.js @@ -421,7 +421,7 @@ Ext4.define('MCC.panel.MccImportPanel', { Ext4.Msg.wait('Loading...'); var fieldMap = { - 'Id/mccAlias/externalId': 'MCC_ID', + 'Id/mccAlias/externalAlias': 'MCC_ID', 'Id': 'Center Id', 'alternateIds': 'Previous IDs', 'colony': 'Colony', @@ -453,7 +453,10 @@ Ext4.define('MCC.panel.MccImportPanel', { columns: Object.keys(fieldMap).join(','), scope: this, failure: LDK.Utils.getErrorCallback(), - filterArray: [LABKEY.Filter.create('colony', colonyName)], + filterArray: [ + LABKEY.Filter.create('colony', colonyName), + LABKEY.Filter.create('calculated_status', 'Alive') + ], success: function (results) { Ext4.Msg.hide(); @@ -561,7 +564,7 @@ Ext4.define('MCC.panel.MccImportPanel', { LABKEY.Query.selectRows({ schemaName: 'study', queryName: 'demographics', - columns: 'Id,alternateIds,dam,sire,birth,death,colony,objectid,lsid,Id/mccAlias/externalId,Id/death/date,Id/MostRecentDeparture/MostRecentDeparture', + columns: 'Id,alternateIds,dam,sire,birth,death,colony,objectid,lsid,Id/mccAlias/externalAlias,Id/death/date,Id/MostRecentDeparture/MostRecentDeparture', scope: this, failure: LDK.Utils.getErrorCallback(), success: function(results) { @@ -604,7 +607,7 @@ Ext4.define('MCC.panel.MccImportPanel', { row.existingRecord = row.Id && demographicsRecords.allIds.indexOf(row.Id.toLowerCase()) > -1; if (row.existingRecord) { var existingRecord = demographicsRecords.rowMap[row.Id.toLowerCase()]; - existingRecord.mccAlias = existingRecord['Id/mccAlias/externalId'] + existingRecord.mccAlias = existingRecord['Id/mccAlias/externalAlias'] if (existingRecord.colony !== row.colony) { row.errors.push('Colony does not match existing row: ' + existingRecord.colony); diff --git a/mcc/src/org/labkey/mcc/MccModule.java b/mcc/src/org/labkey/mcc/MccModule.java index 2528fcdc..638a94fc 100644 --- a/mcc/src/org/labkey/mcc/MccModule.java +++ b/mcc/src/org/labkey/mcc/MccModule.java @@ -141,7 +141,7 @@ protected void doStartupAfterSpringConfig(ModuleContext moduleContext) ContentSecurityPolicyFilter.registerAllowedSources(this.getClass().getName(), Directive.Connection, "https://cdn.datatables.net", "https://code.jquery.com", "https://*.fontawesome.com", "https://oss.maxcdn.com"); ContentSecurityPolicyFilter.registerAllowedSources(this.getClass().getName(), Directive.Style, "https://cdn.datatables.net", "https://code.jquery.com", "https://www.gstatic.com"); ContentSecurityPolicyFilter.registerAllowedSources(this.getClass().getName(), Directive.Image, "https://cdn.datatables.net"); - ContentSecurityPolicyFilter.registerAllowedSources(this.getClass().getName(), Directive.Font, "https://*.fontawesome.com", "https://fonts.googleapis.com"); + ContentSecurityPolicyFilter.registerAllowedSources(this.getClass().getName(), Directive.Font, "https://*.fontawesome.com", "https://fonts.googleapis.com", "https://www.gstatic.com"); } @Override From 6629e5ae5b05f065eca6f4cfe74daa3351be4896 Mon Sep 17 00:00:00 2001 From: bbimber Date: Mon, 2 Mar 2026 16:58:13 -0800 Subject: [PATCH 15/23] Major refactor of underlying SBT SQL queries (#212) * Major refactor of underlying SBT SQL queries * Small code cleanup --- GenotypeAssays/resources/views/sbtReview.html | 16 ++++------ .../resources/web/genotypeassays/buttons.js | 2 +- .../genotypeassays/panel/HaplotypePanel.js | 14 ++++----- .../window/PublishResultsWindow.js | 10 ------- .../GenotypeAssaysController.java | 29 +++++++------------ .../genotypeassays/GenotypeAssaysManager.java | 3 +- .../pipeline/MhcCleanupPipelineJob.java | 6 ---- 7 files changed, 24 insertions(+), 56 deletions(-) diff --git a/GenotypeAssays/resources/views/sbtReview.html b/GenotypeAssays/resources/views/sbtReview.html index 695e7fd1..5c0b6363 100644 --- a/GenotypeAssays/resources/views/sbtReview.html +++ b/GenotypeAssays/resources/views/sbtReview.html @@ -159,11 +159,9 @@ //sort: '-percent', containerPath: Laboratory.Utils.getQueryContainerPath(), removeableFilters: [ - LABKEY.Filter.create('percent_from_locus', 0.25, LABKEY.Filter.Types.GTE) + LABKEY.Filter.create('percent_from_locus', 0.25, LABKEY.Filter.Types.GTE), + LABKEY.Filter.create('analysis_id', analysisId, LABKEY.Filter.Types.EQUALS) ], - parameters: { - AnalysisId: analysisId - }, scope: this, success: this.onDataRegionLoad }) @@ -180,12 +178,10 @@ containerPath: Laboratory.Utils.getQueryContainerPath(), removeableFilters: [ LABKEY.Filter.create('percent_from_locus', 0.25, LABKEY.Filter.Types.GTE), - LABKEY.Filter.create('total_reads', 5, LABKEY.Filter.Types.GT), - LABKEY.Filter.create('totalLineages', 1, LABKEY.Filter.Types.EQUAL) + LABKEY.Filter.create('total_reads_from_locus', 5, LABKEY.Filter.Types.GT), + LABKEY.Filter.create('totalLineages', 1, LABKEY.Filter.Types.EQUAL), + LABKEY.Filter.create('analysis_id', analysisId, LABKEY.Filter.Types.EQUAL) ], - parameters: { - AnalysisId: analysisId - }, scope: this, success: this.onDataRegionLoad }) @@ -195,7 +191,7 @@ title: 'Haplotype Matches', items: [{ xtype: 'genotypeassays-haplotypepanel', - analysisId: [analysisId] + analysisIds: [analysisId] }] },{ xtype: 'ldk-querypanel', diff --git a/GenotypeAssays/resources/web/genotypeassays/buttons.js b/GenotypeAssays/resources/web/genotypeassays/buttons.js index f2c02242..6ba13c61 100644 --- a/GenotypeAssays/resources/web/genotypeassays/buttons.js +++ b/GenotypeAssays/resources/web/genotypeassays/buttons.js @@ -28,7 +28,7 @@ GenotypeAssays.buttons = new function(){ var newForm = Ext4.DomHelper.append(document.getElementsByTagName('body')[0], '
' + - '' + + '' + '
'); newForm.submit(); } diff --git a/GenotypeAssays/resources/web/genotypeassays/panel/HaplotypePanel.js b/GenotypeAssays/resources/web/genotypeassays/panel/HaplotypePanel.js index aacc05bd..fb690dc3 100644 --- a/GenotypeAssays/resources/web/genotypeassays/panel/HaplotypePanel.js +++ b/GenotypeAssays/resources/web/genotypeassays/panel/HaplotypePanel.js @@ -1,7 +1,7 @@ Ext4.define('GenotypeAssays.panel.HaplotypePanel', { extend: 'Ext.panel.Panel', alias: 'widget.genotypeassays-haplotypepanel', - analysisId: null, + analysisIds: null, showCheckBoxes: false, initComponent: function(){ @@ -461,13 +461,11 @@ Ext4.define('GenotypeAssays.panel.HaplotypePanel', { schemaName: 'sequenceanalysis', queryName: 'alignment_summary_by_lineage', columns: 'analysis_id,analysis_id/readset,analysis_id/readset/subjectId,lineages,loci,total,total_reads,percent,total_reads_from_locus,percent_from_locus', - parameters: { - AnalysisId: this.analysisId - }, apiVersion: 13.2, scope: this, filterArray: [ - LABKEY.Filter.create('percent_from_locus', minPct || 0, LABKEY.Filter.Types.GTE) + LABKEY.Filter.create('percent_from_locus', minPct || 0, LABKEY.Filter.Types.GTE), + LABKEY.Filter.create('analysis_id', this.analysisIds, LABKEY.Filter.Types.IN) ], failure: LDK.Utils.getErrorCallback(), success: function(results){ @@ -501,13 +499,13 @@ Ext4.define('GenotypeAssays.panel.HaplotypePanel', { schemaName: 'sequenceanalysis', queryName: 'alignment_summary_grouped', columns: 'analysis_id,lineages,loci,alleles,total_reads,percent,total_reads_from_locus,percent_from_locus', + filterArray: [ + LABKEY.Filter.create('analysis_id', this.analysisIds, LABKEY.Filter.Types.IN) + ], // This is designed to remove the view-level sorts: sort: 'analysis_id', apiVersion: 13.2, scope: this, - parameters: { - AnalysisId: this.analysisId - }, failure: LDK.Utils.getErrorCallback(), success: function(results){ this.lineageToAlleleMap = {}; diff --git a/GenotypeAssays/resources/web/genotypeassays/window/PublishResultsWindow.js b/GenotypeAssays/resources/web/genotypeassays/window/PublishResultsWindow.js index d2969f4b..7be55345 100644 --- a/GenotypeAssays/resources/web/genotypeassays/window/PublishResultsWindow.js +++ b/GenotypeAssays/resources/web/genotypeassays/window/PublishResultsWindow.js @@ -11,17 +11,8 @@ Ext4.define('GenotypeAssays.window.PublishResultsWindow', { return; } - const analysisId = dr.getParameters()?.AnalysisId; - if (!analysisId) { - Ext4.Msg.alert('Error', 'Error: unable to find analysisId. This should not occur.'); - LDK.Assert.assertNotEmpty('Unable to find AnalysisId parameter from the DataRegion in PublishResultsWindow'); - - return; - } - Ext4.create('GenotypeAssays.window.PublishResultsWindow', { dataRegionName: dataRegionName, - analysisId: analysisId, actionName: 'cacheAnalyses' }).show(); } @@ -116,7 +107,6 @@ Ext4.define('GenotypeAssays.window.PublishResultsWindow', { scope: this, jsonData: { alleleNames: alleleNames, - analysisId: this.analysisId, json: Ext4.encode(this.json), protocolId: protocol }, diff --git a/GenotypeAssays/src/org/labkey/genotypeassays/GenotypeAssaysController.java b/GenotypeAssays/src/org/labkey/genotypeassays/GenotypeAssaysController.java index eb14a419..b2b6cf87 100644 --- a/GenotypeAssays/src/org/labkey/genotypeassays/GenotypeAssaysController.java +++ b/GenotypeAssays/src/org/labkey/genotypeassays/GenotypeAssaysController.java @@ -24,6 +24,7 @@ import org.labkey.api.action.MutatingApiAction; import org.labkey.api.action.SpringActionController; import org.labkey.api.data.DbSchema; +import org.labkey.api.data.DbSchemaType; import org.labkey.api.data.SqlExecutor; import org.labkey.api.data.TableInfo; import org.labkey.api.exp.api.ExpProtocol; @@ -31,6 +32,7 @@ import org.labkey.api.security.RequiresPermission; import org.labkey.api.security.permissions.ReadPermission; import org.labkey.api.security.permissions.UpdatePermission; +import org.labkey.api.util.HtmlString; import org.labkey.api.util.Pair; import org.labkey.api.util.URLHelper; import org.labkey.api.view.HtmlView; @@ -53,7 +55,7 @@ public GenotypeAssaysController() } @RequiresPermission(ReadPermission.class) - public class MigrateLegacySSPAction extends ConfirmAction + public static class MigrateLegacySSPAction extends ConfirmAction { @Override public void validateCommand(Object form, Errors errors) @@ -64,11 +66,11 @@ public void validateCommand(Object form, Errors errors) @Override public ModelAndView getConfirmView(Object form, BindException errors) throws Exception { - DbSchema schema = DbSchema.get("SSP_Assay"); + DbSchema schema = DbSchema.get("SSP_Assay", DbSchemaType.Module); if (schema == null) - return new HtmlView("Either the legacy SSP module has not been installed, or it has already been removed"); + return new HtmlView(HtmlString.of("Either the legacy SSP module has not been installed, or it has already been removed")); else - return new HtmlView("This allows an admin to copy any primers stored in the original SSP Assay module into the new genotyping module. Any data has already been copied. Do you want to continue?"); + return new HtmlView(HtmlString.of("This allows an admin to copy any primers stored in the original SSP Assay module into the new genotyping module. Any data has already been copied. Do you want to continue?")); } @Override @@ -76,7 +78,7 @@ public boolean handlePost(Object form, BindException errors) throws Exception { try { - DbSchema schema = DbSchema.get("SSP_Assay"); + DbSchema schema = DbSchema.get("SSP_Assay", DbSchemaType.Module); if (schema == null) return true; //module not installed @@ -113,7 +115,7 @@ public URLHelper getSuccessURL(Object form) } @RequiresPermission(UpdatePermission.class) - public class CacheAnalysesAction extends MutatingApiAction + public static class CacheAnalysesAction extends MutatingApiAction { @Override public ApiResponse execute(CacheAnalysesForm form, BindException errors) @@ -133,7 +135,7 @@ public ApiResponse execute(CacheAnalysesForm form, BindException errors) } String[] alleleNames = Arrays.stream(form.getAlleleNames()).map(StringEscapeUtils::unescapeHtml4).toArray(String[]::new); - Pair, List> ret = GenotypeAssaysManager.get().cacheAnalyses(getViewContext(), form.getAnalysisId(), protocol, alleleNames); + Pair, List> ret = GenotypeAssaysManager.get().cacheAnalyses(getViewContext(), protocol, alleleNames); resultProperties.put("runsCreated", ret.first); resultProperties.put("runsDeleted", ret.second); } @@ -159,7 +161,6 @@ public static class CacheAnalysesForm { private String[] _alleleNames; private String _json; - private int _analysisId; private int _protocolId; public String[] getAlleleNames() @@ -191,20 +192,10 @@ public void setJson(String json) { _json = json; } - - public int getAnalysisId() - { - return _analysisId; - } - - public void setAnalysisId(int analysisId) - { - _analysisId = analysisId; - } } @RequiresPermission(UpdatePermission.class) - public class CacheHaplotypesAction extends MutatingApiAction + public static class CacheHaplotypesAction extends MutatingApiAction { @Override public ApiResponse execute(CacheAnalysesForm form, BindException errors) diff --git a/GenotypeAssays/src/org/labkey/genotypeassays/GenotypeAssaysManager.java b/GenotypeAssays/src/org/labkey/genotypeassays/GenotypeAssaysManager.java index a4425d0a..ba6e35d8 100644 --- a/GenotypeAssays/src/org/labkey/genotypeassays/GenotypeAssaysManager.java +++ b/GenotypeAssays/src/org/labkey/genotypeassays/GenotypeAssaysManager.java @@ -90,7 +90,7 @@ public static GenotypeAssaysManager get() return _instance; } - public Pair, List> cacheAnalyses(final ViewContext ctx, final int analysisId, final ExpProtocol protocol, String[] pks) throws IllegalArgumentException + public Pair, List> cacheAnalyses(final ViewContext ctx, final ExpProtocol protocol, String[] pks) throws IllegalArgumentException { final User u = ctx.getUser(); final List runsCreated = new ArrayList<>(); @@ -125,7 +125,6 @@ public Pair, List> cacheAnalyses(final ViewContext ctx, final i AtomicInteger records = new AtomicInteger(); TableSelector tsAlignments = new TableSelector(tableAlignments, cols.values(), new SimpleFilter(FieldKey.fromString("key"), Arrays.asList(pks), CompareType.IN), null); - tsAlignments.setNamedParameters(Map.of("AnalysisId", analysisId)); tsAlignments.forEach(new Selector.ForEachBlock() { diff --git a/primeseq/src/org/labkey/primeseq/pipeline/MhcCleanupPipelineJob.java b/primeseq/src/org/labkey/primeseq/pipeline/MhcCleanupPipelineJob.java index 05a526ae..fe2f8b24 100644 --- a/primeseq/src/org/labkey/primeseq/pipeline/MhcCleanupPipelineJob.java +++ b/primeseq/src/org/labkey/primeseq/pipeline/MhcCleanupPipelineJob.java @@ -285,7 +285,6 @@ private void processAnalysis(int analysisId) dataFilter.addCondition(FieldKey.fromString("percent_from_locus"), getPipelineJob().getLineageThreshold(), CompareType.GT); TableSelector ts = new TableSelector(QueryService.get().getUserSchema(getJob().getUser(), getJob().getContainer(), "sequenceanalysis").getTable("alignment_summary_by_lineage"), PageFlowUtil.set("lineages", "percent_from_locus"), dataFilter, null); - ts.setNamedParameters(Map.of("AnalysisId", analysisId)); ts.forEachResults(rs -> { existingData.put(rs.getString(FieldKey.fromString("lineages")), rs.getDouble(FieldKey.fromString("percent_from_locus"))); @@ -337,7 +336,6 @@ private void processAnalysis(int analysisId) filter.addCondition(FieldKey.fromString("percent_from_locus"), getPipelineJob().getAlleleGroupThreshold(), CompareType.LT); ts = new TableSelector(QueryService.get().getUserSchema(getJob().getUser(), getJob().getContainer(), "sequenceanalysis").getTable("alignment_summary_grouped"), PageFlowUtil.set("rowids"), filter, null); - ts.setNamedParameters(Map.of("AnalysisId", analysisId)); List lowFreqRowIdList = ts.getArrayList(String.class); if (!lowFreqRowIdList.isEmpty()) { @@ -373,7 +371,6 @@ private void processAnalysis(int analysisId) filter.addCondition(FieldKey.fromString("loci"), "MHC", CompareType.CONTAINS); ts = new TableSelector(QueryService.get().getUserSchema(getJob().getUser(), getJob().getContainer(), "sequenceanalysis").getTable("alignment_summary_grouped"), PageFlowUtil.set("rowids"), filter, null); - ts.setNamedParameters(Map.of("AnalysisId", analysisId)); List rowIdList = ts.getArrayList(String.class); if (!rowIdList.isEmpty()) { @@ -392,7 +389,6 @@ private void processAnalysis(int analysisId) SimpleFilter nAlignmentFilter = new SimpleFilter(FieldKey.fromString("analysis_id"), analysisId, CompareType.EQUAL); nAlignmentFilter.addCondition(FieldKey.fromString("nAlignments"), 1, CompareType.GT); ts = new TableSelector(QueryService.get().getUserSchema(getJob().getUser(), getJob().getContainer(), "sequenceanalysis").getTable("alignment_summary_grouped"), PageFlowUtil.set("rowids"), nAlignmentFilter, null); - ts.setNamedParameters(Map.of("AnalysisId", analysisId)); List redundantAlignmentSets = ts.getArrayList(String.class); if (!redundantAlignmentSets.isEmpty()) { @@ -460,7 +456,6 @@ private void processAnalysis(int analysisId) // verify ending data: final Map endingData = new HashMap<>(); ts = new TableSelector(QueryService.get().getUserSchema(getJob().getUser(), getJob().getContainer(), "sequenceanalysis").getTable("alignment_summary_by_lineage"), PageFlowUtil.set("lineages", "percent_from_locus"), dataFilter, null); - ts.setNamedParameters(Map.of("AnalysisId", analysisId)); ts.forEachResults(rs -> { endingData.put(rs.getString(FieldKey.fromString("lineages")), rs.getDouble(FieldKey.fromString("percent_from_locus"))); }); @@ -531,7 +526,6 @@ public AlignmentGroupCompare(final int analysisId, Container c, User u) this.analysisId = analysisId; TableSelector ts = new TableSelector(QueryService.get().getUserSchema(u, c, "sequenceanalysis").getTable("alignment_summary_grouped"), PageFlowUtil.set("analysis_id", "alleles", "lineages", "totalLineages", "total_reads", "total_forward", "total_reverse", "valid_pairs", "rowids"), new SimpleFilter(FieldKey.fromString("analysis_id"), analysisId), null); - ts.setNamedParameters(Map.of("AnalysisId", analysisId)); ts.forEachResults(rs -> { if (rs.getString(FieldKey.fromString("alleles")) == null) { From 59511d2053a621392452e8be369f7909b3af5c50 Mon Sep 17 00:00:00 2001 From: bbimber Date: Mon, 9 Mar 2026 14:34:23 -0700 Subject: [PATCH 16/23] Add logging to GeographicOriginStep --- mGAP/src/org/labkey/mgap/pipeline/GeographicOriginStep.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mGAP/src/org/labkey/mgap/pipeline/GeographicOriginStep.java b/mGAP/src/org/labkey/mgap/pipeline/GeographicOriginStep.java index b6da9bf1..34cf4656 100644 --- a/mGAP/src/org/labkey/mgap/pipeline/GeographicOriginStep.java +++ b/mGAP/src/org/labkey/mgap/pipeline/GeographicOriginStep.java @@ -86,6 +86,7 @@ public void complete(PipelineJob job, List inputs, List> toInsert = new ArrayList<>(); try (CSVReader reader = new CSVReader(Readers.getReader(so.getFile()), '\t')) { @@ -120,6 +121,10 @@ public void complete(PipelineJob job, List inputs, List Date: Tue, 10 Mar 2026 15:12:45 -0700 Subject: [PATCH 17/23] Fix column title --- tcrdb/resources/schemas/tcrdb.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tcrdb/resources/schemas/tcrdb.xml b/tcrdb/resources/schemas/tcrdb.xml index 0df8f091..a68916e0 100644 --- a/tcrdb/resources/schemas/tcrdb.xml +++ b/tcrdb/resources/schemas/tcrdb.xml @@ -165,7 +165,7 @@ CDR3 With Segments - CDR3 With Productivity + CDR3 With Productivity Cognate CDR3s From ced3f6f613270068446fd35cba8f5c6222d66bf8 Mon Sep 17 00:00:00 2001 From: bbimber Date: Wed, 11 Mar 2026 16:43:12 -0700 Subject: [PATCH 18/23] Update MCC import templates --- .../mcc/exampleData/MCC_Data_Template.xlsx | Bin 14358 -> 17031 bytes mcc/resources/web/mcc/panel/MccImportPanel.js | 26 ++++++++++-------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/mcc/resources/web/mcc/exampleData/MCC_Data_Template.xlsx b/mcc/resources/web/mcc/exampleData/MCC_Data_Template.xlsx index 916878789c50f9193e630fefc4ac6a63807cae7c..26b4e6ee8e985cd865678f1f147c2cfd388d1874 100644 GIT binary patch delta 12086 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 diff --git a/mcc/resources/web/mcc/panel/MccImportPanel.js b/mcc/resources/web/mcc/panel/MccImportPanel.js index 32bcd794..797829bd 100644 --- a/mcc/resources/web/mcc/panel/MccImportPanel.js +++ b/mcc/resources/web/mcc/panel/MccImportPanel.js @@ -138,7 +138,7 @@ Ext4.define('MCC.panel.MccImportPanel', { expectInImport: true },{ name: 'availability', - // NOTE: availalble was a typo in one generation of the input templates: + // NOTE: available was a typo in one generation of the input templates: labels: ['Available to Transfer', 'available to transfer', 'available to transfer'], allowRowSpan: false, allowBlank: true, @@ -396,7 +396,7 @@ Ext4.define('MCC.panel.MccImportPanel', { getPanelItems: function(){ return [{ style: 'padding-top: 10px;', - html: 'This page is designed to help import MCC animal-level data. Use the fields below to download the excel template and paste data to import.

' + html: 'This page is designed to help import MCC animal-level data. Use the fields below to download the excel template and paste data to import. The general idea is: 1) Download a blank excel template. This excel workbook contains dropdowns, etc., 2) Use the second button to download a table with the current data for the selected colony. 3) Copy/paste that raw data into the template.

' },{ layout: 'hbox', style: 'margin-bottom: 20px;', @@ -424,27 +424,26 @@ Ext4.define('MCC.panel.MccImportPanel', { 'Id/mccAlias/externalAlias': 'MCC_ID', 'Id': 'Center Id', 'alternateIds': 'Previous IDs', - 'colony': 'Colony', + 'colony': 'Current Colony', + 'source': 'Source Colony', 'gender': 'Sex', 'birth': 'Birth', 'calculated_status': 'status', 'Id/MostRecentDeparture/destination': 'Shipping Destination', - 'Id/MostRecentDeparture/MostRecentDeparture': 'Most Recent Departure Date', + 'Id/MostRecentDeparture/MostRecentDeparture': 'Shipping Date', 'death': 'Death', - 'deathCause': 'Cause of death', - 'dam': 'Dam', - 'sire': 'Sire', + 'deathCause': 'Cause of Death', + 'dam': 'Material ID', + 'sire': 'Paternal ID', 'Id/MostRecentWeight/MostRecentWeightGrams': 'Weight (g)', 'Id/MostRecentWeight/MostRecentWeightDate': 'Date of Weight', - 'u24_status': 'U24 assigned?', + 'u24_status': 'U24 Status', 'Id/mostRecentObservations/availability::observation': 'Availability', 'Id/mostRecentObservations/current_housing_status::observation': 'Current Housing Status', 'breeding partner ID': 'Breeding Partner ID', 'Id/mostRecentObservations/infant_history::observation': 'Infant History', 'Id/mostRecentObservations/fertility_status::observation': 'Fertility Status', - 'Id/mostRecentObservations/medical_history::observation': 'Medical History', - 'Id/mostRecentObservations/usage_current::observation': 'Usage (Current)', - 'Id/mostRecentObservations/usage_future::observation': 'Usage (Future)' + 'Id/mostRecentObservations/medical_history::observation': 'Medical History' } LABKEY.Query.selectRows({ @@ -463,7 +462,10 @@ Ext4.define('MCC.panel.MccImportPanel', { const rows = results.rows.map(row => { const newRow = [] Object.keys(fieldMap).forEach(key => { - if (row[key] !== undefined) { + // Always leave these empty: + if (key === 'Id/MostRecentWeight/MostRecentWeightGrams' || key === 'Id/MostRecentWeight/MostRecentWeightDate') { + newRow.push('') + } else if (row[key] !== undefined) { newRow.push(Ext4.isArray(row[key]) ? row[key].join(',') : row[key]) } else { newRow.push('') From 55d7f1ef388511317d9bfd41da67708a7c1bb4cc Mon Sep 17 00:00:00 2001 From: bbimber Date: Wed, 11 Mar 2026 20:25:02 -0700 Subject: [PATCH 19/23] Change entry pattern of MCC Request ID (#213) --- .../web/mcc/window/MarkShippedWindow.js | 27 +++++++++---------- .../org/labkey/test/tests/mcc/MccTest.java | 1 + 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/mcc/resources/web/mcc/window/MarkShippedWindow.js b/mcc/resources/web/mcc/window/MarkShippedWindow.js index 9cbf6818..8206e5e0 100644 --- a/mcc/resources/web/mcc/window/MarkShippedWindow.js +++ b/mcc/resources/web/mcc/window/MarkShippedWindow.js @@ -32,6 +32,11 @@ Ext4.define('MCC.window.MarkShippedWindow', { html: 'This will:
1) Mark the selected animals as shipped from this center
2) Enter a new demographics record in the selected study
3) Preserve the MCC ID for each animal.', border: false, style: 'padding-bottom: 10px;' + },{ + xtype: 'ldk-integerfield', + fieldLabel: 'Request ID', + itemId: 'requestId', + allowBlank: false },{ xtype: 'datefield', fieldLabel: 'Effective Date', @@ -71,9 +76,9 @@ Ext4.define('MCC.window.MarkShippedWindow', { containerPath: ctx.MCCInternalDataContainer, schemaName: 'core', queryName: 'containers', - columns: 'EntityId,Name,Parent,Path', + columns: 'EntityId,title,Parent,Path', containerFilter: 'CurrentAndSubfolders', - sort: 'Name', + sort: 'title', autoLoad: true, listeners: { load: function(store) { @@ -111,10 +116,6 @@ Ext4.define('MCC.window.MarkShippedWindow', { xtype: 'displayfield', value: 'Animal ID', width: 150 - },{ - xtype: 'displayfield', - value: 'Request ID', - width: 150 },{ xtype: 'displayfield', value: 'Keep Existing ID?', @@ -129,11 +130,6 @@ Ext4.define('MCC.window.MarkShippedWindow', { fields = fields.concat([{ xtype: 'displayfield', value: animalId, - },{ - xtype: 'ldk-integerfield', - minValue: 1, - itemId: 'requestId-' + animalId, - allowBlank: true },{ xtype: 'checkbox', itemId: 'usePreviousId-' + animalId, @@ -158,7 +154,7 @@ Ext4.define('MCC.window.MarkShippedWindow', { return { layout: { type: 'table', - columns: 4 + columns: 3 }, width: 600, border: false, @@ -183,12 +179,13 @@ Ext4.define('MCC.window.MarkShippedWindow', { var win = btn.up('window'); var lsids = win.rowIds; + var requestId = win.down('#requestId').getValue(); var effectiveDate = win.down('#effectiveDate').getValue(); var centerName = win.down('#centerName').getValue(); var targetFolder = win.down('#targetFolder').getValue(); - if (!effectiveDate || !centerName || !targetFolder) { - Ext4.Msg.alert('Error', 'Must provide date, center name, and target folder'); + if (!requestId || !effectiveDate || !centerName || !targetFolder) { + Ext4.Msg.alert('Error', 'Must provide request Id, date, center name, and target folder'); return; } @@ -250,6 +247,7 @@ Ext4.define('MCC.window.MarkShippedWindow', { }, doSave: function(win, results, preexistingIdsInTargetFolder){ + var requestId = win.down('#requestId').getValue(); var effectiveDate = win.down('#effectiveDate').getValue(); var centerName = win.down('#centerName').getValue(); var targetFolder = win.down('#targetFolder').getValue(); @@ -259,7 +257,6 @@ Ext4.define('MCC.window.MarkShippedWindow', { var hadError = false; Ext4.Array.forEach(results.rows, function(row){ var effectiveId = win.down('#usePreviousId-' + row.Id).getValue() ? row.Id : win.down('#newId-' + row.Id).getValue(); - var requestId = win.down('#requestId-' + row.Id).getValue(); // This should be checked above, although perhaps case sensitivity could get involved: LDK.Assert.assertNotEmpty('Missing effective ID after query', effectiveId); 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 9c5caf4f..3dccbc07 100644 --- a/mcc/test/src/org/labkey/test/tests/mcc/MccTest.java +++ b/mcc/test/src/org/labkey/test/tests/mcc/MccTest.java @@ -122,6 +122,7 @@ private void testAnimalImportAndTransfer() throws Exception sleep(100); Ext4ComboRef.getForLabel(this, "Target Folder").setComboByDisplayValue("Other"); + Ext4FieldRef.getForLabel(this, "Request ID").setValue(12345); waitAndClick(Ext4Helper.Locators.ext4Button("Submit")); // This should fail initially: From 6bf45d7b0b72c204f84c39d54a175b1757662245 Mon Sep 17 00:00:00 2001 From: bbimber Date: Wed, 11 Mar 2026 20:30:03 -0700 Subject: [PATCH 20/23] Test fix --- mcc/test/src/org/labkey/test/tests/mcc/MccTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 3dccbc07..60c7a2f7 100644 --- a/mcc/test/src/org/labkey/test/tests/mcc/MccTest.java +++ b/mcc/test/src/org/labkey/test/tests/mcc/MccTest.java @@ -121,8 +121,9 @@ private void testAnimalImportAndTransfer() throws Exception waitAndClick(Ext4Helper.Locators.ext4Button("OK")); sleep(100); - Ext4ComboRef.getForLabel(this, "Target Folder").setComboByDisplayValue("Other"); Ext4FieldRef.getForLabel(this, "Request ID").setValue(12345); + + Ext4ComboRef.getForLabel(this, "Target Folder").setComboByDisplayValue("Other"); waitAndClick(Ext4Helper.Locators.ext4Button("Submit")); // This should fail initially: From fea2daec4cf6c6c9cdd164d316b9b338bc5abf61 Mon Sep 17 00:00:00 2001 From: bbimber Date: Thu, 12 Mar 2026 07:18:47 -0700 Subject: [PATCH 21/23] Bugfix to MarkShippedWindow --- mcc/resources/etls/mcc.xml | 2 +- mcc/resources/web/mcc/window/MarkShippedWindow.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mcc/resources/etls/mcc.xml b/mcc/resources/etls/mcc.xml index 3dd604ab..0fc088ca 100644 --- a/mcc/resources/etls/mcc.xml +++ b/mcc/resources/etls/mcc.xml @@ -149,7 +149,7 @@ objectid - + diff --git a/mcc/resources/web/mcc/window/MarkShippedWindow.js b/mcc/resources/web/mcc/window/MarkShippedWindow.js index 8206e5e0..dc9a0387 100644 --- a/mcc/resources/web/mcc/window/MarkShippedWindow.js +++ b/mcc/resources/web/mcc/window/MarkShippedWindow.js @@ -66,7 +66,7 @@ Ext4.define('MCC.window.MarkShippedWindow', { fieldLabel: 'Target Folder', itemId: 'targetFolder', allowBlank: false, - displayField: 'Name', + displayField: 'title', valueField: 'Path', triggerAction: 'all', queryMode: 'local', From 25caf7a666a6c47f1c0a3ffea301d669089e8ebc Mon Sep 17 00:00:00 2001 From: bbimber Date: Thu, 12 Mar 2026 07:55:52 -0700 Subject: [PATCH 22/23] Force re-cache of all IDs in MCC ETL --- mcc/resources/etls/mcc.xml | 6 +++ .../web/mcc/window/MarkShippedWindow.js | 6 +-- .../labkey/mcc/etl/CacheDemographicsStep.java | 51 +++++++++++++++++++ 3 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 mcc/src/org/labkey/mcc/etl/CacheDemographicsStep.java diff --git a/mcc/resources/etls/mcc.xml b/mcc/resources/etls/mcc.xml index 0fc088ca..1a133c54 100644 --- a/mcc/resources/etls/mcc.xml +++ b/mcc/resources/etls/mcc.xml @@ -45,6 +45,12 @@ + + + + + + Copy to target diff --git a/mcc/resources/web/mcc/window/MarkShippedWindow.js b/mcc/resources/web/mcc/window/MarkShippedWindow.js index dc9a0387..a6c03a09 100644 --- a/mcc/resources/web/mcc/window/MarkShippedWindow.js +++ b/mcc/resources/web/mcc/window/MarkShippedWindow.js @@ -66,7 +66,7 @@ Ext4.define('MCC.window.MarkShippedWindow', { fieldLabel: 'Target Folder', itemId: 'targetFolder', allowBlank: false, - displayField: 'title', + displayField: 'DisplayName', valueField: 'Path', triggerAction: 'all', queryMode: 'local', @@ -76,9 +76,9 @@ Ext4.define('MCC.window.MarkShippedWindow', { containerPath: ctx.MCCInternalDataContainer, schemaName: 'core', queryName: 'containers', - columns: 'EntityId,title,Parent,Path', + columns: 'EntityId,DisplayName,Parent,Path', containerFilter: 'CurrentAndSubfolders', - sort: 'title', + sort: 'DisplayName', autoLoad: true, listeners: { load: function(store) { diff --git a/mcc/src/org/labkey/mcc/etl/CacheDemographicsStep.java b/mcc/src/org/labkey/mcc/etl/CacheDemographicsStep.java new file mode 100644 index 00000000..45bf5f4f --- /dev/null +++ b/mcc/src/org/labkey/mcc/etl/CacheDemographicsStep.java @@ -0,0 +1,51 @@ +package org.labkey.mcc.etl; + +import org.apache.xmlbeans.XmlException; +import org.jetbrains.annotations.NotNull; +import org.labkey.api.data.TableInfo; +import org.labkey.api.data.TableSelector; +import org.labkey.api.di.TaskRefTask; +import org.labkey.api.ehr.EHRDemographicsService; +import org.labkey.api.pipeline.PipelineJob; +import org.labkey.api.pipeline.PipelineJobException; +import org.labkey.api.pipeline.RecordedActionSet; +import org.labkey.api.query.QueryService; +import org.labkey.api.writer.ContainerUser; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class CacheDemographicsStep implements TaskRefTask +{ + protected ContainerUser _containerUser; + + @Override + public RecordedActionSet run(@NotNull PipelineJob job) throws PipelineJobException + { + TableInfo demographics = QueryService.get().getUserSchema(_containerUser.getUser(), _containerUser.getContainer(), "study").getTable("demographics"); + List ids = new TableSelector(demographics, Collections.singleton("Id"), null, null).getArrayList(String.class); + + EHRDemographicsService.get().getAnimals(_containerUser.getContainer(), ids); + + return new RecordedActionSet(); + } + + @Override + public List getRequiredSettings() + { + return Collections.emptyList(); + } + + @Override + public void setSettings(Map settings) throws XmlException + { + + } + + @Override + public void setContainerUser(ContainerUser containerUser) + { + _containerUser = containerUser; + } +} From 2da691dc4a4f31569c24c5cc597497d89a46c856 Mon Sep 17 00:00:00 2001 From: bbimber Date: Thu, 12 Mar 2026 10:05:55 -0700 Subject: [PATCH 23/23] Update to MCC test --- mcc/test/src/org/labkey/test/tests/mcc/MccTest.java | 2 ++ 1 file changed, 2 insertions(+) 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 60c7a2f7..bb7b9668 100644 --- a/mcc/test/src/org/labkey/test/tests/mcc/MccTest.java +++ b/mcc/test/src/org/labkey/test/tests/mcc/MccTest.java @@ -182,6 +182,7 @@ private void testAnimalImportAndTransfer() throws Exception waitAndClick(Ext4Helper.Locators.ext4Button("OK")); sleep(100); + Ext4FieldRef.getForLabel(this, "Request ID").setValue(12345); Ext4ComboRef.getForLabel(this, "Target Folder").setComboByDisplayValue("Other"); _ext4Helper.queryOne("#usePreviousId-Animal2", Ext4FieldRef.class).setChecked(true); waitAndClick(Ext4Helper.Locators.ext4Button("Submit")); @@ -239,6 +240,7 @@ private void testAnimalImportAndTransfer() throws Exception waitAndClick(Ext4Helper.Locators.ext4Button("OK")); sleep(100); + Ext4FieldRef.getForLabel(this, "Request ID").setValue(12345); Ext4ComboRef.getForLabel(this, "Target Folder").setComboByDisplayValue("Other"); _ext4Helper.queryOne("#newId-12345", Ext4FieldRef.class).setValue("TheNewId"); waitAndClick(Ext4Helper.Locators.ext4Button("Submit"));