From 80b0aff1b3d14888560f11d5f48868578f680fa4 Mon Sep 17 00:00:00 2001 From: Kevin Krouse Date: Mon, 2 Mar 2020 21:05:16 -0800 Subject: [PATCH 1/8] Item #6862: provenance loadRun API - include provenance properties in the loadRun response - share serialization of experiment objects in lineage API and loadRun API - add Identifiable.detailsURL --- api/src/org/labkey/api/exp/Identifiable.java | 8 + .../org/labkey/api/exp/IdentifiableBase.java | 16 + api/src/org/labkey/api/exp/Lsid.java | 2 +- api/src/org/labkey/api/exp/LsidManager.java | 21 +- api/src/org/labkey/api/exp/api/DataType.java | 4 +- .../org/labkey/api/exp/api/ExpLineage.java | 71 +--- .../labkey/api/exp/api/ExpLineageOptions.java | 11 + api/src/org/labkey/api/exp/api/ExpObject.java | 4 +- .../api/exp/api/ExperimentJSONConverter.java | 324 ++++++++++++++---- .../labkey/api/exp/api/ProvenanceService.java | 8 +- api/webapp/clientapi/core/Experiment.js | 3 + .../src/org/labkey/assay/AssayController.java | 2 +- .../labkey/experiment/ExperimentModule.java | 6 +- .../src/org/labkey/experiment/XarReader.java | 2 +- .../src/org/labkey/experiment/api/Data.java | 11 + .../org/labkey/experiment/api/DataClass.java | 11 + .../experiment/api/ExpChildObjectImpl.java | 12 +- .../labkey/experiment/api/ExpDataImpl.java | 7 +- .../experiment/api/ExpExperimentImpl.java | 4 +- .../experiment/api/ExpMaterialImpl.java | 6 +- .../api/ExpProtocolApplicationImpl.java | 4 +- .../experiment/api/ExpProtocolImpl.java | 4 +- .../experiment/api/ExpProtocolInputImpl.java | 4 +- .../org/labkey/experiment/api/ExpRunImpl.java | 9 +- .../experiment/api/ExpRunInputImpl.java | 4 +- .../experiment/api/ExpSampleSetImpl.java | 6 +- .../labkey/experiment/api/ExperimentRun.java | 14 + .../experiment/api/IdentifiableEntity.java | 2 + .../labkey/experiment/api/LineageTest.java | 2 +- .../labkey/experiment/api/LogDataType.java | 6 +- .../org/labkey/experiment/api/Material.java | 11 + .../labkey/experiment/api/MaterialSource.java | 12 + .../controllers/exp/ExperimentController.java | 4 +- .../pipeline/ExpGeneratorHelper.java | 4 +- 34 files changed, 421 insertions(+), 198 deletions(-) diff --git a/api/src/org/labkey/api/exp/Identifiable.java b/api/src/org/labkey/api/exp/Identifiable.java index d307f060255..c3073cc61ce 100644 --- a/api/src/org/labkey/api/exp/Identifiable.java +++ b/api/src/org/labkey/api/exp/Identifiable.java @@ -15,7 +15,9 @@ */ package org.labkey.api.exp; +import org.jetbrains.annotations.Nullable; import org.labkey.api.data.Container; +import org.labkey.api.view.ActionURL; /** * Base functionality for objects that have an LSID. @@ -34,4 +36,10 @@ default String getLSIDNamespacePrefix() String getName(); Container getContainer(); + + default @Nullable ActionURL detailsURL() + { + return null; + } + } diff --git a/api/src/org/labkey/api/exp/IdentifiableBase.java b/api/src/org/labkey/api/exp/IdentifiableBase.java index ff76abd4f7d..8a977ea922a 100644 --- a/api/src/org/labkey/api/exp/IdentifiableBase.java +++ b/api/src/org/labkey/api/exp/IdentifiableBase.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.util.URLHelper; +import org.labkey.api.view.ActionURL; import java.io.Serializable; import java.util.Objects; @@ -29,6 +32,7 @@ public class IdentifiableBase implements Identifiable, Serializable { private String _lsid; private String _name; + private ActionURL _detailsURL; // some entities copy the exp.object.objectid value private Integer objectId; protected Container container; @@ -44,11 +48,17 @@ public IdentifiableBase(String lsid) } public IdentifiableBase(OntologyObject oo) + { + this(oo, null); + } + + public IdentifiableBase(OntologyObject oo, ActionURL detailsURL) { this(); _lsid = oo.getObjectURI(); objectId = oo.getObjectId(); container = oo.getContainer(); + _detailsURL = detailsURL; } public String getLSID() @@ -97,6 +107,12 @@ public void setContainer(Container container) this.container = container; } + @Override + public @Nullable ActionURL detailsURL() + { + return _detailsURL; + } + @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..5112a109162 100644 --- a/api/src/org/labkey/api/exp/LsidManager.java +++ b/api/src/org/labkey/api/exp/LsidManager.java @@ -34,6 +34,7 @@ import org.labkey.api.security.permissions.ReadPermission; import org.labkey.api.settings.AppProps; import org.labkey.api.util.PageFlowUtil; +import org.labkey.api.util.URLHelper; import org.labkey.api.view.ActionURL; import java.util.HashMap; @@ -132,15 +133,21 @@ public Identifiable getObject(Lsid lsid) if (oo == null) return null; - return new IdentifiableBase(oo); + return new IdentifiableBase(oo, getDisplayURL(oo)); } - @Override - public @Nullable ActionURL getDisplayURL(Lsid lsid) + protected @Nullable ActionURL getDisplayURL(@NotNull OntologyObject oo) { return null; } + @Override + public final @Nullable ActionURL getDisplayURL(Lsid lsid) + { + Identifiable obj = getObject(lsid); + return obj == null ? null : obj.detailsURL(); + } + @Override public Container getContainer(Lsid lsid) { @@ -177,13 +184,9 @@ public Identifiable getObject(Lsid lsid) } @Override - public @Nullable ActionURL getDisplayURL(Lsid lsid) + protected @Nullable ActionURL getDisplayURL(@NotNull OntologyObject oo) { - Container c = getContainer(lsid); - if (c == null) - return null; - - return PageFlowUtil.urlProvider(AssayUrls.class).getAssayResultRowURL(_provider, c, lsid); + return PageFlowUtil.urlProvider(AssayUrls.class).getAssayResultRowURL(_provider, oo.getContainer(), new Lsid(oo.getObjectURI())); } } diff --git a/api/src/org/labkey/api/exp/api/DataType.java b/api/src/org/labkey/api/exp/api/DataType.java index e8abfc28878..0534e06b3f1 100644 --- a/api/src/org/labkey/api/exp/api/DataType.java +++ b/api/src/org/labkey/api/exp/api/DataType.java @@ -18,7 +18,7 @@ import com.google.common.base.MoreObjects; import org.labkey.api.exp.Lsid; -import org.labkey.api.util.URLHelper; +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 +38,7 @@ public String getNamespacePrefix() return _namespacePrefix; } - public URLHelper getDetailsURL(ExpData dataObject) + public ActionURL getDetailsURL(ExpData dataObject) { return null; } diff --git a/api/src/org/labkey/api/exp/api/ExpLineage.java b/api/src/org/labkey/api/exp/api/ExpLineage.java index c5056d790ce..54e437edd2d 100644 --- a/api/src/org/labkey/api/exp/api/ExpLineage.java +++ b/api/src/org/labkey/api/exp/api/ExpLineage.java @@ -23,6 +23,7 @@ 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 +319,7 @@ else if (parent instanceof ExpData) return datas; } - public JSONObject toJSON(boolean requestedWithSingleSeed) + public JSONObject toJSON(User user, boolean requestedWithSingleSeed, boolean includeProperties) { Map nodeMeta = processNodes(); Map values = new HashMap<>(); @@ -328,7 +329,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(), includeProperties)); } } else @@ -345,7 +346,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, includeProperties)); } } @@ -365,73 +366,21 @@ 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, boolean includeProperties) { 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 + json = ExperimentJSONConverter.serialize(node, user, includeProperties); - 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()); - - 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..1342c2dbb78 100644 --- a/api/src/org/labkey/api/exp/api/ExpLineageOptions.java +++ b/api/src/org/labkey/api/exp/api/ExpLineageOptions.java @@ -34,6 +34,7 @@ public class ExpLineageOptions private String _cpasType; private boolean _forLookup = false; private boolean _useObjectIds = false; + private boolean _includeProperties = false; public ExpLineageOptions() { @@ -140,4 +141,14 @@ public void setUseObjectIds(boolean useObjectIds) { _useObjectIds = useObjectIds; } + + public boolean isIncludeProperties() + { + return _includeProperties; + } + + public void setIncludeProperties(boolean includeProperties) + { + _includeProperties = includeProperties; + } } 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..22474a713ab 100644 --- a/api/src/org/labkey/api/exp/api/ExperimentJSONConverter.java +++ b/api/src/org/labkey/api/exp/api/ExperimentJSONConverter.java @@ -20,19 +20,28 @@ import org.jetbrains.annotations.Nullable; import org.json.JSONArray; import org.json.JSONObject; +import org.json.JSONString; +import org.labkey.api.assay.AssayProtocolSchema; +import org.labkey.api.assay.AssayProvider; +import org.labkey.api.assay.AssayService; import org.labkey.api.data.Container; +import org.labkey.api.exp.Identifiable; +import org.labkey.api.exp.LsidManager; import org.labkey.api.exp.ObjectProperty; import org.labkey.api.exp.OntologyManager; import org.labkey.api.exp.PropertyDescriptor; import org.labkey.api.exp.PropertyType; import org.labkey.api.exp.property.Domain; import org.labkey.api.exp.property.DomainProperty; +import org.labkey.api.exp.query.SamplesSchema; import org.labkey.api.files.FileContentService; import org.labkey.api.pipeline.PipeRoot; import org.labkey.api.pipeline.PipelineService; +import org.labkey.api.query.SchemaKey; 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 +52,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 +70,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 +79,9 @@ 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 = "schemaName"; + public static final String QUERY_NAME = "queryName"; + // Run properties public static final String PROTOCOL = "protocol"; public static final String DATA_INPUTS = "dataInputs"; @@ -85,36 +100,87 @@ public class ExperimentJSONConverter // Domain kinds public static final String VOCABULARY_DOMAIN = "Vocabulary"; + public static JSONObject serialize(Identifiable node, User user, boolean includeProperties) + { + if (node instanceof ExpExperiment) + return serializeRunGroup((ExpExperiment)node, null, includeProperties); + else if (node instanceof ExpRun) + return serializeRun((ExpRun)node, null, user, false, includeProperties); + else if (node instanceof ExpMaterial) + return serializeMaterial((ExpMaterial)node, includeProperties); + else if (node instanceof ExpData) + return serializeData((ExpData)node, user, includeProperties); + else if (node instanceof ExpObject) + return serializeStandardProperties((ExpObject)node, null, includeProperties); + else + return serializeIdentifiable(node); + } + public static JSONObject serializeRunGroup(ExpExperiment runGroup, Domain domain) { - JSONObject jsonObject = serializeStandardProperties(runGroup, domain != null ? domain.getProperties() : Collections.emptyList()); + return serializeRunGroup(runGroup, domain, true); + } + + public static JSONObject serializeRunGroup(ExpExperiment runGroup, Domain domain, boolean includeProperties) + { + JSONObject jsonObject = serializeStandardProperties(runGroup, domain != null ? domain.getProperties() : Collections.emptyList(), includeProperties); jsonObject.put(COMMENT, runGroup.getComments()); return jsonObject; } public static JSONObject serializeRun(ExpRun run, Domain domain, User user) { - JSONObject jsonObject = serializeStandardProperties(run, domain == null ? null : domain.getProperties()); - jsonObject.put(COMMENT, run.getComments()); - jsonObject.put(PROTOCOL, serializeProtocol(run.getProtocol(), user)); + return serializeRun(run, domain, user, true, true); + } - JSONArray inputDataArray = new JSONArray(); - for (ExpData data : run.getDataInputs().keySet()) + public static JSONObject serializeRun(ExpRun run, Domain domain, User user, boolean includeInputsAndOutputs, boolean includeProperties) + { + JSONObject jsonObject = serializeStandardProperties(run, domain == null ? null : domain.getProperties(), includeProperties); + if (includeProperties) { - inputDataArray.put(ExperimentJSONConverter.serializeData(data, user)); + jsonObject.put(COMMENT, run.getComments()); + jsonObject.put(PROTOCOL, serializeProtocol(run.getProtocol(), user)); + + if (includeInputsAndOutputs) + { + JSONArray inputDataArray = new JSONArray(); + for (ExpData data : run.getDataInputs().keySet()) + { + inputDataArray.put(ExperimentJSONConverter.serializeData(data, user, true)); + } + jsonObject.put(DATA_INPUTS, inputDataArray); + + JSONArray inputMaterialArray = new JSONArray(); + for (ExpMaterial material : run.getMaterialInputs().keySet()) + { + JSONObject jsonMaterial = ExperimentJSONConverter.serializeMaterial(material, true); + jsonMaterial.put(ROLE, run.getMaterialInputs().get(material)); + inputMaterialArray.put(jsonMaterial); + } + jsonObject.put(MATERIAL_INPUTS, inputMaterialArray); + + serializeRunOutputs(jsonObject, run.getDataOutputs(), run.getMaterialOutputs(), user); + + serializeProvenanceProperties(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()); + AssayService assayService = AssayService.get(); + if (assayService != null) + { + AssayProvider provider = assayService.getProvider(run); + if (provider != null) + { + SchemaKey schemaKey = AssayProtocolSchema.schemaName(provider, protocol); + jsonObject.put(SCHEMA_NAME, schemaKey.toString()); + jsonObject.put(QUERY_NAME, "Runs"); + } + } } - jsonObject.put(MATERIAL_INPUTS, inputMaterialArray); - - serializeRunOutputs(jsonObject, run.getDataOutputs(), run.getMaterialOutputs(), user); return jsonObject; } @@ -126,7 +192,7 @@ 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 = serializeBaseProperties(protocol); return jsonObject; } @@ -143,26 +209,92 @@ protected static void serializeRunOutputs(@NotNull JSONObject obj, Collection + Map.of("from", serializeProvenanceObject(pair.getKey()), + "to", serializeProvenanceObject(pair.getValue())) + ).collect(Collectors.toUnmodifiableList())); + } + } + } + + // For now, just return the lsid if it isn't null + // CONSIDER: Use LsidManager to find the object and call serialize() ? + public static Object serializeProvenanceObject(String objectUri) + { + if (objectUri == null) + return null; + + return objectUri; + } + + // CONSIDER: Include OntologyObject properties for non-ExpObject Identifiable types + public static JSONObject serializeIdentifiable(@NotNull Identifiable obj) { - JSONObject jsonObject = new JSONObject(); + JSONObject json = new JSONObject(); + + json.put(NAME, obj.getName()); + json.put(LSID, obj.getLSID()); + json.put(URL, obj.detailsURL()); + return json; + } + + // Serialize only the base properties -- does not include object properties + public static JSONObject serializeBaseProperties(ExpObject object) + { // Standard properties on all experiment objects - jsonObject.put(NAME, object.getName()); - jsonObject.put(LSID, object.getLSID()); + JSONObject jsonObject = serializeIdentifiable(object); jsonObject.put(ID, object.getRowId()); if (object.getCreatedBy() != null) { @@ -182,70 +314,96 @@ public static JSONObject serializeStandardProperties(ExpObject object) } // Serialize standard properties including object properties and the optional domain properties - public static JSONObject serializeStandardProperties(ExpObject object, @Nullable List properties) + public static JSONObject serializeStandardProperties(ExpObject object, @Nullable List properties, boolean includeProperties) { - JSONObject jsonObject = serializeStandardProperties(object); - - // Add the custom properties - Set seenPropertyURIs = new HashSet<>(); - JSONObject propertiesObject = new JSONObject(); - if (properties != null) + JSONObject jsonObject; + if (includeProperties) { - for (DomainProperty dp : properties) + jsonObject = serializeBaseProperties(object); + + // Add the custom properties + Set seenPropertyURIs = new HashSet<>(); + JSONObject propertiesObject = new JSONObject(); + if (properties != null) { - seenPropertyURIs.add(dp.getPropertyURI()); - Object value = object.getProperty(dp); - if (dp.getPropertyDescriptor().getPropertyType() == PropertyType.FILE_LINK && value instanceof File) + for (DomainProperty dp : properties) { - // 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) + seenPropertyURIs.add(dp.getPropertyURI()); + Object value = object.getProperty(dp); + if (dp.getPropertyDescriptor().getPropertyType() == PropertyType.FILE_LINK && value instanceof File) { - // 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) + // 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, true); + } + else { - jsonFile.put(PIPELINE_PATH, pipeRoot.relativePath(f)); + // 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 = jsonFile; } + propertiesObject.put(dp.getName(), value); } - propertiesObject.put(dp.getName(), value); } - } - var objectProps = object.getObjectProperties(); - for (var propPair : objectProps.entrySet()) + var objectProps = object.getObjectProperties(); + 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()); + } + + if (!propertiesObject.isEmpty()) + jsonObject.put(PROPERTIES, propertiesObject); + } + else { - String propertyURI = propPair.getKey(); - if (seenPropertyURIs.contains(propertyURI)) - continue; - seenPropertyURIs.add(propertyURI); - ObjectProperty op = propPair.getValue(); - propertiesObject.put(propertyURI, op.value()); + jsonObject = serializeBaseProperties(object); } - - if (!propertiesObject.isEmpty()) - jsonObject.put(PROPERTIES, propertiesObject); - return jsonObject; } public static JSONObject serializeData(ExpData data, @Nullable User user) { - JSONObject jsonObject = serializeStandardProperties(data, null); + return serializeData(data, user, true); + } + + public static JSONObject serializeData(ExpData data, @Nullable User user, boolean includeProperties) + { + final ExpDataClass dc = data.getDataClass(user); + + JSONObject jsonObject = serializeStandardProperties(data, null, includeProperties); + + if (includeProperties) + { + if (dc != null) + { + JSONObject dataClassJsonObject = serializeStandardProperties(dc, null, 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 +416,33 @@ public static JSONObject serializeData(ExpData data, @Nullable User user) } } - ExpDataClass dc = data.getDataClass(user); + jsonObject.put(CPAS_TYPE, data.getCpasType()); 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(SCHEMA_NAME, "exp.data"); + jsonObject.put(QUERY_NAME, dc.getName()); } + return jsonObject; } public static JSONObject serializeMaterial(ExpMaterial material) + { + return serializeMaterial(material, true); + } + + public static JSONObject serializeMaterial(ExpMaterial material, boolean includeProperties) { ExpSampleSet sampleSet = material.getSampleSet(); JSONObject jsonObject; if (sampleSet == null) { - jsonObject = serializeStandardProperties(material, null); + jsonObject = serializeStandardProperties(material, null, includeProperties); } else { - jsonObject = serializeStandardProperties(material, sampleSet.getDomain().getProperties()); + jsonObject = serializeStandardProperties(material, sampleSet.getDomain().getProperties(), includeProperties); if (sampleSet.hasNameAsIdCol()) { JSONObject properties = jsonObject.optJSONObject(ExperimentJSONConverter.PROPERTIES); @@ -290,8 +452,18 @@ public static JSONObject serializeMaterial(ExpMaterial material) jsonObject.put(ExperimentJSONConverter.PROPERTIES, properties); } - JSONObject sampleSetJson = serializeStandardProperties(sampleSet, null); - jsonObject.put(SAMPLE_SET, sampleSetJson); + if (includeProperties) + { + JSONObject sampleSetJson = serializeStandardProperties(sampleSet, null, false); + jsonObject.put(SAMPLE_SET, sampleSetJson); + } + } + + jsonObject.put(CPAS_TYPE, material.getCpasType()); + if (sampleSet != null) + { + jsonObject.put(SCHEMA_NAME, SamplesSchema.SCHEMA_NAME); + jsonObject.put(QUERY_NAME, sampleSet.getName()); } return jsonObject; diff --git a/api/src/org/labkey/api/exp/api/ProvenanceService.java b/api/src/org/labkey/api/exp/api/ProvenanceService.java index d4f2ddbe743..82a46d4ea85 100644 --- a/api/src/org/labkey/api/exp/api/ProvenanceService.java +++ b/api/src/org/labkey/api/exp/api/ProvenanceService.java @@ -16,7 +16,13 @@ * */ 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; static ProvenanceService get() { diff --git a/api/webapp/clientapi/core/Experiment.js b/api/webapp/clientapi/core/Experiment.js index 1ced42367b3..7fd51a67071 100644 --- a/api/webapp/clientapi/core/Experiment.js +++ b/api/webapp/clientapi/core/Experiment.js @@ -469,6 +469,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) @@ -489,6 +490,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; diff --git a/assay/src/org/labkey/assay/AssayController.java b/assay/src/org/labkey/assay/AssayController.java index 89ec43b549c..d9807ec09bb 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 getCustomSearchJson(User user, @NotNull String resour if (data == null) return null; - return ExperimentJSONConverter.serializeData(data, user); + return ExperimentJSONConverter.serializeData(data, user, true); } }); ss.addResourceResolver("materialSource", new SearchService.ResourceResolver(){ @@ -312,7 +312,7 @@ public Map getCustomSearchJson(User user, @NotNull String resour if (sampleSet == null) return null; - Map properties = ExperimentJSONConverter.serializeStandardProperties(sampleSet, null); + Map properties = ExperimentJSONConverter.serializeStandardProperties(sampleSet, null, true); //Need to map to proper Icon properties.put("type", "sampleSet"); @@ -333,7 +333,7 @@ public Map getCustomSearchJson(User user, @NotNull String resour if (material == null) return null; - return ExperimentJSONConverter.serializeMaterial(material); + return ExperimentJSONConverter.serializeMaterial(material, true); } }); ss.addDocumentProvider(this); diff --git a/experiment/src/org/labkey/experiment/XarReader.java b/experiment/src/org/labkey/experiment/XarReader.java index f4f5ca31589..713567c30bb 100644 --- a/experiment/src/org/labkey/experiment/XarReader.java +++ b/experiment/src/org/labkey/experiment/XarReader.java @@ -798,7 +798,7 @@ private void loadExperimentRun(ExperimentRunType a, List startingMa { //make the lsid unique and retry String suffix = Long.toString(Math.round(Math.random() * 100)); - pRunLSID = new Lsid.LsidBuilder(pRunLSID).setObjectId(pRunLSID.getObjectId() + "." + suffix).build(); + pRunLSID = pRunLSID.edit().setObjectId(pRunLSID.getObjectId() + "." + suffix).build(); run = ExperimentServiceImpl.get().getExperimentRun(pRunLSID.toString()); } } diff --git a/experiment/src/org/labkey/experiment/api/Data.java b/experiment/src/org/labkey/experiment/api/Data.java index 447e69458a1..a3916b47005 100644 --- a/experiment/src/org/labkey/experiment/api/Data.java +++ b/experiment/src/org/labkey/experiment/api/Data.java @@ -18,6 +18,9 @@ import org.jetbrains.annotations.Nullable; import org.labkey.api.exp.api.ExpData; import org.labkey.api.util.FileUtil; +import org.labkey.api.util.URLHelper; +import org.labkey.api.view.ActionURL; +import org.labkey.experiment.controllers.exp.ExperimentController; import java.io.File; import java.net.URI; @@ -103,6 +106,14 @@ public void setClassId(Integer classId) this.classId = classId; } + @Override + public @Nullable ActionURL detailsURL() + { + ActionURL ret = new ActionURL(ExperimentController.ShowDataAction.class, getContainer()); + ret.addParameter("rowId", Integer.toString(getRowId())); + return ret; + } + public boolean equals(Object o) { if (this == o) return true; diff --git a/experiment/src/org/labkey/experiment/api/DataClass.java b/experiment/src/org/labkey/experiment/api/DataClass.java index c54283f7507..2861ebfafb8 100644 --- a/experiment/src/org/labkey/experiment/api/DataClass.java +++ b/experiment/src/org/labkey/experiment/api/DataClass.java @@ -17,6 +17,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.labkey.api.view.ActionURL; +import org.labkey.experiment.controllers.exp.ExperimentController; import java.util.Objects; @@ -73,6 +75,15 @@ public void setCategory(String category) _category = category; } + @Nullable + @Override + public ActionURL detailsURL() + { + ActionURL ret = new ActionURL(ExperimentController.ShowDataClassAction.class, getContainer()); + ret.addParameter("rowId", getRowId()); + return ret; + } + @Override public boolean equals(Object o) { diff --git a/experiment/src/org/labkey/experiment/api/ExpChildObjectImpl.java b/experiment/src/org/labkey/experiment/api/ExpChildObjectImpl.java index ec30b1bb1f3..0577193bfe5 100644 --- a/experiment/src/org/labkey/experiment/api/ExpChildObjectImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpChildObjectImpl.java @@ -16,13 +16,13 @@ package org.labkey.experiment.api; -import org.labkey.api.exp.api.ExpObject; -import org.labkey.api.exp.PropertyDescriptor; -import org.labkey.api.exp.OntologyManager; +import org.labkey.api.data.Container; import org.labkey.api.exp.Lsid; -import org.labkey.api.util.URLHelper; +import org.labkey.api.exp.OntologyManager; +import org.labkey.api.exp.PropertyDescriptor; +import org.labkey.api.exp.api.ExpObject; import org.labkey.api.security.User; -import org.labkey.api.data.Container; +import org.labkey.api.view.ActionURL; import java.util.Date; @@ -89,7 +89,7 @@ public int getRowId() throw new UnsupportedOperationException(); } - public URLHelper detailsURL() + public ActionURL detailsURL() { return null; } diff --git a/experiment/src/org/labkey/experiment/api/ExpDataImpl.java b/experiment/src/org/labkey/experiment/api/ExpDataImpl.java index aadbc432a21..7942644f0f3 100644 --- a/experiment/src/org/labkey/experiment/api/ExpDataImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpDataImpl.java @@ -131,16 +131,15 @@ public void setComment(User user, String comment) throws ValidationException @Override @Nullable - public URLHelper detailsURL() + public ActionURL detailsURL() { DataType dataType = getDataType(); if (dataType != null) { return dataType.getDetailsURL(this); } - ActionURL ret = new ActionURL(ExperimentController.ShowDataAction.class, getContainer()); - ret.addParameter("rowId", Integer.toString(getRowId())); - return ret; + + return _object.detailsURL(); } @Override diff --git a/experiment/src/org/labkey/experiment/api/ExpExperimentImpl.java b/experiment/src/org/labkey/experiment/api/ExpExperimentImpl.java index e2675f6bb73..3a4e3d4c936 100644 --- a/experiment/src/org/labkey/experiment/api/ExpExperimentImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpExperimentImpl.java @@ -28,8 +28,8 @@ import org.labkey.api.exp.api.ExperimentService; import org.labkey.api.query.BatchValidationException; import org.labkey.api.security.User; -import org.labkey.api.util.URLHelper; import org.labkey.api.util.UnexpectedException; +import org.labkey.api.view.ActionURL; import java.util.ArrayList; import java.util.Collections; @@ -53,7 +53,7 @@ public Container getContainer() return _object.getContainer(); } - public URLHelper detailsURL() + public ActionURL detailsURL() { return null; } diff --git a/experiment/src/org/labkey/experiment/api/ExpMaterialImpl.java b/experiment/src/org/labkey/experiment/api/ExpMaterialImpl.java index deea268d658..fcccd202bd3 100644 --- a/experiment/src/org/labkey/experiment/api/ExpMaterialImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpMaterialImpl.java @@ -109,11 +109,9 @@ public void setLSID(Lsid lsid) } @Override - public URLHelper detailsURL() + public ActionURL detailsURL() { - ActionURL ret = new ActionURL(ExperimentController.ShowMaterialAction.class, getContainer()); - ret.addParameter("rowId", Integer.toString(getRowId())); - return ret; + return _object.detailsURL(); } diff --git a/experiment/src/org/labkey/experiment/api/ExpProtocolApplicationImpl.java b/experiment/src/org/labkey/experiment/api/ExpProtocolApplicationImpl.java index 05da9f11702..dbd29d9f378 100644 --- a/experiment/src/org/labkey/experiment/api/ExpProtocolApplicationImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpProtocolApplicationImpl.java @@ -36,7 +36,7 @@ import org.labkey.api.query.FieldKey; import org.labkey.api.security.User; import org.labkey.api.util.Pair; -import org.labkey.api.util.URLHelper; +import org.labkey.api.view.ActionURL; import java.util.ArrayList; import java.util.Collections; @@ -61,7 +61,7 @@ public ExpProtocolApplicationImpl(ProtocolApplication app) super(app); } - public URLHelper detailsURL() + public ActionURL detailsURL() { return null; } diff --git a/experiment/src/org/labkey/experiment/api/ExpProtocolImpl.java b/experiment/src/org/labkey/experiment/api/ExpProtocolImpl.java index 9e76d268acd..06b2ad88a05 100644 --- a/experiment/src/org/labkey/experiment/api/ExpProtocolImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpProtocolImpl.java @@ -38,7 +38,7 @@ import org.labkey.api.query.FieldKey; import org.labkey.api.query.RuntimeValidationException; import org.labkey.api.security.User; -import org.labkey.api.util.URLHelper; +import org.labkey.api.view.ActionURL; import java.util.ArrayList; import java.util.Collection; @@ -58,7 +58,7 @@ public ExpProtocolImpl(Protocol protocol) super(protocol); } - public URLHelper detailsURL() + public ActionURL detailsURL() { return null; } diff --git a/experiment/src/org/labkey/experiment/api/ExpProtocolInputImpl.java b/experiment/src/org/labkey/experiment/api/ExpProtocolInputImpl.java index edf0f48c71e..603bfe78b51 100644 --- a/experiment/src/org/labkey/experiment/api/ExpProtocolInputImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpProtocolInputImpl.java @@ -25,7 +25,7 @@ import org.labkey.api.exp.api.ExpProtocolInputCriteria; import org.labkey.api.exp.api.ExpRunItem; import org.labkey.api.security.User; -import org.labkey.api.util.URLHelper; +import org.labkey.api.view.ActionURL; import java.util.Date; @@ -50,7 +50,7 @@ public int getRowId() return _object.getRowId(); } - public URLHelper detailsURL() + public ActionURL detailsURL() { return null; } diff --git a/experiment/src/org/labkey/experiment/api/ExpRunImpl.java b/experiment/src/org/labkey/experiment/api/ExpRunImpl.java index 0794be256e5..c640d072451 100644 --- a/experiment/src/org/labkey/experiment/api/ExpRunImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpRunImpl.java @@ -20,6 +20,7 @@ import org.apache.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.labkey.api.assay.AssayFileWriter; import org.labkey.api.cache.DbCache; import org.labkey.api.cloud.CloudStoreService; import org.labkey.api.data.Container; @@ -52,14 +53,12 @@ import org.labkey.api.security.User; import org.labkey.api.security.permissions.DeletePermission; import org.labkey.api.settings.AppProps; -import org.labkey.api.assay.AssayFileWriter; import org.labkey.api.util.FileUtil; import org.labkey.api.util.NetworkDrive; -import org.labkey.api.util.URLHelper; +import org.labkey.api.view.ActionURL; import org.labkey.api.view.UnauthorizedException; import org.labkey.experiment.DotGraph; import org.labkey.experiment.ExperimentRunGraph; -import org.labkey.experiment.controllers.exp.ExperimentController; import java.io.File; import java.io.IOException; @@ -108,9 +107,9 @@ public ExpRunImpl(ExperimentRun run) } @Override - public URLHelper detailsURL() + public ActionURL detailsURL() { - return ExperimentController.getRunGraphURL(getContainer(), getRowId()); + return _object.detailsURL(); } @Override diff --git a/experiment/src/org/labkey/experiment/api/ExpRunInputImpl.java b/experiment/src/org/labkey/experiment/api/ExpRunInputImpl.java index da633cd0cf6..3d5f77f60b9 100644 --- a/experiment/src/org/labkey/experiment/api/ExpRunInputImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpRunInputImpl.java @@ -22,7 +22,7 @@ import org.labkey.api.exp.api.ExpRunInput; import org.labkey.api.exp.api.ExperimentService; import org.labkey.api.security.User; -import org.labkey.api.util.URLHelper; +import org.labkey.api.view.ActionURL; import java.util.Date; @@ -89,7 +89,7 @@ public final int getRowId() } @Override - public @Nullable URLHelper detailsURL() + public @Nullable ActionURL detailsURL() { return null; } diff --git a/experiment/src/org/labkey/experiment/api/ExpSampleSetImpl.java b/experiment/src/org/labkey/experiment/api/ExpSampleSetImpl.java index 311a0923c6d..d89422b809c 100644 --- a/experiment/src/org/labkey/experiment/api/ExpSampleSetImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpSampleSetImpl.java @@ -98,11 +98,9 @@ public ExpSampleSetImpl(MaterialSource ms) } @Override - public URLHelper detailsURL() + public ActionURL detailsURL() { - ActionURL ret = new ActionURL(ExperimentController.ShowMaterialSourceAction.class, getContainer()); - ret.addParameter("rowId", Integer.toString(getRowId())); - return ret; + return _object.detailsURL(); } @Override diff --git a/experiment/src/org/labkey/experiment/api/ExperimentRun.java b/experiment/src/org/labkey/experiment/api/ExperimentRun.java index fc9b11d07dd..16ac8685913 100644 --- a/experiment/src/org/labkey/experiment/api/ExperimentRun.java +++ b/experiment/src/org/labkey/experiment/api/ExperimentRun.java @@ -15,7 +15,14 @@ */ package org.labkey.experiment.api; +import org.jetbrains.annotations.Nullable; +import org.labkey.api.assay.AssayUrls; +import org.labkey.api.data.Container; +import org.labkey.api.exp.api.ExpProtocol; import org.labkey.api.exp.api.ExperimentService; +import org.labkey.api.util.PageFlowUtil; +import org.labkey.api.view.ActionURL; +import org.labkey.experiment.controllers.exp.ExperimentController; /** * Bean class for the exp.experimentrun table. @@ -101,6 +108,13 @@ public void setBatchId(Integer batchId) _batchId = batchId; } + @Override + public @Nullable ActionURL detailsURL() + { + Container c = getContainer(); + return ExperimentController.getRunGraphURL(getContainer(), getRowId()); + } + public boolean equals(Object o) { if (this == o) return true; diff --git a/experiment/src/org/labkey/experiment/api/IdentifiableEntity.java b/experiment/src/org/labkey/experiment/api/IdentifiableEntity.java index 89d90df944f..028e6fc6e87 100644 --- a/experiment/src/org/labkey/experiment/api/IdentifiableEntity.java +++ b/experiment/src/org/labkey/experiment/api/IdentifiableEntity.java @@ -15,11 +15,13 @@ */ package org.labkey.experiment.api; +import org.jetbrains.annotations.Nullable; import org.labkey.api.data.Container; import org.labkey.api.exp.Identifiable; import org.labkey.api.exp.IdentifiableBase; import org.labkey.api.security.User; import org.labkey.api.util.GUID; +import org.labkey.api.util.URLHelper; import java.util.Date; import java.util.List; diff --git a/experiment/src/org/labkey/experiment/api/LineageTest.java b/experiment/src/org/labkey/experiment/api/LineageTest.java index a7d74ed0774..a3c2f814a59 100644 --- a/experiment/src/org/labkey/experiment/api/LineageTest.java +++ b/experiment/src/org/labkey/experiment/api/LineageTest.java @@ -459,7 +459,7 @@ public void testObjectInputOutput() throws Exception assertEquals(Set.of(b1, b2), lineage.getNodeChildren(run)); // verify json structure - JSONObject json = lineage.toJSON(true); + JSONObject json = lineage.toJSON(user, true, false); assertEquals(a1Lsid.toString(), json.getString("seed")); JSONObject nodes = json.getJSONObject("nodes"); diff --git a/experiment/src/org/labkey/experiment/api/LogDataType.java b/experiment/src/org/labkey/experiment/api/LogDataType.java index 5f238d34e8a..97866f6adcd 100644 --- a/experiment/src/org/labkey/experiment/api/LogDataType.java +++ b/experiment/src/org/labkey/experiment/api/LogDataType.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - + package org.labkey.experiment.api; import org.labkey.api.exp.api.DataType; import org.labkey.api.exp.api.ExpData; -import org.labkey.api.util.URLHelper; +import org.labkey.api.view.ActionURL; public class LogDataType extends DataType { @@ -26,7 +26,7 @@ public LogDataType() { super("Log"); } - public URLHelper getDetailsURL(ExpData dataObject) + public ActionURL getDetailsURL(ExpData dataObject) { return null; } diff --git a/experiment/src/org/labkey/experiment/api/Material.java b/experiment/src/org/labkey/experiment/api/Material.java index 1e7109a7c3f..b8877a82b3f 100644 --- a/experiment/src/org/labkey/experiment/api/Material.java +++ b/experiment/src/org/labkey/experiment/api/Material.java @@ -16,6 +16,9 @@ package org.labkey.experiment.api; import org.labkey.api.exp.api.ExpMaterial; +import org.labkey.api.util.URLHelper; +import org.labkey.api.view.ActionURL; +import org.labkey.experiment.controllers.exp.ExperimentController; /** * Bean class for the exp.material table. @@ -29,6 +32,14 @@ public Material() setCpasType(ExpMaterial.DEFAULT_CPAS_TYPE); } + @Override + public ActionURL detailsURL() + { + ActionURL ret = new ActionURL(ExperimentController.ShowMaterialAction.class, getContainer()); + ret.addParameter("rowId", Integer.toString(getRowId())); + return ret; + } + public boolean equals(Object o) { if (this == o) return true; diff --git a/experiment/src/org/labkey/experiment/api/MaterialSource.java b/experiment/src/org/labkey/experiment/api/MaterialSource.java index e5ed006421a..e98473f20e7 100644 --- a/experiment/src/org/labkey/experiment/api/MaterialSource.java +++ b/experiment/src/org/labkey/experiment/api/MaterialSource.java @@ -16,6 +16,10 @@ package org.labkey.experiment.api; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.labkey.api.util.URLHelper; +import org.labkey.api.view.ActionURL; +import org.labkey.experiment.controllers.exp.ExperimentController; /** * Bean class for the exp.materialsource table. Referred to as sample sets within the UI. @@ -127,6 +131,14 @@ public void setMaterialParentImportAliasMap(String materialParentImportAliasMap) _materialParentImportAliasMap = materialParentImportAliasMap; } + @Override + public @Nullable ActionURL detailsURL() + { + ActionURL ret = new ActionURL(ExperimentController.ShowMaterialSourceAction.class, getContainer()); + ret.addParameter("rowId", Integer.toString(getRowId())); + return ret; + } + @Override public boolean equals(Object o) { diff --git a/experiment/src/org/labkey/experiment/controllers/exp/ExperimentController.java b/experiment/src/org/labkey/experiment/controllers/exp/ExperimentController.java index b0741689ae2..a15ee738e81 100644 --- a/experiment/src/org/labkey/experiment/controllers/exp/ExperimentController.java +++ b/experiment/src/org/labkey/experiment/controllers/exp/ExperimentController.java @@ -6546,7 +6546,7 @@ public void validateForm(ExpLineageOptions options, Errors errors) public Object execute(ExpLineageOptions options, BindException errors) { ExpLineage lineage = ExperimentServiceImpl.get().getLineage(getContainer(), getUser(), _seeds, options); - return new ApiSimpleResponse(lineage.toJSON(options.isSingleSeedRequested())); + return new ApiSimpleResponse(lineage.toJSON(getUser(), options.isSingleSeedRequested(), options.isIncludeProperties())); } } @@ -6598,7 +6598,7 @@ public Object execute(Object o, BindException errors) throws Exception SearchService.SearchHit hit = search.find(docId); if (hit == null) { - Map props = ExperimentJSONConverter.serializeData(d, getUser()); + Map props = ExperimentJSONConverter.serializeData(d, getUser(), true); props.put("docid", docId); notInIndex.add(props); } diff --git a/experiment/src/org/labkey/experiment/pipeline/ExpGeneratorHelper.java b/experiment/src/org/labkey/experiment/pipeline/ExpGeneratorHelper.java index 89e55d07c3e..660de8be639 100644 --- a/experiment/src/org/labkey/experiment/pipeline/ExpGeneratorHelper.java +++ b/experiment/src/org/labkey/experiment/pipeline/ExpGeneratorHelper.java @@ -382,13 +382,13 @@ static private ExpRunImpl insertRun(PipelineJob job, Set actions static private Lsid createOutputProtocolLSID(Lsid parentProtocolLSID) { - Lsid result = new Lsid.LsidBuilder(parentProtocolLSID).setObjectId(parentProtocolLSID.getObjectId() + ".Output").build(); + Lsid result = parentProtocolLSID.edit().setObjectId(parentProtocolLSID.getObjectId() + ".Output").build(); return result; } static private ExpProtocol ensureProtocol(PipelineJob job, Map protocolCache, List protocolSequence, Lsid lsidIn, String description) { - Lsid.LsidBuilder lsid = new Lsid.LsidBuilder(lsidIn); + Lsid.LsidBuilder lsid = lsidIn.edit(); int version = 1; while (true) { From e235ebdfade9dd1a897529ddc34fdc57686874f7 Mon Sep 17 00:00:00 2001 From: Kevin Krouse Date: Mon, 16 Mar 2020 09:51:23 -0700 Subject: [PATCH 2/8] Add provenance input/output refs to XAR protocol application export --- api/schemas/expTypes.xsd | 12 ++++++ .../api/exp/api/ExperimentJSONConverter.java | 43 +++++++++++-------- .../labkey/api/exp/api/ProvenanceService.java | 2 + .../org/labkey/experiment/XarExporter.java | 28 ++++++++++++ 4 files changed, 68 insertions(+), 17 deletions(-) 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/exp/api/ExperimentJSONConverter.java b/api/src/org/labkey/api/exp/api/ExperimentJSONConverter.java index 22474a713ab..3550f566e35 100644 --- a/api/src/org/labkey/api/exp/api/ExperimentJSONConverter.java +++ b/api/src/org/labkey/api/exp/api/ExperimentJSONConverter.java @@ -245,26 +245,35 @@ public static void serializeProvenanceProperties(@NotNull JSONObject obj, ExpRun } } - // Include provenance outputs of the run in this format: - // { - // objectOutputs: [{ - // from: 'urn:lsid:input1', to: 'urn:lsid:output1' - // },{ - // from: 'urn:lsid:input2', to: 'urn:lsid:output1' - // }] - // } ExpProtocolApplication outputApp = run.getOutputProtocolApplication(); if (outputApp != null) { - var outputSet = svc.getProvenanceObjectUris(outputApp.getRowId()); - if (!outputSet.isEmpty()) - { - obj.put(ProvenanceService.PROVENANCE_OBJECT_OUTPUTS, - outputSet.stream().map(pair -> - Map.of("from", serializeProvenanceObject(pair.getKey()), - "to", serializeProvenanceObject(pair.getValue())) - ).collect(Collectors.toUnmodifiableList())); - } + 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(pair -> + Map.of("from", serializeProvenanceObject(pair.getKey()), + "to", serializeProvenanceObject(pair.getValue())) + ).collect(Collectors.toUnmodifiableList())); } } diff --git a/api/src/org/labkey/api/exp/api/ProvenanceService.java b/api/src/org/labkey/api/exp/api/ProvenanceService.java index 82a46d4ea85..23ecf52bb1b 100644 --- a/api/src/org/labkey/api/exp/api/ProvenanceService.java +++ b/api/src/org/labkey/api/exp/api/ProvenanceService.java @@ -24,6 +24,8 @@ public interface ProvenanceService String PROVENANCE_OBJECT_OUTPUTS = "objectOutputs"; String PROVENANCE_OUTPUT_PROPERTY = PROVENANCE_PROPERTY_PREFIX + ":" + PROVENANCE_OBJECT_OUTPUTS; + String PROVENANCE_OBJECT_MAP = "provenanceMap"; + static ProvenanceService get() { return ServiceRegistry.get().getService(ProvenanceService.class); diff --git a/experiment/src/org/labkey/experiment/XarExporter.java b/experiment/src/org/labkey/experiment/XarExporter.java index 5d166f836fd..4dfa563d057 100644 --- a/experiment/src/org/labkey/experiment/XarExporter.java +++ b/experiment/src/org/labkey/experiment/XarExporter.java @@ -52,6 +52,7 @@ import org.labkey.api.exp.api.ExpRun; import org.labkey.api.exp.api.ExpSampleSet; import org.labkey.api.exp.api.ExperimentService; +import org.labkey.api.exp.api.ProvenanceService; import org.labkey.api.exp.property.Domain; import org.labkey.api.exp.property.DomainProperty; import org.labkey.api.exp.property.IPropertyValidator; @@ -61,6 +62,7 @@ import org.labkey.api.study.assay.AssayPublishService; import org.labkey.api.util.DateUtil; import org.labkey.api.util.FileUtil; +import org.labkey.api.util.Pair; import org.labkey.experiment.api.Data; import org.labkey.experiment.api.DataInput; import org.labkey.experiment.api.ExpDataImpl; @@ -450,6 +452,32 @@ private void addProtocolApplication(ExpProtocolApplication application, ExpRun r populateMaterial(xMaterial, material); } + ProvenanceService pvs = ProvenanceService.get(); + if (pvs != null) + { + var provURIs = pvs.getProvenanceObjectUris(application.getRowId()); + if (!provURIs.isEmpty()) + { + ProtocolApplicationBaseType.ProvenanceMap xProvMap = xApplication.addNewProvenanceMap(); + for (Pair pair : provURIs) + { + if (StringUtils.isEmpty(pair.first) && StringUtils.isEmpty(pair.second)) + continue; + + var objectRefs = xProvMap.addNewObjectRefs(); + if (!StringUtils.isEmpty(pair.first)) + { + objectRefs.setFrom(_relativizedLSIDs.relativize(pair.first)); + } + + if (!StringUtils.isEmpty(pair.second)) + { + objectRefs.setTo(_relativizedLSIDs.relativize(pair.second)); + } + } + } + } + PropertyCollectionType appProperties = getProperties(application.getLSID(), run.getContainer()); if (appProperties != null) { From 44e499069958e8310294afdb1379637149593381 Mon Sep 17 00:00:00 2001 From: Kevin Krouse Date: Thu, 26 Mar 2020 17:40:55 -0700 Subject: [PATCH 3/8] - add LABKEY.Experiment.resolve() for resolving an LSID into an object - add QueryRowReference to Identifiable and include it when in lineage and loadRun response - optionally include run steps in LABKEY.Experiment.loadRun() response - optionally include provenance map in LABKEY.Experiment.loadRun() response --- .../api/assay/AbstractAssayProvider.java | 48 +- .../org/labkey/api/assay/AssayProvider.java | 6 + api/src/org/labkey/api/assay/AssayUrls.java | 2 +- api/src/org/labkey/api/data/FilterInfo.java | 18 + api/src/org/labkey/api/exp/Identifiable.java | 5 + .../org/labkey/api/exp/IdentifiableBase.java | 17 - api/src/org/labkey/api/exp/LsidManager.java | 94 +++- .../api/exp/api/AssayJSONConverter.java | 28 +- api/src/org/labkey/api/exp/api/DataType.java | 7 + .../exp/api/DefaultExperimentSaveHandler.java | 5 +- .../org/labkey/api/exp/api/ExpLineage.java | 11 +- .../labkey/api/exp/api/ExpLineageOptions.java | 40 +- .../api/exp/api/ExperimentJSONConverter.java | 447 ++++++++++++------ .../api/exp/api/ProtocolImplementation.java | 6 + .../labkey/api/exp/api/ResolveLsidsForm.java | 68 +++ .../labkey/api/query/QueryRowReference.java | 113 +++++ api/webapp/clientapi/core/Experiment.js | 64 ++- .../labkey/api/assay/plate/PlateTemplate.java | 6 +- .../org/labkey/api/assay/plate/WellGroup.java | 4 + .../api/assay/plate/WellGroupTemplate.java | 11 +- .../src/org/labkey/assay/AssayController.java | 40 +- assay/src/org/labkey/assay/AssayManager.java | 10 +- .../org/labkey/assay/ModuleAssayCache.java | 6 +- .../assay/actions/GetAssayRunAction.java | 3 +- .../assay/actions/GetAssayRunsAction.java | 47 +- .../assay/actions/SaveAssayRunsAction.java | 3 +- .../src/org/labkey/assay/plate/PlateImpl.java | 9 +- .../org/labkey/assay/plate/PlateManager.java | 42 +- .../labkey/assay/plate/PlateTemplateImpl.java | 18 +- .../labkey/assay/plate/PropertySetImpl.java | 10 +- .../org/labkey/assay/plate/WellGroupImpl.java | 11 + .../assay/plate/WellGroupTemplateImpl.java | 18 + .../assay/plate/view/plateTemplateList.jsp | 6 +- .../org/labkey/assay/view/batchDetails.jsp | 5 +- .../labkey/assay/view/moduleAssayUpload.jsp | 3 +- .../src/org/labkey/assay/view/runDetails.jsp | 3 +- .../labkey/experiment/api/ExpDataImpl.java | 17 + .../experiment/api/ExpExperimentImpl.java | 14 +- .../experiment/api/ExpMaterialImpl.java | 14 +- .../org/labkey/experiment/api/ExpRunImpl.java | 37 ++ .../labkey/experiment/api/LineageTest.java | 3 +- .../experiment/api/MaterialProtocolInput.java | 30 +- .../controllers/exp/ExperimentController.java | 104 +++- .../experiment/types/TypesController.java | 56 ++- .../src/org/labkey/query/CustomViewUtil.java | 8 +- .../labkey/study/model/StudyLsidHandler.java | 16 +- 46 files changed, 1120 insertions(+), 413 deletions(-) create mode 100644 api/src/org/labkey/api/exp/api/ResolveLsidsForm.java create mode 100644 api/src/org/labkey/api/query/QueryRowReference.java 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/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 c3073cc61ce..349a5f4b7e2 100644 --- a/api/src/org/labkey/api/exp/Identifiable.java +++ b/api/src/org/labkey/api/exp/Identifiable.java @@ -17,6 +17,7 @@ import org.jetbrains.annotations.Nullable; import org.labkey.api.data.Container; +import org.labkey.api.query.QueryRowReference; import org.labkey.api.view.ActionURL; /** @@ -42,4 +43,8 @@ default String getLSIDNamespacePrefix() 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 8a977ea922a..fd4c092c9de 100644 --- a/api/src/org/labkey/api/exp/IdentifiableBase.java +++ b/api/src/org/labkey/api/exp/IdentifiableBase.java @@ -15,10 +15,7 @@ */ package org.labkey.api.exp; -import org.jetbrains.annotations.Nullable; import org.labkey.api.data.Container; -import org.labkey.api.util.URLHelper; -import org.labkey.api.view.ActionURL; import java.io.Serializable; import java.util.Objects; @@ -32,7 +29,6 @@ public class IdentifiableBase implements Identifiable, Serializable { private String _lsid; private String _name; - private ActionURL _detailsURL; // some entities copy the exp.object.objectid value private Integer objectId; protected Container container; @@ -43,22 +39,14 @@ public IdentifiableBase() public IdentifiableBase(String lsid) { - this(); _lsid = lsid; } public IdentifiableBase(OntologyObject oo) { - this(oo, null); - } - - public IdentifiableBase(OntologyObject oo, ActionURL detailsURL) - { - this(); _lsid = oo.getObjectURI(); objectId = oo.getObjectId(); container = oo.getContainer(); - _detailsURL = detailsURL; } public String getLSID() @@ -107,11 +95,6 @@ public void setContainer(Container container) this.container = container; } - @Override - public @Nullable ActionURL detailsURL() - { - return _detailsURL; - } @Override public boolean equals(Object o) diff --git a/api/src/org/labkey/api/exp/LsidManager.java b/api/src/org/labkey/api/exp/LsidManager.java index 5112a109162..5f4e7ea3e5c 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,12 +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.URLHelper; +import org.labkey.api.util.Pair; import org.labkey.api.view.ActionURL; import java.util.HashMap; @@ -68,9 +71,9 @@ public static LsidManager get() return INSTANCE; } - public interface LsidHandler + public interface LsidHandler { - Identifiable getObject(Lsid lsid); + O getObject(Lsid lsid); @Nullable ActionURL getDisplayURL(Lsid lsid); @@ -80,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 O getObject(Lsid lsid); public Container getContainer(Lsid lsid) { - ExpObject run = getObject(lsid); + O run = getObject(lsid); return run == null ? null : run.getContainer(); } @@ -97,7 +100,7 @@ public boolean hasPermission(Lsid lsid, @NotNull User user, @NotNull Class { public ExpRun getObject(Lsid lsid) { @@ -124,27 +127,22 @@ 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 O getObject(Lsid lsid) { OntologyObject oo = OntologyManager.getOntologyObject(null, lsid.toString()); if (oo == null) return null; - return new IdentifiableBase(oo, getDisplayURL(oo)); - } - - protected @Nullable ActionURL getDisplayURL(@NotNull OntologyObject oo) - { - return null; + return (O)new IdentifiableBase(oo); } @Override public final @Nullable ActionURL getDisplayURL(Lsid lsid) { - Identifiable obj = getObject(lsid); + O obj = getObject(lsid); return obj == null ? null : obj.detailsURL(); } @@ -166,28 +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 QueryRowReference getQueryRowReference() + { + 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 - protected @Nullable ActionURL getDisplayURL(@NotNull OntologyObject oo) + public AssayResultIdentifiable getObject(Lsid lsid) { - return PageFlowUtil.urlProvider(AssayUrls.class).getAssayResultRowURL(_provider, oo.getContainer(), new Lsid(oo.getObjectURI())); + 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 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 0534e06b3f1..ad512bb765e 100644 --- a/api/src/org/labkey/api/exp/api/DataType.java +++ b/api/src/org/labkey/api/exp/api/DataType.java @@ -17,7 +17,9 @@ 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.query.QueryRowReference; import org.labkey.api.view.ActionURL; /** @@ -43,6 +45,11 @@ public ActionURL getDetailsURL(ExpData dataObject) return null; } + public @Nullable QueryRowReference getQueryRowReference(ExpData dataObject) + { + return null; + } + public String urlFlag(boolean flagged) { 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 54e437edd2d..47f069b2316 100644 --- a/api/src/org/labkey/api/exp/api/ExpLineage.java +++ b/api/src/org/labkey/api/exp/api/ExpLineage.java @@ -319,7 +319,7 @@ else if (parent instanceof ExpData) return datas; } - public JSONObject toJSON(User user, boolean requestedWithSingleSeed, boolean includeProperties) + public JSONObject toJSON(User user, boolean requestedWithSingleSeed, ExperimentJSONConverter.Settings settings) { Map nodeMeta = processNodes(); Map values = new HashMap<>(); @@ -329,7 +329,7 @@ public JSONObject toJSON(User user, boolean requestedWithSingleSeed, boolean inc { for (Identifiable seed : _seeds) { - nodes.put(seed.getLSID(), nodeToJSON(seed, user, new JSONArray(), new JSONArray(), includeProperties)); + nodes.put(seed.getLSID(), nodeToJSON(seed, user, new JSONArray(), new JSONArray(), settings)); } } else @@ -346,7 +346,7 @@ public JSONObject toJSON(User user, boolean requestedWithSingleSeed, boolean inc children.put(edge.toChildJSON()); Identifiable obj = nodeMeta.get(node.getKey()); - nodes.put(node.getKey(), nodeToJSON(obj, user, parents, children, includeProperties)); + nodes.put(node.getKey(), nodeToJSON(obj, user, parents, children, settings)); } } @@ -366,14 +366,13 @@ public JSONObject toJSON(User user, boolean requestedWithSingleSeed, boolean inc return new JSONObject(values); } - private JSONObject nodeToJSON(Identifiable node, User user, JSONArray parents, JSONArray children, boolean includeProperties) + private JSONObject nodeToJSON(Identifiable node, User user, JSONArray parents, JSONArray children, ExperimentJSONConverter.Settings settings) { JSONObject json = new JSONObject(); if (node != null) { - // TODO: get rowId and maybe cpasType and schemaName/queryName for assay result row type - json = ExperimentJSONConverter.serialize(node, user, includeProperties); + json = ExperimentJSONConverter.serialize(node, user, settings); json.put("type", node.getLSIDNamespacePrefix()); } diff --git a/api/src/org/labkey/api/exp/api/ExpLineageOptions.java b/api/src/org/labkey/api/exp/api/ExpLineageOptions.java index 1342c2dbb78..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; @@ -34,7 +28,6 @@ public class ExpLineageOptions private String _cpasType; private boolean _forLookup = false; private boolean _useObjectIds = false; - private boolean _includeProperties = false; public ExpLineageOptions() { @@ -57,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; @@ -142,13 +113,4 @@ public void setUseObjectIds(boolean useObjectIds) _useObjectIds = useObjectIds; } - public boolean isIncludeProperties() - { - return _includeProperties; - } - - public void setIncludeProperties(boolean includeProperties) - { - _includeProperties = includeProperties; - } } diff --git a/api/src/org/labkey/api/exp/api/ExperimentJSONConverter.java b/api/src/org/labkey/api/exp/api/ExperimentJSONConverter.java index 3550f566e35..990f23a93a0 100644 --- a/api/src/org/labkey/api/exp/api/ExperimentJSONConverter.java +++ b/api/src/org/labkey/api/exp/api/ExperimentJSONConverter.java @@ -20,24 +20,21 @@ import org.jetbrains.annotations.Nullable; import org.json.JSONArray; import org.json.JSONObject; -import org.json.JSONString; -import org.labkey.api.assay.AssayProtocolSchema; -import org.labkey.api.assay.AssayProvider; -import org.labkey.api.assay.AssayService; import org.labkey.api.data.Container; +import org.labkey.api.data.FilterInfo; import org.labkey.api.exp.Identifiable; -import org.labkey.api.exp.LsidManager; +import org.labkey.api.exp.Lsid; import org.labkey.api.exp.ObjectProperty; import org.labkey.api.exp.OntologyManager; import org.labkey.api.exp.PropertyDescriptor; import org.labkey.api.exp.PropertyType; import org.labkey.api.exp.property.Domain; import org.labkey.api.exp.property.DomainProperty; -import org.labkey.api.exp.query.SamplesSchema; import org.labkey.api.files.FileContentService; import org.labkey.api.pipeline.PipeRoot; import org.labkey.api.pipeline.PipelineService; -import org.labkey.api.query.SchemaKey; +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; @@ -79,8 +76,9 @@ 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 = "schemaName"; - public static final String QUERY_NAME = "queryName"; + 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"; @@ -89,6 +87,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"; @@ -96,90 +95,134 @@ 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 serialize(Identifiable node, User user, boolean includeProperties) + 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) { if (node instanceof ExpExperiment) - return serializeRunGroup((ExpExperiment)node, null, includeProperties); + return serializeRunGroup((ExpExperiment)node, null, settings); else if (node instanceof ExpRun) - return serializeRun((ExpRun)node, null, user, false, includeProperties); + return serializeRun((ExpRun)node, null, user, settings); else if (node instanceof ExpMaterial) - return serializeMaterial((ExpMaterial)node, includeProperties); + return serializeMaterial((ExpMaterial)node, settings); else if (node instanceof ExpData) - return serializeData((ExpData)node, user, includeProperties); + return serializeData((ExpData)node, user, settings); else if (node instanceof ExpObject) - return serializeStandardProperties((ExpObject)node, null, includeProperties); + return serializeStandardProperties((ExpObject)node, null, settings); else return serializeIdentifiable(node); } - public static JSONObject serializeRunGroup(ExpExperiment runGroup, Domain domain) - { - return serializeRunGroup(runGroup, domain, true); - } - - public static JSONObject serializeRunGroup(ExpExperiment runGroup, Domain domain, boolean includeProperties) + public static JSONObject serializeRunGroup(ExpExperiment runGroup, Domain domain, @NotNull Settings settings) { - JSONObject jsonObject = serializeStandardProperties(runGroup, domain != null ? domain.getProperties() : Collections.emptyList(), includeProperties); + JSONObject jsonObject = serializeStandardProperties(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) - { - return serializeRun(run, domain, user, true, true); - } - - public static JSONObject serializeRun(ExpRun run, Domain domain, User user, boolean includeInputsAndOutputs, boolean includeProperties) + public static JSONObject serializeRun(ExpRun run, Domain domain, User user, @NotNull Settings settings) { - JSONObject jsonObject = serializeStandardProperties(run, domain == null ? null : domain.getProperties(), includeProperties); - if (includeProperties) + JSONObject jsonObject = serializeStandardProperties(run, domain == null ? null : domain.getProperties(), settings); + if (settings.isIncludeProperties()) { jsonObject.put(COMMENT, run.getComments()); jsonObject.put(PROTOCOL, serializeProtocol(run.getProtocol(), user)); + } - if (includeInputsAndOutputs) - { - JSONArray inputDataArray = new JSONArray(); - for (ExpData data : run.getDataInputs().keySet()) - { - inputDataArray.put(ExperimentJSONConverter.serializeData(data, user, true)); - } - jsonObject.put(DATA_INPUTS, inputDataArray); - - JSONArray inputMaterialArray = new JSONArray(); - for (ExpMaterial material : run.getMaterialInputs().keySet()) - { - JSONObject jsonMaterial = ExperimentJSONConverter.serializeMaterial(material, true); - jsonMaterial.put(ROLE, run.getMaterialInputs().get(material)); - inputMaterialArray.put(jsonMaterial); - } - jsonObject.put(MATERIAL_INPUTS, inputMaterialArray); + if (settings.isIncludeInputsAndOutputs()) + { + ExpProtocolApplication inputApp = run.getInputProtocolApplication(); + jsonObject.put(DATA_INPUTS, serializeRunInputs(inputApp.getDataInputs(), user, settings)); + jsonObject.put(MATERIAL_INPUTS, serializeRunInputs(inputApp.getMaterialInputs(), user, settings)); - serializeRunOutputs(jsonObject, run.getDataOutputs(), run.getMaterialOutputs(), user); + // Inputs into the final output step are outputs of the entire run + ExpProtocolApplication outputApp = run.getOutputProtocolApplication(); + jsonObject.put(DATA_OUTPUTS, serializeRunInputs(outputApp.getDataInputs(), user, settings)); + jsonObject.put(MATERIAL_OUTPUTS, serializeRunInputs(outputApp.getMaterialInputs(), user, settings)); - serializeProvenanceProperties(jsonObject, run); - } + serializeRunLevelProvenanceProperties(jsonObject, run); } ExpProtocol protocol = run.getProtocol(); if (protocol != null) { jsonObject.put(CPAS_TYPE, protocol.getLSID()); - AssayService assayService = AssayService.get(); - if (assayService != null) + } + + if (settings.isIncludeRunSteps()) + { + JSONArray steps = new JSONArray(); + for (ExpProtocolApplication protApp : run.getProtocolApplications()) { - AssayProvider provider = assayService.getProvider(run); - if (provider != null) - { - SchemaKey schemaKey = AssayProtocolSchema.schemaName(provider, protocol); - jsonObject.put(SCHEMA_NAME, schemaKey.toString()); - jsonObject.put(QUERY_NAME, "Runs"); - } + // 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; @@ -196,32 +239,125 @@ public static JSONObject serializeProtocol(ExpProtocol protocol, User user) 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, true)); + 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, true)); + outputMaterialArray.put(ExperimentJSONConverter.serializeMaterial(material, settings)); } obj.put(MATERIAL_OUTPUTS, outputMaterialArray); } - public static void serializeProvenanceProperties(@NotNull JSONObject obj, ExpRun run) + 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 = serializeCustomProperties(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 = serializeStandardProperties(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_INPUTS, 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) @@ -288,23 +424,38 @@ public static Object serializeProvenanceObject(String objectUri) } // CONSIDER: Include OntologyObject properties for non-ExpObject Identifiable types + @NotNull public static JSONObject serializeIdentifiable(@NotNull Identifiable obj) { JSONObject json = new JSONObject(); json.put(NAME, obj.getName()); json.put(LSID, obj.getLSID()); - json.put(URL, obj.detailsURL()); + 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 only the base properties -- does not include object properties - public static JSONObject serializeBaseProperties(ExpObject object) + @NotNull + public static JSONObject serializeBaseProperties(@NotNull ExpObject object) { // Standard properties on all experiment objects JSONObject jsonObject = serializeIdentifiable(object); - jsonObject.put(ID, object.getRowId()); + int rowId = object.getRowId(); + if (rowId != 0) + { + jsonObject.put(ID, rowId); + } if (object.getCreatedBy() != null) { jsonObject.put(CREATED_BY, object.getCreatedBy().getEmail()); @@ -323,90 +474,100 @@ public static JSONObject serializeBaseProperties(ExpObject object) } // Serialize standard properties including object properties and the optional domain properties - public static JSONObject serializeStandardProperties(ExpObject object, @Nullable List properties, boolean includeProperties) + @NotNull + public static JSONObject serializeStandardProperties(@NotNull ExpObject object, @Nullable List properties, @NotNull Settings settings) { JSONObject jsonObject; - if (includeProperties) + if (settings.isIncludeProperties()) + { + jsonObject = serializeBaseProperties(object); + + JSONObject propertiesObject = serializeCustomProperties(object, properties, settings); + if (!propertiesObject.isEmpty()) + jsonObject.put(PROPERTIES, propertiesObject); + } + else { jsonObject = serializeBaseProperties(object); + } + + return jsonObject; + } - // Add the custom properties - Set seenPropertyURIs = new HashSet<>(); - JSONObject propertiesObject = new JSONObject(); - if (properties != null) + @NotNull + private static JSONObject serializeCustomProperties(@NotNull ExpObject object, @Nullable List properties, @NotNull ExperimentJSONConverter.Settings settings) + { + // Add the custom properties + Set seenPropertyURIs = new HashSet<>(); + JSONObject propertiesObject = new JSONObject(); + if (properties != null) + { + for (DomainProperty dp : properties) { - for (DomainProperty dp : properties) + seenPropertyURIs.add(dp.getPropertyURI()); + Object value = object.getProperty(dp); + if (dp.getPropertyDescriptor().getPropertyType() == PropertyType.FILE_LINK && value instanceof File) { - 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) { - // 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, true); - } - else + // 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(object.getContainer()); + if (pipeRoot != null) { - // 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; + jsonFile.put(PIPELINE_PATH, pipeRoot.relativePath(f)); } + value = jsonFile; } - propertiesObject.put(dp.getName(), value); } + propertiesObject.put(dp.getName(), value); } - - - var objectProps = object.getObjectProperties(); - 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()); - } - - if (!propertiesObject.isEmpty()) - jsonObject.put(PROPERTIES, propertiesObject); } - else + + var objectProps = object.getObjectProperties(); + for (var propPair : objectProps.entrySet()) { - jsonObject = serializeBaseProperties(object); + String propertyURI = propPair.getKey(); + if (seenPropertyURIs.contains(propertyURI)) + continue; + seenPropertyURIs.add(propertyURI); + ObjectProperty op = propPair.getValue(); + propertiesObject.put(propertyURI, op.value()); } - return jsonObject; + return propertiesObject; } - 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, true); + return serializeData(data, user, DEFAULT_SETTINGS); } - public static JSONObject serializeData(ExpData data, @Nullable User user, boolean includeProperties) + @NotNull + public static JSONObject serializeData(@NotNull ExpData data, @Nullable User user, @NotNull Settings settings) { final ExpDataClass dc = data.getDataClass(user); - JSONObject jsonObject = serializeStandardProperties(data, null, includeProperties); + JSONObject jsonObject = serializeStandardProperties(data, null, settings); - if (includeProperties) + if (settings.isIncludeProperties()) { if (dc != null) { - JSONObject dataClassJsonObject = serializeStandardProperties(dc, null, false); + JSONObject dataClassJsonObject = serializeStandardProperties(dc, null, settings.withIncludeProperties(false)); if (dc.getCategory() != null) dataClassJsonObject.put(DATA_CLASS_CATEGORY, dc.getCategory()); jsonObject.put(DATA_CLASS, dataClassJsonObject); @@ -426,32 +587,32 @@ public static JSONObject serializeData(ExpData data, @Nullable User user, boolea } jsonObject.put(CPAS_TYPE, data.getCpasType()); - if (dc != null) - { - jsonObject.put(SCHEMA_NAME, "exp.data"); - jsonObject.put(QUERY_NAME, dc.getName()); - } return jsonObject; } - public static JSONObject serializeMaterial(ExpMaterial material) + @Deprecated(forRemoval = true) + @NotNull + public static JSONObject serializeMaterial(@NotNull ExpMaterial material) { - return serializeMaterial(material, true); + return serializeMaterial(material, DEFAULT_SETTINGS); } - public static JSONObject serializeMaterial(ExpMaterial material, boolean includeProperties) + // 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, includeProperties); + jsonObject = serializeStandardProperties(material, null, settings); } else { - jsonObject = serializeStandardProperties(material, sampleSet.getDomain().getProperties(), includeProperties); + jsonObject = serializeStandardProperties(material, sampleSet.getDomain().getProperties(), settings); if (sampleSet.hasNameAsIdCol()) { JSONObject properties = jsonObject.optJSONObject(ExperimentJSONConverter.PROPERTIES); @@ -461,23 +622,19 @@ public static JSONObject serializeMaterial(ExpMaterial material, boolean include jsonObject.put(ExperimentJSONConverter.PROPERTIES, properties); } - if (includeProperties) + if (settings.isIncludeProperties()) { - JSONObject sampleSetJson = serializeStandardProperties(sampleSet, null, false); + JSONObject sampleSetJson = serializeStandardProperties(sampleSet, null, settings.withIncludeProperties(false)); jsonObject.put(SAMPLE_SET, sampleSetJson); } } jsonObject.put(CPAS_TYPE, material.getCpasType()); - if (sampleSet != null) - { - jsonObject.put(SCHEMA_NAME, SamplesSchema.SCHEMA_NAME); - jsonObject.put(QUERY_NAME, sampleSet.getName()); - } return jsonObject; } + @NotNull public static Map convertProperties(Map propertiesJsonObject, List dps, Container container, boolean ignoreMissingProperties) throws ValidationException { Map properties = new HashMap<>(); @@ -506,9 +663,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/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..181712c2afb --- /dev/null +++ b/api/src/org/labkey/api/query/QueryRowReference.java @@ -0,0 +1,113 @@ +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.data.FilterInfo; +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 it's 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 fc9846a1cba..27f4ec88174 100644 --- a/api/webapp/clientapi/core/Experiment.js +++ b/api/webapp/clientapi/core/Experiment.js @@ -268,6 +268,9 @@ 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: *
    @@ -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' } @@ -531,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 a 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(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 d9807ec09bb..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); %>