diff --git a/api/schemas/expTypes.xsd b/api/schemas/expTypes.xsd index 255c79726c7..95c56f430ca 100644 --- a/api/schemas/expTypes.xsd +++ b/api/schemas/expTypes.xsd @@ -230,6 +230,18 @@ + + + + + + + + + + + + diff --git a/api/src/org/labkey/api/assay/AbstractAssayProvider.java b/api/src/org/labkey/api/assay/AbstractAssayProvider.java index cedeb960653..76447a2a7ce 100644 --- a/api/src/org/labkey/api/assay/AbstractAssayProvider.java +++ b/api/src/org/labkey/api/assay/AbstractAssayProvider.java @@ -1533,10 +1533,56 @@ public String getRunLSIDPrefix() return "urn:lsid:" + Lsid.encodePart(AppProps.getInstance().getDefaultLsidAuthority()) + ":" + Lsid.encodePart(getResultRowLSIDPrefix()); } + @Override + public Pair getAssayResultRowIdFromLsid(Container container, Lsid assayResultRowLsid) + { + assert getResultRowLSIDPrefix().equals(assayResultRowLsid.getNamespacePrefix()); + String namespaceSuffix = assayResultRowLsid.getNamespaceSuffix(); + + // LSID namespace suffix format expected to be: "Protocol-" + + ExpProtocol protocol = null; + if (namespaceSuffix.startsWith("Protocol-")) + { + try + { + int protocolId = Integer.parseInt(namespaceSuffix.substring("Protocol-".length())); + if (protocolId > 0) + protocol = ExperimentService.get().getExpProtocol(protocolId); + } + catch (NumberFormatException ex) + { + // ignore + } + } + + if (protocol == null) + return null; + + // LSID object id expected to be rowId + int rowId = -1; + try + { + rowId = Integer.parseInt(assayResultRowLsid.getObjectId()); + } + catch (NumberFormatException ex) + { + // ignore + } + + if (rowId <= 0) + return null; + + return Pair.of(protocol, rowId); + } + @Override public @Nullable ActionURL getResultRowURL(Container container, Lsid lsid) { - return PageFlowUtil.urlProvider(AssayUrls.class).getAssayResultRowURL(this, container, lsid); + var pair = getAssayResultRowIdFromLsid(container, lsid); + if (pair == null) + return null; + + return PageFlowUtil.urlProvider(AssayUrls.class).getAssayResultRowURL(this, container, pair.first, pair.second); } @Override diff --git a/api/src/org/labkey/api/assay/AbstractAssayTsvDataHandler.java b/api/src/org/labkey/api/assay/AbstractAssayTsvDataHandler.java index 5965a22c449..f8079c6579c 100644 --- a/api/src/org/labkey/api/assay/AbstractAssayTsvDataHandler.java +++ b/api/src/org/labkey/api/assay/AbstractAssayTsvDataHandler.java @@ -234,8 +234,10 @@ else if (mvIndicatorColumns.contains(column.name)) } else { - // It's not an expected column. Is it an MV indicator column? - if (!settings.isAllowUnexpectedColumns() && !mvIndicatorColumns.contains(column.name)) + // It's not an expected column. Is it an MV indicator column or prov:objectInput column? + if (!settings.isAllowUnexpectedColumns() && + !mvIndicatorColumns.contains(column.name) && + !column.name.equalsIgnoreCase(ProvenanceService.PROVENANCE_INPUT_PROPERTY)) { column.load = false; } diff --git a/api/src/org/labkey/api/assay/AssayProvider.java b/api/src/org/labkey/api/assay/AssayProvider.java index 08e352bbdf8..835fa749f0f 100644 --- a/api/src/org/labkey/api/assay/AssayProvider.java +++ b/api/src/org/labkey/api/assay/AssayProvider.java @@ -311,6 +311,12 @@ enum Scope */ @Nullable String getResultRowLSIDExpression(); + /** + * Extract the ExpProtocol and rowId from an assay result row LSID. + */ + @Nullable + Pair getAssayResultRowIdFromLsid(Container container, Lsid assayResultRowLsid); + /** * Get the URL for an assay result row's LSID. */ diff --git a/api/src/org/labkey/api/assay/AssayUrls.java b/api/src/org/labkey/api/assay/AssayUrls.java index b081d9300d5..a6fa2670621 100644 --- a/api/src/org/labkey/api/assay/AssayUrls.java +++ b/api/src/org/labkey/api/assay/AssayUrls.java @@ -55,7 +55,7 @@ public interface AssayUrls extends UrlProvider ActionURL getAssayResultsURL(Container container, ExpProtocol protocol); ActionURL getAssayResultsURL(Container container, ExpProtocol protocol, int... runIds); ActionURL getAssayResultsURL(Container container, ExpProtocol protocol, ContainerFilter containerFilter, int... runIds); - @Nullable ActionURL getAssayResultRowURL(AssayProvider provider, Container container, Lsid assayResultRowLsid); + @Nullable ActionURL getAssayResultRowURL(AssayProvider provider, Container container, ExpProtocol protocol, int rowId); ActionURL getShowUploadJobsURL(Container container, ExpProtocol protocol, ContainerFilter containerFilter); diff --git a/api/src/org/labkey/api/data/FilterInfo.java b/api/src/org/labkey/api/data/FilterInfo.java index 26fd58c9438..0c25960d536 100644 --- a/api/src/org/labkey/api/data/FilterInfo.java +++ b/api/src/org/labkey/api/data/FilterInfo.java @@ -16,10 +16,14 @@ package org.labkey.api.data; +import org.json.JSONObject; import org.labkey.api.query.FieldKey; import org.labkey.api.util.URLHelper; import java.io.Serializable; +import java.util.Map; + +import static org.labkey.api.util.PageFlowUtil.encode; /** * Bean to capture a single filter on a single column. @@ -94,4 +98,18 @@ public void applyToURL(URLHelper url, String regionName, FieldKey fieldKey) String valueStr = value != null ? value : ""; url.addParameter(regionName + "." + fieldKey.toString() + "~" + opStr, valueStr); } + + public Map toMap() + { + return Map.of( + "fieldKey", this.field.toString(), + "op", this.op != null ? this.op.getPreferredUrlKey() : "", + "value", this.value + ); + } + + public String toString() + { + return encode(field.toString()) + "~" + (this.op != null ? this.op.getPreferredUrlKey() : "") + "=" + encode(value); + } } diff --git a/api/src/org/labkey/api/exp/Identifiable.java b/api/src/org/labkey/api/exp/Identifiable.java index d307f060255..349a5f4b7e2 100644 --- a/api/src/org/labkey/api/exp/Identifiable.java +++ b/api/src/org/labkey/api/exp/Identifiable.java @@ -15,7 +15,10 @@ */ package org.labkey.api.exp; +import org.jetbrains.annotations.Nullable; import org.labkey.api.data.Container; +import org.labkey.api.query.QueryRowReference; +import org.labkey.api.view.ActionURL; /** * Base functionality for objects that have an LSID. @@ -34,4 +37,14 @@ default String getLSIDNamespacePrefix() String getName(); Container getContainer(); + + default @Nullable ActionURL detailsURL() + { + return null; + } + + default @Nullable QueryRowReference getQueryRowReference() + { + return null; + } } diff --git a/api/src/org/labkey/api/exp/IdentifiableBase.java b/api/src/org/labkey/api/exp/IdentifiableBase.java index ff76abd4f7d..fd4c092c9de 100644 --- a/api/src/org/labkey/api/exp/IdentifiableBase.java +++ b/api/src/org/labkey/api/exp/IdentifiableBase.java @@ -39,13 +39,11 @@ public IdentifiableBase() public IdentifiableBase(String lsid) { - this(); _lsid = lsid; } public IdentifiableBase(OntologyObject oo) { - this(); _lsid = oo.getObjectURI(); objectId = oo.getObjectId(); container = oo.getContainer(); @@ -97,6 +95,7 @@ public void setContainer(Container container) this.container = container; } + @Override public boolean equals(Object o) { diff --git a/api/src/org/labkey/api/exp/Lsid.java b/api/src/org/labkey/api/exp/Lsid.java index 886307b92ee..9395743382f 100644 --- a/api/src/org/labkey/api/exp/Lsid.java +++ b/api/src/org/labkey/api/exp/Lsid.java @@ -693,7 +693,7 @@ public void testBuilder() assertEquals(b.toString(), lsid3.toString()); Lsid lsid4 = b.setObjectId("OBJ").build(); - Lsid.LsidBuilder t = new Lsid.LsidBuilder(lsid1); + Lsid.LsidBuilder t = lsid1.edit(); assertEquals(lsid1,t.build()); assertEquals(lsid1.toString(),t.toString()); t.setVersion("3"); diff --git a/api/src/org/labkey/api/exp/LsidManager.java b/api/src/org/labkey/api/exp/LsidManager.java index d71e67df2e3..5e307f7c8a7 100644 --- a/api/src/org/labkey/api/exp/LsidManager.java +++ b/api/src/org/labkey/api/exp/LsidManager.java @@ -18,6 +18,7 @@ import org.apache.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.labkey.api.assay.AssayProtocolSchema; import org.labkey.api.assay.AssayProvider; import org.labkey.api.assay.AssayUrls; import org.labkey.api.data.Container; @@ -29,11 +30,14 @@ import org.labkey.api.exp.api.ExpProtocol; import org.labkey.api.exp.api.ExpRun; import org.labkey.api.exp.api.ExperimentService; +import org.labkey.api.query.FieldKey; +import org.labkey.api.query.QueryRowReference; import org.labkey.api.security.User; import org.labkey.api.security.permissions.Permission; import org.labkey.api.security.permissions.ReadPermission; import org.labkey.api.settings.AppProps; import org.labkey.api.util.PageFlowUtil; +import org.labkey.api.util.Pair; import org.labkey.api.view.ActionURL; import java.util.HashMap; @@ -67,9 +71,9 @@ public static LsidManager get() return INSTANCE; } - public interface LsidHandler + public interface LsidHandler { - Identifiable getObject(Lsid lsid); + I getObject(Lsid lsid); @Nullable ActionURL getDisplayURL(Lsid lsid); @@ -79,13 +83,13 @@ public interface LsidHandler boolean hasPermission(Lsid lsid, @NotNull User user, @NotNull Class perm); } - public abstract static class ExpObjectLsidHandler implements LsidHandler + public abstract static class ExpObjectLsidHandler implements LsidHandler { - public abstract ExpObject getObject(Lsid lsid); + public abstract I getObject(Lsid lsid); public Container getContainer(Lsid lsid) { - ExpObject run = getObject(lsid); + I run = getObject(lsid); return run == null ? null : run.getContainer(); } @@ -96,7 +100,7 @@ public boolean hasPermission(Lsid lsid, @NotNull User user, @NotNull Class { public ExpRun getObject(Lsid lsid) { @@ -123,22 +127,23 @@ protected ActionURL getDisplayURL(Container c, ExpProtocol protocol, ExpRun run) // This is different from ExpObjectLsidHandler in that it supports generic // OntologyObjects that don't fit into the ExpObject class hierarchy. - public static class OntologyObjectLsidHandler implements LsidHandler + public static class OntologyObjectLsidHandler implements LsidHandler { @Override - public Identifiable getObject(Lsid lsid) + public I getObject(Lsid lsid) { OntologyObject oo = OntologyManager.getOntologyObject(null, lsid.toString()); if (oo == null) return null; - return new IdentifiableBase(oo); + return (I)new IdentifiableBase(oo); } @Override - public @Nullable ActionURL getDisplayURL(Lsid lsid) + public final @Nullable ActionURL getDisplayURL(Lsid lsid) { - return null; + I obj = getObject(lsid); + return obj == null ? null : obj.detailsURL(); } @Override @@ -159,32 +164,76 @@ public boolean hasPermission(Lsid lsid, @NotNull User user, @NotNull Class pair = provider.getAssayResultRowIdFromLsid(oo.getContainer(), new Lsid(oo.getObjectURI())); + if (pair != null) + { + _protocol = pair.first; + _rowId = pair.second; + } + else + { + _protocol = null; + _rowId = 0; + } } @Override - public Identifiable getObject(Lsid lsid) + public @Nullable ActionURL detailsURL() { - assert _provider.getResultRowLSIDPrefix().equals(lsid.getNamespacePrefix()); - return super.getObject(lsid); + var urls = PageFlowUtil.urlProvider(AssayUrls.class); + if (urls == null) + return null; + + return urls.getAssayResultRowURL(_provider, getContainer(), _protocol, _rowId); } @Override - public @Nullable ActionURL getDisplayURL(Lsid lsid) + public @Nullable QueryRowReference getQueryRowReference() { - Container c = getContainer(lsid); - if (c == null) + var schemaKey = AssayProtocolSchema.schemaName(_provider, _protocol); + return new QueryRowReference(getContainer(), schemaKey, AssayProtocolSchema.DATA_TABLE_NAME, FieldKey.fromParts("rowId"), _rowId); + } + } + + public static class AssayResultLsidHandler extends OntologyObjectLsidHandler + { + private final AssayProvider _provider; + + public AssayResultLsidHandler(AssayProvider provider) + { + _provider = provider; + assert _provider.getResultRowLSIDPrefix() != null; + } + + @Override + public AssayResultIdentifiable getObject(Lsid lsid) + { + assert _provider.getResultRowLSIDPrefix().equals(lsid.getNamespacePrefix()); + OntologyObject oo = OntologyManager.getOntologyObject(null, lsid.toString()); + if (oo == null) + return null; + + Pair pair = _provider.getAssayResultRowIdFromLsid(oo.getContainer(), lsid); + if (pair == null) return null; - return PageFlowUtil.urlProvider(AssayUrls.class).getAssayResultRowURL(_provider, c, lsid); + return new AssayResultIdentifiable(_provider, oo, pair.first, pair.second); } + } public void registerHandlerFinder(LsidHandlerFinder finder) diff --git a/api/src/org/labkey/api/exp/api/AssayJSONConverter.java b/api/src/org/labkey/api/exp/api/AssayJSONConverter.java index d648e46aabf..841ba842b2b 100644 --- a/api/src/org/labkey/api/exp/api/AssayJSONConverter.java +++ b/api/src/org/labkey/api/exp/api/AssayJSONConverter.java @@ -19,6 +19,8 @@ import org.json.JSONObject; import org.labkey.api.action.ApiResponse; import org.labkey.api.action.ApiSimpleResponse; +import org.labkey.api.assay.AbstractTsvAssayProvider; +import org.labkey.api.assay.AssayProvider; import org.labkey.api.data.ColumnInfo; import org.labkey.api.data.SimpleFilter; import org.labkey.api.data.Sort; @@ -29,8 +31,6 @@ import org.labkey.api.query.FieldKey; import org.labkey.api.query.QueryService; import org.labkey.api.security.User; -import org.labkey.api.assay.AbstractTsvAssayProvider; -import org.labkey.api.assay.AssayProvider; import java.util.ArrayList; import java.util.Arrays; @@ -59,14 +59,14 @@ public class AssayJSONConverter // Run properties public static final String DATA_ROWS = "dataRows"; - public static JSONObject serializeBatch(ExpExperiment batch, AssayProvider provider, ExpProtocol protocol, User user) + public static JSONObject serializeBatch(ExpExperiment batch, AssayProvider provider, ExpProtocol protocol, User user, ExperimentJSONConverter.Settings settings) { - JSONObject jsonObject = ExperimentJSONConverter.serializeRunGroup(batch, provider != null ? provider.getBatchDomain(protocol) : null); + JSONObject jsonObject = ExperimentJSONConverter.serializeRunGroup(batch, provider != null ? provider.getBatchDomain(protocol) : null, settings); JSONArray runsArray = new JSONArray(); for (ExpRun run : batch.getRuns()) { - runsArray.put(serializeRun(run, provider, protocol, user)); + runsArray.put(serializeRun(run, provider, protocol, user, settings)); } jsonObject.put(RUNS, runsArray); @@ -111,9 +111,15 @@ public static JSONArray serializeDataRows(ExpData data, AssayProvider provider, return dataRows; } + @Deprecated(forRemoval = true) public static JSONObject serializeRun(ExpRun run, AssayProvider provider, ExpProtocol protocol, User user) { - JSONObject jsonObject = ExperimentJSONConverter.serializeRun(run, provider != null ? provider.getRunDomain(protocol) : null, user); + return serializeRun(run, provider, protocol, user, ExperimentJSONConverter.DEFAULT_SETTINGS); + } + + public static JSONObject serializeRun(ExpRun run, AssayProvider provider, ExpProtocol protocol, User user, ExperimentJSONConverter.Settings settings) + { + JSONObject jsonObject = ExperimentJSONConverter.serializeRun(run, provider != null ? provider.getRunDomain(protocol) : null, user, settings); JSONArray dataRows = new JSONArray(); if (provider != null) @@ -139,16 +145,16 @@ else if (datas.size() > 1) return jsonObject; } - public static ApiResponse serializeRuns(AssayProvider provider, ExpProtocol protocol, List runs, User user) + public static ApiResponse serializeRuns(AssayProvider provider, ExpProtocol protocol, List runs, User user, ExperimentJSONConverter.Settings settings) { JSONObject result = new JSONObject(); result.put(ASSAY_ID, protocol.getRowId()); JSONArray runsArray = new JSONArray(); - for(ExpRun run: runs) + for (ExpRun run: runs) { - runsArray.put(serializeRun(run, provider, protocol, user)); + runsArray.put(serializeRun(run, provider, protocol, user, settings)); } result.put(RUNS, runsArray); @@ -165,7 +171,7 @@ public static ApiResponse serializeResult(AssayProvider provider, ExpProtocol pr if (batch != null) { - batchObject = serializeBatch(batch, provider, protocol, user); + batchObject = serializeBatch(batch, provider, protocol, user, ExperimentJSONConverter.DEFAULT_SETTINGS); } else { @@ -185,7 +191,7 @@ public static ApiResponse serializeResult(AssayProvider provider, ExpProtocol pr for (ExpExperiment batch : batches) { - batchesArray.put(serializeBatch(batch, provider, protocol, user)); + batchesArray.put(serializeBatch(batch, provider, protocol, user, ExperimentJSONConverter.DEFAULT_SETTINGS)); } result.put(BATCHES, batchesArray); diff --git a/api/src/org/labkey/api/exp/api/DataType.java b/api/src/org/labkey/api/exp/api/DataType.java index e8abfc28878..ad512bb765e 100644 --- a/api/src/org/labkey/api/exp/api/DataType.java +++ b/api/src/org/labkey/api/exp/api/DataType.java @@ -17,8 +17,10 @@ package org.labkey.api.exp.api; import com.google.common.base.MoreObjects; +import org.jetbrains.annotations.Nullable; import org.labkey.api.exp.Lsid; -import org.labkey.api.util.URLHelper; +import org.labkey.api.query.QueryRowReference; +import org.labkey.api.view.ActionURL; /** * Recognizes {@link ExpData} based on the namespace prefix in their LSIDs to identify specific flavors that have custom handling within the @@ -38,7 +40,12 @@ public String getNamespacePrefix() return _namespacePrefix; } - public URLHelper getDetailsURL(ExpData dataObject) + public ActionURL getDetailsURL(ExpData dataObject) + { + return null; + } + + public @Nullable QueryRowReference getQueryRowReference(ExpData dataObject) { return null; } diff --git a/api/src/org/labkey/api/exp/api/DefaultExperimentSaveHandler.java b/api/src/org/labkey/api/exp/api/DefaultExperimentSaveHandler.java index a910078e4c5..e9368ba8475 100644 --- a/api/src/org/labkey/api/exp/api/DefaultExperimentSaveHandler.java +++ b/api/src/org/labkey/api/exp/api/DefaultExperimentSaveHandler.java @@ -17,6 +17,7 @@ import org.apache.commons.beanutils.ConversionException; import org.apache.log4j.Logger; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.json.JSONArray; import org.json.JSONException; @@ -304,7 +305,7 @@ public void handleProperties(ViewContext context, ExpObject object, List entry : ExperimentJSONConverter.convertProperties(propertiesJsonObject, dps, context.getContainer(), true).entrySet()) { - object.setProperty(context.getUser(), entry.getKey(), entry.getValue()); + object.setProperty(context.getUser(), entry.getKey(), entry.getValue()); // handle inputs/outputs } } @@ -331,6 +332,7 @@ private void handleStandardProperties(ViewContext context, JSONObject jsonObject } } + @NotNull protected Map getInputData(ViewContext context, JSONArray inputDataArray) throws ValidationException { Map inputData = new HashMap<>(); @@ -343,6 +345,7 @@ protected Map getInputData(ViewContext context, JSONArray input return inputData; } + @NotNull protected Map getInputMaterial(ViewContext context, JSONArray inputMaterialArray) throws ValidationException { Map inputMaterial = new HashMap<>(); diff --git a/api/src/org/labkey/api/exp/api/ExpLineage.java b/api/src/org/labkey/api/exp/api/ExpLineage.java index c5056d790ce..c4e559bace3 100644 --- a/api/src/org/labkey/api/exp/api/ExpLineage.java +++ b/api/src/org/labkey/api/exp/api/ExpLineage.java @@ -17,12 +17,8 @@ import org.json.JSONArray; import org.json.JSONObject; -import org.labkey.api.assay.AssayProtocolSchema; -import org.labkey.api.assay.AssayProvider; -import org.labkey.api.assay.AssayService; import org.labkey.api.exp.Identifiable; -import org.labkey.api.exp.query.SamplesSchema; -import org.labkey.api.query.SchemaKey; +import org.labkey.api.security.User; import org.labkey.api.util.Pair; import java.util.Collections; @@ -318,7 +314,7 @@ else if (parent instanceof ExpData) return datas; } - public JSONObject toJSON(boolean requestedWithSingleSeed) + public JSONObject toJSON(User user, boolean requestedWithSingleSeed, ExperimentJSONConverter.Settings settings) { Map nodeMeta = processNodes(); Map values = new HashMap<>(); @@ -328,7 +324,7 @@ public JSONObject toJSON(boolean requestedWithSingleSeed) { for (Identifiable seed : _seeds) { - nodes.put(seed.getLSID(), nodeToJSON(seed, new JSONArray(), new JSONArray())); + nodes.put(seed.getLSID(), nodeToJSON(seed, user, new JSONArray(), new JSONArray(), settings)); } } else @@ -345,7 +341,7 @@ public JSONObject toJSON(boolean requestedWithSingleSeed) children.put(edge.toChildJSON()); Identifiable obj = nodeMeta.get(node.getKey()); - nodes.put(node.getKey(), nodeToJSON(obj, parents, children)); + nodes.put(node.getKey(), nodeToJSON(obj, user, parents, children, settings)); } } @@ -365,73 +361,20 @@ public JSONObject toJSON(boolean requestedWithSingleSeed) return new JSONObject(values); } - private JSONObject nodeToJSON(Identifiable node, JSONArray parents, JSONArray children) + private JSONObject nodeToJSON(Identifiable node, User user, JSONArray parents, JSONArray children, ExperimentJSONConverter.Settings settings) { JSONObject json = new JSONObject(); - json.put("parents", parents); - json.put("children", children); if (node != null) { - json.put("name", node.getName()); - json.put("lsid", node.getLSID()); - json.put("type", node.getLSIDNamespacePrefix()); - - // TODO: get rowId and maybe cpasType and schemaName/queryName for assay result row type - - if (node instanceof ExpObject) - { - json.put("rowId", ((ExpObject)node).getRowId()); - json.put("url", ((ExpObject)node).detailsURL()); - } - - if (node instanceof ExpMaterial) - { - ExpMaterial material = (ExpMaterial) node; - json.put("cpasType", material.getCpasType()); - - ExpSampleSet ss = material.getSampleSet(); - if (ss != null) - { - json.put("schemaName", SamplesSchema.SCHEMA_NAME); - json.put("queryName", ss.getName()); - } - } - else if (node instanceof ExpData) - { - ExpData data = (ExpData) node; - json.put("cpasType", data.getCpasType()); + json = ExperimentJSONConverter.serialize(node, user, settings); - ExpDataClass dc = data.getDataClass(null); - if (dc != null) - { - json.put("schemaName", "exp.data"); - json.put("queryName", dc.getName()); - } - } - else if (node instanceof ExpRun) - { - ExpRun run = (ExpRun)node; - - ExpProtocol protocol = run.getProtocol(); - if (protocol != null) - { - json.put("cpasType", protocol.getLSID()); - AssayService assayService = AssayService.get(); - if (assayService != null) - { - AssayProvider provider = assayService.getProvider(run); - if (provider != null) - { - SchemaKey schemaKey = AssayProtocolSchema.schemaName(provider, protocol); - json.put("schemaName", schemaKey.toString()); - json.put("queryName", "Runs"); - } - } - } - } + json.put("type", node.getLSIDNamespacePrefix()); } + json.put("parents", parents); + json.put("children", children); + return json; } diff --git a/api/src/org/labkey/api/exp/api/ExpLineageOptions.java b/api/src/org/labkey/api/exp/api/ExpLineageOptions.java index be47c92dd66..dd91bcce89a 100644 --- a/api/src/org/labkey/api/exp/api/ExpLineageOptions.java +++ b/api/src/org/labkey/api/exp/api/ExpLineageOptions.java @@ -15,18 +15,12 @@ */ package org.labkey.api.exp.api; -import com.fasterxml.jackson.annotation.JsonIgnore; - -import java.util.List; - /** * Captures options for doing an lineage search * Created by Nick Arnold on 2/12/2016. */ -public class ExpLineageOptions +public class ExpLineageOptions extends ResolveLsidsForm { - private boolean _singleSeedRequested = false; - private List _lsids; private int _depth; private boolean _parents = true; private boolean _children = true; @@ -56,28 +50,6 @@ public void setDepth(int depth) _depth = depth; } - public void setLsid(String lsid) - { - _lsids = List.of(lsid); - _singleSeedRequested = true; - } - - public List getLsids() - { - return _lsids; - } - - public void setLsids(List lsids) - { - _lsids = lsids; - } - - @JsonIgnore - public boolean isSingleSeedRequested() - { - return _singleSeedRequested; - } - public boolean isParents() { return _parents; @@ -140,4 +112,5 @@ public void setUseObjectIds(boolean useObjectIds) { _useObjectIds = useObjectIds; } + } diff --git a/api/src/org/labkey/api/exp/api/ExpObject.java b/api/src/org/labkey/api/exp/api/ExpObject.java index adf0abc6e39..138ce75e7f1 100644 --- a/api/src/org/labkey/api/exp/api/ExpObject.java +++ b/api/src/org/labkey/api/exp/api/ExpObject.java @@ -26,7 +26,7 @@ import org.labkey.api.query.BatchValidationException; import org.labkey.api.query.ValidationException; import org.labkey.api.security.User; -import org.labkey.api.util.URLHelper; +import org.labkey.api.view.ActionURL; import java.util.Date; import java.util.Map; @@ -44,7 +44,7 @@ public interface ExpObject extends Identifiable, Comparable void setLSID(Lsid lsid); void setName(String name); @Nullable - URLHelper detailsURL(); + ActionURL detailsURL(); Container getContainer(); void setContainer(Container container); diff --git a/api/src/org/labkey/api/exp/api/ExperimentJSONConverter.java b/api/src/org/labkey/api/exp/api/ExperimentJSONConverter.java index 170a3e8458f..fba5c3dccd8 100644 --- a/api/src/org/labkey/api/exp/api/ExperimentJSONConverter.java +++ b/api/src/org/labkey/api/exp/api/ExperimentJSONConverter.java @@ -21,6 +21,8 @@ import org.json.JSONArray; import org.json.JSONObject; import org.labkey.api.data.Container; +import org.labkey.api.exp.Identifiable; +import org.labkey.api.exp.Lsid; import org.labkey.api.exp.ObjectProperty; import org.labkey.api.exp.OntologyManager; import org.labkey.api.exp.PropertyDescriptor; @@ -30,9 +32,12 @@ import org.labkey.api.files.FileContentService; import org.labkey.api.pipeline.PipeRoot; import org.labkey.api.pipeline.PipelineService; +import org.labkey.api.query.QueryRowReference; +import org.labkey.api.query.QueryParam; import org.labkey.api.query.ValidationException; import org.labkey.api.security.User; import org.labkey.api.security.permissions.ReadPermission; +import org.labkey.api.util.Pair; import org.labkey.api.util.URIUtil; import java.io.File; @@ -43,6 +48,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; /** * Serializes and deserializes experiment objects to and from JSON. @@ -60,6 +66,8 @@ public class ExperimentJSONConverter public static final String MODIFIED_BY = "modifiedBy"; public static final String NAME = "name"; public static final String LSID = "lsid"; + public static final String CPAS_TYPE = "cpasType"; + public static final String URL = "url"; public static final String PROPERTIES = "properties"; public static final String COMMENT = "comment"; public static final String DATA_FILE_URL = "dataFileURL"; @@ -67,6 +75,10 @@ public class ExperimentJSONConverter public static final String PIPELINE_PATH = "pipelinePath"; //path relative to pipeline root public static final String PROTOCOL_NAME = "protocolName"; // non-assay backed protocol name + public static final String SCHEMA_NAME = QueryParam.schemaName.name(); + public static final String QUERY_NAME = QueryParam.queryName.name(); + public static final String PK_FILTERS = "pkFilters"; + // Run properties public static final String PROTOCOL = "protocol"; public static final String DATA_INPUTS = "dataInputs"; @@ -74,6 +86,7 @@ public class ExperimentJSONConverter public static final String ROLE = "role"; public static final String DATA_OUTPUTS = "dataOutputs"; public static final String MATERIAL_OUTPUTS = "materialOutputs"; + public static final String STEPS = "steps"; // Material properties public static final String SAMPLE_SET = "sampleSet"; @@ -81,40 +94,135 @@ public class ExperimentJSONConverter // Data properties public static final String DATA_CLASS = "dataClass"; public static final String DATA_CLASS_CATEGORY = "category"; + public static final String EDGE = "edge"; + public static final String PROTOCOL_INPUT = "protocolInput"; + + // Protocol Application properties + public static final String APPLICATION_TYPE = "applicationType"; + public static final String ACTION_SEQUENCE = "activitySequence"; + public static final String ACTIVITY_DATE = "activityDate"; + public static final String START_TIME = "startTime"; + public static final String END_TIME = "endTime"; + public static final String RECORD_COUNT = "recordCount"; + public static final String PARAMETERS = "parameters"; // Domain kinds public static final String VOCABULARY_DOMAIN = "Vocabulary"; - public static JSONObject serializeRunGroup(ExpExperiment runGroup, Domain domain) + public static final Settings DEFAULT_SETTINGS = new Settings(); + + public static class Settings + { + private final boolean includeProperties; + private final boolean includeInputsAndOutputs; + private final boolean includeRunSteps; + + public Settings() + { + this(true, true, false); + } + + public Settings(boolean includeProperties, boolean includeInputsAndOutputs, boolean includeRunSteps) + { + this.includeProperties = includeProperties; + this.includeInputsAndOutputs = includeInputsAndOutputs; + this.includeRunSteps = includeRunSteps; + } + + public boolean isIncludeProperties() + { + return includeProperties; + } + + public boolean isIncludeInputsAndOutputs() + { + return includeInputsAndOutputs; + } + + public boolean isIncludeRunSteps() + { + return includeRunSteps; + } + + public Settings withIncludeProperties(boolean b) + { + return new Settings(b, includeInputsAndOutputs, includeRunSteps); + } + + public Settings withIncludeInputsAndOutputs(boolean b) + { + return new Settings(includeProperties, b, includeRunSteps); + } + } + + @NotNull + public static JSONObject serialize(@NotNull Identifiable node, @NotNull User user, @NotNull Settings settings) { - JSONObject jsonObject = serializeStandardProperties(runGroup, domain != null ? domain.getProperties() : Collections.emptyList()); + if (node instanceof ExpExperiment) + return serializeRunGroup((ExpExperiment)node, null, settings); + else if (node instanceof ExpRun) + return serializeRun((ExpRun)node, null, user, settings); + else if (node instanceof ExpMaterial) + return serializeMaterial((ExpMaterial)node, settings); + else if (node instanceof ExpData) + return serializeData((ExpData)node, user, settings); + else if (node instanceof ExpObject) + return serializeExpObject((ExpObject)node, null, settings); + else + return serializeIdentifiable(node, settings); + } + + public static JSONObject serializeRunGroup(ExpExperiment runGroup, Domain domain, @NotNull Settings settings) + { + JSONObject jsonObject = serializeExpObject(runGroup, domain != null ? domain.getProperties() : Collections.emptyList(), settings); jsonObject.put(COMMENT, runGroup.getComments()); return jsonObject; } - public static JSONObject serializeRun(ExpRun run, Domain domain, User user) + public static JSONObject serializeRun(ExpRun run, Domain domain, User user, @NotNull Settings settings) { - JSONObject jsonObject = serializeStandardProperties(run, domain == null ? null : domain.getProperties()); - jsonObject.put(COMMENT, run.getComments()); - jsonObject.put(PROTOCOL, serializeProtocol(run.getProtocol(), user)); + JSONObject jsonObject = serializeExpObject(run, domain == null ? null : domain.getProperties(), settings); + if (settings.isIncludeProperties()) + { + jsonObject.put(COMMENT, run.getComments()); + jsonObject.put(PROTOCOL, serializeProtocol(run.getProtocol(), user)); + } - JSONArray inputDataArray = new JSONArray(); - for (ExpData data : run.getDataInputs().keySet()) + if (settings.isIncludeInputsAndOutputs()) { - inputDataArray.put(ExperimentJSONConverter.serializeData(data, user)); + ExpProtocolApplication inputApp = run.getInputProtocolApplication(); + jsonObject.put(DATA_INPUTS, serializeRunInputs(inputApp.getDataInputs(), user, settings)); + jsonObject.put(MATERIAL_INPUTS, serializeRunInputs(inputApp.getMaterialInputs(), user, settings)); + + // Inputs into the final output step are outputs of the entire run + ExpProtocolApplication outputApp = run.getOutputProtocolApplication(); + jsonObject.put(DATA_OUTPUTS, serializeRunDataOutputs(outputApp.getDataInputs(), user, settings)); + jsonObject.put(MATERIAL_OUTPUTS, serializeRunInputs(outputApp.getMaterialInputs(), user, settings)); + + serializeRunLevelProvenanceProperties(jsonObject, run); } - jsonObject.put(DATA_INPUTS, inputDataArray); - JSONArray inputMaterialArray = new JSONArray(); - for (ExpMaterial material : run.getMaterialInputs().keySet()) + ExpProtocol protocol = run.getProtocol(); + if (protocol != null) { - JSONObject jsonMaterial = ExperimentJSONConverter.serializeMaterial(material); - jsonMaterial.put(ROLE, run.getMaterialInputs().get(material)); - inputMaterialArray.put(jsonMaterial); + jsonObject.put(CPAS_TYPE, protocol.getLSID()); } - jsonObject.put(MATERIAL_INPUTS, inputMaterialArray); - serializeRunOutputs(jsonObject, run.getDataOutputs(), run.getMaterialOutputs(), user); + if (settings.isIncludeRunSteps()) + { + JSONArray steps = new JSONArray(); + for (ExpProtocolApplication protApp : run.getProtocolApplications()) + { + // We can skip the initial input and final steps ince we've already included the run-level inputs and + // outputs and there aren't usually any interesting properties on the initial and final steps. + if (protApp.getApplicationType() == ExpProtocol.ApplicationType.ExperimentRun || protApp.getApplicationType() == ExpProtocol.ApplicationType.ExperimentRunOutput) + continue; + + JSONObject step = serializeRunProtocolApplication(protApp, run, user, settings); + steps.put(step); + } + jsonObject.put(STEPS, steps); + } return jsonObject; } @@ -126,44 +234,272 @@ public static JSONObject serializeProtocol(ExpProtocol protocol, User user) // Just include basic protocol properties for now. // See GetProtocolAction and GWTProtocol for serializing an assay protocol with domain fields. - JSONObject jsonObject = serializeStandardProperties(protocol); + JSONObject jsonObject = serializeExpObject(protocol, null, DEFAULT_SETTINGS.withIncludeProperties(false)); return jsonObject; } - public static JSONObject serializeRunOutputs(Collection data, Collection materials, User user) + public static JSONObject serializeRunOutputs(Collection data, Collection materials, User user, @NotNull Settings settings) { JSONObject obj = new JSONObject(); - serializeRunOutputs(obj, data, materials, user); + serializeRunOutputs(obj, data, materials, user, settings); return obj; } - protected static void serializeRunOutputs(@NotNull JSONObject obj, Collection data, Collection materials, User user) + protected static void serializeRunOutputs(@NotNull JSONObject obj, Collection data, Collection materials, User user, @NotNull Settings settings) { JSONArray outputDataArray = new JSONArray(); for (ExpData d : data) { if (null != d.getFile() || null != d.getDataClass(user)) - outputDataArray.put(ExperimentJSONConverter.serializeData(d, user)); + outputDataArray.put(ExperimentJSONConverter.serializeData(d, user, settings)); } obj.put(DATA_OUTPUTS, outputDataArray); JSONArray outputMaterialArray = new JSONArray(); for (ExpMaterial material : materials) { - outputMaterialArray.put(ExperimentJSONConverter.serializeMaterial(material)); + outputMaterialArray.put(ExperimentJSONConverter.serializeMaterial(material, settings)); } obj.put(MATERIAL_OUTPUTS, outputMaterialArray); } - // Serialize only the base properties -- does not include object properties - public static JSONObject serializeStandardProperties(ExpObject object) + protected static JSONArray serializeRunDataOutputs(Collection inputs, User user, Settings settings) + { + // filter out any output data that have a file URL or aren't a DataClass + return serializeRunInputs(inputs.stream().filter(input -> { + ExpData d = input.getData(); + return d != null && (d.getFile() != null || d.getDataClass(user) != null); + }).collect(Collectors.toList()), user, settings); + } + + protected static JSONArray serializeRunInputs(Collection inputs, User user, Settings settings) + { + JSONArray jsonArray = new JSONArray(); + + for (ExpRunInput runInput : inputs) + { + JSONObject json; + if (runInput instanceof ExpDataRunInput) + { + json = ExperimentJSONConverter.serializeData(((ExpDataRunInput)runInput).getData(), user, settings); + } + else if (runInput instanceof ExpMaterialRunInput) + { + json = ExperimentJSONConverter.serializeMaterial(((ExpMaterialRunInput)runInput).getMaterial(), settings); + } + else + { + throw new IllegalArgumentException("Unknown run input: " + runInput); + } + + json.put(ROLE, runInput.getRole()); + + if (settings.isIncludeProperties()) + { + JSONObject edgeProperties = serializeOntologyProperties(runInput, null, settings); + if (!edgeProperties.isEmpty()) + { + JSONObject edgeJson = new JSONObject(); + // NOTE: The standard properties aren't interesting for the MaterialInput/DataInput edge + edgeJson.put(LSID, runInput.getLSID()); + edgeJson.put(PROPERTIES, edgeProperties); + json.put(EDGE, edgeJson); + } + } + + ExpProtocolInput protocolInput = runInput.getProtocolInput(); + if (protocolInput != null) + { + Lsid lsid = Lsid.parse(protocolInput.getLSID()); + json.put(PROTOCOL_INPUT, lsid.getObjectId()); + } + + jsonArray.put(json); + } + + return jsonArray; + } + + protected static JSONObject serializeRunProtocolApplication(@NotNull ExpProtocolApplication protApp, ExpRun run, User user, Settings settings) + { + JSONObject json = serializeExpObject(protApp, null, settings); + + json.put(ACTION_SEQUENCE, protApp.getActionSequence()); + json.put(APPLICATION_TYPE, protApp.getApplicationType().toString()); + if (protApp.getComments() != null) + json.put(COMMENT, protApp.getComments()); + + if (protApp.getActivityDate() != null) + json.put(ACTIVITY_DATE, protApp.getActivityDate()); + + if (protApp.getStartTime() != null) + json.put(START_TIME, protApp.getStartTime()); + + if (protApp.getEndTime() != null) + json.put(END_TIME, protApp.getEndTime()); + + if (protApp.getRecordCount() != null) + json.put(RECORD_COUNT, protApp.getRecordCount()); + + json.put(PROTOCOL, serializeProtocol(protApp.getProtocol(), user)); + + if (settings.isIncludeInputsAndOutputs()) + { + json.put(DATA_INPUTS, serializeRunInputs(protApp.getDataInputs(), user, settings)); + json.put(MATERIAL_INPUTS, serializeRunInputs(protApp.getMaterialInputs(), user, settings)); + + json.put(DATA_OUTPUTS, serializeRunInputs(protApp.getDataOutputs(), user, settings)); + json.put(MATERIAL_OUTPUTS, serializeRunInputs(protApp.getMaterialOutputs(), user, settings)); + + // provenance + provenanceMap(json, protApp); + } + + // CONSIDER: parameters +// List parameters = ExperimentService.get().getProtocolApplicationParameters(application.getRowId()); +// if (!parameters.isEmpty()) +// { +// json.put(PARAMETERS, parameters.stream().map()); +// } + + return json; + } + + public static void serializeRunLevelProvenanceProperties(@NotNull JSONObject obj, ExpRun run) + { + ProvenanceService svc = ProvenanceService.get(); + if (svc == null) + return; + + // Include provenance inputs of the run in this format: + // { + // objectInputs: [ "urn:lsid:lsid1", "urn:lsid:lsid" ] + // } + ExpProtocolApplication inputApp = run.getInputProtocolApplication(); + if (inputApp != null) + { + var inputSet = svc.getProvenanceObjectUris(inputApp.getRowId()); + if (!inputSet.isEmpty()) + { + obj.put(ProvenanceService.PROVENANCE_OBJECT_INPUTS, + inputSet.stream() + .map(Pair::getKey) + .map(ExperimentJSONConverter::serializeProvenanceObject) + .collect(Collectors.toUnmodifiableList())); + } + } + + ExpProtocolApplication outputApp = run.getOutputProtocolApplication(); + if (outputApp != null) + { + provenanceMap(obj, outputApp); + } + } + + // Include provenance object mapping for the run in this format: + // { + // provenanceMap: [{ + // from: 'urn:lsid:input1', to: 'urn:lsid:output1' + // },{ + // from: 'urn:lsid:input2', to: 'urn:lsid:output1' + // }] + // } + public static void provenanceMap(@NotNull JSONObject obj, ExpProtocolApplication app) + { + ProvenanceService svc = ProvenanceService.get(); + if (svc == null) + return; + + var outputSet = svc.getProvenanceObjectUris(app.getRowId()); + if (!outputSet.isEmpty()) + { + obj.put(ProvenanceService.PROVENANCE_OBJECT_MAP, + outputSet.stream() + .map(ExperimentJSONConverter::serializeProvenancePair) + .collect(Collectors.toUnmodifiableList())); + } + } + + private static Map serializeProvenancePair(Pair pair) + { + var map = new HashMap(); + if (pair.first != null) + map.put("from", serializeProvenanceObject(pair.first)); + if (pair.second != null) + map.put("to", serializeProvenanceObject(pair.second)); + return map; + } + + // For now, just return the lsid if it isn't null + // CONSIDER: Use LsidManager to find the object and call serialize() ? + private static Object serializeProvenanceObject(String objectUri) + { + if (objectUri == null) + return null; + + return objectUri; + } + + /** + * Serialize {@link Identifiable} java bean properties (Name, LSID, URL, and schema/query/pkFilters) + */ + private static JSONObject serializeIdentifiableBean(@NotNull Identifiable obj) { - JSONObject jsonObject = new JSONObject(); + JSONObject json = new JSONObject(); - // Standard properties on all experiment objects - jsonObject.put(NAME, object.getName()); - jsonObject.put(LSID, object.getLSID()); - jsonObject.put(ID, object.getRowId()); + json.put(NAME, obj.getName()); + json.put(LSID, obj.getLSID()); + var url = obj.detailsURL(); + if (url != null) + json.put(URL, url); + + QueryRowReference rowRef = obj.getQueryRowReference(); + if (rowRef != null) + { + json.put(SCHEMA_NAME, rowRef.getSchemaKey().toString()); + json.put(QUERY_NAME, rowRef.getQueryName()); + json.put(PK_FILTERS, rowRef.getPkFilters().stream().map(f -> Map.of("fieldKey", f.first.toString(), "value", f.second)).collect(Collectors.toList())); + } + return json; + } + + /** + * Serialize {@link Identifiable} java bean properties (Name, LSID, URL, and schema/query/pkFilters) + * as well as any object properties for the object. + */ + @NotNull + public static JSONObject serializeIdentifiable(@NotNull Identifiable obj, Settings settings) + { + JSONObject json = serializeIdentifiableBean(obj); + + if (settings.isIncludeProperties()) + { + Set seenPropertyURIs = new HashSet<>(); + JSONObject propertiesObject = new JSONObject(); + Map objectProps = OntologyManager.getPropertyObjects(obj.getContainer(), obj.getLSID()); + serializeOntologyProperties(propertiesObject, obj.getContainer(), seenPropertyURIs, objectProps, settings); + if (!propertiesObject.isEmpty()) + json.put(PROPERTIES, propertiesObject); + } + + return json; + } + + /** + * Serialize ExpObject java bean properties (ID, CreatedBy, Comment) and include object properties and the optional domain properties. + */ + @NotNull + public static JSONObject serializeExpObject(@NotNull ExpObject object, @Nullable List properties, @NotNull Settings settings) + { + // While serializeIdentifiable can include object properties, we call serializeIdentifiableBean + // instead and use serializeOntologyProperties(ExpObject) so the object properties will be + // fetched using ExpObject.getProperty(). + JSONObject jsonObject = serializeIdentifiableBean(object); + int rowId = object.getRowId(); + if (rowId != 0) + { + jsonObject.put(ID, rowId); + } if (object.getCreatedBy() != null) { jsonObject.put(CREATED_BY, object.getCreatedBy().getEmail()); @@ -178,15 +514,22 @@ public static JSONObject serializeStandardProperties(ExpObject object) if (comment != null) jsonObject.put(COMMENT, object.getComment()); + if (settings.isIncludeProperties()) + { + JSONObject propertiesObject = serializeOntologyProperties(object, properties, settings); + if (!propertiesObject.isEmpty()) + jsonObject.put(PROPERTIES, propertiesObject); + } + return jsonObject; } - // Serialize standard properties including object properties and the optional domain properties - public static JSONObject serializeStandardProperties(ExpObject object, @Nullable List properties) + /** + * Serialize the custom ontology properties associated with the object. + */ + @NotNull + private static JSONObject serializeOntologyProperties(@NotNull ExpObject object, @Nullable List properties, @NotNull ExperimentJSONConverter.Settings settings) { - JSONObject jsonObject = serializeStandardProperties(object); - - // Add the custom properties Set seenPropertyURIs = new HashSet<>(); JSONObject propertiesObject = new JSONObject(); if (properties != null) @@ -195,57 +538,93 @@ public static JSONObject serializeStandardProperties(ExpObject object, @Nullable { seenPropertyURIs.add(dp.getPropertyURI()); Object value = object.getProperty(dp); - if (dp.getPropertyDescriptor().getPropertyType() == PropertyType.FILE_LINK && value instanceof File) - { - // We need to return files not as simple string properties with the path, but as an Exp.Data object - // with multiple values - File f = (File)value; - ExpData data = ExperimentService.get().getExpDataByURL(f, object.getContainer()); - if (data != null) - { - // If we can find a row in the data table, return that - value = serializeData(data, null); - } - else - { - // Otherwise, return a subset of all the data fields that we know about - JSONObject jsonFile = new JSONObject(); - jsonFile.put(ABSOLUTE_PATH, f.getAbsolutePath()); - PipeRoot pipeRoot = PipelineService.get().findPipelineRoot(object.getContainer()); - if (pipeRoot != null) - { - jsonFile.put(PIPELINE_PATH, pipeRoot.relativePath(f)); - } - value = jsonFile; - } - } + value = serializePropertyValue(object.getContainer(), dp.getPropertyDescriptor().getPropertyType(), settings, value); propertiesObject.put(dp.getName(), value); } } - var objectProps = object.getObjectProperties(); + serializeOntologyProperties(propertiesObject, object.getContainer(), seenPropertyURIs, objectProps, settings); + + return propertiesObject; + } + + private static void serializeOntologyProperties(JSONObject json, Container c, + Set seenPropertyURIs, Map objectProps, + Settings settings) + { for (var propPair : objectProps.entrySet()) { String propertyURI = propPair.getKey(); if (seenPropertyURIs.contains(propertyURI)) continue; seenPropertyURIs.add(propertyURI); + ObjectProperty op = propPair.getValue(); - propertiesObject.put(propertyURI, op.value()); - } + PropertyDescriptor pd = OntologyManager.getPropertyDescriptor(op.getPropertyURI(), c); + PropertyType type = pd != null ? pd.getPropertyType() : op.getPropertyType(); + Object value = serializePropertyValue(c, type, settings, op.value()); + json.put(propertyURI, op.value()); + } + } - if (!propertiesObject.isEmpty()) - jsonObject.put(PROPERTIES, propertiesObject); + private static Object serializePropertyValue(Container container, PropertyType type, Settings settings, Object value) + { + if (type == PropertyType.FILE_LINK && value instanceof File) + { + // We need to return files not as simple string properties with the path, but as an Exp.Data object + // with multiple values + File f = (File) value; + ExpData data = ExperimentService.get().getExpDataByURL(f, container); + if (data != null) + { + // If we can find a row in the data table, return that + value = serializeData(data, null, settings); + } + else + { + // Otherwise, return a subset of all the data fields that we know about + JSONObject jsonFile = new JSONObject(); + jsonFile.put(ABSOLUTE_PATH, f.getAbsolutePath()); + PipeRoot pipeRoot = PipelineService.get().findPipelineRoot(container); + if (pipeRoot != null) + { + jsonFile.put(PIPELINE_PATH, pipeRoot.relativePath(f)); + } + value = jsonFile; + } + } - return jsonObject; + return value; } - public static JSONObject serializeData(ExpData data, @Nullable User user) + @Deprecated(forRemoval = true) + @NotNull + public static JSONObject serializeData(@NotNull ExpData data, @Nullable User user) + { + return serializeData(data, user, DEFAULT_SETTINGS); + } + + @NotNull + public static JSONObject serializeData(@NotNull ExpData data, @Nullable User user, @NotNull Settings settings) { - JSONObject jsonObject = serializeStandardProperties(data, null); + final ExpDataClass dc = data.getDataClass(user); + + JSONObject jsonObject = serializeExpObject(data, null, settings); + + if (settings.isIncludeProperties()) + { + if (dc != null) + { + JSONObject dataClassJsonObject = serializeExpObject(dc, null, settings.withIncludeProperties(false)); + if (dc.getCategory() != null) + dataClassJsonObject.put(DATA_CLASS_CATEGORY, dc.getCategory()); + jsonObject.put(DATA_CLASS, dataClassJsonObject); + } + } + jsonObject.put(DATA_FILE_URL, data.getDataFileUrl()); File f = data.getFile(); if (f != null) @@ -258,29 +637,33 @@ public static JSONObject serializeData(ExpData data, @Nullable User user) } } - ExpDataClass dc = data.getDataClass(user); - if (dc != null) - { - JSONObject dataClassJsonObject = serializeStandardProperties(dc, null); - if (dc.getCategory() != null) - dataClassJsonObject.put(DATA_CLASS_CATEGORY, dc.getCategory()); - jsonObject.put(DATA_CLASS, dataClassJsonObject); - } + jsonObject.put(CPAS_TYPE, data.getCpasType()); + return jsonObject; } - public static JSONObject serializeMaterial(ExpMaterial material) + @Deprecated(forRemoval = true) + @NotNull + public static JSONObject serializeMaterial(@NotNull ExpMaterial material) + { + return serializeMaterial(material, DEFAULT_SETTINGS); + } + + // TODO: Include MaterialInput edge properties (role and properties) + // TODO: Include protocol input + @NotNull + public static JSONObject serializeMaterial(@NotNull ExpMaterial material, @NotNull Settings settings) { ExpSampleSet sampleSet = material.getSampleSet(); JSONObject jsonObject; if (sampleSet == null) { - jsonObject = serializeStandardProperties(material, null); + jsonObject = serializeExpObject(material, null, settings); } else { - jsonObject = serializeStandardProperties(material, sampleSet.getDomain().getProperties()); + jsonObject = serializeExpObject(material, sampleSet.getDomain().getProperties(), settings); if (sampleSet.hasNameAsIdCol()) { JSONObject properties = jsonObject.optJSONObject(ExperimentJSONConverter.PROPERTIES); @@ -290,13 +673,19 @@ public static JSONObject serializeMaterial(ExpMaterial material) jsonObject.put(ExperimentJSONConverter.PROPERTIES, properties); } - JSONObject sampleSetJson = serializeStandardProperties(sampleSet, null); - jsonObject.put(SAMPLE_SET, sampleSetJson); + if (settings.isIncludeProperties()) + { + JSONObject sampleSetJson = serializeExpObject(sampleSet, null, settings.withIncludeProperties(false)); + jsonObject.put(SAMPLE_SET, sampleSetJson); + } } + jsonObject.put(CPAS_TYPE, material.getCpasType()); + return jsonObject; } + @NotNull public static Map convertProperties(Map propertiesJsonObject, List dps, Container container, boolean ignoreMissingProperties) throws ValidationException { Map properties = new HashMap<>(); @@ -325,9 +714,17 @@ public static Map convertProperties(Map domain.getDomainKind().getKindName().equals(VOCABULARY_DOMAIN)); //only properties that exist in any vocabulary in this container are saved in the batch - if(propertyInVocabulary) + if (propertyInVocabulary) { value = convertProperty(value, pd, container); properties.put(pd, value); diff --git a/api/src/org/labkey/api/exp/api/ProtocolImplementation.java b/api/src/org/labkey/api/exp/api/ProtocolImplementation.java index 67f39f41575..07492cacbfa 100644 --- a/api/src/org/labkey/api/exp/api/ProtocolImplementation.java +++ b/api/src/org/labkey/api/exp/api/ProtocolImplementation.java @@ -17,6 +17,7 @@ package org.labkey.api.exp.api; import org.labkey.api.data.Container; +import org.labkey.api.query.QueryRowReference; import org.labkey.api.security.User; import java.util.List; @@ -58,4 +59,9 @@ public boolean deleteRunWhenInputDeleted() public void onRunDeleted(Container container, User user) { } + + public QueryRowReference getQueryRowReference(ExpProtocol protocol, ExpRun run) + { + return null; + } } diff --git a/api/src/org/labkey/api/exp/api/ProvenanceService.java b/api/src/org/labkey/api/exp/api/ProvenanceService.java index d4f2ddbe743..23ecf52bb1b 100644 --- a/api/src/org/labkey/api/exp/api/ProvenanceService.java +++ b/api/src/org/labkey/api/exp/api/ProvenanceService.java @@ -16,7 +16,15 @@ * */ public interface ProvenanceService { - String PROVENANCE_INPUT_PROPERTY = "prov:objectInputs"; + String PROVENANCE_PROPERTY_PREFIX = "prov"; + + String PROVENANCE_OBJECT_INPUTS = "objectInputs"; + String PROVENANCE_INPUT_PROPERTY = PROVENANCE_PROPERTY_PREFIX + ":" + PROVENANCE_OBJECT_INPUTS; + + String PROVENANCE_OBJECT_OUTPUTS = "objectOutputs"; + String PROVENANCE_OUTPUT_PROPERTY = PROVENANCE_PROPERTY_PREFIX + ":" + PROVENANCE_OBJECT_OUTPUTS; + + String PROVENANCE_OBJECT_MAP = "provenanceMap"; static ProvenanceService get() { diff --git a/api/src/org/labkey/api/exp/api/ResolveLsidsForm.java b/api/src/org/labkey/api/exp/api/ResolveLsidsForm.java new file mode 100644 index 00000000000..a3a5ae48f2e --- /dev/null +++ b/api/src/org/labkey/api/exp/api/ResolveLsidsForm.java @@ -0,0 +1,68 @@ +package org.labkey.api.exp.api; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import java.util.List; + +public class ResolveLsidsForm +{ + private boolean _singleSeedRequested = false; + private List _lsids; + + // serialization options + private boolean _includeProperties = false; + private boolean _includeInputsAndOutputs = false; + private boolean _includeRunSteps = false; + + public List getLsids() + { + return _lsids; + } + + public void setLsids(List lsids) + { + _lsids = lsids; + } + + public void setLsid(String lsid) + { + _lsids = List.of(lsid); + _singleSeedRequested = true; + } + + @JsonIgnore + public boolean isSingleSeedRequested() + { + return _singleSeedRequested; + } + + public boolean isIncludeProperties() + { + return _includeProperties; + } + + public void setIncludeProperties(boolean includeProperties) + { + _includeProperties = includeProperties; + } + + public boolean isIncludeInputsAndOutputs() + { + return _includeInputsAndOutputs; + } + + public void setIncludeInputsAndOutputs(boolean includeInputsAndOutputs) + { + _includeInputsAndOutputs = includeInputsAndOutputs; + } + + public boolean isIncludeRunSteps() + { + return _includeRunSteps; + } + + public void setIncludeRunSteps(boolean includeRunSteps) + { + _includeRunSteps = includeRunSteps; + } +} diff --git a/api/src/org/labkey/api/query/QueryRowReference.java b/api/src/org/labkey/api/query/QueryRowReference.java new file mode 100644 index 00000000000..8d77a3b816c --- /dev/null +++ b/api/src/org/labkey/api/query/QueryRowReference.java @@ -0,0 +1,112 @@ +package org.labkey.api.query; + +import org.jetbrains.annotations.NotNull; +import org.labkey.api.data.CompareType; +import org.labkey.api.data.Container; +import org.labkey.api.util.Pair; +import org.labkey.api.view.ActionURL; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.labkey.api.util.PageFlowUtil.encode; + +/** + * Reference a single row within a table by its query coordinates: container, schemaName, queryName, and a set of pk filters. + */ +public class QueryRowReference +{ + final @NotNull Container _container; + final @NotNull SchemaKey _schemaKey; + final @NotNull String _queryName; + final @NotNull List> _pkFilters; + + public QueryRowReference(@NotNull Container c, @NotNull SchemaKey schemaKey, @NotNull String queryName, @NotNull FieldKey pkCol, int pkValue) + { + this(c, schemaKey, queryName, List.of(Pair.of(pkCol, pkValue))); + } + + public QueryRowReference(@NotNull Container c, @NotNull SchemaKey schemaKey, @NotNull String queryName, @NotNull FieldKey pkCol, @NotNull String pkValue) + { + this(c, schemaKey, queryName, List.of(Pair.of(pkCol, pkValue))); + } + + public QueryRowReference(@NotNull Container c, @NotNull SchemaKey schemaKey, @NotNull String queryName, @NotNull Pair pkFilter) + { + this(c, schemaKey, queryName, List.of(pkFilter)); + } + + public QueryRowReference(@NotNull Container c, @NotNull SchemaKey schemaKey, @NotNull String queryName, @NotNull List> pkFilters) + { + _container = c; + _schemaKey = schemaKey; + _queryName = queryName; + _pkFilters = pkFilters; + if (pkFilters.isEmpty()) + throw new IllegalArgumentException(); + } + + public @NotNull Container getContainer() + { + return _container; + } + + public @NotNull SchemaKey getSchemaKey() + { + return _schemaKey; + } + + public @NotNull String getQueryName() + { + return _queryName; + } + + public @NotNull List> getPkFilters() + { + return _pkFilters; + } + + public ActionURL toExecuteQueryURL() + { + ActionURL url = QueryService.get().urlDefault(_container, QueryAction.executeQuery, _schemaKey.toString(), _queryName); + _pkFilters.forEach(f -> { + url.addFilter(QueryView.DATAREGIONNAME_DEFAULT, f.first, CompareType.EQUAL, String.valueOf(f.second)); + }); + return url; + } + + /** + * Create URL query params representing the schemaName, queryName, and pkFilters + * similar to {@link CustomViewXmlReader.getFilterAndSortString} + * + * e.g, schemaName=exp&queryName=Data&query.rowId~eq=1234 + */ + public String toFilterAndSortString() + { + StringBuilder ret = new StringBuilder(); + + ret.append(QueryParam.schemaName).append("=").append(encode(_schemaKey.toString())); + ret.append("&"); + ret.append(QueryParam.queryName).append("=").append(encode(_queryName)); + for (var f : _pkFilters) + { + ret.append("&"); + ret.append(QueryView.DATAREGIONNAME_DEFAULT).append(".").append(encode(f.first.toString())); + ret.append("~"); + ret.append(CompareType.EQUAL.getPreferredUrlKey()); + ret.append("="); + ret.append(encode(String.valueOf(f.second))); + } + + return ret.toString(); + } + + /** + * Compact form of the query coordinates for debugging. + */ + @Override + public String toString() + { + return _schemaKey.toString() + "." + _queryName + "&" + _pkFilters.stream().map(f -> encode(f.first.toString()) + "=" + encode(String.valueOf(f.second))).collect(Collectors.joining("&")); + } +} diff --git a/api/webapp/clientapi/core/Experiment.js b/api/webapp/clientapi/core/Experiment.js index 99ca87205ad..d3f758297de 100644 --- a/api/webapp/clientapi/core/Experiment.js +++ b/api/webapp/clientapi/core/Experiment.js @@ -167,7 +167,7 @@ LABKEY.Experiment = new function() * @param {Number} config.assayId The assay protocol id. * @param {Number} config.batchId The batch id. * @param {function} config.success The function to call when the function finishes successfully. - * This function will be called with a the parameters: + * This function will be called with the parameters: *
    *
  • batch A new {@link LABKEY.Exp.RunGroup} object. *
  • response The original response @@ -217,7 +217,7 @@ LABKEY.Experiment.loadBatch({ * @param {Number} config.assayId The assay protocol id. * @param {Number} config.batchIds The list of batch ids. * @param {function} config.success The function to call when the function finishes successfully. - * This function will be called with a the parameters: + * This function will be called with the parameters: *
      *
    • batches The list of {@link LABKEY.Exp.RunGroup} objects. *
    • response The original response @@ -268,8 +268,11 @@ LABKEY.Experiment.loadBatch({ * @param config An object that contains the following configuration parameters * @param {Array} config.lsids. The list of run lsids. * @param {Array} config.runIds The list of run ids. + * @param {Boolean} config.includeProperties Include properties set on the experiment objects. + * @param {Boolean} config.includeInputsAndOutputs Include run and step inputs and outputs. + * @param {Boolean} config.includeRunSteps Include run steps. * @param {function} config.success The function to call when the function finishes successfully. - * This function will be called with a the parameters: + * This function will be called with the parameters: *
        *
      • runs The list of {@link LABKEY.Exp.Run} objects. *
      • response The original response @@ -296,16 +299,25 @@ LABKEY.Experiment.loadBatch({ return runs; } + var jsonData = {}; + if (config.runIds) + jsonData.runIds = config.runIds; + if (config.lsids) + jsonData.lsids = config.lsids; + if (config.includeProperties !== undefined) + jsonData.includeProperties = config.includeProperties; + if (config.includeInputsAndOutputs !== undefined) + jsonData.includeInputsAndOutputs = config.includeInputsAndOutputs; + if (config.includeRunSteps !== undefined) + jsonData.includeRunSteps = config.includeRunSteps; + LABKEY.Ajax.request({ url: LABKEY.ActionURL.buildURL("assay", "getAssayRuns.api", LABKEY.ActionURL.getContainer()), method: 'POST', success: getSuccessCallbackWrapper(createExp, LABKEY.Utils.getOnSuccess(config), config.scope), failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true), scope: config.scope, - jsonData : { - runIds: config.runIds, - lsids: config.lsids - }, + jsonData : jsonData, headers : { 'Content-Type' : 'application/json' } @@ -494,6 +506,7 @@ LABKEY.Experiment.saveBatch({ * @param {Boolean} [config.children] Include children in the lineage response. Defaults to true. * @param {String} [config.expType] Optional experiment type to filter response -- either "Data", "Material", or "ExperimentRun". Defaults to include all. * @param {String} [config.cpasType] Optional LSID of a SampleSet or DataClass to filter the response. Defaults to include all. + * @param {Boolean} [config.includeProperties] Include node properties in the lineage response. Defaults to false. * @static */ lineage : function (config) @@ -514,6 +527,8 @@ LABKEY.Experiment.saveBatch({ params.children = config.children; if (config.hasOwnProperty('depth')) params.depth = config.depth; + if (config.hasOwnProperty('includeProperties')) + params.includeProperties = config.includeProperties; if (config.expType) params.expType = config.expType; @@ -528,6 +543,50 @@ LABKEY.Experiment.saveBatch({ failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true), scope: config.scope }); + }, + + /** + * Resolve LSIDs. + * @param config An object that contains the following configuration parameters + * @param {Array} config.lsids. The list of run lsids. + * @param {Boolean} config.includeProperties Include properties set on the experiment objects. + * @param {Boolean} config.includeInputsAndOutputs Include run and step inputs and outputs. + * @param {Boolean} config.includeRunSteps Include run steps. + * @param {function} config.success The function to call when the function finishes successfully. + * This function will be called with the parameters: + *
          + *
        • runs The list of {@link LABKEY.Exp.Run} objects. + *
        • response The original response + *
        + * @param {function} [config.failure] The function to call if this function encounters an error. + * This function will be called with the following parameters: + *
          + *
        • response The original response + *
        + * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this). + * @see The Module Assay documentation for more information. + * @static + */ + resolve : function (config) + { + var params = {}; + if (config.lsids) + params.lsids = config.lsids; + if (config.includeProperties !== undefined) + params.includeProperties = config.includeProperties; + if (config.includeInputsAndOutputs !== undefined) + params.includeInputsAndOutputs = config.includeInputsAndOutputs; + if (config.includeRunSteps !== undefined) + params.includeRunSteps = config.includeRunSteps; + + LABKEY.Ajax.request({ + method: 'GET', + url: LABKEY.ActionURL.buildURL("experiment", "resolve.api"), + params: params, + success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope), + failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true), + scope: config.scope + }); } }; }; diff --git a/assay/api-src/org/labkey/api/assay/plate/PlateTemplate.java b/assay/api-src/org/labkey/api/assay/plate/PlateTemplate.java index 2374b31d217..75185785e3c 100644 --- a/assay/api-src/org/labkey/api/assay/plate/PlateTemplate.java +++ b/assay/api-src/org/labkey/api/assay/plate/PlateTemplate.java @@ -17,7 +17,9 @@ package org.labkey.api.assay.plate; import org.jetbrains.annotations.Nullable; +import org.labkey.api.exp.Identifiable; import org.labkey.api.study.PropertySet; +import org.labkey.api.view.ActionURL; import java.util.List; import java.util.Map; @@ -27,7 +29,7 @@ * Date: Oct 20, 2006 * Time: 1:02:47 PM */ -public interface PlateTemplate extends PropertySet +public interface PlateTemplate extends PropertySet, Identifiable { String getName(); @@ -60,4 +62,6 @@ public interface PlateTemplate extends PropertySet int getWellGroupCount(WellGroup.Type type); String getType(); + + @Nullable ActionURL detailsURL(); } diff --git a/assay/api-src/org/labkey/api/assay/plate/WellGroup.java b/assay/api-src/org/labkey/api/assay/plate/WellGroup.java index b0f6e79f11d..73d544ab903 100644 --- a/assay/api-src/org/labkey/api/assay/plate/WellGroup.java +++ b/assay/api-src/org/labkey/api/assay/plate/WellGroup.java @@ -16,6 +16,10 @@ package org.labkey.api.assay.plate; +import org.jetbrains.annotations.Nullable; +import org.labkey.api.util.PageFlowUtil; +import org.labkey.api.view.ActionURL; + import java.util.List; import java.util.Set; diff --git a/assay/api-src/org/labkey/api/assay/plate/WellGroupTemplate.java b/assay/api-src/org/labkey/api/assay/plate/WellGroupTemplate.java index b211e64de1e..18f08682139 100644 --- a/assay/api-src/org/labkey/api/assay/plate/WellGroupTemplate.java +++ b/assay/api-src/org/labkey/api/assay/plate/WellGroupTemplate.java @@ -16,7 +16,10 @@ package org.labkey.api.assay.plate; +import org.jetbrains.annotations.Nullable; +import org.labkey.api.exp.Identifiable; import org.labkey.api.study.PropertySet; +import org.labkey.api.view.ActionURL; import java.util.List; @@ -25,7 +28,7 @@ * Date: Oct 23, 2006 * Time: 1:33:19 PM */ -public interface WellGroupTemplate extends PropertySet +public interface WellGroupTemplate extends PropertySet, Identifiable { Integer getRowId(); @@ -43,4 +46,10 @@ default void setPositions(List positions) boolean contains(Position position); String getPositionDescription(); + + default @Nullable ActionURL detailsURL() + { + return null; + } + } diff --git a/assay/src/org/labkey/assay/AssayController.java b/assay/src/org/labkey/assay/AssayController.java index 89ec43b549c..08b1e867743 100644 --- a/assay/src/org/labkey/assay/AssayController.java +++ b/assay/src/org/labkey/assay/AssayController.java @@ -778,7 +778,7 @@ public String getResponse(AssayFileUploadForm form, Map - ExpProtocol protocol = null; - if (namespaceSuffix.startsWith("Protocol-")) - { - try - { - int protocolId = Integer.parseInt(namespaceSuffix.substring("Protocol-".length())); - if (protocolId > 0) - protocol = ExperimentService.get().getExpProtocol(protocolId); - } - catch (NumberFormatException ex) - { - // ignore - } - } - - if (protocol == null) - return null; - - // LSID object id expected to be rowId - int rowId = -1; - try - { - rowId = Integer.parseInt(assayResultRowLsid.getObjectId()); - } - catch (NumberFormatException ex) - { - // ignore - } - - if (rowId <= 0) - return null; - ActionURL resultsURL = getAssayResultsURL(container, protocol); resultsURL.addFilter("Data", FieldKey.fromParts("rowId"), CompareType.EQUAL, rowId); return resultsURL; diff --git a/assay/src/org/labkey/assay/AssayManager.java b/assay/src/org/labkey/assay/AssayManager.java index bde32f8633f..62f46346b96 100644 --- a/assay/src/org/labkey/assay/AssayManager.java +++ b/assay/src/org/labkey/assay/AssayManager.java @@ -249,9 +249,7 @@ public PipelineProvider findPipelineProvider(String name) private class ModuleAssayLsidHandlerFinder implements LsidHandlerFinder { // ExpRunLsidHandler has no state, so safe to use a singleton. - private final LsidHandler _fileBasedAssayLsidHandler = new ExpRunLsidHandler(); - // AssayResultLsidHandler has no state, so safe to use a singleton. - private final LsidHandler _fileBasedAssayResultLsidHandler = new LsidManager.OntologyObjectLsidHandler(); + private final ExpRunLsidHandler _fileBasedAssayLsidHandler = new ExpRunLsidHandler(); @Nullable @Override @@ -261,8 +259,10 @@ public LsidHandler findHandler(String authority, String namespacePrefix) { if (getModuleAssayCollections().getRunLsidPrefixes().contains(namespacePrefix)) return _fileBasedAssayLsidHandler; - else if (getModuleAssayCollections().getResultLsidPrefixes().contains(namespacePrefix)) - return _fileBasedAssayResultLsidHandler; + + AssayProvider provider = getModuleAssayCollections().getResultLsidPrefixes().get(namespacePrefix); + if (provider != null) + return new LsidManager.AssayResultLsidHandler(provider); } return null; diff --git a/assay/src/org/labkey/assay/ModuleAssayCache.java b/assay/src/org/labkey/assay/ModuleAssayCache.java index 1fe1e7bf16a..d7c98c9268c 100644 --- a/assay/src/org/labkey/assay/ModuleAssayCache.java +++ b/assay/src/org/labkey/assay/ModuleAssayCache.java @@ -65,7 +65,7 @@ class ModuleAssayCollections private final List _assayProviders = new LinkedList<>(); private final Map _pipelineProviders = new HashMap<>(); private final Set _runLsidPrefixes = new HashSet<>(); - private final Set _resultLsidPrefixes = new HashSet<>(); + private final Map _resultLsidPrefixes = new HashMap<>(); private ModuleAssayCollections() { @@ -93,7 +93,7 @@ private ModuleAssayCollections() } _runLsidPrefixes.add(provider.getRunLSIDPrefix()); if (provider.getResultRowLSIDPrefix() != null) - _resultLsidPrefixes.add(provider.getResultRowLSIDPrefix()); + _resultLsidPrefixes.put(provider.getResultRowLSIDPrefix(), provider); } } } @@ -113,7 +113,7 @@ public Set getRunLsidPrefixes() return _runLsidPrefixes; } - public Set getResultLsidPrefixes() + public Map getResultLsidPrefixes() { return _resultLsidPrefixes; } diff --git a/assay/src/org/labkey/assay/actions/GetAssayRunAction.java b/assay/src/org/labkey/assay/actions/GetAssayRunAction.java index 4a5a05c2dee..abcbc9efbe7 100644 --- a/assay/src/org/labkey/assay/actions/GetAssayRunAction.java +++ b/assay/src/org/labkey/assay/actions/GetAssayRunAction.java @@ -10,6 +10,7 @@ import org.labkey.api.exp.api.AssayJSONConverter; import org.labkey.api.exp.api.ExpProtocol; import org.labkey.api.exp.api.ExpRun; +import org.labkey.api.exp.api.ExperimentJSONConverter; import org.labkey.api.exp.api.ExperimentService; import org.labkey.api.security.RequiresPermission; import org.labkey.api.security.permissions.ReadPermission; @@ -35,7 +36,7 @@ else if (loadAssayRunForm.getRunId() != null) ExpProtocol protocol = run.getProtocol(); AssayProvider provider = AssayService.get().getProvider(protocol); - result.put("run", AssayJSONConverter.serializeRun(run, provider, protocol, getUser())); + result.put("run", AssayJSONConverter.serializeRun(run, provider, protocol, getUser(), ExperimentJSONConverter.DEFAULT_SETTINGS)); return new ApiSimpleResponse(result); diff --git a/assay/src/org/labkey/assay/actions/GetAssayRunsAction.java b/assay/src/org/labkey/assay/actions/GetAssayRunsAction.java index 80c5f12ff18..67a843f4f93 100644 --- a/assay/src/org/labkey/assay/actions/GetAssayRunsAction.java +++ b/assay/src/org/labkey/assay/actions/GetAssayRunsAction.java @@ -11,6 +11,7 @@ import org.labkey.api.exp.api.AssayJSONConverter; import org.labkey.api.exp.api.ExpProtocol; import org.labkey.api.exp.api.ExpRun; +import org.labkey.api.exp.api.ExperimentJSONConverter; import org.labkey.api.exp.api.ExperimentSaveHandler; import org.labkey.api.exp.api.ExperimentService; import org.labkey.api.security.RequiresPermission; @@ -30,19 +31,20 @@ public ApiResponse execute(AssayRunsForm assayRunsForm, BindException errors) th { List runs = new ArrayList<>(); JSONObject result = new JSONObject(); + var settings = new ExperimentJSONConverter.Settings(assayRunsForm.includeProperties, assayRunsForm.includeInputsAndOutputs, assayRunsForm.includeRunSteps); if (assayRunsForm.getLsids() != null && !assayRunsForm.getLsids().isEmpty()) { runs = assayRunsForm.getLsids().stream() .map(this::getRun) - .map(this::serializeRun) + .map(run -> this.serializeRun(run, settings)) .collect(Collectors.toList()); } else if (assayRunsForm.getRunIds() != null && !assayRunsForm.getRunIds().isEmpty()) { runs = assayRunsForm.getRunIds().stream() .map(this::getRun) - .map(this::serializeRun) + .map(run -> this.serializeRun(run, settings)) .collect(Collectors.toList()); } else @@ -55,12 +57,12 @@ else if (assayRunsForm.getRunIds() != null && !assayRunsForm.getRunIds().isEmpty return new ApiSimpleResponse(result); } - JSONObject serializeRun(@NotNull ExpRun run) + JSONObject serializeRun(@NotNull ExpRun run, ExperimentJSONConverter.Settings settings) { ExpProtocol protocol = run.getProtocol(); AssayProvider provider = AssayService.get().getProvider(protocol); - return AssayJSONConverter.serializeRun(run, provider, run.getProtocol(), getUser()); + return AssayJSONConverter.serializeRun(run, provider, run.getProtocol(), getUser(), settings); } ExpRun getRun(int runId) @@ -89,8 +91,11 @@ ExpRun getRun(String lsid) static class AssayRunsForm { - List lsids = new ArrayList<>(); - List runIds = new ArrayList<>(); + private List lsids = new ArrayList<>(); + private List runIds = new ArrayList<>(); + private boolean includeProperties = true; + private boolean includeInputsAndOutputs = true; + private boolean includeRunSteps = false; public List getLsids() { @@ -111,5 +116,35 @@ public void setRunIds(List runIds) { this.runIds = runIds; } + + public boolean isIncludeProperties() + { + return includeProperties; + } + + public void setIncludeProperties(boolean includeProperties) + { + this.includeProperties = includeProperties; + } + + public boolean isIncludeInputsAndOutputs() + { + return includeInputsAndOutputs; + } + + public void setIncludeInputsAndOutputs(boolean includeInputsAndOutputs) + { + this.includeInputsAndOutputs = includeInputsAndOutputs; + } + + public boolean isIncludeRunSteps() + { + return includeRunSteps; + } + + public void setIncludeRunSteps(boolean includeRunSteps) + { + this.includeRunSteps = includeRunSteps; + } } } diff --git a/assay/src/org/labkey/assay/actions/SaveAssayRunsAction.java b/assay/src/org/labkey/assay/actions/SaveAssayRunsAction.java index 84552952161..322e60785c8 100644 --- a/assay/src/org/labkey/assay/actions/SaveAssayRunsAction.java +++ b/assay/src/org/labkey/assay/actions/SaveAssayRunsAction.java @@ -10,6 +10,7 @@ import org.labkey.api.exp.api.DefaultExperimentSaveHandler; import org.labkey.api.exp.api.ExpProtocol; import org.labkey.api.exp.api.ExpRun; +import org.labkey.api.exp.api.ExperimentJSONConverter; import org.labkey.api.exp.api.ExperimentSaveHandler; import org.labkey.api.exp.api.ExperimentService; import org.labkey.api.security.RequiresPermission; @@ -56,7 +57,7 @@ private ApiResponse executeAction(ExperimentSaveHandler saveHandler, ExpProtocol transaction.commit(); } - return AssayJSONConverter.serializeRuns(provider, protocol, runs, getUser()); + return AssayJSONConverter.serializeRuns(provider, protocol, runs, getUser(), ExperimentJSONConverter.DEFAULT_SETTINGS); } } diff --git a/assay/src/org/labkey/assay/plate/PlateImpl.java b/assay/src/org/labkey/assay/plate/PlateImpl.java index 5ee6cd0d287..cbeccafe4df 100644 --- a/assay/src/org/labkey/assay/plate/PlateImpl.java +++ b/assay/src/org/labkey/assay/plate/PlateImpl.java @@ -21,8 +21,9 @@ import org.labkey.api.assay.plate.Position; import org.labkey.api.assay.plate.WellGroup; import org.labkey.api.assay.plate.WellGroupTemplate; +import org.labkey.api.view.ActionURL; +import org.labkey.assay.PlateController; -import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -73,6 +74,12 @@ public PlateImpl(PlateTemplateImpl template, double[][] wellValues, @Nullable bo setContainer(template.getContainer()); } + @Override + public @Nullable ActionURL detailsURL() + { + return PlateManager.get().getDetailsURL(this); + } + @Override public WellImpl getWell(int row, int col) diff --git a/assay/src/org/labkey/assay/plate/PlateManager.java b/assay/src/org/labkey/assay/plate/PlateManager.java index 8b1bcf8337a..eb2fb5de869 100644 --- a/assay/src/org/labkey/assay/plate/PlateManager.java +++ b/assay/src/org/labkey/assay/plate/PlateManager.java @@ -67,6 +67,7 @@ import org.labkey.api.util.Pair; import org.labkey.api.util.TestContext; import org.labkey.api.view.ActionURL; +import org.labkey.assay.PlateController; import org.labkey.assay.TsvAssayProvider; import org.labkey.assay.query.AssayDbSchema; @@ -838,30 +839,28 @@ public PlateTypeHandler getPlateTypeHandler(String plateTypeName) return _plateTypeHandlers.get(plateTypeName); } - private static class PlateLsidHandler implements LsidManager.LsidHandler + private static class PlateLsidHandler implements LsidManager.LsidHandler { - protected PlateImpl getPlate(Lsid lsid) - { - return PlateManager.get().getPlate(lsid.toString()); - } - @Nullable public ActionURL getDisplayURL(Lsid lsid) { - PlateImpl plate = getPlate(lsid); + Plate plate = getObject(lsid); if (plate == null) return null; - return PlateManager.get().getDetailsURL(plate); + return plate.detailsURL(); } - public ExpObject getObject(Lsid lsid) + public Plate getObject(Lsid lsid) { - throw new UnsupportedOperationException("Not Yet Implemented."); + if (lsid == null) + return null; + + return PlateManager.get().getPlate(lsid.toString()); } public Container getContainer(Lsid lsid) { - PlateImpl plate = getPlate(lsid); + Plate plate = getObject(lsid); if (plate == null) return null; return plate.getContainer(); @@ -876,32 +875,27 @@ public boolean hasPermission(Lsid lsid, @NotNull User user, @NotNull Class { - protected WellGroup getWellGroup(Lsid lsid) - { - return PlateManager.get().getWellGroup(lsid.toString()); - } - @Nullable public ActionURL getDisplayURL(Lsid lsid) { - if (lsid == null) - return null; - WellGroup wellGroup = getWellGroup(lsid); + WellGroup wellGroup = getObject(lsid); if (wellGroup == null) return null; - return PlateManager.get().getDetailsURL(wellGroup.getPlate()); + return wellGroup.detailsURL(); } - public ExpObject getObject(Lsid lsid) + public WellGroup getObject(Lsid lsid) { - throw new UnsupportedOperationException("Not Yet Implemented."); + if (lsid == null) + return null; + return PlateManager.get().getWellGroup(lsid.toString()); } public Container getContainer(Lsid lsid) { - WellGroup wellGroup = getWellGroup(lsid); + WellGroup wellGroup = getObject(lsid); if (wellGroup == null) return null; return wellGroup.getContainer(); diff --git a/assay/src/org/labkey/assay/plate/PlateTemplateImpl.java b/assay/src/org/labkey/assay/plate/PlateTemplateImpl.java index f3c279f26b1..747e174c663 100644 --- a/assay/src/org/labkey/assay/plate/PlateTemplateImpl.java +++ b/assay/src/org/labkey/assay/plate/PlateTemplateImpl.java @@ -24,10 +24,12 @@ import org.labkey.api.assay.plate.WellGroup; import org.labkey.api.assay.plate.WellGroupTemplate; import org.labkey.api.data.Container; +import org.labkey.api.query.QueryRowReference; import org.labkey.api.util.GUID; +import org.labkey.api.view.ActionURL; +import org.labkey.assay.PlateController; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -72,6 +74,20 @@ public PlateTemplateImpl(Container container, String name, String type, int rowC _dataFileId = GUID.makeGUID(); } + @Override + public @Nullable ActionURL detailsURL() + { + return new ActionURL(PlateController.DesignerAction.class, getContainer()) + .addParameter("templateName", getName()) + .addParameter("plateId", getRowId()); + } + + @Override + public @Nullable QueryRowReference getQueryRowReference() + { + return null; + } + @Override public WellGroupTemplate addWellGroup(String name, WellGroup.Type type, Position upperLeft, Position lowerRight) { diff --git a/assay/src/org/labkey/assay/plate/PropertySetImpl.java b/assay/src/org/labkey/assay/plate/PropertySetImpl.java index c583952c15e..55c23ab763e 100644 --- a/assay/src/org/labkey/assay/plate/PropertySetImpl.java +++ b/assay/src/org/labkey/assay/plate/PropertySetImpl.java @@ -16,17 +16,21 @@ package org.labkey.assay.plate; -import org.labkey.api.study.PropertySet; import org.labkey.api.data.Container; +import org.labkey.api.exp.Identifiable; +import org.labkey.api.study.PropertySet; -import java.util.*; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; /** * User: brittp * Date: Oct 20, 2006 * Time: 4:43:49 PM */ -public class PropertySetImpl implements PropertySet +public abstract class PropertySetImpl implements PropertySet { private Map _properties = new HashMap<>(); private String _lsid; diff --git a/assay/src/org/labkey/assay/plate/WellGroupImpl.java b/assay/src/org/labkey/assay/plate/WellGroupImpl.java index 448f21b4ad4..461a00739e5 100644 --- a/assay/src/org/labkey/assay/plate/WellGroupImpl.java +++ b/assay/src/org/labkey/assay/plate/WellGroupImpl.java @@ -16,6 +16,7 @@ package org.labkey.assay.plate; +import org.jetbrains.annotations.Nullable; import org.labkey.api.assay.dilution.DilutionCurve; import org.labkey.api.assay.dilution.DilutionDataRow; import org.labkey.api.assay.dilution.DilutionManager; @@ -28,6 +29,7 @@ import org.labkey.api.data.statistics.StatsService; import org.labkey.api.exp.api.ExpRun; import org.labkey.api.exp.api.ExperimentService; +import org.labkey.api.view.ActionURL; import java.util.*; @@ -66,6 +68,15 @@ public WellGroupImpl(PlateImpl plate, WellGroupTemplateImpl template) setProperty(entry.getKey(), entry.getValue()); } + @Override + public @Nullable ActionURL detailsURL() + { + if (_plate == null) + return null; + + return PlateManager.get().getDetailsURL(_plate); + } + @Override public synchronized Set getOverlappingGroups() { diff --git a/assay/src/org/labkey/assay/plate/WellGroupTemplateImpl.java b/assay/src/org/labkey/assay/plate/WellGroupTemplateImpl.java index 7bfc203c090..e0a354f3778 100644 --- a/assay/src/org/labkey/assay/plate/WellGroupTemplateImpl.java +++ b/assay/src/org/labkey/assay/plate/WellGroupTemplateImpl.java @@ -16,9 +16,13 @@ package org.labkey.assay.plate; +import org.jetbrains.annotations.Nullable; +import org.labkey.api.assay.plate.PlateService; +import org.labkey.api.assay.plate.PlateTemplate; import org.labkey.api.assay.plate.Position; import org.labkey.api.assay.plate.WellGroup; import org.labkey.api.assay.plate.WellGroupTemplate; +import org.labkey.api.view.ActionURL; import java.util.ArrayList; import java.util.Collections; @@ -54,6 +58,20 @@ public WellGroupTemplateImpl(PlateTemplateImpl owner, String name, WellGroup.Typ _positions = sortPositions(positions); } + @Override + public @Nullable ActionURL detailsURL() + { + if (_plateId == null) + return null; + + PlateTemplate template = PlateService.get().getPlateTemplate(getContainer(), _plateId); + if (template == null) + return null; + + return template.detailsURL(); + } + + private static List sortPositions(List positions) { List sortedPositions = new ArrayList<>(positions); diff --git a/assay/src/org/labkey/assay/plate/view/plateTemplateList.jsp b/assay/src/org/labkey/assay/plate/view/plateTemplateList.jsp index 5aa7adb27a6..e6d328cbc29 100644 --- a/assay/src/org/labkey/assay/plate/view/plateTemplateList.jsp +++ b/assay/src/org/labkey/assay/plate/view/plateTemplateList.jsp @@ -108,10 +108,6 @@ { Integer runCount = plateTemplateRunCount.get(template); - ActionURL editUrl = new ActionURL(PlateController.DesignerAction.class, getContainer()) - .addParameter("templateName", template.getName()) - .addParameter("plateId", template.getRowId()); - Link.LinkBuilder editLink = new Link.LinkBuilder("edit"); if (runCount > 0) { @@ -121,7 +117,7 @@ } else { - editLink.href(editUrl); + editLink.href(template.detailsURL()); } %> diff --git a/assay/src/org/labkey/assay/view/batchDetails.jsp b/assay/src/org/labkey/assay/view/batchDetails.jsp index 1549a7c0c8f..d72d55d546b 100644 --- a/assay/src/org/labkey/assay/view/batchDetails.jsp +++ b/assay/src/org/labkey/assay/view/batchDetails.jsp @@ -19,10 +19,11 @@ <%@ page import="org.labkey.api.exp.api.AssayJSONConverter" %> <%@ page import="org.labkey.api.exp.api.ExpExperiment" %> <%@ page import="org.labkey.api.exp.api.ExpProtocol" %> +<%@ page import="org.labkey.api.exp.api.ExperimentJSONConverter" %> <%@ page import="org.labkey.api.view.HttpView" %> <%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.assay.ModuleAssayProvider" %> <%@ page import="org.labkey.assay.AssayController" %> +<%@ page import="org.labkey.assay.ModuleAssayProvider" %> <%@ page import="java.util.Map" %> <%@ page extends="org.labkey.api.jsp.JspBase" %> <% @@ -33,7 +34,7 @@ ExpExperiment batch = bean.expExperiment; Map assay = AssayController.serializeAssayDefinition(bean.expProtocol, bean.provider, getContainer(), getUser()); - JSONObject batchJson = AssayJSONConverter.serializeBatch(batch, provider, protocol, getUser()); + JSONObject batchJson = AssayJSONConverter.serializeBatch(batch, provider, protocol, getUser(), ExperimentJSONConverter.DEFAULT_SETTINGS); %>