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