Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
c4c68d4
Support auditUserComment for domain update/delete
XingY May 8, 2025
11b5243
Add detailed auditing of domain changes & comment ability
XingY May 12, 2025
9894ab1
Merge branch 'develop' into fb_domainAudit
XingY May 12, 2025
6257385
clean
XingY May 12, 2025
724904e
Merge branch 'develop' into fb_domainAudit
XingY May 12, 2025
4842438
switch to use domain.delete for study dataset delete
XingY May 12, 2025
ca526b9
fix regex/validator, add selenium tests
XingY May 13, 2025
0dd74aa
fix dataset delete
XingY May 13, 2025
b255592
fix dataset delete
XingY May 13, 2025
850e993
update tests
XingY May 13, 2025
987dbcd
fix folder exclusion audit
XingY May 14, 2025
43cc174
fix audit detail for field format change
XingY May 14, 2025
cf08a84
selenium tests
XingY May 14, 2025
24db680
audit unique indices update
XingY May 14, 2025
95af4ec
code review changes
XingY May 14, 2025
2f33703
switch to use old and new record map for domain event details
XingY May 16, 2025
485de76
populate new record map during domain creation
XingY May 16, 2025
782f5ab
clean
XingY May 16, 2025
8c721f2
clean
XingY May 16, 2025
51b623a
Merge branch 'develop' into fb_domainAudit
XingY May 16, 2025
39237ce
Merge branch 'develop' into fb_domainAudit
XingY May 16, 2025
59d7f37
fixes and update tests
XingY May 19, 2025
dcc884a
Merge branch 'develop' into fb_domainAudit
labkey-danield May 20, 2025
301f215
update tests
XingY May 20, 2025
c28d300
Merge branch 'develop' into fb_domainAudit
XingY May 20, 2025
b2bd762
support calculated field audit changes, genId update audit, update/ad…
XingY May 21, 2025
606bdd7
revert isCalculatedColumn getter
XingY May 21, 2025
a5b72b6
Fix text -> flag field type conversion audit
XingY May 21, 2025
ec5aef7
add more tests
XingY May 22, 2025
423c00f
fix more tests
XingY May 22, 2025
1ffc59d
Issue 53108: Max length of Aliquot Naming Pattern is less than Naming…
XingY May 22, 2025
92ac24f
code review changes
XingY May 22, 2025
c26f270
refactor
XingY May 23, 2025
fcea799
Merge branch 'develop' into fb_domainAudit
XingY May 23, 2025
a44b1ce
Merge branch 'develop' into fb_domainAudit
XingY May 23, 2025
e1ed9e3
Merge branch 'develop' into fb_domainAudit
XingY May 23, 2025
9243d92
Merge branch 'develop' into fb_domainAudit
XingY May 23, 2025
410a9c9
Merge branch 'develop' into fb_domainAudit
XingY May 24, 2025
7de2e20
publish
XingY May 24, 2025
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
38 changes: 38 additions & 0 deletions api/gwtsrc/org/labkey/api/gwt/client/assay/model/GWTProtocol.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@
package org.labkey.api.gwt.client.assay.model;

import com.google.gwt.user.client.rpc.IsSerializable;
import org.apache.commons.lang3.StringUtils;
import org.labkey.api.gwt.client.model.GWTContainer;
import org.labkey.api.gwt.client.model.GWTDomain;
import org.labkey.api.gwt.client.model.GWTPropertyDescriptor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -76,6 +78,7 @@ public class GWTProtocol implements IsSerializable
private boolean _plateMetadata;
private String _status;
private List<String> _excludedContainerIds;
private String _auditUserComment;

public GWTProtocol()
{
Expand Down Expand Up @@ -436,4 +439,39 @@ public void setExcludedContainerIds(List<String> excludedContainerIds)
{
_excludedContainerIds = excludedContainerIds;
}

public String getAuditUserComment()
{
return _auditUserComment;
}

public void setAuditUserComment(String auditUserComment)
{
_auditUserComment = auditUserComment;
}

public Map<String, Object> getAuditRecordMap()
{
Map<String, Object> map = new LinkedHashMap<>();
map.put("Name", getName());
if (!StringUtils.isEmpty(getDescription()))
map.put("Description", getDescription());
if (!StringUtils.isEmpty(getStatus()))
map.put("Status", getStatus());
String autoCopyTargetContainerId = getAutoCopyTargetContainer() != null ? getAutoCopyTargetContainer().getEntityId() : getAutoCopyTargetContainerId();
if (!StringUtils.isEmpty(autoCopyTargetContainerId))
map.put("AutoCopyTargetContainer", autoCopyTargetContainerId);
if (!StringUtils.isEmpty(getAutoLinkCategory()))
map.put("AutoLinkCategory", getAutoLinkCategory());
map.put("SaveScriptFiles", isSaveScriptFiles());
map.put("IsEditableResults", isEditableResults());
map.put("IsEditableRuns", isEditableRuns());
map.put("IsBackgroundUpload", isBackgroundUpload());
map.put("IsQcEnabled", isQcEnabled());
map.put("IsPlateMetadataEnabled", isPlateMetadata());

return map;
}


}
53 changes: 38 additions & 15 deletions api/gwtsrc/org/labkey/api/gwt/client/model/GWTIndex.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
/*
* Copyright (c) 2018-2019 LabKey Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright (c) 2018-2019 LabKey Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.labkey.api.gwt.client.model;

import com.google.gwt.user.client.rpc.IsSerializable;
import org.apache.commons.lang3.StringUtils;
import org.labkey.api.data.PropertyStorageSpec;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
* User: kevink
Expand Down Expand Up @@ -69,4 +74,22 @@ public void setUnique(boolean unique)
{
_unique = unique;
}

public String toStringVal()
{
if (_columnNames == null || _columnNames.isEmpty())
return "";

return StringUtils.join(_columnNames, ", ") + ", unique: " + isUnique();
}

public static List<String> toStringVals(List<GWTIndex> indices, Set<PropertyStorageSpec.Index> excludeBaseIndices)
{
if (indices == null || indices.isEmpty())
return null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Can just return Collections.emptyList() then callers do not need to worry about null values (and could be annotated @NotNull.


Set<String> excludeIndices = excludeBaseIndices == null ? Collections.emptySet() : excludeBaseIndices.stream().map(PropertyStorageSpec.Index::toStringVal).collect(Collectors.toSet());
return indices.stream().map(GWTIndex::toStringVal).filter(v -> !excludeIndices.contains(v)).sorted().toList();
}

}
9 changes: 6 additions & 3 deletions api/src/org/labkey/api/assay/AbstractAssayProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
import org.labkey.api.exp.api.IAssayDomainType;
import org.labkey.api.exp.property.Domain;
import org.labkey.api.exp.property.DomainProperty;
import org.labkey.api.exp.property.DomainUtil;
import org.labkey.api.exp.property.PropertyService;
import org.labkey.api.exp.query.ExpRunTable;
import org.labkey.api.files.FileContentService;
Expand Down Expand Up @@ -1246,7 +1247,7 @@ public boolean hasUsefulDetailsPage()
private static final String SCRIPT_PATH_DELIMITER = "|";

@Override
public ValidationException setValidationAndAnalysisScripts(ExpProtocol protocol, @NotNull List<AnalysisScript> scripts) throws ExperimentException
public Pair<ValidationException, Pair<String, String>> setValidationAndAnalysisScripts(ExpProtocol protocol, @NotNull List<AnalysisScript> scripts) throws ExperimentException
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: A typed record would provide more context than type Pair<String, String> about what these strings are.

{
Map<String, ObjectProperty> props = new HashMap<>(protocol.getObjectProperties());
String propertyURI = ScriptType.TRANSFORM.getPropertyURI(protocol);
Expand Down Expand Up @@ -1290,9 +1291,11 @@ public ValidationException setValidationAndAnalysisScripts(ExpProtocol protocol,

// don't persist if any validation error has severity=ERROR
if (validationErrors.getErrors().stream().anyMatch(e -> SEVERITY.ERROR == e.getSeverity()))
return validationErrors;
return new Pair<>(validationErrors, null);

JSONArray json = AnalysisScript.toJson(scripts);
ObjectProperty oldProp = props.get(propertyURI);
String oldJson = oldProp == null ? null : oldProp.getStringValue();
if (json != null)
{
ObjectProperty prop = new ObjectProperty(protocol.getLSID(), protocol.getContainer(),
Expand All @@ -1305,7 +1308,7 @@ public ValidationException setValidationAndAnalysisScripts(ExpProtocol protocol,
}
protocol.setObjectProperties(props);

return validationErrors;
return new Pair<>(validationErrors, new Pair<>(oldJson, json == null ? null : json.toString()));
}

/** For migrating legacy assay designs that have separate transform and validation script properties */
Expand Down
3 changes: 2 additions & 1 deletion api/src/org/labkey/api/assay/AssayProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,9 @@ enum Scope
/**
* File based QC and analysis scripts can be added to a protocol and invoked when the validate
* method is called. Set to an empty list if no scripts exist.
* @return ValidationException, a pair of old and new string representation of the script description (for audit use)
*/
ValidationException setValidationAndAnalysisScripts(ExpProtocol protocol, @NotNull List<AnalysisScript> scripts) throws ExperimentException;
Pair<ValidationException, Pair<String, String>> setValidationAndAnalysisScripts(ExpProtocol protocol, @NotNull List<AnalysisScript> scripts) throws ExperimentException;

@NotNull
List<AnalysisScript> getValidationAndAnalysisScripts(ExpProtocol protocol, Scope scope);
Expand Down
10 changes: 5 additions & 5 deletions api/src/org/labkey/api/audit/AbstractAuditHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public void addAuditEvent(User user, Container c, TableInfo table, @Nullable Aud
{
case INSERT:
{
String newRecord = AbstractAuditTypeProvider.encodeForDataMap(c, row);
String newRecord = AbstractAuditTypeProvider.encodeForDataMap(row);
if (newRecord != null)
event.setNewRecordMap(newRecord, c);
break;
Expand All @@ -126,7 +126,7 @@ public void addAuditEvent(User user, Container c, TableInfo table, @Nullable Aud
{
if (existingRow.isEmpty())
{
String newRecord = AbstractAuditTypeProvider.encodeForDataMap(c, row);
String newRecord = AbstractAuditTypeProvider.encodeForDataMap(row);
if (newRecord != null)
event.setNewRecordMap(newRecord, c);
}
Expand All @@ -138,7 +138,7 @@ public void addAuditEvent(User user, Container c, TableInfo table, @Nullable Aud
}
case DELETE:
{
String oldRecord = AbstractAuditTypeProvider.encodeForDataMap(c, row);
String oldRecord = AbstractAuditTypeProvider.encodeForDataMap(row);
if (oldRecord != null)
event.setOldRecordMap(oldRecord, c);
break;
Expand Down Expand Up @@ -177,11 +177,11 @@ private void setOldAndNewMapsForUpdate(DetailedAuditTypeEvent event, Container c
// allow for adding fields that may be present in the updated row but not represented in the original row
addDetailedModifiedFields(existingRow, modifiedRow, row);

String oldRecord = AbstractAuditTypeProvider.encodeForDataMap(c, originalRow);
String oldRecord = AbstractAuditTypeProvider.encodeForDataMap(originalRow);
if (oldRecord != null)
event.setOldRecordMap(oldRecord, c);

String newRecord = AbstractAuditTypeProvider.encodeForDataMap(c, modifiedRow);
String newRecord = AbstractAuditTypeProvider.encodeForDataMap(modifiedRow);
if (newRecord != null)
event.setNewRecordMap(newRecord, c);
}
Expand Down
26 changes: 25 additions & 1 deletion api/src/org/labkey/api/audit/AbstractAuditTypeProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
*/
package org.labkey.api.audit;

import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.labkey.api.audit.data.DataMapColumn;
import org.labkey.api.audit.data.DataMapDiffColumn;
import org.labkey.api.audit.query.AbstractAuditDomainKind;
Expand Down Expand Up @@ -48,6 +50,7 @@
import org.labkey.api.exp.property.PropertyService;
import org.labkey.api.gwt.client.DefaultValueType;
import org.labkey.api.query.AliasedColumn;
import org.labkey.api.query.DetailsURL;
import org.labkey.api.query.FieldKey;
import org.labkey.api.query.UserSchema;
import org.labkey.api.security.User;
Expand Down Expand Up @@ -345,6 +348,16 @@ public static Container getDomainContainer()
}

protected void appendValueMapColumns(AbstractTableInfo table)
{
appendValueMapColumns(table, null);
}

protected void appendValueMapColumns(AbstractTableInfo table, String eventName)
{
appendValueMapColumns(table, eventName, false);
}

protected void appendValueMapColumns(AbstractTableInfo table, String eventName, boolean noUrl)
{
MutableColumnInfo oldCol = table.getMutableColumn(FieldKey.fromString(OLD_RECORD_PROP_NAME));
MutableColumnInfo newCol = table.getMutableColumn(FieldKey.fromString(NEW_RECORD_PROP_NAME));
Expand All @@ -370,6 +383,17 @@ protected void appendValueMapColumns(AbstractTableInfo table)
// add a column to show the differences between old and new values
if (oldCol != null && newCol != null)
table.addColumn(new DataMapDiffColumn(table, COLUMN_NAME_DATA_CHANGES, oldCol, newCol));

if (!noUrl)
{
String urlStr = "audit-detailedAuditChanges.view?auditRowId=${rowId}";
if (StringUtils.isEmpty(eventName))
urlStr = urlStr + "&auditEventType=" + eventName;
DetailsURL url = DetailsURL.fromString(urlStr);
url.setStrictContainerContextEval(true);
table.setDetailsURL(url);
}

}

@Override
Expand All @@ -394,7 +418,7 @@ public static Map<String, String> decodeFromDataMap(String properties)
}
}

public static String encodeForDataMap(Container c, Map<String, ?> properties)
public static String encodeForDataMap(Map<String, ?> properties)
{
if (properties == null) return null;

Expand Down
4 changes: 2 additions & 2 deletions api/src/org/labkey/api/audit/SampleTimelineAuditEvent.java
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ public void setOldRecordMap(String oldRecordMap, Container container)
if (label != null)
{
row.put("samplestatelabel", label);
oldRecordMap = AbstractAuditTypeProvider.encodeForDataMap(container, row);
oldRecordMap = AbstractAuditTypeProvider.encodeForDataMap(row);
}
}
super.setOldRecordMap(oldRecordMap);
Expand All @@ -228,7 +228,7 @@ public void setNewRecordMap(String newRecordMap, Container container)
if (label != null)
{
row.put("samplestatelabel", label);
newRecordMap = AbstractAuditTypeProvider.encodeForDataMap(container, row);
newRecordMap = AbstractAuditTypeProvider.encodeForDataMap(row);
}
}
super.setNewRecordMap(newRecordMap, container);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,13 +248,13 @@ public Domain createDomain(GWTDomain domain, JSONObject arguments, Container con

@Override
public ValidationException updateDomain(GWTDomain<? extends GWTPropertyDescriptor> original, GWTDomain<? extends GWTPropertyDescriptor> update,
JSONObject options, Container container, User user, boolean includeWarnings)
JSONObject options, Container container, User user, boolean includeWarnings, @Nullable String auditUserComment)
{
throw new UnsupportedOperationException();
}

@Override
public void deleteDomain(User user, Domain domain)
public void deleteDomain(User user, Domain domain, @Nullable String auditUserComment)
{
throw new UnsupportedOperationException();
}
Expand Down
24 changes: 24 additions & 0 deletions api/src/org/labkey/api/data/ConditionalFormat.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.labkey.api.data;

import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.fhcrc.cpas.exp.xml.PropertyDescriptorType;
Expand Down Expand Up @@ -376,4 +377,27 @@ private static boolean addToXML(List<? extends GWTConditionalFormat> formats, Co

return success;
}

public String toStringVal()
{
return getFilter() + ": " + getCssStyle();
}

public static @Nullable String toStringVal(List<? extends GWTConditionalFormat> formats)
{
if (formats == null || formats.isEmpty())
return null;

List<String> strings = new ArrayList<>();
formats.forEach(format -> {
if (format instanceof ConditionalFormat c)
strings.add(c.toStringVal());
else
{
ConditionalFormat conditionalFormat = new ConditionalFormat(format);
strings.add(conditionalFormat.toStringVal());
}
});
return StringUtils.join(strings, ", ");
}
}
9 changes: 9 additions & 0 deletions api/src/org/labkey/api/data/PropertyStorageSpec.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.labkey.api.data;

import org.apache.commons.lang3.StringUtils;
import org.labkey.api.collections.CaseInsensitiveHashSet;
import org.labkey.api.exp.MvColumn;
import org.labkey.api.exp.PropertyDescriptor;
Expand Down Expand Up @@ -500,6 +501,14 @@ public int hashCode()
{
return Objects.hash(Arrays.hashCode(columnNames), isClustered, isClustered);
}

public String toStringVal()
{
if (columnNames == null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Should this check for empty as well? Seems a bit incongruent to return when null, but then join and display the unique flag setting when columnNames is empty.

return "";

return StringUtils.join(columnNames, ", ") + ", unique: " + isUnique;
}
}

public enum Special
Expand Down
2 changes: 1 addition & 1 deletion api/src/org/labkey/api/dataiterator/SimpleTranslator.java
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ private Object getSingleValue(Object k, Collection<?> vs)
if (vs.size() == 1)
return vs.iterator().next();

throw new ConversionException("Found " + vs.size() + " values matching: " + String.valueOf(k));
throw new ConversionExceptionWithMessage("Found " + vs.size() + " values matching: " + String.valueOf(k));
}
}

Expand Down
Loading