Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 33 additions & 32 deletions api/src/org/labkey/api/audit/SampleTimelineAuditEvent.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.jetbrains.annotations.Nullable;
import org.labkey.api.collections.CaseInsensitiveHashMap;
import org.labkey.api.data.Container;
import org.labkey.api.exp.api.ExperimentService;
import org.labkey.api.qc.DataState;
import org.labkey.api.qc.SampleStatusService;
import org.labkey.api.query.QueryService;
Expand Down Expand Up @@ -224,46 +225,38 @@ public Map<String, Object> getAuditLogMessageElements()
return elements;
}

/**
* If the sample state changed, explicitly add in the Status Label value to the map so that it will render in the
* audit log timeline event even if the DataState row is later deleted. Also, remove the aliquot rollup calculated
* fields from the data.
*/
@Override
public void setOldRecordMap(String oldRecordMap, Container container)
{
if (oldRecordMap != null)
{
Map<String, String> row = new CaseInsensitiveHashMap<>(AbstractAuditTypeProvider.decodeFromDataMap(oldRecordMap));
EXCLUDED_DETAIL_FIELDS.forEach(row::remove);
String label = getStatusLabel(row, container);
if (label != null)
{
row.put("samplestatelabel", label);
oldRecordMap = AbstractAuditTypeProvider.encodeForDataMap(row);
}
}
super.setOldRecordMap(oldRecordMap);
super.setOldRecordMap(withResolvedLabels(oldRecordMap, container));
}

/**
* If the sample state changed, explicitly add in the Status Label value to the map so that it will render in the
* audit log timeline event even if the DataState row is later deleted. Also, remove the aliquot rollup calculated
* fields from the data.
*/
@Override
public void setNewRecordMap(String newRecordMap, Container container)
{
if (newRecordMap != null)
{
Map<String, String> row = new CaseInsensitiveHashMap<>(AbstractAuditTypeProvider.decodeFromDataMap(newRecordMap));
EXCLUDED_DETAIL_FIELDS.forEach(row::remove);
String label = getStatusLabel(row, container);
if (label != null)
row.put("samplestatelabel", label);
newRecordMap = AbstractAuditTypeProvider.encodeForDataMap(row);
}
super.setNewRecordMap(newRecordMap, container);
super.setNewRecordMap(withResolvedLabels(newRecordMap, container), container);
}

/**
* If the sample state or color changed, explicitly add the resolved Status/Color label to the map so it renders in
* the audit log timeline event even if the DataState/DataColor row is later deleted. Also removes the aliquot rollup
* calculated fields from the data.
*/
private String withResolvedLabels(String recordMap, Container container)
{
if (recordMap == null)
return null;

Map<String, String> row = new CaseInsensitiveHashMap<>(AbstractAuditTypeProvider.decodeFromDataMap(recordMap));
EXCLUDED_DETAIL_FIELDS.forEach(row::remove);

String statusLabel = getStatusLabel(row, container);
row.put("samplestatelabel", statusLabel);

String colorLabel = getColorLabel(row, container);
row.put("expmaterialcolorlabel", colorLabel);

return AbstractAuditTypeProvider.encodeForDataMap(row);
}

private String getStatusLabel(Map<String, String> row, Container container)
Expand All @@ -276,4 +269,12 @@ private String getStatusLabel(Map<String, String> row, Container container)
}
return null;
}

private String getColorLabel(Map<String, String> row, Container container)
{
String value = row.get(ExpMaterialColor.name());
if (!StringUtils.isBlank(value))
return ExperimentService.get().getDataColorLabel(container, Long.parseLong(value));
return null;
}
}
3 changes: 3 additions & 0 deletions api/src/org/labkey/api/data/ContainerManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -1953,7 +1953,10 @@ private static boolean delete(final Container c, User user, @Nullable String com

ExperimentService experimentService = ExperimentService.get();
if (experimentService != null)
{
experimentService.removeContainerDataTypeExclusions(c.getId());
experimentService.removeContainerDataColorExclusions(c.getId());
}

// Issue 17015: After we've committed the transaction, be sure that we remove this container from the cache
tx.addCommitTask(() ->
Expand Down
4 changes: 4 additions & 0 deletions api/src/org/labkey/api/exp/api/ExpMaterial.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ public interface ExpMaterial extends ExpRunItem

void setSampleStateId(Long stateId);

Long getSampleColorId();

void setSampleColorId(Long colorId);

Date getMaterialExpDate();

ActionURL detailsURL(Container container, boolean checkForOverride);
Expand Down
23 changes: 23 additions & 0 deletions api/src/org/labkey/api/exp/api/ExperimentService.java
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ public interface ExperimentService extends ExperimentRunTypeSource

String EXPERIMENTAL_FEATURE_ALLOW_ROW_ID_MERGE = "org.labkey.experiment.api.SampleTypeUpdateServiceDI#ALLOW_ROW_ID_SAMPLE_MERGE";

String EXPERIMENTAL_SAMPLE_COLORS = "org.labkey.api.exp.api.ExperimentService#SAMPLE_COLORS";

int SIMPLE_PROTOCOL_FIRST_STEP_SEQUENCE = 1;
int SIMPLE_PROTOCOL_CORE_STEP_SEQUENCE = 10;
int SIMPLE_PROTOCOL_EXTRA_STEP_SEQUENCE = 15;
Expand Down Expand Up @@ -710,6 +712,8 @@ static void validateParentAlias(Map<String, String> aliasMap, Set<String> reserv

SampleStatusTable createSampleStatusTable(ExpSchema expSchema, ContainerFilter cf);

TableInfo createDataColorTable(ExpSchema expSchema, ContainerFilter cf);

ExpUnreferencedSampleFilesTable createUnreferencedSampleFilesTable(ExpSchema expSchema, ContainerFilter cf);

FilteredTable<ExpSchema> createFieldsTable(ExpSchema expSchema, ContainerFilter cf);
Expand Down Expand Up @@ -1139,6 +1143,25 @@ List<? extends ExpProtocol> getExpProtocolsWithParameterValue(

String getDisabledDataTypeAuditMsg(DataTypeForExclusion type, List<Long> ids, boolean isUpdate);

@NotNull Set<Long> getDataTypeExcludedColors(DataTypeForExclusion dataType, long dataTypeId);

/** The data type rowIds (e.g. sample type rowIds) that currently exclude the given color. Inverse of {@link #getDataTypeExcludedColors}. */
@NotNull Set<Long> getDataTypesExcludingColor(DataTypeForExclusion dataType, long colorRowId);

@NotNull Set<Long> getActiveDataTypeColors(@NotNull Container container, DataTypeForExclusion dataType, long dataTypeId);

@Nullable String getDataColorLabel(@NotNull Container container, long colorRowId);

boolean ensureDataColorExclusions(long dataTypeId, DataTypeForExclusion dataType, @Nullable Collection<Long> disabledColorRowIds, @NotNull Container container, User user);

@NotNull Set<Long> updateColorDataTypeExclusions(long colorRowId, DataTypeForExclusion dataType, @Nullable Collection<Long> newlyDisabledDataTypeIds, @Nullable Collection<Long> newlyEnabledDataTypeIds, @NotNull Container container, User user);

void removeDataColorExclusionsForColor(long colorRowId);

void removeDataColorExclusionsForDataType(long dataTypeId, DataTypeForExclusion dataType);

void removeContainerDataColorExclusions(String containerId);

void registerRunInputsViewProvider(QueryViewProvider<ExpRun> provider);

void registerRunOutputsViewProvider(QueryViewProvider<ExpRun> providers);
Expand Down
5 changes: 4 additions & 1 deletion api/src/org/labkey/api/exp/api/SampleTypeDomainKind.java
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,8 @@ public Domain createDomain(GWTDomain<GWTPropertyDescriptor> domain, @Nullable Sa
Map<String, Map<String, Object>> aliases = null;
List<String> excludedContainerIds = null;
List<String> excludedDashboardContainerIds = null;
List<Integer> excludedSampleColorIds = null;


if (arguments != null)
{
Expand All @@ -556,12 +558,13 @@ public Domain createDomain(GWTDomain<GWTPropertyDescriptor> domain, @Nullable Sa
aliases = arguments.getImportAliases();
excludedContainerIds = arguments.getExcludedContainerIds();
excludedDashboardContainerIds = arguments.getExcludedDashboardContainerIds();
excludedSampleColorIds = arguments.getDisabledSampleColorRowIds();
}
ExpSampleType st;
try
{
st = SampleTypeService.get().createSampleType(container, user, name, description, properties, indices, idCol1, idCol2, idCol3, parentCol, nameExpression, aliquotNameExpression,
templateInfo, aliases, labelColor, metricUnit, autoLinkTargetContainer, autoLinkCategory, category, domain.getDisabledSystemFields(), excludedContainerIds, excludedDashboardContainerIds, arguments != null ? arguments.getAuditRecordMap() : null);
templateInfo, aliases, labelColor, metricUnit, autoLinkTargetContainer, autoLinkCategory, category, domain.getDisabledSystemFields(), excludedContainerIds, excludedDashboardContainerIds, excludedSampleColorIds, arguments != null ? arguments.getAuditRecordMap() : null);
}
catch (ExperimentException e)
{
Expand Down
11 changes: 11 additions & 0 deletions api/src/org/labkey/api/exp/api/SampleTypeDomainKindProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public SampleTypeDomainKindProperties(ExpSampleType st)
private String category;
private List<String> excludedContainerIds;
private List<String> excludedDashboardContainerIds;
private List<Integer> disabledSampleColorRowIds;

//Ignored on import/save, use Domain.name & Domain.description instead
private String name;
Expand Down Expand Up @@ -285,6 +286,16 @@ public List<String> getExcludedDashboardContainerIds()
return excludedDashboardContainerIds;
}

public List<Integer> getDisabledSampleColorRowIds()
{
return disabledSampleColorRowIds;
}

public void setDisabledSampleColorRowIds(List<Integer> disabledSampleColorRowIds)
{
this.disabledSampleColorRowIds = disabledSampleColorRowIds;
}

public void setExcludedDashboardContainerIds(List<String> excludedDashboardContainerIds)
{
this.excludedDashboardContainerIds = excludedDashboardContainerIds;
Expand Down
4 changes: 3 additions & 1 deletion api/src/org/labkey/api/exp/api/SampleTypeService.java
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ static void setInstance(SampleTypeService impl)

Map<String, ExpSampleType> getSampleTypesForRoles(Container container, ContainerFilter filter, ExpProtocol.ApplicationType type);

void auditSampleColorExclusion(Container container, long sampleTypeRowId, @Nullable String auditUserComment, User user);

/**
* Create a new SampleType with the provided properties.
* If a 'Name' property exists in the list, it will be used as the 'id' property of the SampleType.
Expand Down Expand Up @@ -150,7 +152,7 @@ ExpSampleType createSampleType(Container container, User user, String name, Stri
ExpSampleType createSampleType(Container c, User u, String name, String description, List<GWTPropertyDescriptor> properties, List<GWTIndex> indices, int idCol1, int idCol2, int idCol3, int parentCol,
String nameExpression, String aliquotNameExpression, @Nullable TemplateInfo templateInfo, @Nullable Map<String, Map<String, Object>> importAliases, @Nullable String labelColor, @Nullable String metricUnit,
@Nullable Container autoLinkTargetContainer, @Nullable String autoLinkCategory, @Nullable String category, @Nullable List<String> disabledSystemField,
@Nullable List<String> excludedContainerIds, @Nullable List<String> excludedDashboardContainerIds, @Nullable Map<String, Object> changeDetails)
@Nullable List<String> excludedContainerIds, @Nullable List<String> excludedDashboardContainerIds, @Nullable List<Integer> excludedSampleColorIds, @Nullable Map<String, Object> changeDetails)
throws ExperimentException;

@NotNull
Expand Down
1 change: 1 addition & 0 deletions api/src/org/labkey/api/exp/query/ExpMaterialTable.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ enum Column
RunId, // database table only
RunApplication,
RunApplicationOutput,
ExpMaterialColor,
SampleSet,
SampleState,
SourceApplicationId, // database table only
Expand Down
8 changes: 8 additions & 0 deletions api/src/org/labkey/api/exp/query/ExpSchema.java
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,14 @@ public TableInfo createTable(ExpSchema expSchema, String queryName, ContainerFil
return ExperimentService.get().createSampleStatusTable(expSchema, cf);
}
},
DataColors
{
@Override
public TableInfo createTable(ExpSchema expSchema, String queryName, ContainerFilter cf)
{
return ExperimentService.get().createDataColorTable(expSchema, cf);
}
},
Fields
{
@Override
Expand Down
7 changes: 4 additions & 3 deletions api/src/org/labkey/api/query/AbstractQueryUpdateService.java
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,8 @@ protected DataIteratorBuilder _toDataIteratorBuilder(String debugName, List<Map<

/** @deprecated switch to using DIB based method */
@Deprecated
protected List<Map<String, Object>> _insertRowsUsingInsertRow(User user, Container container, List<Map<String, Object>> rows, BatchValidationException errors, Map<String, Object> extraScriptContext)
protected List<Map<String, Object>> _insertRowsUsingInsertRow(User user, Container container, List<Map<String, Object>> rows, BatchValidationException errors,
@Nullable Map<Enum, Object> configParameters, Map<String, Object> extraScriptContext)
throws DuplicateKeyException, BatchValidationException, QueryUpdateServiceException, SQLException
{
if (!hasInsertRowsPermission(user))
Expand Down Expand Up @@ -664,7 +665,7 @@ else if (SqlDialect.isTransactionException(sqlx) && errors.hasErrors())
if (hasTableScript)
getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.INSERT, null, false, errors, extraScriptContext);

addAuditEvent(user, container, QueryService.AuditAction.INSERT, null, result, null, providedValues);
addAuditEvent(user, container, QueryService.AuditAction.INSERT, configParameters, result, null, providedValues);

return result;
}
Expand Down Expand Up @@ -715,7 +716,7 @@ public List<Map<String, Object>> insertRows(User user, Container container, List
{
try
{
List<Map<String,Object>> ret = _insertRowsUsingInsertRow(user, container, rows, errors, extraScriptContext);
List<Map<String,Object>> ret = _insertRowsUsingInsertRow(user, container, rows, errors, configParameters, extraScriptContext);
afterInsertUpdate(null==ret?0:ret.size(), errors);
if (errors.hasErrors())
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2026 LabKey Corporation
*
* Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
*/

CREATE TABLE exp.DataColors
(
RowId SERIAL NOT NULL,
Container ENTITYID NOT NULL,
Label VARCHAR(64) NOT NULL,
Color VARCHAR(7) NOT NULL,
Archived BOOLEAN NOT NULL DEFAULT FALSE,
Created TIMESTAMP,
CreatedBy INT,
Modified TIMESTAMP,
ModifiedBy INT,

CONSTRAINT PK_DataColors PRIMARY KEY (RowId),
CONSTRAINT FK_DataColors_Container FOREIGN KEY (Container) REFERENCES core.Containers (EntityId)
);

CREATE UNIQUE INDEX UQ_DataColors_Label ON exp.DataColors (Container, LOWER(Label));

ALTER TABLE exp.Material ADD COLUMN ExpMaterialColor INT;
ALTER TABLE exp.Material ADD CONSTRAINT FK_Material_ExpMaterialColor FOREIGN KEY (ExpMaterialColor) REFERENCES exp.DataColors (RowId);
CREATE INDEX IX_Material_ExpMaterialColor ON exp.Material (ExpMaterialColor) WHERE ExpMaterialColor IS NOT NULL;

CREATE TABLE exp.DataTypeColorExclusion
(
RowId SERIAL NOT NULL,
Container ENTITYID NOT NULL,
DataTypeRowId INT NOT NULL,
DataType VARCHAR(20) NOT NULL,
ColorRowId INT NOT NULL,
Created TIMESTAMP,
CreatedBy INT,
Modified TIMESTAMP,
ModifiedBy INT,

CONSTRAINT PK_DataTypeColorExclusion PRIMARY KEY (RowId),
CONSTRAINT UQ_DataTypeColorExclusion UNIQUE (DataTypeRowId, DataType, ColorRowId),
CONSTRAINT FK_DataTypeColorExclusion_Container FOREIGN KEY (Container) REFERENCES core.Containers (EntityId),
CONSTRAINT FK_DataTypeColorExclusion_Color FOREIGN KEY (ColorRowId) REFERENCES exp.DataColors (RowId)
);
30 changes: 30 additions & 0 deletions experiment/resources/schemas/exp.xml
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,9 @@
<column columnName="SampleState">
<description>Represents the status of the sample</description>
</column>
<column columnName="ExpMaterialColor">
<description>The color assigned to this individual sample</description>
</column>
<column columnName="StoredAmount">
<columnTitle>Amount</columnTitle>
</column>
Expand Down Expand Up @@ -1047,6 +1050,7 @@
<column columnName="RootMaterialRowId"/>
<column columnName="AliquotedFromLSID"/>
<column columnName="SampleState"/>
<column columnName="ExpMaterialColor"/>
<column columnName="AliquotCount"/>
<column columnName="AliquotVolume"/>
<column columnName="AliquotUnit"/>
Expand Down Expand Up @@ -1255,4 +1259,30 @@
<column columnName="ModifiedBy"/>
</columns>
</table>
<table tableName="DataColors" tableDbType="TABLE">
<columns>
<column columnName="RowId"/>
<column columnName="Container"/>
<column columnName="Label"/>
<column columnName="Color"/>
<column columnName="Archived"/>
<column columnName="Created"/>
<column columnName="CreatedBy"/>
<column columnName="Modified"/>
<column columnName="ModifiedBy"/>
</columns>
</table>
<table tableName="DataTypeColorExclusion" tableDbType="TABLE">
<columns>
<column columnName="RowId"/>
<column columnName="Container"/>
<column columnName="DataTypeRowId"/>
<column columnName="DataType"/>
<column columnName="ColorRowId"/>
<column columnName="Created"/>
<column columnName="CreatedBy"/>
<column columnName="Modified"/>
<column columnName="ModifiedBy"/>
</columns>
</table>
</tables>
Loading