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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions deploy/nginx/common/routes-core.conf
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,22 @@ location = /ContentService/data/species/all {
proxy_pass http://content_node;
}

# An object with everything around it, for the Pathway Browser's details panel
# and the website's detail pages: graph-core's findEnhancedObjectById, written
# by the domain serialiser, and a reference entity -- when asked to summarise --
# as the content service's SummaryEntity, rebuilt in node. Byte-identical to
# Java in the default view; in the nested views (the site's), identical as
# data, the getter-only key order being the JVM's of the day.
location ~ ^/ContentService/data/query/enhanced/v2/[^/]+$ {
include /etc/nginx/common/upstream-proxy.conf;
proxy_pass http://content_node;
}

# An object with its neighbours, their species, and their regulations,
# references and markers one step further: graph-core's
# findOldEnhancedObjectById, written by the domain serialiser. The entity
# popup, the website's content pages and the IDG panel read it. Not
# /enhanced/v2/{id}, which Java still serves. Byte-identical to Java over
# popup, the website's content pages and the IDG panel read it. Byte-identical
# to Java over
# 6,000 objects of every class.
location ~ ^/ContentService/data/query/enhanced/[^/]+$ {
include /etc/nginx/common/upstream-proxy.conf;
Expand Down
3 changes: 2 additions & 1 deletion projects/website-angular/src/scripts/proxy-order.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ describe('the dev server proxy', () => {
}

for (const url of [
'/ContentService/data/query/enhanced/v2/R-HSA-70486',
'/ContentService/data/query/enhanced/v2/uniprot:P01112?summariseReferenceEntity=true&includeRef=true',
'/ContentService/data/query/enhanced/R-HSA-70486',
'/ContentService/data/query/enhanced/70486?includeRef=true',
// Java's /query/enhanced/{id} with the id `normalPathway`, not
Expand Down Expand Up @@ -193,7 +195,6 @@ describe('the dev server proxy', () => {
'/ContentService/data/query/70486/',
// Java's POST /query/ids (a batch of objects); node serves no POST.
'/ContentService/data/query/ids',
'/ContentService/data/query/enhanced/v2/70486',
'/ContentService/data/query/abridged/70486',
'/ContentService/data/person/68285/authoredPathwaysAndMore',
// Only the ancestors of an event are ported.
Expand Down
2 changes: 1 addition & 1 deletion projects/website-angular/src/scripts/serve-prod.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ describe('serve-prod and the API roots', () => {
['/ContentService/data/query/R-HSA-70486', 'content-node'],
['/ContentService/data/query/R-HSA-70486?view=nested', 'content-node'],
['/ContentService/data/query/enhanced/R-HSA-70486', 'content-node'],
['/ContentService/data/query/enhanced/v2/R-HSA-70486', 'backend'],
['/ContentService/data/query/enhanced/v2/R-HSA-70486', 'content-node'],
['/ContentService/data/query/ids', 'backend'],
]) {
it(`sends ${url} to ${to}`, async () => {
Expand Down
6 changes: 4 additions & 2 deletions proxy.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,10 @@ module.exports = {
},
])
),
// An object with its neighbours and some of theirs (the older enhanced
// query). Not /enhanced/v2/, which Java still serves.
// An object with everything around it, for the details panel and the
// website's detail pages; a reference entity as a summary of its entities.
'^/ContentService/data/query/enhanced/v2/[^/?]+(\\?|$)': contentNode,
// An object with its neighbours and some of theirs (the older enhanced query).
'^/ContentService/data/query/enhanced/[^/?]+(\\?|$)': contentNode,
// An entity or an event and everything below it, for the object tree.
'^/ContentService/data/(entity|event)/[^/?]+/in-depth(\\?|$)': contentNode,
Expand Down
8 changes: 6 additions & 2 deletions tools/content-node/domain-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,10 @@
["type","OutputForReactionLikeEvent.getType",7],
["type","PublicationAuthor.getType",7],
["type","RepeatedUnit.getType",7],
["type","RepeatedUnitForPhysicalEntity.getType",7]
["type","RepeatedUnitForPhysicalEntity.getType",7],
["summarisedEntities","summarisedEntities",7],
["hasModifiedResidue","SummaryEntity.getHasModifiedResidue",1],
["className","SummaryEntity.getClassName",7]
],
"json": {
"Affiliation": [0,1,2,3,4,5,6,7,8,9],
Expand Down Expand Up @@ -366,7 +369,8 @@
"PublicationAuthor": [253,233,234,235],
"RepeatedUnit": [254,233,234,235],
"RepeatedUnitForPhysicalEntity": [255,233,234,235],
"Default": [221,233,234,235]
"Default": [221,233,234,235],
"SummaryEntity": [0,1,2,3,4,5,11,19,7,22,50,23,80,81,82,24,26,27,30,83,84,34,85,35,86,87,88,89,90,91,92,38,39,41,93,94,47,48,256,201,10,12,200,14,16,137,185,202,203,204,205,206,207,40,211,212,213,215,216,154,127,153,155,257,258,158,71,101,102,104,103,105,106,107,9]
},
"fields": [
["referenceSequence","referenceSequence","OUTGOING","ReferenceSequence","",null],
Expand Down
207 changes: 206 additions & 1 deletion tools/content-node/domain.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ function composedOf(entity) {
throw new Error(`domain: ${entity.schemaClass} is not a CompositionAggregator`);
}

const isA = (schemaClass, target) => supertypes(schemaClass).has(target);
/** Whether a class is, or descends from, another (Class.isAssignableFrom). */
export const isA = (schemaClass, target) => supertypes(schemaClass).has(target);

/**
* What SDN's mapping makes of one query result: the root and every node
Expand Down Expand Up @@ -345,6 +346,15 @@ const COMPUTED = {
'Has.getStoichiometry': (relation) => relation.stoichiometry,
'Has.getElement': (relation) => relation.element,
'CompositionAggregator.getComposedOf': composedOf,
// The content service's SummaryEntity (summaryEntity, below).
'SummaryEntity.getHasModifiedResidue': expanding('hasModifiedResidue'),
// Otherwise super.getClassName(): DatabaseObject's, the class's own name.
'SummaryEntity.getClassName': (entity) =>
({
ReferenceGeneProduct: 'Protein',
ReferenceDNASequence: 'DNA Sequence',
ReferenceRNASequence: 'RNA Sequence',
})[entity.fields.referenceType] ?? 'SummaryEntity',
};

/** The computed getters, by name, for snapshot-domain.mjs to check against graph-core. */
Expand Down Expand Up @@ -592,6 +602,201 @@ export function oldEnhancedQuery({ idType, outgoingOnly }) {
[r IN rs | ${rel('r')}] AS relationships`;
}

/**
* graph-core's findEnhancedObjectById (/query/enhanced/v2), as its
* AdvancedDatabaseObjectRepository writes it: the object -- or, summarising
* a reference entity, the object and every physical entity referring to it --
* with everything related to each (outgoing only, or both ways), their
* species, reference entities, authors, compartments, regulations and the
* rest one step further. UpdateTrackers are left out; so, unless
* `includeDisease`, is anything in disease. Java's text, with its RETURN
* taken apart as findByIdQuery's is.
*/
export function enhancedQuery({ idType, summarise, includeDisease, outgoingOnly }) {
const node = (n) => `{id: id(${n}), labels: labels(${n}), props: properties(${n})}`;
const rel = (r) =>
`{id: id(${r}), type: type(${r}), start: id(startNode(${r})), end: id(endNode(${r})), props: properties(${r})}`;
const expand = !summarise
? 'WITH root, root AS n '
: `OPTIONAL MATCH (root:ReferenceEntity)<-[:referenceEntity]-(pe:PhysicalEntity${includeDisease ? '' : ' {isInDisease: false}'})
WITH collect(pe) + root AS ns, root
UNWIND ns AS n `;
const filter = includeDisease
? 'WHERE NOT m:UpdateTracker '
: 'WHERE (m.isInDisease IS NULL OR m.isInDisease = false) AND NOT m:UpdateTracker ';
return `MATCH (root:DatabaseObject) WHERE root.${idType} = ${idType === 'dbId' ? 'toInteger($id)' : '$id'}
${expand}
OPTIONAL MATCH (n)-[r1]-${outgoingOnly ? '>' : ''}(m:DatabaseObject)
${filter}
OPTIONAL MATCH (m)-[r2:species]->(s)
OPTIONAL MATCH (m)-[r9:referenceEntity]->(rf:ReferenceEntity)
OPTIONAL MATCH (m:Publication)<-[r5:author]-(p2:Person)
OPTIONAL MATCH (m:InstanceEdit)<-[r6:author]-(p3:Person)
OPTIONAL MATCH (m:Event)-[r7:compartment]->(c:Compartment)
OPTIONAL MATCH (m)-[r3:regulator|regulatedBy|physicalEntity|crossReference|referenceGene|
referenceTranscript|literatureReference|marker|regulation|
catalystActivity|activeUnit|activity|psiMod|modification|
goBiologicalProcess]-(o)
OPTIONAL MATCH (o:Publication)<-[r4:author]-(p:Person)
OPTIONAL MATCH (o)-[r8:species]->(s1:Species)
WITH root,
collect(DISTINCT n) AS ns, collect(DISTINCT m) AS ms, collect(DISTINCT s) AS ss,
collect(DISTINCT o) AS os, collect(DISTINCT p) AS ps, collect(DISTINCT p2) AS ps2,
collect(DISTINCT p3) AS ps3, collect(DISTINCT c) AS cs, collect(DISTINCT s1) AS s1s,
collect(DISTINCT rf) AS rfs,
collect(DISTINCT r1) AS r1s, collect(DISTINCT r2) AS r2s, collect(DISTINCT r3) AS r3s,
collect(DISTINCT r4) AS r4s, collect(DISTINCT r5) AS r5s, collect(DISTINCT r6) AS r6s,
collect(DISTINCT r7) AS r7s, collect(DISTINCT r8) AS r8s, collect(DISTINCT r9) AS r9s
RETURN ${node('root')} AS root,
[x IN ns + ms + ss + os + ps + ps2 + ps3 + cs + s1s + rfs | ${node('x')}] AS nodes,
[r IN r1s + r2s + r3s + r4s + r5s + r6s + r7s + r8s + r9s | ${rel('r')}] AS relationships`;
}

/**
* The content service's SummaryEntity: a reference entity written as one
* physical entity standing for all that refer to it, which /query/enhanced/v2
* answers with when asked to summarise. Its constructor, in its order:
*
* - the reference entity's identity, names, database, links and cross
* references; a sequence's genes, checksum, comments, keywords, length and
* species; a gene product's chains, genes and transcripts; an isoform's
* variant and parent;
* - the physical entities, sorted -- in the reference entity's own list too,
* which is sorted in place -- by their modified residues (none for all but
* an EWAS), then dbId, as `summarisedEntities`;
* - then, entity by entity, each property merged in: a list joined to what
* is there already, each element once (LinkedHashSet), through the
* SummaryEntity's own getter and PhysicalEntity's setter -- which for the
* stoichiometric ones (compartment, componentOf, memberOf, candidateOf)
* expands and re-aggregates; a single value (species, referenceType)
* replaced by the entity's if it has one.
*/
export function summaryEntity(reference) {
const summary = new Entity(reference.id, 'SummaryEntity', {});
const from = reference.fields;
const to = summary.fields;
const copy = (...names) => {
for (const name of names) to[name] = from[name];
};
copy('dbId', 'stId', 'displayName', 'name');
to.referenceEntity = reference;
copy(
'moleculeType',
'databaseName',
'identifier',
'url',
'referenceDatabase',
'otherIdentifier',
'crossReference'
);
const is = (name) => isA(reference.schemaClass, name);
if (is('ReferenceSequence'))
copy(
'geneName',
'secondaryIdentifier',
'checksum',
'comment',
'description',
'keyword',
'sequenceLength',
'species',
'isSequenceChanged'
);
if (is('ReferenceGeneProduct')) copy('chain', 'referenceGene', 'referenceTranscript');
if (is('ReferenceIsoform')) copy('variantIdentifier', 'isoformParent');

const residues = (entity) =>
isA(entity.schemaClass, 'EntityWithAccessionedSequence')
? (entity.fields.hasModifiedResidue ?? []).length
: 0;
const entities = from.physicalEntity ?? [];
entities.sort(
(a, b) => residues(a) - residues(b) || Number(a.fields.dbId) - Number(b.fields.dbId)
);
to.summarisedEntities = entities;

const union = (existing, added) => [...new Set([...(existing ?? []), ...added])];
const unionRelations = (existing, added) => {
const out = [...(existing ?? [])];
for (const relation of added)
if (!out.some((kept) => kept.element === relation.element)) out.push(relation);
return out;
};
// PhysicalEntity's setter for a stoichiometric list: one relation per element,
// in the order given (Has.Util.aggregateStoichiometry).
const aggregate = (elements, relationClass) =>
elements.map(
(element, order) => new Relation(relationClass, element, { order, stoichiometry: 1 })
);
const plain = [
'disease',
'crossReference',
'isRequired',
'inferredFrom',
'inferredTo',
'catalystActivities',
'literatureReference',
'positivelyRegulates',
'negativelyRegulates',
'markingReferences',
];
const stoichiometric = {
compartment: 'HasCompartment',
componentOf: 'ComponentOf',
memberOf: 'MemberOf',
candidateOf: 'CandidateOf',
};
// In the accessors' order (SummaryEntity.accessors).
const order = [
'disease',
'crossReference',
'compartment',
'componentOf',
'memberOf',
'candidateOf',
'isRequired',
'inferredFrom',
'inferredTo',
'consumedByEvent',
'producedByEvent',
'catalystActivities',
'literatureReference',
'positivelyRegulates',
'negativelyRegulates',
'markingReferences',
];
for (const entity of entities) {
for (const name of order) {
if (plain.includes(name)) {
const value = entity.fields[name];
if (!value?.length) continue;
to[name] = union(to[name], value);
} else if (name in stoichiometric) {
const value = expand(entity.fields[name]);
if (!value?.length) continue;
to[name] = aggregate(union(expand(to[name]), value), stoichiometric[name]);
} else {
// inputFor / outputFor: the relations themselves, one per element.
const value = entity.fields[name];
if (!value?.length) continue;
to[name] = unionRelations(to[name], value);
}
}
if (isA(entity.schemaClass, 'GenomeEncodedEntity') || isA(entity.schemaClass, 'SimpleEntity')) {
if (entity.fields.species !== undefined && entity.fields.species !== null)
to.species = entity.fields.species;
}
if (
isA(entity.schemaClass, 'SimpleEntity') ||
isA(entity.schemaClass, 'EntityWithAccessionedSequence')
) {
if (entity.fields.referenceType !== undefined && entity.fields.referenceType !== null)
to.referenceType = entity.fields.referenceType;
}
}
return summary;
}

/**
* Nodes on their own, as SDN maps a query that returns only nodes: each with
* its properties and no relationships, one instance per node, so a node that
Expand Down
Loading
Loading