From 516c2670f31d727668c5eb6d302f100594d207b7 Mon Sep 17 00:00:00 2001 From: XingY Date: Thu, 2 Oct 2025 19:24:00 -0700 Subject: [PATCH 1/7] Issue 53063: Support "Aliquoted From" with space --- .../org/labkey/api/exp/api/ExpMaterial.java | 1 + .../org/labkey/api/exp/api/ExpSampleType.java | 9 ++ .../labkey/api/exp/api/ExperimentService.java | 6 + .../api/ExpDataClassDataTableImpl.java | 26 +++++ .../experiment/api/ExpMaterialTableImpl.java | 2 +- .../experiment/api/ExpSampleTypeTestCase.jsp | 73 ++++++++++-- .../experiment/api/ExperimentServiceImpl.java | 3 +- .../api/SampleTypeUpdateServiceDI.java | 107 +++++++++++------- .../controllers/exp/ExperimentController.java | 1 + 9 files changed, 176 insertions(+), 52 deletions(-) diff --git a/api/src/org/labkey/api/exp/api/ExpMaterial.java b/api/src/org/labkey/api/exp/api/ExpMaterial.java index 95bde41b9f7..c1b9b829fdb 100644 --- a/api/src/org/labkey/api/exp/api/ExpMaterial.java +++ b/api/src/org/labkey/api/exp/api/ExpMaterial.java @@ -35,6 +35,7 @@ public interface ExpMaterial extends ExpRunItem String MATERIAL_INPUTS_PREFIX_LC = MATERIAL_INPUTS_PREFIX.toLowerCase(); String MATERIAL_OUTPUT_CHILD = "MaterialOutputs"; String ALIQUOTED_FROM_INPUT = "AliquotedFrom"; + String ALIQUOTED_FROM_INPUT_LABEL = "Aliquoted From"; @Nullable ExpSampleType getSampleType(); diff --git a/api/src/org/labkey/api/exp/api/ExpSampleType.java b/api/src/org/labkey/api/exp/api/ExpSampleType.java index 496afe9e2c8..6760198a338 100644 --- a/api/src/org/labkey/api/exp/api/ExpSampleType.java +++ b/api/src/org/labkey/api/exp/api/ExpSampleType.java @@ -18,6 +18,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.labkey.api.collections.CaseInsensitiveHashMap; import org.labkey.api.data.Container; import org.labkey.api.data.ContainerFilter; import org.labkey.api.exp.ExperimentException; @@ -242,6 +243,14 @@ String createSampleName(@NotNull Map rowMap, @NotNull Map getImportAliases() throws IOException; + @NotNull + default Map getImportAliasesIncludingAliquot() throws IOException + { + Map aliases = new CaseInsensitiveHashMap<>(getImportAliases()); + aliases.put(ExpMaterial.ALIQUOTED_FROM_INPUT_LABEL, ExpMaterial.ALIQUOTED_FROM_INPUT); + return aliases; + } + @NotNull Map getRequiredImportAliases() throws IOException; @NotNull Map> getImportAliasMap() throws IOException; diff --git a/api/src/org/labkey/api/exp/api/ExperimentService.java b/api/src/org/labkey/api/exp/api/ExperimentService.java index 5c6a0141513..49447695996 100644 --- a/api/src/org/labkey/api/exp/api/ExperimentService.java +++ b/api/src/org/labkey/api/exp/api/ExperimentService.java @@ -506,6 +506,12 @@ static boolean isInputOutputColumn(String columnName) ExpMaterial.MATERIAL_OUTPUT_CHILD.equalsIgnoreCase(prefix); } + static boolean isAliquotedFromColumn(String columnName) + { + return ExpMaterial.ALIQUOTED_FROM_INPUT.equalsIgnoreCase(columnName) || + ExpMaterial.ALIQUOTED_FROM_INPUT_LABEL.equalsIgnoreCase(columnName); + } + // convert MaterialInputs/Blood/Type to MaterialInputs/Blood$SType static @Nullable String getEncodedLineageKey(String inputColumn /*not encoded*/) { diff --git a/experiment/src/org/labkey/experiment/api/ExpDataClassDataTableImpl.java b/experiment/src/org/labkey/experiment/api/ExpDataClassDataTableImpl.java index 01899ea6430..9b3bd485f40 100644 --- a/experiment/src/org/labkey/experiment/api/ExpDataClassDataTableImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpDataClassDataTableImpl.java @@ -1370,6 +1370,32 @@ else if (name != null) return dataRow; } + + @Override + protected Map updateRow(User user, Container container, Map row, @NotNull Map oldRow, boolean allowOwner, boolean retainCreation) + throws InvalidKeyException, ValidationException, QueryUpdateServiceException, SQLException + { + Map result = super.updateRow(user, container, row, oldRow, allowOwner, retainCreation); + + // add MaterialInput/DataInputs field from parent alias + try + { + Map parentAliases = _dataClass.getImportAliases(); + for (String alias : parentAliases.keySet()) + { + if (row.containsKey(alias)) + result.put(parentAliases.get(alias), result.get(alias)); + } + } + catch (IOException e) + { + throw new RuntimeException(e); + } + + return result; + + } + @Override protected Map _update(User user, Container c, Map row, Map oldRow, Object[] keys) throws SQLException, ValidationException { diff --git a/experiment/src/org/labkey/experiment/api/ExpMaterialTableImpl.java b/experiment/src/org/labkey/experiment/api/ExpMaterialTableImpl.java index 864eae8bf50..706b0cc5403 100644 --- a/experiment/src/org/labkey/experiment/api/ExpMaterialTableImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpMaterialTableImpl.java @@ -1726,7 +1726,7 @@ public DataIteratorBuilder persistRows(DataIteratorBuilder data, DataIteratorCon // TODO: subclass PersistDataIteratorBuilder to index Materials! not DataClass! try { - var persist = new ExpDataIterators.PersistDataIteratorBuilder(data, this, propertiesTable, _ss, getUserSchema().getContainer(), getUserSchema().getUser(), _ss.getImportAliases(), sampleTypeObjectId) + var persist = new ExpDataIterators.PersistDataIteratorBuilder(data, this, propertiesTable, _ss, getUserSchema().getContainer(), getUserSchema().getUser(), _ss.getImportAliasesIncludingAliquot(), sampleTypeObjectId) .setFileLinkDirectory(SAMPLETYPE_FILE_DIRECTORY); ExperimentServiceImpl experimentServiceImpl = ExperimentServiceImpl.get(); SearchService.TaskIndexingQueue queue = SearchService.get().defaultTask().getQueue(getContainer(), SearchService.PRIORITY.modified); diff --git a/experiment/src/org/labkey/experiment/api/ExpSampleTypeTestCase.jsp b/experiment/src/org/labkey/experiment/api/ExpSampleTypeTestCase.jsp index 035d1407080..222099a7717 100644 --- a/experiment/src/org/labkey/experiment/api/ExpSampleTypeTestCase.jsp +++ b/experiment/src/org/labkey/experiment/api/ExpSampleTypeTestCase.jsp @@ -468,23 +468,80 @@ public void testNameExpression() throws Exception List> aliquotRows = new ArrayList<>(); aliquotRows.add(CaseInsensitiveHashMap.of("aliquotedFrom", expectedName1, "AliquotCount", 10)); - aliquotRows.add(CaseInsensitiveHashMap.of("Aliquotedfrom", expectedName2, "aliquotCount", 5)); - aliquotRows.add(CaseInsensitiveHashMap.of("ALIQUOTEDFROM", expectedName1, "Aliquotcount", 15)); - aliquotRows.add(CaseInsensitiveHashMap.of("AliquotedFrom", expectedName3, "ALIQUOTCOUNT", 2)); - List> aliquots = insertSampleRows(sampleTypeName, aliquotRows); - assertExpectedName(st, expectedName1 + "-ALI-0004"); assertEquals(expectedName1, aliquots.get(0).get("AliquotedFrom")); + aliquotRows = new ArrayList<>(); + aliquotRows.add(CaseInsensitiveHashMap.of("Aliquotedfrom", expectedName2, "aliquotCount", 5)); + aliquots = insertSampleRows(sampleTypeName, aliquotRows); assertExpectedName(st, expectedName2 + "-ALI-0005"); - assertEquals(expectedName2, aliquots.get(1).get("aliquotedFrom")); + assertEquals(expectedName2, aliquots.get(0).get("aliquotedFrom")); + aliquotRows = new ArrayList<>(); + aliquotRows.add(CaseInsensitiveHashMap.of("ALIQUOTEDFROM", expectedName1, "Aliquotcount", 15)); + aliquots = insertSampleRows(sampleTypeName, aliquotRows); assertExpectedName(st, expectedName1 + "-ALI-0006"); - assertEquals(expectedName1, aliquots.get(2).get("aliquotedfrom")); + assertEquals(expectedName1, aliquots.get(0).get("aliquotedfrom")); + aliquotRows = new ArrayList<>(); + aliquotRows.add(CaseInsensitiveHashMap.of("AliquotedFrom", expectedName3, "ALIQUOTCOUNT", 2)); + aliquots = insertSampleRows(sampleTypeName, aliquotRows); assertExpectedName(st, expectedName3 + "-ALI-0007"); - assertEquals(expectedName3, aliquots.get(3).get("ALIQUOTEDFROM")); + assertEquals(expectedName3, aliquots.get(0).get("ALIQUOTEDFROM")); + + // Issue 53063: Support "Aliquoted From" + aliquotRows = new ArrayList<>(); + aliquotRows.add(CaseInsensitiveHashMap.of("Aliquoted From", expectedName2, "ALIQUOTCOUNT", 2)); + aliquots = insertSampleRows(sampleTypeName, aliquotRows); + assertExpectedName(st, expectedName2 + "-ALI-0008"); + assertEquals(expectedName2, aliquots.get(0).get("ALIQUOTEDFROM")); + + aliquotRows = new ArrayList<>(); + aliquotRows.add(CaseInsensitiveHashMap.of("aliquoted from", expectedName3, "ALIQUOTCOUNT", 3)); + aliquots = insertSampleRows(sampleTypeName, aliquotRows); + assertExpectedName(st, expectedName3 + "-ALI-0009"); + assertEquals(expectedName3, aliquots.get(0).get("aliquotedFrom")); + + // test the default aliquot naming pattern + st.setAliquotNameExpression(""); + st.save(user); + + aliquotRows = new ArrayList<>(); + aliquotRows.add(CaseInsensitiveHashMap.of("aliquotedFrom", expectedName1)); + aliquots = insertSampleRows(sampleTypeName, aliquotRows); + assertExpectedName(st, expectedName1 + "-1"); + assertEquals(expectedName1, aliquots.get(0).get("AliquotedFrom")); + + aliquotRows = new ArrayList<>(); + aliquotRows.add(CaseInsensitiveHashMap.of("Aliquotedfrom", expectedName1)); + aliquots = insertSampleRows(sampleTypeName, aliquotRows); + assertExpectedName(st, expectedName1 + "-2"); + assertEquals(expectedName1, aliquots.get(0).get("aliquotedFrom")); + + aliquotRows = new ArrayList<>(); + aliquotRows.add(CaseInsensitiveHashMap.of("ALIQUOTEDFROM", expectedName1)); + aliquots = insertSampleRows(sampleTypeName, aliquotRows); + assertExpectedName(st, expectedName1 + "-3"); + assertEquals(expectedName1, aliquots.get(0).get("aliquotedfrom")); + + aliquotRows = new ArrayList<>(); + aliquotRows.add(CaseInsensitiveHashMap.of("AliquotedFrom", expectedName1)); + aliquots = insertSampleRows(sampleTypeName, aliquotRows); + assertExpectedName(st, expectedName1 + "-4"); + assertEquals(expectedName1, aliquots.get(0).get("ALIQUOTEDFROM")); + + aliquotRows = new ArrayList<>(); + aliquotRows.add(CaseInsensitiveHashMap.of("Aliquoted From", expectedName1)); + aliquots = insertSampleRows(sampleTypeName, aliquotRows); + assertExpectedName(st, expectedName1 + "-5"); + assertEquals(expectedName1, aliquots.get(0).get("ALIQUOTEDFROM")); + + aliquotRows = new ArrayList<>(); + aliquotRows.add(CaseInsensitiveHashMap.of("aliquoted from", expectedName1)); + aliquots = insertSampleRows(sampleTypeName, aliquotRows); + assertExpectedName(st, expectedName1 + "-6"); + assertEquals(expectedName1, aliquots.get(0).get("aliquotedFrom")); } @Test diff --git a/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java b/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java index d1441d648b5..ef6090b0683 100644 --- a/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java @@ -10256,6 +10256,7 @@ else if (inputColumns != null) allColumns.addAll(inputColumns); Map parentAliasColumnMap = new CaseInsensitiveHashMap<>(); + parentAliasColumnMap.put(ExpMaterial.ALIQUOTED_FROM_INPUT_LABEL, ExpMaterial.ALIQUOTED_FROM_INPUT); try { if (currentDataType instanceof ExpDataClass dataClass) @@ -10274,7 +10275,7 @@ else if (currentDataType instanceof ExpSampleType sampleType) String columnName = inputColName; if (parentAliasColumnMap.containsKey(columnName)) columnName = parentAliasColumnMap.get(columnName); - if (ExperimentService.isInputOutputColumn(columnName) || "parent".equalsIgnoreCase(columnName)) + if (ExperimentService.isInputOutputColumn(columnName) || ExperimentService.isAliquotedFromColumn(columnName) || "parent".equalsIgnoreCase(columnName)) { if (seenColumns.contains(columnName)) throw new ApiUsageException(String.format(DUPLICATE_COLUMN_IN_DATA_ERROR, columnName)); diff --git a/experiment/src/org/labkey/experiment/api/SampleTypeUpdateServiceDI.java b/experiment/src/org/labkey/experiment/api/SampleTypeUpdateServiceDI.java index 461eb6f64fe..2bab07481dc 100644 --- a/experiment/src/org/labkey/experiment/api/SampleTypeUpdateServiceDI.java +++ b/experiment/src/org/labkey/experiment/api/SampleTypeUpdateServiceDI.java @@ -858,6 +858,31 @@ public static boolean isAliquotStatusChangeNeedRecalc(Collection available return false; } + @Override + protected Map updateRow(User user, Container container, Map row, @NotNull Map oldRow, boolean allowOwner, boolean retainCreation) + throws InvalidKeyException, ValidationException, QueryUpdateServiceException, SQLException + { + Map result = super.updateRow(user, container, row, oldRow, allowOwner, retainCreation); + + // add MaterialInput/DataInputs field from parent alias + try + { + Map parentAliases = _sampleType.getImportAliases(); + for (String alias : parentAliases.keySet()) + { + if (row.containsKey(alias)) + result.put(parentAliases.get(alias), result.get(alias)); + } + } + catch (IOException e) + { + throw new RuntimeException(e); + } + + return result; + + } + @Override protected Map _update(User user, Container c, Map row, Map oldRow, Object[] keys) throws SQLException, ValidationException { @@ -1631,7 +1656,7 @@ public DataIterator getDataIterator(DataIteratorContext context) addColumns.addUniqueIdDbSequenceColumns(ContainerManager.getRoot(), materialTable); // recompute only add when AliquotedFrom column is not null - if (columnNameMap.containsKey(ExpMaterial.ALIQUOTED_FROM_INPUT)) + if (columnNameMap.containsKey(ExpMaterial.ALIQUOTED_FROM_INPUT) || columnNameMap.containsKey(ExpMaterial.ALIQUOTED_FROM_INPUT_LABEL)) { addColumns.addNullColumn(ROOT_RECOMPUTE_ROWID_COL, JdbcType.INTEGER); addColumns.addNullColumn(PARENT_RECOMPUTE_NAME_COL, JdbcType.VARCHAR); @@ -1752,7 +1777,7 @@ static class _GenerateNamesDataIterator extends SimpleTranslator try { - Map importAliasMap = sampleType.getImportAliases(); + Map importAliasMap = sampleType.getImportAliasesIncludingAliquot(); _extraPropsFns.add(() -> Map.of(PARENT_IMPORT_ALIAS_MAP_PROP, importAliasMap)); } catch (IOException e) @@ -1792,24 +1817,7 @@ protected void processNextInput() { Map map = new CaseInsensitiveHashMap<>(((MapDataIterator)getInput()).getMap()); - String aliquotedFrom = null; - Object aliquotedFromObj = map.get(ExpMaterial.ALIQUOTED_FROM_INPUT); - if (aliquotedFromObj != null) - { - if (aliquotedFromObj instanceof String) - { - // Issue 45563: We need the AliquotedFrom name to be quoted so we can properly find the parent, - // but we don't want to include the quotes in the name we generate using AliquotedFrom - aliquotedFrom = StringUtilsLabKey.unquoteString((String) aliquotedFromObj).trim(); - map.put(ExpMaterial.ALIQUOTED_FROM_INPUT, aliquotedFrom); - } - else if (aliquotedFromObj instanceof Number) - { - aliquotedFrom = aliquotedFromObj.toString(); - } - } - - boolean isAliquot = !StringUtils.isEmpty(aliquotedFrom); + boolean isAliquot = isAliquotRow(map); try { @@ -1926,7 +1934,7 @@ void init(TableInfo target, boolean useImportAliases, boolean initRollupCounts) ColumnInfo from = di.getColumnInfo(i); if (from != null) { - if (getAliquotedFromColName().equalsIgnoreCase(from.getName())) + if (isAliquotedFromColName(from.getName())) aliquotedFromDataColInd = i; else if (unitsImportAliasSet.contains(from.getName())) unitDataColInd = i; @@ -2011,10 +2019,12 @@ else if (StoredAmount.name().equalsIgnoreCase(name)) } } - private String getAliquotedFromColName() + private boolean isAliquotedFromColName(String fromCol) { - // for update, AliquotedFromLSID is reselected from existing row. For other actions, "AliquotedFrom" needs to be provided - return _context.getInsertOption().updateOnly ? AliquotedFromLSID.name() : ExpMaterial.ALIQUOTED_FROM_INPUT; + if (_context.getInsertOption().updateOnly) + return AliquotedFromLSID.name().equalsIgnoreCase(fromCol); + + return ExperimentService.isAliquotedFromColumn(fromCol); } private void _addConvertColumn(String name, int fromIndex, JdbcType toType, ForeignKey toFk, int derivationDataColInd, boolean isAliquotField) @@ -2175,6 +2185,36 @@ else if (aliquotedFrom instanceof Number) } } + private static boolean isAliquotRow(Map map, String aliquotedFromColName) + { + String aliquotedFrom = null; + Object aliquotedFromObj = map.get(aliquotedFromColName); + if (aliquotedFromObj == null && map.containsKey(ColumnInfo.labelFromName(ExpMaterial.ALIQUOTED_FROM_INPUT))) + aliquotedFromObj = map.get(ColumnInfo.labelFromName(ExpMaterial.ALIQUOTED_FROM_INPUT)); + if (aliquotedFromObj != null) + { + if (aliquotedFromObj instanceof String) + { + // Issue 45563: We need the AliquotedFrom name to be quoted so we can properly find the parent, + // but we don't want to include the quotes in the name we generate using AliquotedFrom + aliquotedFrom = StringUtilsLabKey.unquoteString((String) aliquotedFromObj).trim(); + if (!StringUtils.isEmpty(aliquotedFrom)) + map.put(ExpMaterial.ALIQUOTED_FROM_INPUT, aliquotedFrom); + } + else if (aliquotedFromObj instanceof Number) + { + aliquotedFrom = aliquotedFromObj.toString(); + } + } + + return !StringUtils.isEmpty(aliquotedFrom); + } + + private static boolean isAliquotRow(Map map) + { + return isAliquotRow(map, ExpMaterial.ALIQUOTED_FROM_INPUT) || isAliquotRow(map, ExpMaterial.ALIQUOTED_FROM_INPUT_LABEL); + } + public static class SampleNameGeneratorState extends NameGeneratorState { private final NameGenerator aliquotNameGenerator; @@ -2195,24 +2235,7 @@ public String nextName(Map map, @Nullable Set parentSamples, @Nullable List>> _extraPropsFns) throws NameGenerator.NameGenerationException { - String aliquotedFrom = null; - Object aliquotedFromObj = map.get(ExpMaterial.ALIQUOTED_FROM_INPUT); - if (aliquotedFromObj != null) - { - if (aliquotedFromObj instanceof String) - { - // Issue 45563: We need the AliquotedFrom name to be quoted so we can properly find the parent, - // but we don't want to include the quotes in the name we generate using AliquotedFrom - aliquotedFrom = StringUtilsLabKey.unquoteString((String) aliquotedFromObj).trim(); - map.put(ExpMaterial.ALIQUOTED_FROM_INPUT, aliquotedFrom); - } - else if (aliquotedFromObj instanceof Number) - { - aliquotedFrom = aliquotedFromObj.toString(); - } - } - - boolean isAliquot = !StringUtils.isEmpty(aliquotedFrom); + boolean isAliquot = isAliquotRow(map); String generatedName = null; if (isAliquot && aliquotNameGenerator != null) diff --git a/experiment/src/org/labkey/experiment/controllers/exp/ExperimentController.java b/experiment/src/org/labkey/experiment/controllers/exp/ExperimentController.java index 657d28931d0..1d4367f3d83 100644 --- a/experiment/src/org/labkey/experiment/controllers/exp/ExperimentController.java +++ b/experiment/src/org/labkey/experiment/controllers/exp/ExperimentController.java @@ -4422,6 +4422,7 @@ protected Map getRenamedColumns() Set aliases = new CaseInsensitiveHashSet(); // Issue 53419: Aliquot parent with number like names that starts with leading zeroes aren't resolved during import aliases.add(ExpMaterial.ALIQUOTED_FROM_INPUT); + aliases.add(ExpMaterial.ALIQUOTED_FROM_INPUT_LABEL); boolean crossTypeImport = getOptionParamValue(AbstractQueryImportAction.Params.crossTypeImport); // Issue 51894: We need to stop conversion to numbers for alias fields for all type // If there are aliases defined for one type that are number fields in another type, this will prevent From 43207654d8696a14a9581c737f01a11baa074186 Mon Sep 17 00:00:00 2001 From: XingY Date: Mon, 6 Oct 2025 21:52:17 -0700 Subject: [PATCH 2/7] comment --- .../src/org/labkey/experiment/api/ExpSampleTypeTestCase.jsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/experiment/src/org/labkey/experiment/api/ExpSampleTypeTestCase.jsp b/experiment/src/org/labkey/experiment/api/ExpSampleTypeTestCase.jsp index 67eb5797bfc..c3fc1591d95 100644 --- a/experiment/src/org/labkey/experiment/api/ExpSampleTypeTestCase.jsp +++ b/experiment/src/org/labkey/experiment/api/ExpSampleTypeTestCase.jsp @@ -503,7 +503,7 @@ public void testNameExpression() throws Exception assertExpectedName(st, expectedName3 + "-ALI-0009"); assertEquals(expectedName3, aliquots.get(0).get("aliquotedFrom")); - // test the default aliquot naming pattern + // test the default aliquot naming pattern (${${AliquotedFrom}-:withCounter} st.setAliquotNameExpression(""); st.save(user); From 2009b56fa1b1660c65ec5eba3a1aad77f898d0db Mon Sep 17 00:00:00 2001 From: XingY Date: Wed, 8 Oct 2025 12:42:00 -0700 Subject: [PATCH 3/7] code review changes --- api/src/org/labkey/api/exp/api/ExpSampleType.java | 1 + 1 file changed, 1 insertion(+) diff --git a/api/src/org/labkey/api/exp/api/ExpSampleType.java b/api/src/org/labkey/api/exp/api/ExpSampleType.java index 6760198a338..7560e159d8f 100644 --- a/api/src/org/labkey/api/exp/api/ExpSampleType.java +++ b/api/src/org/labkey/api/exp/api/ExpSampleType.java @@ -243,6 +243,7 @@ String createSampleName(@NotNull Map rowMap, @NotNull Map getImportAliases() throws IOException; + // Issue 53063: support "Aliquoted From" @NotNull default Map getImportAliasesIncludingAliquot() throws IOException { From 00753479eb5d082c3a57ba58cbd6e44ffef8180d Mon Sep 17 00:00:00 2001 From: XingY Date: Wed, 8 Oct 2025 14:24:10 -0700 Subject: [PATCH 4/7] Fix aliquot name using MaterialInput syntax --- api/src/org/labkey/api/data/NameGeneratorState.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/src/org/labkey/api/data/NameGeneratorState.java b/api/src/org/labkey/api/data/NameGeneratorState.java index fd0083024ec..8adefa8ec1b 100644 --- a/api/src/org/labkey/api/data/NameGeneratorState.java +++ b/api/src/org/labkey/api/data/NameGeneratorState.java @@ -50,9 +50,9 @@ import java.util.stream.Collectors; import static org.labkey.api.data.NameGenerator.getParentImportAliasFieldKeys; -import static org.labkey.api.exp.api.ExpMaterial.ALIQUOTED_FROM_INPUT; import static org.labkey.api.exp.api.ExpRunItem.INPUT_PARENT; import static org.labkey.api.exp.api.ExpRunItem.PARENT_IMPORT_ALIAS_MAP_PROP; +import static org.labkey.api.exp.api.ExperimentService.isAliquotedFromColumn; public class NameGeneratorState implements AutoCloseable { @@ -745,7 +745,7 @@ private void addParentLookupInput(String colName, { String prefix = null; String dataType = null; - if (ALIQUOTED_FROM_INPUT.equalsIgnoreCase(colName)) + if (isAliquotedFromColumn(colName)) { prefix = ExpMaterial.MATERIAL_INPUT_PARENT; dataType = getParentTable() != null ? getParentTable().getName() : null; @@ -791,7 +791,7 @@ private void addInputs(String colName, String[] parts = colName.split("/", 2); String prefix = null; String decodedDataType = null; - if (ALIQUOTED_FROM_INPUT.equalsIgnoreCase(colName)) + if (isAliquotedFromColumn(colName)) { prefix = ExpMaterial.MATERIAL_INPUT_PARENT; decodedDataType = getParentTable() != null ? getParentTable().getName() : null; From a94cbaba59cbf1b5004c759e2a9da492aab16839 Mon Sep 17 00:00:00 2001 From: XingY Date: Wed, 8 Oct 2025 15:12:43 -0700 Subject: [PATCH 5/7] Switch to use set instead of List to avoid duplicate names --- .../labkey/api/data/NameGeneratorState.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/api/src/org/labkey/api/data/NameGeneratorState.java b/api/src/org/labkey/api/data/NameGeneratorState.java index 8adefa8ec1b..d82896a0af5 100644 --- a/api/src/org/labkey/api/data/NameGeneratorState.java +++ b/api/src/org/labkey/api/data/NameGeneratorState.java @@ -362,7 +362,7 @@ private void addParentLookupValues(String parentTypeName, boolean isMaterialParent, @Nullable Map parentImportAliases, ExpObject parentObject, - Map> inputLookupValues, + Map> inputLookupValues, @Nullable NameGenerator altNameGenerator) { String inputType = isMaterialParent ? ExpMaterial.MATERIAL_INPUT_PARENT : ExpData.DATA_INPUT_PARENT; @@ -384,7 +384,7 @@ private void addParentLookupValues(String parentTypeName, continue; // add to Input/ - inputLookupValues.computeIfAbsent(FieldKey.fromParts(INPUT_PARENT, fieldName), (s) -> new ArrayList<>()).add(lookupValue); + inputLookupValues.computeIfAbsent(FieldKey.fromParts(INPUT_PARENT, fieldName), (s) -> new LinkedHashSet<>()).add(lookupValue); // add to importAlias/ if (parentImportAliases != null) @@ -393,19 +393,19 @@ private void addParentLookupValues(String parentTypeName, .entrySet() .stream() .filter(entry -> inputFK.equals(entry.getValue())) - .forEach(entry -> inputLookupValues.computeIfAbsent(FieldKey.fromParts(entry.getKey(), fieldName), (s) -> new ArrayList<>()).add(lookupValue)); + .forEach(entry -> inputLookupValues.computeIfAbsent(FieldKey.fromParts(entry.getKey(), fieldName), (s) -> new LinkedHashSet<>()).add(lookupValue)); } // add to Inputs/ - inputLookupValues.computeIfAbsent(FieldKey.fromParts(inputType, fieldName), (s) -> new ArrayList<>()).add(lookupValue); + inputLookupValues.computeIfAbsent(FieldKey.fromParts(inputType, fieldName), (s) -> new LinkedHashSet<>()).add(lookupValue); // add to Inputs// - inputLookupValues.computeIfAbsent(FieldKey.fromParts(inputType, parentTypeName, fieldName), (s) -> new ArrayList<>()).add(lookupValue); + inputLookupValues.computeIfAbsent(FieldKey.fromParts(inputType, parentTypeName, fieldName), (s) -> new LinkedHashSet<>()).add(lookupValue); } } private void addAncestorLookupValues( ExpRunItem parentObject, - Map> inputLookupValues, + Map> inputLookupValues, @Nullable NameGenerator altNameGenerator) { String parentLsid = parentObject.getLSID(); @@ -481,8 +481,8 @@ else if (parentObject instanceof ExpData expData) if (!ancestorLookupValues.isEmpty()) { - inputLookupValues.putIfAbsent(ancestorFieldKey, new ArrayList<>()); - List lookupValues = inputLookupValues.get(ancestorFieldKey); + inputLookupValues.putIfAbsent(ancestorFieldKey, new LinkedHashSet<>()); + Set lookupValues = inputLookupValues.get(ancestorFieldKey); for (Object lookupVal : ancestorLookupValues) { if (!lookupValues.contains(lookupVal)) @@ -497,7 +497,7 @@ private void addParentLookupContext(String parentTypeName/* already decoded */, String parentName, boolean isMaterialParent, @Nullable Map parentImportAliases, - Map> inputLookupValues, + Map> inputLookupValues, @Nullable NameGenerator altNameGenerator) { NameGenerator.ExpressionSummary expressionSummary = getExpressionSummary(altNameGenerator); @@ -591,7 +591,7 @@ private Map additionalContext(@NotNull Map row if (expressionSummary.hasParentLookup() || expressionSummary.hasParentInputs()) { Map> inputs = new HashMap<>(); - Map> inputLookupValues = new HashMap<>(); + Map> inputLookupValues = new HashMap<>(); inputs.put(FieldKey.fromParts(INPUT_PARENT), new LinkedHashSet<>()); inputs.put(FieldKey.fromParts(ExpData.DATA_INPUT_PARENT), new LinkedHashSet<>()); @@ -663,7 +663,7 @@ else if (value.isEmpty()) ctx.putAll(inputValues); Map lookupValues = new HashMap<>(); - inputLookupValues.forEach((key, value) -> lookupValues.put(key, value.size() > 1 ? value : (value.size() == 1 ? value.get(0) : null))); + inputLookupValues.forEach((key, value) -> lookupValues.put(key, value.size() > 1 ? value : (value.size() == 1 ? value.iterator().next() : null))); ctx.putAll(lookupValues); } @@ -740,7 +740,7 @@ else if (value.isEmpty()) private void addParentLookupInput(String colName, Object value, @Nullable Map parentImportAliases, - Map> inputLookupValues, + Map> inputLookupValues, @Nullable NameGenerator altNameGenerator) { String prefix = null; From 81efd9e8b7e915345797a844c3cc902c53c558dc Mon Sep 17 00:00:00 2001 From: XingY Date: Wed, 8 Oct 2025 15:51:43 -0700 Subject: [PATCH 6/7] Fix cross type import --- experiment/src/org/labkey/experiment/ExpDataIterators.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/experiment/src/org/labkey/experiment/ExpDataIterators.java b/experiment/src/org/labkey/experiment/ExpDataIterators.java index 264733d0a42..fea8c77ebe7 100644 --- a/experiment/src/org/labkey/experiment/ExpDataIterators.java +++ b/experiment/src/org/labkey/experiment/ExpDataIterators.java @@ -162,6 +162,7 @@ import static org.labkey.api.exp.api.ExpData.DATA_INPUTS_PREFIX_LC; import static org.labkey.api.exp.api.ExpData.DATA_INPUT_PARENT; import static org.labkey.api.exp.api.ExpMaterial.ALIQUOTED_FROM_INPUT; +import static org.labkey.api.exp.api.ExpMaterial.ALIQUOTED_FROM_INPUT_LABEL; import static org.labkey.api.exp.api.ExpMaterial.MATERIAL_INPUTS_PREFIX_LC; import static org.labkey.api.exp.api.ExpMaterial.MATERIAL_INPUT_PARENT; import static org.labkey.api.exp.api.ExpRunItem.INPUTS_PREFIX_LC; @@ -2861,6 +2862,7 @@ private TypeData createSampleHeaderRow(ExpSampleTypeImpl sampleType, Container c Map aliasMap = sampleType.getImportAliases(); validFields.addAll(aliasMap.keySet()); validFields.add(ALIQUOTED_FROM_INPUT); + validFields.add(ALIQUOTED_FROM_INPUT_LABEL); validFields.add("StorageUnit"); validFields.add("Storage Unit"); validFields.add("StorageUnitLabel"); From a5fb5aca3efa2b0ca98339d53caecde06920d101 Mon Sep 17 00:00:00 2001 From: XingY Date: Wed, 8 Oct 2025 17:09:54 -0700 Subject: [PATCH 7/7] Issue 53963: Cross-sample-type import gives incorrect row number in message --- api/src/org/labkey/api/query/QueryUpdateService.java | 1 + experiment/src/org/labkey/experiment/ExpDataIterators.java | 2 ++ .../org/labkey/experiment/api/SampleTypeUpdateServiceDI.java | 3 ++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/api/src/org/labkey/api/query/QueryUpdateService.java b/api/src/org/labkey/api/query/QueryUpdateService.java index 78723b7d7ac..53925404dec 100644 --- a/api/src/org/labkey/api/query/QueryUpdateService.java +++ b/api/src/org/labkey/api/query/QueryUpdateService.java @@ -109,6 +109,7 @@ enum ConfigParameters SkipRequiredFieldValidation, // (Bool) skip validation of required fields, used during import when the import of data happens in two hitches (e.g., samples in one file and sample statuses in a second) BulkLoad, // (Bool) skips detailed auditing CheckForCrossProjectData, // (Bool) Check if data belong to other projects + ProcessingPartition, // (Bool) Importing a partitioned file from original file SkipInsertOptionValidation, // (Bool) Skip assert(supportsInsertOption(context.getInsertOption())) for special scenarios (e.g., folder import uses merge action that's otherwise not supported for a table), PreferPKOverObjectUriAsKey, // (Bool) Prefer getPkColumnNames instead of getObjectURIColumnName to use as keys SkipReselectRows, // (Bool) If true, skip qus.getRows and use raw returned rows. Applicable for CommandType.insert/insertWithKeys/update/updateChangingKeys diff --git a/experiment/src/org/labkey/experiment/ExpDataIterators.java b/experiment/src/org/labkey/experiment/ExpDataIterators.java index fea8c77ebe7..e0a9723ecad 100644 --- a/experiment/src/org/labkey/experiment/ExpDataIterators.java +++ b/experiment/src/org/labkey/experiment/ExpDataIterators.java @@ -2592,6 +2592,7 @@ public boolean next() throws BatchValidationException { _context.setCrossTypeImport(false); _context.setCrossFolderImport(false); + _context.putConfigParameter(QueryUpdateService.ConfigParameters.ProcessingPartition, true); boolean hasCrossFolderImport = false; @@ -2611,6 +2612,7 @@ public boolean next() throws BatchValidationException if (_isCrossFolder && !_context.getInsertOption().updateOnly && hasCrossFolderImport) // all updates are cross-folder due to lack of Container column SimpleMetricsService.get().increment(ExperimentService.MODULE_NAME, _isSamples ? "sampleImport" : "dataClassImport", "multiFolderImport"); + _context.putConfigParameter(QueryUpdateService.ConfigParameters.ProcessingPartition, false); _context.setCrossTypeImport(_isCrossType); _context.setCrossFolderImport(_isCrossFolder); } diff --git a/experiment/src/org/labkey/experiment/api/SampleTypeUpdateServiceDI.java b/experiment/src/org/labkey/experiment/api/SampleTypeUpdateServiceDI.java index 2bab07481dc..8417fd01071 100644 --- a/experiment/src/org/labkey/experiment/api/SampleTypeUpdateServiceDI.java +++ b/experiment/src/org/labkey/experiment/api/SampleTypeUpdateServiceDI.java @@ -1848,7 +1848,8 @@ protected void processNextInput() catch (NameGenerator.NameGenerationException e) { // Failed to generate a name due to some part of the expression not in the row - String rowText = _context.isCrossFolderImport() || _context.isCrossTypeImport() ? "" : " on row " + e.getRowNumber(); + // Issue 53963: Cross-sample-type import gives incorrect row number in message + String rowText = _context.getConfigParameterBoolean(QueryUpdateService.ConfigParameters.ProcessingPartition) ? "" : " on row " + e.getRowNumber(); if (isAliquot) addRowError("Failed to generate name for aliquot" + rowText + " using aliquot naming pattern " + _sampleType.getAliquotNameExpression() + ". Check the syntax of the aliquot naming pattern and the data values for the aliquot."); else if (_sampleType.hasNameExpression())