diff --git a/api/src/org/labkey/api/workflow/Action.java b/api/src/org/labkey/api/workflow/Action.java index c9a5d30c6ec..99ccab0a87a 100644 --- a/api/src/org/labkey/api/workflow/Action.java +++ b/api/src/org/labkey/api/workflow/Action.java @@ -295,7 +295,9 @@ else if (_type == WorkflowService.ActionType.AliquotSamples) return messages; } - else if (_type == WorkflowService.ActionType.DeriveSamples || _type == WorkflowService.ActionType.PoolSamples) + else if (_type == WorkflowService.ActionType.DeriveSamples + || _type == WorkflowService.ActionType.PoolSamples + || _type == WorkflowService.ActionType.DeriveSamplesFromSources) { String emptyMessage = prefix + "data about sample types and sample counts per parent is required for action of type " + _type + "."; @@ -358,9 +360,14 @@ else if (countObj instanceof Integer count) if (hasAnySampleStatusKey()) { - String statusMessage = validateStatus(container, prefix, true); + if (_type == WorkflowService.ActionType.DeriveSamplesFromSources) + messages.add(prefix + "data about updating parent status not allowed for action of type " + _type + "."); + else + { + String statusMessage = validateStatus(container, prefix, true); - if (statusMessage != null) messages.add(statusMessage); + if (statusMessage != null) messages.add(statusMessage); + } } return messages; @@ -377,6 +384,67 @@ else if (_type == WorkflowService.ActionType.RemoveFromStorage || _type == Workf return Collections.emptyList(); } + else if (_type == WorkflowService.ActionType.DeriveSources) + { + String emptyMessage = prefix + "data about source types and source counts per parent is required for action of type " + _type + "."; + + if (_inputParameters == null) return List.of(emptyMessage); + + // We can't just check _inputParameters size because it may include sample status keys, so we extract the + // source type IDs and validate against those. + List sourceTypeIds = new ArrayList<>(); + _inputParameters.keys().forEachRemaining(id -> { + if (isSampleStatusKey(id)) return; + sourceTypeIds.add(id); + }); + + if (sourceTypeIds.isEmpty()) + return List.of(emptyMessage); + + // don't allow more than one target source type + if (sourceTypeIds.size() > 1) + return List.of(prefix + "only one source type can be specified for action of type " + _type + "."); + + List messages = new ArrayList<>(); + String sourceTypeId = sourceTypeIds.get(0); + boolean invalidId; + try + { + invalidId = ExperimentService.get().getDataClass(container, Long.parseLong(sourceTypeId), true) == null; + } + catch (NumberFormatException e) + { + invalidId = true; + } + if (invalidId) + messages.add(prefix + "invalid source type ID " + sourceTypeId + "."); + + Object countObj = _inputParameters.get(sourceTypeId); + boolean invalidCount; + if (countObj instanceof String countStr) + { + try + { + invalidCount = Integer.parseInt(countStr) < 0; + } + catch (NumberFormatException e) + { + invalidCount = true; + } + } + else if (countObj instanceof Integer count) + invalidCount = count < 0; + else + invalidCount = true; + + if (invalidCount) + messages.add(prefix + "invalid source count value " + countObj + "."); + + if (hasAnySampleStatusKey()) + messages.add(prefix + "data about updating parent status not allowed for action of type " + _type + "."); + + return messages; + } else { if (_inputParameters != null && !_inputParameters.isEmpty()) diff --git a/api/src/org/labkey/api/workflow/Task.java b/api/src/org/labkey/api/workflow/Task.java index 4571e833701..2316b89bcd9 100644 --- a/api/src/org/labkey/api/workflow/Task.java +++ b/api/src/org/labkey/api/workflow/Task.java @@ -43,6 +43,7 @@ public abstract class Task extends CreatedModified implements Comparable protected String _description; protected String _entityFilter; + protected WorkEntity.EntityType _inputEntityType; protected Integer _status; protected Date _startDate; protected Date _endDate; @@ -93,6 +94,16 @@ public void setEntityFilter(String entityFilter) _entityFilter = entityFilter; } + public WorkEntity.EntityType getInputEntityType() + { + return _inputEntityType; + } + + public void setInputEntityType(WorkEntity.EntityType inputEntityType) + { + _inputEntityType = inputEntityType; + } + @JsonProperty("assignee") public JSONObject getAssigneeJSON() { @@ -278,6 +289,8 @@ public Map toAuditDetailMap() map.put("dueDate", getDueDate()); if (getEntityId() != null) map.put("entityId", getEntityId().toString()); + if (getInputEntityType() != null) + map.put("inputEntityType", getInputEntityType()); map.put("ordinal", getOrdinal()); int actionIndex = 1; for (Action action : getActions()) diff --git a/api/src/org/labkey/api/workflow/WorkflowService.java b/api/src/org/labkey/api/workflow/WorkflowService.java index 47f79911b43..d51bf41b381 100644 --- a/api/src/org/labkey/api/workflow/WorkflowService.java +++ b/api/src/org/labkey/api/workflow/WorkflowService.java @@ -36,24 +36,28 @@ enum WorkflowConfigs enum ActionType { - AssayImport("assay types", "Imported assay data"), - DeriveSamples("derivation sample type parameters", "Derived samples"), - AliquotSamples("aliquot sample type parameters", "Aliquot samples"), - PoolSamples("pooling sample type parameters", "Pooled samples"), - AddToStorage("input parameters", "Added samples to storage"), - MoveInStorage("input parameters", "Moved samples in storage"), - CheckOut("input parameters", "Checked out samples"), - CheckIn("input parameters", "Checked in samples"), - RemoveFromStorage("sample status value", "Removed samples from storage"), - UpdateSampleStatus("sample status value", "Updated sample status"); + AssayImport("assay types", "Imported assay data", WorkEntity.EntityType.Sample), + DeriveSamples("derivation sample type parameters", "Derived samples", WorkEntity.EntityType.Sample), + AliquotSamples("aliquot sample type parameters", "Aliquot samples", WorkEntity.EntityType.Sample), + PoolSamples("pooling sample type parameters", "Pooled samples", WorkEntity.EntityType.Sample), + AddToStorage("input parameters", "Added samples to storage", WorkEntity.EntityType.Sample), + MoveInStorage("input parameters", "Moved samples in storage", WorkEntity.EntityType.Sample), + CheckOut("input parameters", "Checked out samples", WorkEntity.EntityType.Sample), + CheckIn("input parameters", "Checked in samples", WorkEntity.EntityType.Sample), + RemoveFromStorage("sample status value", "Removed samples from storage", WorkEntity.EntityType.Sample), + UpdateSampleStatus("sample status value", "Updated sample status", WorkEntity.EntityType.Sample), + DeriveSamplesFromSources("derivation source type parameters", "Derived samples from sources", WorkEntity.EntityType.Source), + DeriveSources("derivation source type parameters", "Derived sources", WorkEntity.EntityType.Source); private final String _inputDescription; private final String _auditMessage; + private final WorkEntity.EntityType _inputEntityType; - ActionType(String inputDescription, String auditMessage) + ActionType(String inputDescription, String auditMessage, WorkEntity.EntityType inputEntityType) { _inputDescription = inputDescription; _auditMessage = auditMessage; + _inputEntityType = inputEntityType; } public String getInputDescription() @@ -65,6 +69,11 @@ public String getAuditMessage() { return _auditMessage; } + + public WorkEntity.EntityType getInputEntityType() + { + return _inputEntityType; + } } static void setInstance(WorkflowService impl) @@ -84,9 +93,12 @@ static WorkflowService get() void onActionComplete(@NotNull Container container, @NotNull User user, @NotNull Long actionId, @Nullable String userAuditComment); void onActionComplete(@NotNull Container container, @NotNull User user, @NotNull Long taskId, @NotNull ActionType actionType); boolean actionWillAddSamples(Long actionId); + boolean actionWillAddSources(Long actionId); DataIteratorBuilder getSampleCreationDataIteratorBuilder(DataIteratorBuilder data, Container container, User user); + DataIteratorBuilder getSourceCreationDataIteratorBuilder(DataIteratorBuilder data, Container container, User user); + DataIteratorBuilder getActionAuditDataIteratorBuilder(DataIteratorBuilder data, Container container, User user); @Nullable diff --git a/experiment/src/org/labkey/experiment/api/ExpDataClassDataTableImpl.java b/experiment/src/org/labkey/experiment/api/ExpDataClassDataTableImpl.java index db15c9d080a..b43b7970d11 100644 --- a/experiment/src/org/labkey/experiment/api/ExpDataClassDataTableImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpDataClassDataTableImpl.java @@ -124,6 +124,7 @@ import org.labkey.api.view.ActionURL; import org.labkey.api.view.UnauthorizedException; import org.labkey.api.view.ViewContext; +import org.labkey.api.workflow.WorkflowService; import org.labkey.data.xml.TableType; import org.labkey.experiment.ExpDataIterators; import org.labkey.experiment.ExpDataIterators.AliasDataIteratorBuilder; @@ -942,8 +943,24 @@ public DataIteratorBuilder persistRows(DataIteratorBuilder data, DataIteratorCon }, DbScope.CommitTaskOption.POSTCOMMIT)); + DataIteratorBuilder builder = LoggingDataIterator.wrap(step0); - return LoggingDataIterator.wrap(new AliasDataIteratorBuilder(builder, getUserSchema().getContainer(), getUserSchema().getUser(), ExperimentService.get().getTinfoDataAliasMap(), _dataClass, false)); + UserSchema userSchema = getUserSchema(); + builder = LoggingDataIterator.wrap(new AliasDataIteratorBuilder(builder, userSchema.getContainer(), userSchema.getUser(), ExperimentService.get().getTinfoDataAliasMap(), _dataClass, false)); + WorkflowService workService = WorkflowService.get(); + if (workService != null && !context.getInsertOption().allowUpdate) + { + if (context.getConfigParameter(WorkflowService.WorkflowConfigs.ActionId) != null) + { + Long actionId = (Long) context.getConfigParameter(WorkflowService.WorkflowConfigs.ActionId); + + if (workService.actionWillAddSources(actionId)) + builder = workService.getSourceCreationDataIteratorBuilder(builder, userSchema.getContainer(), userSchema.getUser()); + + builder = workService.getActionAuditDataIteratorBuilder(builder, userSchema.getContainer(), userSchema.getUser()); + } + } + return builder; } catch (IOException e) { diff --git a/experiment/src/org/labkey/experiment/api/SampleTypeUpdateServiceDI.java b/experiment/src/org/labkey/experiment/api/SampleTypeUpdateServiceDI.java index a5e5074bd9f..b6eb9194197 100644 --- a/experiment/src/org/labkey/experiment/api/SampleTypeUpdateServiceDI.java +++ b/experiment/src/org/labkey/experiment/api/SampleTypeUpdateServiceDI.java @@ -429,7 +429,7 @@ public DataIteratorBuilder createImportDIB(User user, Container container, DataI { Long actionId = (Long) context.getConfigParameter(WorkflowService.WorkflowConfigs.ActionId); - if (WorkflowService.get().actionWillAddSamples(actionId)) + if (workService.actionWillAddSamples(actionId) && !context.getInsertOption().allowUpdate) { dib = workService.getSampleCreationDataIteratorBuilder(dib, userSchema.getContainer(), userSchema.getUser()); } diff --git a/experiment/src/org/labkey/experiment/controllers/exp/ExperimentController.java b/experiment/src/org/labkey/experiment/controllers/exp/ExperimentController.java index 93211baf5d7..bb8d487ee41 100644 --- a/experiment/src/org/labkey/experiment/controllers/exp/ExperimentController.java +++ b/experiment/src/org/labkey/experiment/controllers/exp/ExperimentController.java @@ -4426,17 +4426,6 @@ protected int importData( tInfo = ExperimentService.get().createMaterialTable(new SamplesSchema(getUser(), getContainer()), ContainerFilter.current(this), null); updateService = tInfo.getUpdateService(); } - if (WorkflowService.get() != null) - { - try - { - WorkflowService.get().populateConfigParams(getViewContext().getRequest(), _context.getConfigParameters()); - } - catch (ValidationException e) - { - errors.addRowError(e); - } - } int count = importData(dl, tInfo, updateService, _context, auditEvent, getUser(), getContainer()); @@ -4551,6 +4540,21 @@ protected Set getLineageImportAliases() throws IOException protected void initContext(DataLoader dl, BatchValidationException errors, @Nullable AuditBehaviorType auditBehaviorType, @Nullable String auditUserComment) { _context = createDataIteratorContext(_insertOption, getOptionParamsMap(), getLookupResolutionType(), auditBehaviorType, auditUserComment, errors, null, getContainer()); + + // Both samples and data classes can be created via a workflow job action, so the action and job ids need to + // be available to the update service for either type. The background import path populates these separately + // via AbstractQueryImportAction.getImportContextBuilder(). + if (WorkflowService.get() != null) + { + try + { + WorkflowService.get().populateConfigParams(getViewContext().getRequest(), _context.getConfigParameters()); + } + catch (ValidationException e) + { + errors.addRowError(e); + } + } } @Override