From 1a3973933a2adff1beefa156bb4a05d6969bf0cc Mon Sep 17 00:00:00 2001 From: labkey-susanh Date: Fri, 24 Oct 2025 10:06:50 -0700 Subject: [PATCH 01/18] More helper methods for getting and setting createdBy/modifiedBy --- .../org/labkey/api/data/CreatedModified.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/api/src/org/labkey/api/data/CreatedModified.java b/api/src/org/labkey/api/data/CreatedModified.java index f27230600b8..36a568fa489 100644 --- a/api/src/org/labkey/api/data/CreatedModified.java +++ b/api/src/org/labkey/api/data/CreatedModified.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import org.json.JSONObject; import org.labkey.api.security.User; +import org.labkey.api.security.UserManager; import java.util.Date; @@ -42,11 +43,22 @@ public JSONObject getCreatedBy() return _createdBy.getUserProps(); } + @JsonIgnore + public User getCreatedByUser() + { + return _createdBy; + } + public void setCreatedBy(User createdBy) { _createdBy = createdBy; } + public void setCreatedBy(int createdById) + { + _createdBy = UserManager.getUser(createdById); + } + @JsonProperty("modified") public Long getModified() { @@ -73,8 +85,19 @@ public JSONObject getModifiedBy() return _modifiedBy.getUserProps(); } + @JsonIgnore + public User getModifiedByUser() + { + return _modifiedBy; + } + public void setModifiedBy(User modifiedBy) { _modifiedBy = modifiedBy; } + + public void setModifiedBy(int modifiedById) + { + _modifiedBy = UserManager.getUser(modifiedById); + } } From b8a2bb292ac48ee2fef2ed5dcaa58f22e497152d Mon Sep 17 00:00:00 2001 From: labkey-susanh Date: Tue, 28 Oct 2025 16:19:02 -0700 Subject: [PATCH 02/18] Parameterize NameExpressionDataIterator with column names --- .../dataiterator/NameExpressionDataIterator.java | 11 ++++++++--- .../NameExpressionDataIteratorBuilder.java | 14 +++++++++++++- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/api/src/org/labkey/api/dataiterator/NameExpressionDataIterator.java b/api/src/org/labkey/api/dataiterator/NameExpressionDataIterator.java index 9eddbcbe92a..1086a0b172e 100644 --- a/api/src/org/labkey/api/dataiterator/NameExpressionDataIterator.java +++ b/api/src/org/labkey/api/dataiterator/NameExpressionDataIterator.java @@ -45,9 +45,14 @@ public class NameExpressionDataIterator extends WrapperDataIterator private final String _counterSeqPrefix; private boolean _allowUserSpecifiedNames = true; // whether manual names specification is allowed or only name expression generation private final List>> _extraPropsFns = new ArrayList<>(); - private Map _importAliases; + private final Map _importAliases; public NameExpressionDataIterator(DataIterator di, DataIteratorContext context, @Nullable TableInfo parentTable, @Nullable Container container, Function getNonConflictCountFn, String counterSeqPrefix, @Nullable Map importAliases) + { + this(di, context, parentTable, container, getNonConflictCountFn, counterSeqPrefix, importAliases, "name", "nameExpression"); + } + + public NameExpressionDataIterator(DataIterator di, DataIteratorContext context, @Nullable TableInfo parentTable, @Nullable Container container, Function getNonConflictCountFn, String counterSeqPrefix, @Nullable Map importAliases, String nameColName, String expressionColName) { super(DataIteratorUtil.wrapMap(di, false)); _context = context; @@ -55,8 +60,8 @@ public NameExpressionDataIterator(DataIterator di, DataIteratorContext context, _importAliases = importAliases; Map map = DataIteratorUtil.createColumnNameMap(di); - _nameCol = map.get("name"); - _expressionCol = map.get("nameExpression"); + _nameCol = map.get(nameColName); + _expressionCol = map.get(expressionColName); assert _nameCol != null; assert _expressionCol != null; diff --git a/api/src/org/labkey/api/dataiterator/NameExpressionDataIteratorBuilder.java b/api/src/org/labkey/api/dataiterator/NameExpressionDataIteratorBuilder.java index 2ec074dd6ba..e31e2cafc09 100644 --- a/api/src/org/labkey/api/dataiterator/NameExpressionDataIteratorBuilder.java +++ b/api/src/org/labkey/api/dataiterator/NameExpressionDataIteratorBuilder.java @@ -21,17 +21,29 @@ public class NameExpressionDataIteratorBuilder implements DataIteratorBuilder { final DataIteratorBuilder _pre; private final TableInfo _parentTable; + private final String _nameColumnName; + private final String _nameExpressionColumnName; public NameExpressionDataIteratorBuilder(DataIteratorBuilder pre, TableInfo parentTable) { _pre = pre; _parentTable = parentTable; + _nameColumnName = "name"; + _nameExpressionColumnName = "nameExpression"; + } + + public NameExpressionDataIteratorBuilder(DataIteratorBuilder pre, TableInfo parentTable, String nameColumn, String nameExpressionColumn) + { + _pre = pre; + _parentTable = parentTable; + _nameColumnName = nameColumn; + _nameExpressionColumnName = nameExpressionColumn; } @Override public DataIterator getDataIterator(DataIteratorContext context) { DataIterator pre = _pre.getDataIterator(context); - return LoggingDataIterator.wrap(new NameExpressionDataIterator(pre, context, _parentTable, null, null, null, null)); + return LoggingDataIterator.wrap(new NameExpressionDataIterator(pre, context, _parentTable, null, null, null, null, _nameColumnName, _nameExpressionColumnName)); } } From 31581d3aff4803edf301141cc7811bc4efa60517 Mon Sep 17 00:00:00 2001 From: labkey-susanh Date: Mon, 3 Nov 2025 18:16:46 -0800 Subject: [PATCH 03/18] Add method for overriding hasInsertRowsPermission --- .../org/labkey/api/query/AbstractQueryUpdateService.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/api/src/org/labkey/api/query/AbstractQueryUpdateService.java b/api/src/org/labkey/api/query/AbstractQueryUpdateService.java index 2984e741704..43e52190349 100644 --- a/api/src/org/labkey/api/query/AbstractQueryUpdateService.java +++ b/api/src/org/labkey/api/query/AbstractQueryUpdateService.java @@ -359,6 +359,11 @@ protected boolean hasImportRowsPermission(User user, Container container, DataIt return hasPermission(user, context.getInsertOption().updateOnly ? UpdatePermission.class : InsertPermission.class); } + protected boolean hasInsertRowsPermission(User user) + { + return hasPermission(user, InsertPermission.class); + } + // override this protected void preImportDIBValidation(@Nullable DataIteratorBuilder in, @Nullable Collection inputColumns) { @@ -526,7 +531,7 @@ protected Map insertRow(User user, Container container, Map> _insertRowsUsingDIB(User user, Container container, List> rows, DataIteratorContext context, @Nullable Map extraScriptContext) { - if (!hasPermission(user, InsertPermission.class)) + if (!hasInsertRowsPermission(user)) throw new UnauthorizedException("You do not have permission to insert data into this table."); return _insertUpdateRowsUsingDIB(user, container, rows, context, extraScriptContext); @@ -586,7 +591,7 @@ protected DataIteratorBuilder _toDataIteratorBuilder(String debugName, List> _insertRowsUsingInsertRow(User user, Container container, List> rows, BatchValidationException errors, Map extraScriptContext) throws DuplicateKeyException, BatchValidationException, QueryUpdateServiceException, SQLException { - if (!hasPermission(user, InsertPermission.class)) + if (!hasInsertRowsPermission(user)) throw new UnauthorizedException("You do not have permission to insert data into this table."); assert(getQueryTable().supportsInsertOption(InsertOption.INSERT)); From e7eb75999ee8410a2d95ec2ac7009f4fe7e73340 Mon Sep 17 00:00:00 2001 From: labkey-susanh Date: Tue, 4 Nov 2025 10:04:07 -0800 Subject: [PATCH 04/18] Fixes for job deletion --- .../org/labkey/api/query/AbstractQueryUpdateService.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/api/src/org/labkey/api/query/AbstractQueryUpdateService.java b/api/src/org/labkey/api/query/AbstractQueryUpdateService.java index 43e52190349..01f8bf7b2f7 100644 --- a/api/src/org/labkey/api/query/AbstractQueryUpdateService.java +++ b/api/src/org/labkey/api/query/AbstractQueryUpdateService.java @@ -364,6 +364,11 @@ protected boolean hasInsertRowsPermission(User user) return hasPermission(user, InsertPermission.class); } + protected boolean hasDeleteRowsPermission(User user) + { + return hasPermission(user, DeletePermission.class); + } + // override this protected void preImportDIBValidation(@Nullable DataIteratorBuilder in, @Nullable Collection inputColumns) { @@ -950,7 +955,7 @@ protected Map deleteRow(User user, Container container, Map> deleteRows(User user, Container container, List> keys, @Nullable Map configParameters, @Nullable Map extraScriptContext) throws InvalidKeyException, BatchValidationException, QueryUpdateServiceException, SQLException { - if (!hasPermission(user, DeletePermission.class)) + if (!hasDeleteRowsPermission(user)) throw new UnauthorizedException("You do not have permission to delete data from this table."); BatchValidationException errors = new BatchValidationException(); @@ -1017,7 +1022,7 @@ protected int truncateRows(User user, Container container) public int truncateRows(User user, Container container, @Nullable Map configParameters, @Nullable Map extraScriptContext) throws BatchValidationException, QueryUpdateServiceException, SQLException { - if (!container.hasPermission(user, AdminPermission.class) && !hasPermission(user, DeletePermission.class)) + if (!container.hasPermission(user, AdminPermission.class) && !hasDeleteRowsPermission(user)) throw new UnauthorizedException("You do not have permission to truncate this table."); BatchValidationException errors = new BatchValidationException(); From 9cd40cd8e9868c18b55d419f1d08e3dcdc777c21 Mon Sep 17 00:00:00 2001 From: labkey-susanh Date: Wed, 5 Nov 2025 15:34:50 -0800 Subject: [PATCH 05/18] Allow update permission check to be overridden too --- .../org/labkey/api/query/AbstractQueryUpdateService.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/api/src/org/labkey/api/query/AbstractQueryUpdateService.java b/api/src/org/labkey/api/query/AbstractQueryUpdateService.java index 01f8bf7b2f7..6e9d7978a69 100644 --- a/api/src/org/labkey/api/query/AbstractQueryUpdateService.java +++ b/api/src/org/labkey/api/query/AbstractQueryUpdateService.java @@ -369,6 +369,11 @@ protected boolean hasDeleteRowsPermission(User user) return hasPermission(user, DeletePermission.class); } + protected boolean hasUpdateRowsPermission(User user) + { + return hasPermission(user, UpdatePermission.class); + } + // override this protected void preImportDIBValidation(@Nullable DataIteratorBuilder in, @Nullable Collection inputColumns) { @@ -560,7 +565,7 @@ protected Map insertRow(User user, Container container, Map> _updateRowsUsingDIB(User user, Container container, List> rows, DataIteratorContext context, @Nullable Map extraScriptContext) { - if (!hasPermission(user, UpdatePermission.class)) + if (!hasUpdateRowsPermission(user)) throw new UnauthorizedException("You do not have permission to update data in this table."); return _insertUpdateRowsUsingDIB(user, container, rows, context, extraScriptContext); @@ -827,7 +832,7 @@ public List> updateRows(User user, Container container, List BatchValidationException errors, @Nullable Map configParameters, Map extraScriptContext) throws InvalidKeyException, BatchValidationException, QueryUpdateServiceException, SQLException { - if (!hasPermission(user, UpdatePermission.class)) + if (!hasUpdateRowsPermission(user)) throw new UnauthorizedException("You do not have permission to update data in this table."); if (oldKeys != null && rows.size() != oldKeys.size()) From db30ede974b490b872d686fe8e0c7164c83cb238 Mon Sep 17 00:00:00 2001 From: labkey-susanh Date: Sat, 8 Nov 2025 16:24:16 -0800 Subject: [PATCH 06/18] Rename EmailOption for workflow notifications --- api/src/org/labkey/api/announcements/EmailOption.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/org/labkey/api/announcements/EmailOption.java b/api/src/org/labkey/api/announcements/EmailOption.java index eda635a1580..8ff2aa6613d 100644 --- a/api/src/org/labkey/api/announcements/EmailOption.java +++ b/api/src/org/labkey/api/announcements/EmailOption.java @@ -38,8 +38,8 @@ public enum EmailOption FILES_NONE(512), FILES_INDIVIDUAL(513), FILES_DAILY_DIGEST(514), - SAMPLEMANAGER_NONE(701), - SAMPLEMANAGER_ALL(702), + WORKFLOW_NONE(701), + WORKFLOW_ALL(702), LABBOOK_NONE(801), LABBOOK_ALL(802); From 0e9638bfe41b379d3d55dcb5f0f105fb2947845f Mon Sep 17 00:00:00 2001 From: labkey-susanh Date: Tue, 11 Nov 2025 16:36:33 -0800 Subject: [PATCH 07/18] Create a base class for App controller URL providers --- api/src/org/labkey/api/util/PageFlowUtil.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/api/src/org/labkey/api/util/PageFlowUtil.java b/api/src/org/labkey/api/util/PageFlowUtil.java index feaf2515c73..688cb1bea11 100644 --- a/api/src/org/labkey/api/util/PageFlowUtil.java +++ b/api/src/org/labkey/api/util/PageFlowUtil.java @@ -57,6 +57,8 @@ import org.labkey.api.module.Module; import org.labkey.api.module.ModuleLoader; import org.labkey.api.notification.NotificationMenuView; +import org.labkey.api.products.ProductMenuProvider; +import org.labkey.api.products.ProductRegistry; import org.labkey.api.query.QueryParam; import org.labkey.api.reader.Readers; import org.labkey.api.security.AuthenticationManager; @@ -3395,4 +3397,40 @@ public static int addScriptNonces(Document doc) return nl.getLength(); } + + public static class AppUrls + { + protected static ActionURL appendFrag(ActionURL url, String... appURLParts) + { + if (url == null) + return null; + + String s = url.getFragment(); + String fragment = (s == null ? "" : s) + "/" + String.join("/", appURLParts); + return (ActionURL) url.setFragment(fragment); + } + + protected static ActionURL app(Container container, boolean includeDefault) + { + return getAppURL(container, includeDefault); + } + + public static ActionURL getAppURL(Container container, boolean includeDefault) + { + ProductMenuProvider menuProvider = ProductRegistry.get().getPrimaryProductMenuForContainer(container); + if (menuProvider != null) + return menuProvider.getAppURL(container); + + return includeDefault ? new ActionURL() : null; + } + + public static String getAppName(Container container) + { + ProductMenuProvider product = ProductRegistry.get().getPrimaryProductMenuForContainer(container); + if (product != null) + return product.getProductName(); + + return ""; + } + } } From 66682b6e6ff712319eef566378b819c820ab2ded Mon Sep 17 00:00:00 2001 From: labkey-susanh Date: Tue, 11 Nov 2025 16:40:32 -0800 Subject: [PATCH 08/18] Getter for date version of longs --- api/src/org/labkey/api/data/CreatedModified.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/api/src/org/labkey/api/data/CreatedModified.java b/api/src/org/labkey/api/data/CreatedModified.java index 36a568fa489..ececda504aa 100644 --- a/api/src/org/labkey/api/data/CreatedModified.java +++ b/api/src/org/labkey/api/data/CreatedModified.java @@ -23,6 +23,12 @@ public Long getCreated() return _created; } + @JsonIgnore + public Date getCreatedDate() + { + return _created == null ? null : new Date(_created); + } + public void setCreated(Long created) { _created = created; @@ -65,6 +71,12 @@ public Long getModified() return _modified; } + @JsonIgnore + public Date getModifiedDate() + { + return _modified == null ? null : new Date(_modified); + } + public void setModified(Long modified) { _modified = modified; From f34ed63516a626a8548647827f3c5016b448b6b5 Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 13 Nov 2025 08:18:53 -0600 Subject: [PATCH 09/18] Rename assay run workflowTask to workflowTaskId since it holds a RowId --- .../api/assay/AssayRunDatabaseContext.java | 7 ++--- .../api/assay/AssayRunUploadContext.java | 8 +++--- .../api/assay/AssayRunUploadContextImpl.java | 8 +++--- .../api/assay/DefaultAssayRunCreator.java | 2 +- .../api/assay/actions/AssayRunUploadForm.java | 10 +++---- .../assay/pipeline/AssayRunAsyncContext.java | 8 +++--- .../pipeline/AssayUploadPipelineJob.java | 2 +- api/src/org/labkey/api/exp/api/ExpRun.java | 4 +-- .../labkey/api/qc/TsvDataExchangeHandler.java | 6 ----- .../assay/actions/ImportRunApiAction.java | 12 ++++----- .../org/labkey/experiment/api/ExpRunImpl.java | 26 ++----------------- .../experiment/api/ExpRunTableImpl.java | 4 +-- 12 files changed, 32 insertions(+), 65 deletions(-) diff --git a/api/src/org/labkey/api/assay/AssayRunDatabaseContext.java b/api/src/org/labkey/api/assay/AssayRunDatabaseContext.java index f19138d5369..9e7ba1ce61d 100644 --- a/api/src/org/labkey/api/assay/AssayRunDatabaseContext.java +++ b/api/src/org/labkey/api/assay/AssayRunDatabaseContext.java @@ -128,12 +128,9 @@ public String getName() } @Override - public @Nullable Long getWorkflowTask() + public @Nullable Long getWorkflowTaskId() { - if (_run.getWorkflowTask() != null) - return _run.getWorkflowTask().getRowId(); - - return null; + return _run.getWorkflowTaskId(); } @Override diff --git a/api/src/org/labkey/api/assay/AssayRunUploadContext.java b/api/src/org/labkey/api/assay/AssayRunUploadContext.java index 4b69318e848..dcfb757336b 100644 --- a/api/src/org/labkey/api/assay/AssayRunUploadContext.java +++ b/api/src/org/labkey/api/assay/AssayRunUploadContext.java @@ -72,7 +72,7 @@ enum ReImportOption String getName(); - default @Nullable Long getWorkflowTask() + default @Nullable Long getWorkflowTaskId() { return null; } @@ -253,7 +253,7 @@ abstract class Factory imple private final ViewContext _context; private final String _comments; private final String _name; - private final Long _workflowTask; + private final Long _workflowTaskId; private final String _targetStudy; private final Long _reRunId; private final ReImportOption _reImportOption; @@ -114,7 +114,7 @@ private AssayRunUploadContextImpl(Factory factory) _logger = factory._logger; _name = factory._name; - _workflowTask = factory._workflowTask; + _workflowTaskId = factory._workflowTaskId; _comments = factory._comments; _rawRunProperties = factory._rawRunProperties == null ? emptyMap() : unmodifiableMap(factory._rawRunProperties); @@ -279,9 +279,9 @@ public String getName() } @Override - public @Nullable Long getWorkflowTask() + public @Nullable Long getWorkflowTaskId() { - return _workflowTask; + return _workflowTaskId; } @Override diff --git a/api/src/org/labkey/api/assay/DefaultAssayRunCreator.java b/api/src/org/labkey/api/assay/DefaultAssayRunCreator.java index f7fbae5aaa8..58103311473 100644 --- a/api/src/org/labkey/api/assay/DefaultAssayRunCreator.java +++ b/api/src/org/labkey/api/assay/DefaultAssayRunCreator.java @@ -171,7 +171,7 @@ public Pair saveExperimentRun( auditEvent.addDetail(TransactionAuditProvider.TransactionDetail.ImportFileName, primaryFile.getName()); run = AssayService.get().createExperimentRun(context.getName(), context.getContainer(), protocol, null == primaryFile ? null : primaryFile.toNioPathForRead().toFile()); run.setComments(context.getComments()); - run.setWorkflowTaskId(context.getWorkflowTask()); + run.setWorkflowTaskId(context.getWorkflowTaskId()); exp = saveExperimentRun(context, exp, run, false, transactionDetails); diff --git a/api/src/org/labkey/api/assay/actions/AssayRunUploadForm.java b/api/src/org/labkey/api/assay/actions/AssayRunUploadForm.java index d230f3ecf73..3ea147def9d 100644 --- a/api/src/org/labkey/api/assay/actions/AssayRunUploadForm.java +++ b/api/src/org/labkey/api/assay/actions/AssayRunUploadForm.java @@ -90,7 +90,7 @@ public class AssayRunUploadForm extends Prot protected Map _runProperties = null; private String _comments; private String _name; - private Long _workflowTask; + private Long _workflowTaskId; private String _dataCollectorName; private boolean _multiRunUpload; private String _uploadStep; @@ -214,14 +214,14 @@ public void setName(String name) } @Override - public @Nullable Long getWorkflowTask() + public @Nullable Long getWorkflowTaskId() { - return _workflowTask; + return _workflowTaskId; } - public void setWorkflowTask(Long workflowTask) + public void setWorkflowTaskId(Long workflowTaskId) { - _workflowTask = workflowTask; + _workflowTaskId = workflowTaskId; } public String getDataCollectorName() diff --git a/api/src/org/labkey/api/assay/pipeline/AssayRunAsyncContext.java b/api/src/org/labkey/api/assay/pipeline/AssayRunAsyncContext.java index e8b97177c41..ff013ed6325 100644 --- a/api/src/org/labkey/api/assay/pipeline/AssayRunAsyncContext.java +++ b/api/src/org/labkey/api/assay/pipeline/AssayRunAsyncContext.java @@ -63,7 +63,7 @@ public class AssayRunAsyncContext implements private String _containerId; private String _runName; private String _runComments; - private Long _runWorkflowTask; + private Long _runWorkflowTaskId; private ActionURL _actionURL; private Map _uploadedData; private Set _uploadedPropertyFiles; @@ -111,7 +111,7 @@ public AssayRunAsyncContext(AssayRunUploadContext originalContext) _targetStudy = originalContext.getTargetStudy(); _runName = originalContext.getName(); _runComments = originalContext.getComments(); - _runWorkflowTask = originalContext.getWorkflowTask(); + _runWorkflowTaskId = originalContext.getWorkflowTaskId(); _container = originalContext.getContainer(); if (_container != null) _containerId = _container.getId(); @@ -298,9 +298,9 @@ public String getName() } @Override - public @Nullable Long getWorkflowTask() + public @Nullable Long getWorkflowTaskId() { - return _runWorkflowTask; + return _runWorkflowTaskId; } @Override diff --git a/api/src/org/labkey/api/assay/pipeline/AssayUploadPipelineJob.java b/api/src/org/labkey/api/assay/pipeline/AssayUploadPipelineJob.java index dbc66f79de4..598e4a238cb 100644 --- a/api/src/org/labkey/api/assay/pipeline/AssayUploadPipelineJob.java +++ b/api/src/org/labkey/api/assay/pipeline/AssayUploadPipelineJob.java @@ -140,7 +140,7 @@ public void doWork() // Create the basic run _run = AssayService.get().createExperimentRun(_context.getName(), getContainer(), _context.getProtocol(), _primaryFile); _run.setComments(_context.getComments()); - _run.setWorkflowTaskId(_context.getWorkflowTask()); + _run.setWorkflowTaskId(_context.getWorkflowTaskId()); // remember which job created the run so we can show this run on the job details page _run.setJobId(PipelineService.get().getJobId(getUser(), getContainer(), getJobGUID())); diff --git a/api/src/org/labkey/api/exp/api/ExpRun.java b/api/src/org/labkey/api/exp/api/ExpRun.java index 4ff577354d1..9a82868d2b8 100644 --- a/api/src/org/labkey/api/exp/api/ExpRun.java +++ b/api/src/org/labkey/api/exp/api/ExpRun.java @@ -133,9 +133,7 @@ public interface ExpRun extends ExpObject, Identifiable void setWorkflowTaskId(@Nullable Long workflowTaskId); - void setWorkflowTask(@Nullable ExpProtocolApplication workflowTask); - - @Nullable ExpProtocolApplication getWorkflowTask(); + @Nullable Long getWorkflowTaskId(); boolean canDelete(User user); } diff --git a/api/src/org/labkey/api/qc/TsvDataExchangeHandler.java b/api/src/org/labkey/api/qc/TsvDataExchangeHandler.java index 552d468f729..a4d1880d417 100644 --- a/api/src/org/labkey/api/qc/TsvDataExchangeHandler.java +++ b/api/src/org/labkey/api/qc/TsvDataExchangeHandler.java @@ -1309,12 +1309,6 @@ public String getName() return "sample upload name"; } - @Override - public @Nullable Long getWorkflowTask() - { - return null; - } - @Override public User getUser() { diff --git a/assay/src/org/labkey/assay/actions/ImportRunApiAction.java b/assay/src/org/labkey/assay/actions/ImportRunApiAction.java index 3f7ce48a844..f7375122e43 100644 --- a/assay/src/org/labkey/assay/actions/ImportRunApiAction.java +++ b/assay/src/org/labkey/assay/actions/ImportRunApiAction.java @@ -113,7 +113,7 @@ public ApiResponse execute(ImportRunApiForm form, BindException errors) throws E Long batchId; String name; - Long workflowTask; + Long workflowTaskId; String comments; CaseInsensitiveHashMap runProperties = null; CaseInsensitiveHashMap batchProperties = null; @@ -148,9 +148,9 @@ public ApiResponse execute(ImportRunApiForm form, BindException errors) throws E batchId = json.optLong(AssayJSONConverter.BATCH_ID); name = json.optString(ExperimentJSONConverter.NAME, null); - workflowTask = json.optLong(ExperimentJSONConverter.WORKFLOW_TASK); - if (workflowTask == 0) - workflowTask = null; + workflowTaskId = json.optLong(ExperimentJSONConverter.WORKFLOW_TASK); + if (workflowTaskId == 0) + workflowTaskId = null; comments = json.optString(ExperimentJSONConverter.COMMENT, null); forceAsync = json.optBoolean("forceAsync"); jobDescription = json.optString("jobDescription", null); @@ -187,7 +187,7 @@ public ApiResponse execute(ImportRunApiForm form, BindException errors) throws E batchId = form.getBatchId(); name = form.getName(); - workflowTask = form.getWorkflowTask(); + workflowTaskId = form.getWorkflowTask(); comments = form.getComment(); runProperties = new CaseInsensitiveHashMap<>(form.getProperties()); batchProperties = new CaseInsensitiveHashMap<>(form.getBatchProperties()); @@ -261,7 +261,7 @@ public ApiResponse execute(ImportRunApiForm form, BindException errors) throws E AssayRunUploadContext.Factory factory = provider.createRunUploadFactory(protocol, getViewContext()) .setName(name) - .setWorkflowTask(workflowTask) + .setWorkflowTaskId(workflowTaskId) .setComments(comments) .setTargetStudy(targetStudy) .setReRunId(reRunId) diff --git a/experiment/src/org/labkey/experiment/api/ExpRunImpl.java b/experiment/src/org/labkey/experiment/api/ExpRunImpl.java index 487e579377f..720323a2df6 100644 --- a/experiment/src/org/labkey/experiment/api/ExpRunImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpRunImpl.java @@ -1047,30 +1047,8 @@ public void setWorkflowTaskId(@Nullable Long workflowTaskId) } @Override - public ExpProtocolApplication getWorkflowTask() + public Long getWorkflowTaskId() { - Long id = _object.getWorkflowTask(); - - if (id == null) { - return null; - } - - if (_workflowTask == null || _workflowTask.getRowId() != id.intValue()) - { - _workflowTask = ExperimentServiceImpl.get().getExpProtocolApplication(id); - } - - return _workflowTask; - } - - @Override - public void setWorkflowTask(ExpProtocolApplication workflowTask) - { - ensureUnlocked(); - - if (workflowTask == null) - _object.setWorkflowTask(null); - else - _object.setWorkflowTask(workflowTask.getRowId()); + return _object.getWorkflowTask(); } } diff --git a/experiment/src/org/labkey/experiment/api/ExpRunTableImpl.java b/experiment/src/org/labkey/experiment/api/ExpRunTableImpl.java index 0b27cc54b5d..28fae4f1751 100644 --- a/experiment/src/org/labkey/experiment/api/ExpRunTableImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpRunTableImpl.java @@ -1020,8 +1020,8 @@ else if (Column.WorkflowTask.toString().equalsIgnoreCase(columnName)) { Long newWorkflowTaskId = value == null ? null : (Long)ConvertUtils.convert(value.toString(), Long.class); Long oldWorkflowTaskID = null; - if (run.getWorkflowTask() != null) - oldWorkflowTaskID = run.getWorkflowTask().getRowId(); + if (run.getWorkflowTaskId() != null) + oldWorkflowTaskID = run.getWorkflowTaskId(); appendPropertyIfChanged(auditComment, Column.WorkflowTask.toString(), oldWorkflowTaskID, newWorkflowTaskId); run.setWorkflowTaskId(newWorkflowTaskId); From b3bbeaa820f7eabda2d964d95bebe86f88991c07 Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 13 Nov 2025 08:19:49 -0600 Subject: [PATCH 10/18] Change exp.ExperimentRun WorkflowTask column to reference workflow.task RowId instead of exp.ProtocolApplication --- .../dbscripts/postgresql/exp-25.013-25.014.sql | 1 + .../dbscripts/sqlserver/exp-25.013-25.014.sql | 1 + .../experiment/ExperimentMigrationSchemaHandler.java | 4 ---- .../src/org/labkey/experiment/ExperimentModule.java | 2 +- .../org/labkey/experiment/api/ExpRunTableImpl.java | 12 +++++++++++- 5 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 experiment/resources/schemas/dbscripts/postgresql/exp-25.013-25.014.sql create mode 100644 experiment/resources/schemas/dbscripts/sqlserver/exp-25.013-25.014.sql diff --git a/experiment/resources/schemas/dbscripts/postgresql/exp-25.013-25.014.sql b/experiment/resources/schemas/dbscripts/postgresql/exp-25.013-25.014.sql new file mode 100644 index 00000000000..57a00daf4cf --- /dev/null +++ b/experiment/resources/schemas/dbscripts/postgresql/exp-25.013-25.014.sql @@ -0,0 +1 @@ +ALTER TABLE exp.ExperimentRun DROP CONSTRAINT FK_Run_WorfklowTask; \ No newline at end of file diff --git a/experiment/resources/schemas/dbscripts/sqlserver/exp-25.013-25.014.sql b/experiment/resources/schemas/dbscripts/sqlserver/exp-25.013-25.014.sql new file mode 100644 index 00000000000..57a00daf4cf --- /dev/null +++ b/experiment/resources/schemas/dbscripts/sqlserver/exp-25.013-25.014.sql @@ -0,0 +1 @@ +ALTER TABLE exp.ExperimentRun DROP CONSTRAINT FK_Run_WorfklowTask; \ No newline at end of file diff --git a/experiment/src/org/labkey/experiment/ExperimentMigrationSchemaHandler.java b/experiment/src/org/labkey/experiment/ExperimentMigrationSchemaHandler.java index 69399581f3f..dc961634076 100644 --- a/experiment/src/org/labkey/experiment/ExperimentMigrationSchemaHandler.java +++ b/experiment/src/org/labkey/experiment/ExperimentMigrationSchemaHandler.java @@ -36,9 +36,6 @@ public ExperimentMigrationSchemaHandler() @Override public void beforeSchema() { - // Work around foreign key cycle between ExperimentRun <-> ProtocolApplication by temporarily dropping FK_Run_WorfklowTask. - // Yes, the FK name is misspelled - new SqlExecutor(getSchema()).execute("ALTER TABLE exp.ExperimentRun DROP CONSTRAINT FK_Run_WorfklowTask"); new SqlExecutor(getSchema()).execute("ALTER TABLE exp.Object DROP CONSTRAINT FK_Object_Object"); // Need to drop self FK until all rows are populated because replaced runs will be inserted before their replacements @@ -123,7 +120,6 @@ public FilterClause getContainerClause(TableInfo sourceTable, Set containe @Override public void afterSchema(DatabaseMigrationConfiguration configuration, DbSchema sourceSchema, DbSchema targetSchema) { - new SqlExecutor(getSchema()).execute("ALTER TABLE exp.ExperimentRun ADD CONSTRAINT FK_Run_WorfklowTask FOREIGN KEY (WorkflowTask) REFERENCES exp.ProtocolApplication (RowId) MATCH SIMPLE ON DELETE SET NULL"); new SqlExecutor(getSchema()).execute("ALTER TABLE exp.Object ADD CONSTRAINT FK_Object_Object FOREIGN KEY (OwnerObjectId) REFERENCES exp.Object (ObjectId)"); new SqlExecutor(getSchema()).execute("ALTER TABLE exp.ExperimentRun ADD CONSTRAINT FK_ExperimentRun_ReplacedByRunId FOREIGN KEY (ReplacedByRunId) REFERENCES exp.ExperimentRun (RowId)"); } diff --git a/experiment/src/org/labkey/experiment/ExperimentModule.java b/experiment/src/org/labkey/experiment/ExperimentModule.java index c64f5ea669b..b49b594449a 100644 --- a/experiment/src/org/labkey/experiment/ExperimentModule.java +++ b/experiment/src/org/labkey/experiment/ExperimentModule.java @@ -196,7 +196,7 @@ public String getName() @Override public Double getSchemaVersion() { - return 25.013; + return 25.014; } @Nullable diff --git a/experiment/src/org/labkey/experiment/api/ExpRunTableImpl.java b/experiment/src/org/labkey/experiment/api/ExpRunTableImpl.java index 28fae4f1751..143dd5c4abf 100644 --- a/experiment/src/org/labkey/experiment/api/ExpRunTableImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpRunTableImpl.java @@ -68,6 +68,7 @@ import org.labkey.api.exp.query.ExpTable; import org.labkey.api.gwt.client.AuditBehaviorType; import org.labkey.api.gwt.client.FacetingBehaviorType; +import org.labkey.api.module.ModuleLoader; import org.labkey.api.query.AbstractQueryUpdateService; import org.labkey.api.query.BatchValidationException; import org.labkey.api.query.DetailsURL; @@ -75,6 +76,7 @@ import org.labkey.api.query.FieldKey; import org.labkey.api.query.InvalidKeyException; import org.labkey.api.query.LookupForeignKey; +import org.labkey.api.query.QueryForeignKey; import org.labkey.api.query.QueryUpdateService; import org.labkey.api.query.QueryUpdateServiceException; import org.labkey.api.query.RowIdForeignKey; @@ -455,7 +457,15 @@ private TableInfo createLookupTableInfo() var workflowTaskCol = wrapColumn(alias, _rootTable.getColumn("WorkflowTask")); workflowTaskCol.setShownInInsertView(false); workflowTaskCol.setShownInUpdateView(false); - workflowTaskCol.setFk(getExpSchema().getProtocolApplicationForeignKey(getContainerFilter())); + // the "workflow" schema is part of the samplemanagement module + if (ModuleLoader.getInstance().hasModule("samplemanagement")) + { + workflowTaskCol.setFk( + QueryForeignKey.from(this.getUserSchema(), getContainerFilter()) + .schema("workflow", getContainer()) + .to("Task", "RowId", "Name") + ); + } workflowTaskCol.setLabel("Workflow Task"); return workflowTaskCol; default: From 3577a8d48c18ca58f6e10af7f1edf6e3ff5d4363 Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 13 Nov 2025 08:20:13 -0600 Subject: [PATCH 11/18] Comment out XarExporter and XarReader handling of workflowTask for now --- .../org/labkey/experiment/XarExporter.java | 18 +++++++-------- .../src/org/labkey/experiment/XarReader.java | 23 ++++++++++--------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/experiment/src/org/labkey/experiment/XarExporter.java b/experiment/src/org/labkey/experiment/XarExporter.java index 1af4efb740e..79769646c9f 100644 --- a/experiment/src/org/labkey/experiment/XarExporter.java +++ b/experiment/src/org/labkey/experiment/XarExporter.java @@ -330,15 +330,15 @@ public void addExperimentRun(ExpRun run) throws ExperimentException addProtocolApplication(application, run, xApplications); } - ExpProtocolApplication workflowTask = run.getWorkflowTask(); - - if (workflowTask != null) - { - // Due to the way ProtocolApplication LSIDs are generated we can't actually round trip them the normal way - // via the LSIDRelativizer. Instead we construct an LSID out of the object ID with a custom prefix. - String workflowObjectId = Lsid.parse(workflowTask.getLSID()).getObjectId(); - xRun.setWorkflowTaskLSID("${WorkflowTaskReference}:" + workflowObjectId); - } + // TODO need to update this for run workflowTaskId support in XAR + //ExpProtocolApplication workflowTask = run.getWorkflowTask(); + //if (workflowTask != null) + //{ + // // Due to the way ProtocolApplication LSIDs are generated we can't actually round trip them the normal way + // // via the LSIDRelativizer. Instead we construct an LSID out of the object ID with a custom prefix. + // String workflowObjectId = Lsid.parse(workflowTask.getLSID()).getObjectId(); + // xRun.setWorkflowTaskLSID("${WorkflowTaskReference}:" + workflowObjectId); + //} // get AssayService.get().getProvider(run).getXarCallbacks().beforeXarExportRun() with simple attempt at caching for common case if (null != AssayService.get()) diff --git a/experiment/src/org/labkey/experiment/XarReader.java b/experiment/src/org/labkey/experiment/XarReader.java index c523f317998..74989c299e4 100644 --- a/experiment/src/org/labkey/experiment/XarReader.java +++ b/experiment/src/org/labkey/experiment/XarReader.java @@ -1114,15 +1114,15 @@ private void loadExperimentRun(ExperimentRunType a, List startingMa vals.setFilePathRoot(FileUtil.getAbsolutePath(_xarSource.getJobRootPath())); vals.setContainer(getContainer()); - String workflowTaskLSID = a.getWorkflowTaskLSID(); - - if (workflowTaskLSID != null) - { - if (!workflowTaskLSID.startsWith("${WorkflowTaskReference}:")) - throw new XarFormatException("Invalid WorkflowTaskLSID encountered: " + workflowTaskLSID); - - workflowTaskLSID = workflowTaskLSID.split(":")[1]; - } + // TODO need to update this for run workflowTaskId support in XAR + //String workflowTaskLSID = a.getWorkflowTaskLSID(); + //if (workflowTaskLSID != null) + //{ + // if (!workflowTaskLSID.startsWith("${WorkflowTaskReference}:")) + // throw new XarFormatException("Invalid WorkflowTaskLSID encountered: " + workflowTaskLSID); + // + // workflowTaskLSID = workflowTaskLSID.split(":")[1]; + //} if (_job != null) { // remember which job created the run so we can show this run on the job details page @@ -1135,8 +1135,8 @@ private void loadExperimentRun(ExperimentRunType a, List startingMa impl.save(getUser()); run = impl.getDataObject(); - if (workflowTaskLSID != null) - _runWorkflowTaskMap.put(run.getRowId(), workflowTaskLSID); + //if (workflowTaskLSID != null) + // _runWorkflowTaskMap.put(run.getRowId(), workflowTaskLSID); String replacedByLSID = a.getReplacedByRunLSID(); if (replacedByLSID != null) @@ -1209,6 +1209,7 @@ private void loadExperimentRun(ExperimentRunType a, List startingMa } /** + * // TODO need to update this for run workflowTaskId support in XAR * This method runs last, and is used to wire up the Workflow Task FK relationship between Exp Runs and * Exp ProtocolApplications. This needs to run last because we have no good way to guarantee import order and ensure * all the appropriate ProtocolApplications are imported before the Exp Runs. From a5b13d78dd12f5bcb8b167eaa142a278df86131b Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 13 Nov 2025 16:54:50 -0600 Subject: [PATCH 12/18] update comments --- experiment/src/org/labkey/experiment/XarExporter.java | 2 +- experiment/src/org/labkey/experiment/XarReader.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/experiment/src/org/labkey/experiment/XarExporter.java b/experiment/src/org/labkey/experiment/XarExporter.java index 79769646c9f..f1e36d6af4f 100644 --- a/experiment/src/org/labkey/experiment/XarExporter.java +++ b/experiment/src/org/labkey/experiment/XarExporter.java @@ -330,7 +330,7 @@ public void addExperimentRun(ExpRun run) throws ExperimentException addProtocolApplication(application, run, xApplications); } - // TODO need to update this for run workflowTaskId support in XAR + // TODO need to update this for run workflowTaskId support in XAR on workflow job folder export/import is supported //ExpProtocolApplication workflowTask = run.getWorkflowTask(); //if (workflowTask != null) //{ diff --git a/experiment/src/org/labkey/experiment/XarReader.java b/experiment/src/org/labkey/experiment/XarReader.java index 74989c299e4..fe4c3ac60b6 100644 --- a/experiment/src/org/labkey/experiment/XarReader.java +++ b/experiment/src/org/labkey/experiment/XarReader.java @@ -1114,14 +1114,14 @@ private void loadExperimentRun(ExperimentRunType a, List startingMa vals.setFilePathRoot(FileUtil.getAbsolutePath(_xarSource.getJobRootPath())); vals.setContainer(getContainer()); - // TODO need to update this for run workflowTaskId support in XAR + // TODO need to update this for run workflowTaskId support in XAR on workflow job folder export/import is supported //String workflowTaskLSID = a.getWorkflowTaskLSID(); //if (workflowTaskLSID != null) //{ // if (!workflowTaskLSID.startsWith("${WorkflowTaskReference}:")) // throw new XarFormatException("Invalid WorkflowTaskLSID encountered: " + workflowTaskLSID); // - // workflowTaskLSID = workflowTaskLSID.split(":")[1]; + // workflowTaskLSID = workflowTaskLSID.split(":")[1]; //} if (_job != null) { From b710e832ef3e5c94776cfab44e63bd1ca56b0a0a Mon Sep 17 00:00:00 2001 From: labkey-susanh Date: Wed, 19 Nov 2025 08:19:39 -0800 Subject: [PATCH 13/18] Generalize NameGenerator to work with other fields --- api/src/org/labkey/api/data/NameGenerator.java | 6 ++++++ api/src/org/labkey/api/data/NameGeneratorState.java | 9 ++++++++- .../api/dataiterator/NameExpressionDataIterator.java | 4 +++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/api/src/org/labkey/api/data/NameGenerator.java b/api/src/org/labkey/api/data/NameGenerator.java index 3d76477eeed..5dc8b680f72 100644 --- a/api/src/org/labkey/api/data/NameGenerator.java +++ b/api/src/org/labkey/api/data/NameGenerator.java @@ -1658,6 +1658,12 @@ public NameGeneratorState createState(boolean incrementSampleCounts) return new NameGeneratorState(this, incrementSampleCounts, _expressionSummary.sampleSummary); } + @NotNull + public NameGeneratorState createState(boolean incrementSampleCounts, String nameField) + { + return new NameGeneratorState(this, incrementSampleCounts, _expressionSummary.sampleSummary, nameField); + } + public String generateName(@NotNull NameGeneratorState state, @NotNull Map rowMap) throws NameGenerationException { return state.nextName(rowMap, null, null, null, null); diff --git a/api/src/org/labkey/api/data/NameGeneratorState.java b/api/src/org/labkey/api/data/NameGeneratorState.java index d82896a0af5..756266e2ef8 100644 --- a/api/src/org/labkey/api/data/NameGeneratorState.java +++ b/api/src/org/labkey/api/data/NameGeneratorState.java @@ -77,12 +77,19 @@ public class NameGeneratorState implements AutoCloseable protected final Map dataCache = new LongHashMap<>(); protected final RemapCache renameCache; private final Map> objectPropertiesCache = new HashMap<>(); + private final String _nameField; public NameGeneratorState(@NotNull NameGenerator nameGenerator, boolean incrementSampleCounts, NameGenerator.SampleNameExpressionSummary expressionSummary) + { + this(nameGenerator, incrementSampleCounts, expressionSummary, "Name"); + } + + public NameGeneratorState(@NotNull NameGenerator nameGenerator, boolean incrementSampleCounts, NameGenerator.SampleNameExpressionSummary expressionSummary, String nameField) { _nameGenerator = nameGenerator; _incrementSampleCounts = incrementSampleCounts; _container = nameGenerator.getContainer(); + _nameField = nameField; DbSequence sampleCounterSequence; DbSequence rootCounterSequence; @@ -274,7 +281,7 @@ private String genName(@NotNull Map rowMap, } // If a name is already provided, just use it as is - Object currNameObj = rowMap.get("Name"); + Object currNameObj = rowMap.get(_nameField); if (currNameObj != null) { String currName = currNameObj.toString(); diff --git a/api/src/org/labkey/api/dataiterator/NameExpressionDataIterator.java b/api/src/org/labkey/api/dataiterator/NameExpressionDataIterator.java index 1086a0b172e..a70505d8c2f 100644 --- a/api/src/org/labkey/api/dataiterator/NameExpressionDataIterator.java +++ b/api/src/org/labkey/api/dataiterator/NameExpressionDataIterator.java @@ -38,6 +38,7 @@ public class NameExpressionDataIterator extends WrapperDataIterator private final Map> _nameGeneratorMap = new HashMap<>(); private final Map _newNames = new HashMap<>(); private final Integer _nameCol; + private final String _nameColName; private final Integer _expressionCol; private final TableInfo _parentTable; private final Container _container; @@ -60,6 +61,7 @@ public NameExpressionDataIterator(DataIterator di, DataIteratorContext context, _importAliases = importAliases; Map map = DataIteratorUtil.createColumnNameMap(di); + _nameColName = nameColName; _nameCol = map.get(nameColName); _expressionCol = map.get(expressionColName); assert _nameCol != null; @@ -95,7 +97,7 @@ private BatchValidationException getErrors() private void addNameGenerator(String nameExpression) { NameGenerator nameGen = new NameGenerator(nameExpression, _parentTable, false, _importAliases, _container, _getNonConflictCountFn, _counterSeqPrefix); - NameGeneratorState state = nameGen.createState(false); + NameGeneratorState state = nameGen.createState(false, _nameColName); _nameGeneratorMap.put(nameExpression, Pair.of(nameGen, state)); } From d74a452f17984ae7c94c57bb8fa87d715046eb1a Mon Sep 17 00:00:00 2001 From: labkey-susanh Date: Wed, 19 Nov 2025 08:25:27 -0800 Subject: [PATCH 14/18] Move NamePlusIdDataIterator to API for general use --- .../labkey/api/dataiterator}/NamePlusIdDataIterator.java | 7 +------ assay/src/org/labkey/assay/plate/query/PlateSetTable.java | 1 + assay/src/org/labkey/assay/plate/query/PlateTable.java | 1 + 3 files changed, 3 insertions(+), 6 deletions(-) rename {assay/src/org/labkey/assay/plate/query => api/src/org/labkey/api/dataiterator}/NamePlusIdDataIterator.java (89%) diff --git a/assay/src/org/labkey/assay/plate/query/NamePlusIdDataIterator.java b/api/src/org/labkey/api/dataiterator/NamePlusIdDataIterator.java similarity index 89% rename from assay/src/org/labkey/assay/plate/query/NamePlusIdDataIterator.java rename to api/src/org/labkey/api/dataiterator/NamePlusIdDataIterator.java index 90d067bfc1b..ce24e2d5c67 100644 --- a/assay/src/org/labkey/assay/plate/query/NamePlusIdDataIterator.java +++ b/api/src/org/labkey/api/dataiterator/NamePlusIdDataIterator.java @@ -1,4 +1,4 @@ -package org.labkey.assay.plate.query; +package org.labkey.api.dataiterator; import org.apache.commons.lang3.StringUtils; import org.labkey.api.collections.CaseInsensitiveHashMap; @@ -6,11 +6,6 @@ import org.labkey.api.data.NameGenerator; import org.labkey.api.data.NameGeneratorState; import org.labkey.api.data.TableInfo; -import org.labkey.api.dataiterator.DataIterator; -import org.labkey.api.dataiterator.DataIteratorContext; -import org.labkey.api.dataiterator.DataIteratorUtil; -import org.labkey.api.dataiterator.MapDataIterator; -import org.labkey.api.dataiterator.WrapperDataIterator; import org.labkey.api.query.BatchValidationException; import org.labkey.api.query.ValidationException; diff --git a/assay/src/org/labkey/assay/plate/query/PlateSetTable.java b/assay/src/org/labkey/assay/plate/query/PlateSetTable.java index 11b35ed8311..dc5cd4927d7 100644 --- a/assay/src/org/labkey/assay/plate/query/PlateSetTable.java +++ b/assay/src/org/labkey/assay/plate/query/PlateSetTable.java @@ -20,6 +20,7 @@ import org.labkey.api.dataiterator.DataIteratorContext; import org.labkey.api.dataiterator.DetailedAuditLogDataIterator; import org.labkey.api.dataiterator.LoggingDataIterator; +import org.labkey.api.dataiterator.NamePlusIdDataIterator; import org.labkey.api.dataiterator.SimpleTranslator; import org.labkey.api.dataiterator.StandardDataIteratorBuilder; import org.labkey.api.dataiterator.TableInsertDataIteratorBuilder; diff --git a/assay/src/org/labkey/assay/plate/query/PlateTable.java b/assay/src/org/labkey/assay/plate/query/PlateTable.java index 225114eb401..224a5982f9a 100644 --- a/assay/src/org/labkey/assay/plate/query/PlateTable.java +++ b/assay/src/org/labkey/assay/plate/query/PlateTable.java @@ -40,6 +40,7 @@ import org.labkey.api.dataiterator.DataIteratorContext; import org.labkey.api.dataiterator.DetailedAuditLogDataIterator; import org.labkey.api.dataiterator.LoggingDataIterator; +import org.labkey.api.dataiterator.NamePlusIdDataIterator; import org.labkey.api.dataiterator.SimpleTranslator; import org.labkey.api.dataiterator.StandardDataIteratorBuilder; import org.labkey.api.dataiterator.TableInsertDataIteratorBuilder; From 5376be82311b56697b9302e106ed74d6fd06c558 Mon Sep 17 00:00:00 2001 From: labkey-susanh Date: Mon, 24 Nov 2025 13:54:57 -0800 Subject: [PATCH 15/18] Use this --- .../api/dataiterator/NameExpressionDataIteratorBuilder.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/api/src/org/labkey/api/dataiterator/NameExpressionDataIteratorBuilder.java b/api/src/org/labkey/api/dataiterator/NameExpressionDataIteratorBuilder.java index e31e2cafc09..f3711bdd741 100644 --- a/api/src/org/labkey/api/dataiterator/NameExpressionDataIteratorBuilder.java +++ b/api/src/org/labkey/api/dataiterator/NameExpressionDataIteratorBuilder.java @@ -26,10 +26,7 @@ public class NameExpressionDataIteratorBuilder implements DataIteratorBuilder public NameExpressionDataIteratorBuilder(DataIteratorBuilder pre, TableInfo parentTable) { - _pre = pre; - _parentTable = parentTable; - _nameColumnName = "name"; - _nameExpressionColumnName = "nameExpression"; + this(pre, parentTable, "name", "nameExpression"); } public NameExpressionDataIteratorBuilder(DataIteratorBuilder pre, TableInfo parentTable, String nameColumn, String nameExpressionColumn) From 771597d315c735f7a1431e064266c9ef382b3bc1 Mon Sep 17 00:00:00 2001 From: labkey-susanh Date: Mon, 24 Nov 2025 16:12:59 -0800 Subject: [PATCH 16/18] Remove check for isSampleWorkflow in run deletion --- experiment/src/org/labkey/experiment/api/ExpRunImpl.java | 5 +---- .../org/labkey/experiment/api/ExperimentServiceImpl.java | 4 +--- .../experiment/controllers/exp/ExperimentController.java | 8 +++----- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/experiment/src/org/labkey/experiment/api/ExpRunImpl.java b/experiment/src/org/labkey/experiment/api/ExpRunImpl.java index 730ec7086ad..c55c241e91f 100644 --- a/experiment/src/org/labkey/experiment/api/ExpRunImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpRunImpl.java @@ -571,15 +571,12 @@ public void deleteProtocolApplications(List datasToDelete, User use public boolean canDelete(User user) { ExpProtocolImpl protocol = getProtocol(); - boolean isWorkflow = ExpProtocol.isSampleWorkflowProtocol(protocol.getLSID()); - if (isWorkflow && getContainer().hasPermission(user, SampleWorkflowDeletePermission.class)) - return true; // Issue 50776: To update lineage we need to delete existing runs if ((ExperimentServiceImpl.get().isSampleAliquot(protocol) || ExperimentServiceImpl.get().isSampleDerivation(protocol)) && getContainer().hasPermission(user, UpdatePermission.class)) return true; - return !isWorkflow && getContainer().hasPermission(user, DeletePermission.class); + return getContainer().hasPermission(user, DeletePermission.class); } // Clean up DataInput and MaterialInput exp.object and properties diff --git a/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java b/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java index e3bc1bbb24b..561ec8651f1 100644 --- a/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java @@ -4239,9 +4239,7 @@ public void deleteExperimentRuns(Container container, final User user, @Nullable protocolImpl = protocolImpls.computeIfAbsent(protocol, ExpProtocol::getImplementation); if (!run.canDelete(user)) - throw new UnauthorizedException("You do not have permission to delete " + - (ExpProtocol.isSampleWorkflowProtocol(run.getProtocol().getLSID()) ? "jobs" : "runs") - + " in " + run.getContainer()); + throw new UnauthorizedException("You do not have permission to delete runs in " + run.getContainer()); StudyPublishService publishService = StudyPublishService.get(); if (publishService != null) { diff --git a/experiment/src/org/labkey/experiment/controllers/exp/ExperimentController.java b/experiment/src/org/labkey/experiment/controllers/exp/ExperimentController.java index 09ab704bce0..fada708f5ab 100644 --- a/experiment/src/org/labkey/experiment/controllers/exp/ExperimentController.java +++ b/experiment/src/org/labkey/experiment/controllers/exp/ExperimentController.java @@ -3395,9 +3395,8 @@ public ModelAndView getView(DeleteForm deleteForm, boolean reshow, BindException if (run != null) { if (!run.canDelete(getUser())) - throw new UnauthorizedException("You do not have permission to delete " + - (ExpProtocol.isSampleWorkflowProtocol(run.getProtocol().getLSID()) ? "jobs" : "runs") - + " in " + run.getContainer()); + throw new UnauthorizedException("You do not have permission to delete runs in " + + run.getContainer()); runs.add(run); idToRunMap.put(run.getRowId(), run); @@ -3499,8 +3498,7 @@ public ApiResponse execute(DeleteRunForm form, BindException errors) throw new NotFoundException("Could not find run with ID " + form.getRunId()); } if (!run.canDelete(getUser())) - throw new UnauthorizedException("You do not have permission to delete " - + (ExpProtocol.isSampleWorkflowProtocol(run.getProtocol().getLSID()) ? "jobs" : "runs") + " in this container."); + throw new UnauthorizedException("You do not have permission to delete runs in this container."); run.delete(getUser()); return new ApiSimpleResponse("success", true); From 51463d1b31ad8d2128ef17d38998ac5c174c2511 Mon Sep 17 00:00:00 2001 From: labkey-susanh Date: Mon, 24 Nov 2025 18:54:51 -0800 Subject: [PATCH 17/18] Add TODO reminder for code removal --- api/src/org/labkey/api/exp/api/ExpProtocol.java | 1 + 1 file changed, 1 insertion(+) diff --git a/api/src/org/labkey/api/exp/api/ExpProtocol.java b/api/src/org/labkey/api/exp/api/ExpProtocol.java index 5754e4062d9..10593ad4d0b 100644 --- a/api/src/org/labkey/api/exp/api/ExpProtocol.java +++ b/api/src/org/labkey/api/exp/api/ExpProtocol.java @@ -175,6 +175,7 @@ static boolean isSampleWorkflowTaskProtocol(String lsid) return lsid.contains(ExperimentService.SAMPLE_MANAGEMENT_TASK_PROTOCOL_PREFIX); } + // TODO remove this and its relatives once the workflow folder import/export rewrite has happened. static boolean isSampleWorkflowProtocol(String lsid) { return isSampleWorkflowTaskProtocol(lsid) || isSampleWorkflowJobProtocol(lsid); From 3815a73077f365a7d3116670824501cc1bc0f266 Mon Sep 17 00:00:00 2001 From: cnathe Date: Fri, 28 Nov 2025 07:58:03 -0600 Subject: [PATCH 18/18] ExpObject getCreatedById() and getModifiedById() to get the id instead of the User object --- api/src/org/labkey/api/exp/api/ExpObject.java | 2 ++ .../labkey/experiment/api/AbstractRunItemImpl.java | 12 ++++++++++++ .../labkey/experiment/api/ExpChildObjectImpl.java | 12 ++++++++++++ .../experiment/api/ExpIdentifiableEntityImpl.java | 12 ++++++++++++ .../experiment/api/ExpProtocolApplicationImpl.java | 14 ++++++++++++++ .../experiment/api/ExpProtocolInputImpl.java | 14 ++++++++++++++ .../org/labkey/experiment/api/ExpRunInputImpl.java | 12 ++++++++++++ 7 files changed, 78 insertions(+) diff --git a/api/src/org/labkey/api/exp/api/ExpObject.java b/api/src/org/labkey/api/exp/api/ExpObject.java index f019e223a09..7a1a86c3382 100644 --- a/api/src/org/labkey/api/exp/api/ExpObject.java +++ b/api/src/org/labkey/api/exp/api/ExpObject.java @@ -70,8 +70,10 @@ default void setComment(User user, String comment, boolean index) throws Validat } User getCreatedBy(); + Integer getCreatedById(); Date getCreated(); User getModifiedBy(); + Integer getModifiedById(); Date getModified(); void save(User user) throws BatchValidationException; diff --git a/experiment/src/org/labkey/experiment/api/AbstractRunItemImpl.java b/experiment/src/org/labkey/experiment/api/AbstractRunItemImpl.java index 749ad7dd970..6bb4fb43885 100644 --- a/experiment/src/org/labkey/experiment/api/AbstractRunItemImpl.java +++ b/experiment/src/org/labkey/experiment/api/AbstractRunItemImpl.java @@ -193,12 +193,24 @@ public User getCreatedBy() return _object.getCreatedBy() == null ? null : UserManager.getUser(_object.getCreatedBy().intValue()); } + @Override + public Integer getCreatedById() + { + return _object.getCreatedBy(); + } + @Override public User getModifiedBy() { return _object.getModifiedBy() == null ? null : UserManager.getUser(_object.getModifiedBy().intValue()); } + @Override + public Integer getModifiedById() + { + return _object.getModifiedBy(); + } + @Override public Date getModified() { diff --git a/experiment/src/org/labkey/experiment/api/ExpChildObjectImpl.java b/experiment/src/org/labkey/experiment/api/ExpChildObjectImpl.java index d6b58922c2c..5511a8c67bd 100644 --- a/experiment/src/org/labkey/experiment/api/ExpChildObjectImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpChildObjectImpl.java @@ -110,6 +110,12 @@ public User getCreatedBy() return _parent.getCreatedBy(); } + @Override + public Integer getCreatedById() + { + return _parent.getCreatedById(); + } + @Override public Date getCreated() { @@ -122,6 +128,12 @@ public User getModifiedBy() return _parent.getModifiedBy(); } + @Override + public Integer getModifiedById() + { + return _parent.getModifiedById(); + } + @Override public Date getModified() { diff --git a/experiment/src/org/labkey/experiment/api/ExpIdentifiableEntityImpl.java b/experiment/src/org/labkey/experiment/api/ExpIdentifiableEntityImpl.java index 632144bb85f..16bb28b8a53 100644 --- a/experiment/src/org/labkey/experiment/api/ExpIdentifiableEntityImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpIdentifiableEntityImpl.java @@ -46,6 +46,12 @@ public User getCreatedBy() return UserManager.getUser(_object.getCreatedBy()); } + @Override + public Integer getCreatedById() + { + return _object.getCreatedBy(); + } + @Override public Date getModified() { @@ -57,4 +63,10 @@ public User getModifiedBy() { return UserManager.getUser(_object.getModifiedBy()); } + + @Override + public Integer getModifiedById() + { + return _object.getModifiedBy(); + } } \ No newline at end of file diff --git a/experiment/src/org/labkey/experiment/api/ExpProtocolApplicationImpl.java b/experiment/src/org/labkey/experiment/api/ExpProtocolApplicationImpl.java index 621bcfdcf7f..b314ac59a11 100644 --- a/experiment/src/org/labkey/experiment/api/ExpProtocolApplicationImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpProtocolApplicationImpl.java @@ -108,6 +108,13 @@ public User getCreatedBy() return null == run ? null : run.getCreatedBy(); } + @Override + public Integer getCreatedById() + { + ExpRun run = getRun(); + return null == run ? null : run.getCreatedById(); + } + @Override public User getModifiedBy() { @@ -115,6 +122,13 @@ public User getModifiedBy() return null == run ? null : run.getModifiedBy(); } + @Override + public Integer getModifiedById() + { + ExpRun run = getRun(); + return null == run ? null : run.getModifiedById(); + } + @Override public Date getModified() { diff --git a/experiment/src/org/labkey/experiment/api/ExpProtocolInputImpl.java b/experiment/src/org/labkey/experiment/api/ExpProtocolInputImpl.java index fd6778dc9c9..a936ea75a82 100644 --- a/experiment/src/org/labkey/experiment/api/ExpProtocolInputImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpProtocolInputImpl.java @@ -71,6 +71,13 @@ public User getCreatedBy() return null == protocol ? null : protocol.getCreatedBy(); } + @Override + public Integer getCreatedById() + { + ExpProtocol protocol = getProtocol(); + return null == protocol ? null : protocol.getCreatedById(); + } + @Override public User getModifiedBy() { @@ -78,6 +85,13 @@ public User getModifiedBy() return null == protocol ? null : protocol.getModifiedBy(); } + @Override + public Integer getModifiedById() + { + ExpProtocol protocol = getProtocol(); + return null == protocol ? null : protocol.getModifiedById(); + } + @Override public Date getModified() { diff --git a/experiment/src/org/labkey/experiment/api/ExpRunInputImpl.java b/experiment/src/org/labkey/experiment/api/ExpRunInputImpl.java index d851aa06371..6e9f448afbb 100644 --- a/experiment/src/org/labkey/experiment/api/ExpRunInputImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpRunInputImpl.java @@ -114,6 +114,12 @@ public final User getCreatedBy() return getTargetApplication().getCreatedBy(); } + @Override + public final Integer getCreatedById() + { + return getTargetApplication().getCreatedById(); + } + @Override public final Date getCreated() { @@ -126,6 +132,12 @@ public final User getModifiedBy() return getTargetApplication().getModifiedBy(); } + @Override + public final Integer getModifiedById() + { + return getTargetApplication().getModifiedById(); + } + @Override public final Date getModified() {