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); 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 53cd6ffbc4c..16e9664f3a0 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/data/CreatedModified.java b/api/src/org/labkey/api/data/CreatedModified.java index f27230600b8..ececda504aa 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; @@ -22,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; @@ -42,17 +49,34 @@ 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() { return _modified; } + @JsonIgnore + public Date getModifiedDate() + { + return _modified == null ? null : new Date(_modified); + } + public void setModified(Long modified) { _modified = modified; @@ -73,8 +97,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); + } } 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 9eddbcbe92a..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; @@ -45,9 +46,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 +61,9 @@ public NameExpressionDataIterator(DataIterator di, DataIteratorContext context, _importAliases = importAliases; Map map = DataIteratorUtil.createColumnNameMap(di); - _nameCol = map.get("name"); - _expressionCol = map.get("nameExpression"); + _nameColName = nameColName; + _nameCol = map.get(nameColName); + _expressionCol = map.get(expressionColName); assert _nameCol != null; assert _expressionCol != null; @@ -90,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)); } diff --git a/api/src/org/labkey/api/dataiterator/NameExpressionDataIteratorBuilder.java b/api/src/org/labkey/api/dataiterator/NameExpressionDataIteratorBuilder.java index 2ec074dd6ba..f3711bdd741 100644 --- a/api/src/org/labkey/api/dataiterator/NameExpressionDataIteratorBuilder.java +++ b/api/src/org/labkey/api/dataiterator/NameExpressionDataIteratorBuilder.java @@ -21,17 +21,26 @@ 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) + { + this(pre, parentTable, "name", "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)); } } 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/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/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); 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/api/src/org/labkey/api/query/AbstractQueryUpdateService.java b/api/src/org/labkey/api/query/AbstractQueryUpdateService.java index b1175306ddd..bc11979ccbf 100644 --- a/api/src/org/labkey/api/query/AbstractQueryUpdateService.java +++ b/api/src/org/labkey/api/query/AbstractQueryUpdateService.java @@ -359,6 +359,21 @@ 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); + } + + 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) { @@ -526,7 +541,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); @@ -550,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); @@ -586,7 +601,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)); @@ -821,7 +836,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()) @@ -949,7 +964,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(); @@ -1016,7 +1031,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(); 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 ""; + } + } } diff --git a/assay/src/org/labkey/assay/actions/ImportRunApiAction.java b/assay/src/org/labkey/assay/actions/ImportRunApiAction.java index 1e53cc32c22..853469b332b 100644 --- a/assay/src/org/labkey/assay/actions/ImportRunApiAction.java +++ b/assay/src/org/labkey/assay/actions/ImportRunApiAction.java @@ -104,7 +104,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; @@ -139,9 +139,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); @@ -178,7 +178,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()); @@ -260,7 +260,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/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; 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 75ade1977e5..f2a14d2f06e 100644 --- a/experiment/src/org/labkey/experiment/ExperimentMigrationSchemaHandler.java +++ b/experiment/src/org/labkey/experiment/ExperimentMigrationSchemaHandler.java @@ -49,9 +49,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 @@ -242,7 +239,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 16aa700727a..44879fe3998 100644 --- a/experiment/src/org/labkey/experiment/ExperimentModule.java +++ b/experiment/src/org/labkey/experiment/ExperimentModule.java @@ -200,7 +200,7 @@ public String getName() @Override public Double getSchemaVersion() { - return 25.013; + return 25.014; } @Nullable diff --git a/experiment/src/org/labkey/experiment/XarExporter.java b/experiment/src/org/labkey/experiment/XarExporter.java index 1af4efb740e..f1e36d6af4f 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 on workflow job folder export/import is supported + //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..fe4c3ac60b6 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 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]; + //} 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. 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/ExpRunImpl.java b/experiment/src/org/labkey/experiment/api/ExpRunImpl.java index 73ac80aecae..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 @@ -1047,30 +1044,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/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() { diff --git a/experiment/src/org/labkey/experiment/api/ExpRunTableImpl.java b/experiment/src/org/labkey/experiment/api/ExpRunTableImpl.java index 0b27cc54b5d..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: @@ -1020,8 +1030,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); diff --git a/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java b/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java index b9b8859e1e4..47ceb3fc71b 100644 --- a/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java @@ -4250,9 +4250,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);