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
10 changes: 10 additions & 0 deletions deploy/nginx/common/routes-core.conf
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,16 @@ location ~ ^/ContentService/data/(entity|event)/[^/]+/in-depth$ {
proxy_pass http://content_node;
}

# An event's or entity's counterpart in another species, one id by GET or up
# to twenty by POST (graph-core's OrthologyService), written by the domain
# serialiser; the POST's map in the order Java's HashMap writes it. The
# Pathway Browser asks both when the species is changed. Byte-identical to
# Java, errors included -- bar '.', which is not found here and Java's 500.
location ~ ^/ContentService/data/(orthology/[^/]+/species|orthologies/ids/species)/[^/]+$ {
include /etc/nginx/common/upstream-proxy.conf;
proxy_pass http://content_node;
}

# A person, by dbId or ORCID, and the publications they authored (by dbId,
# ORCID or e-mail address), written by the domain serialiser in each of Java's
# views. Java matches the person by a property no index serves -- 1.1s by dbId;
Expand Down
16 changes: 15 additions & 1 deletion projects/website-angular/src/scripts/proxy-order.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,17 @@ describe('the dev server proxy', () => {
});
}

for (const url of [
'/ContentService/data/orthology/R-HSA-69620/species/48892',
'/ContentService/data/orthology/69620/species/0xBEFC?view=nested',
// The POST the species selector sends.
'/ContentService/data/orthologies/ids/species/48892',
]) {
it(`sends ${url} to the content service`, async () => {
expect(await targetOf(url)).toBe(contentNode);
});
}

for (const other of [
// Other attributes of the same Java endpoint are not ported.
'/ContentService/data/query/70486/displayName',
Expand All @@ -193,9 +204,12 @@ describe('the dev server proxy', () => {
'/ContentService/data/query/70486/NORMALPATHWAY',
// Spring also matches a trailing slash; only the plain object is routed.
'/ContentService/data/query/70486/',
// Java's POST /query/ids (a batch of objects); node serves no POST.
// Java's POST /query/ids (a batch of objects), which node does not serve.
'/ContentService/data/query/ids',
'/ContentService/data/query/abridged/70486',
// Spring's own 404s: no species, or more after it.
'/ContentService/data/orthology/R-HSA-69620/species/',
'/ContentService/data/orthologies/ids/species/48892/x',
'/ContentService/data/person/68285/authoredPathwaysAndMore',
// Only the ancestors of an event are ported.
'/ContentService/data/event/R-HSA-5672965/ancestorsAndMore',
Expand Down
4 changes: 4 additions & 0 deletions proxy.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ module.exports = {
'^/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,
// An event's or entity's counterpart in another species: one id (GET), or
// up to twenty (POST), for the species selector.
'^/ContentService/data/(orthology/[^/?]+/species|orthologies/ids/species)/[^/?]+(\\?|$)':
contentNode,
// A person, and the publications they authored. Not the other attributes
// under /person/{id}/, which Java serves.
'^/ContentService/data/person/[^/?]+(/publications)?(\\?|$)': contentNode,
Expand Down
21 changes: 18 additions & 3 deletions tools/content-node/diff.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,19 @@ function flag(name, fallback = '') {

/** Fetch one path from both, as text plus content type. */
async function fetchBoth(path) {
// A POST endpoint is sent its sample body, or the --ids given, as text.
const endpoint = endpointFor(path);
const post = endpoint?.method === 'post';
const ids = flag('ids');
const init = post
? {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: ids || endpoint.sampleBody,
}
: {};
const get = async (base) => {
const response = await fetch(base + path, { redirect: 'follow' });
const response = await fetch(base + path, { redirect: 'follow', ...init });
return {
status: response.status,
type: (response.headers.get('content-type') || '').split(';')[0].trim(),
Expand Down Expand Up @@ -196,14 +207,18 @@ function paths() {

const skipped = [];
const expanded = chosen.flatMap((endpoint) => {
if (!endpoint.path.includes('{id}')) return [endpoint.path];
// Path variables other than {id} take the endpoint's own sample values.
let template = endpoint.path;
for (const [name, value] of Object.entries(endpoint.samplePath ?? {}))
template = template.replace(`{${name}}`, value);
if (!template.includes('{id}')) return [template];
// An endpoint that takes an id is expanded over the ids given, or over its
// own sample when none are. Without the sample it expanded to *nothing* and
// was silently absent from every run: `/data/pathways/top/{id}` was merged
// for weeks, never once compared, and carried three real differences.
const use = ids.length ? ids : endpoint.sample ? [endpoint.sample] : [];
if (!use.length) skipped.push(endpoint.path);
return use.map((id) => endpoint.path.replace('{id}', id));
return use.map((id) => template.replace('{id}', id));
});

// Said out loud. A run that tests fewer endpoints than the table holds is the
Expand Down
21 changes: 21 additions & 0 deletions tools/content-node/domain.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@ class Relation {
}
}

/**
* A Java Map written as a JSON object, its entries in the order given -- the
* Map's own iteration order, which the caller decides (see JavaHashMap in
* service.mjs). Its values are written in one pass with everything else, so
* `@id` numbering runs on across them as Jackson's does.
*/
export class JsonMap {
constructor(entries) {
this.entries = entries;
}
}

/** Has.Util.wrapUniqueElements: a plain relationship as Has.Default, for composedOf. */
const wrap = (entities, type) =>
(entities ?? []).map((entity) => new Relation('Default', entity, {}, type));
Expand Down Expand Up @@ -429,6 +441,15 @@ export function writeDomain(value, view = VIEW.flatten, { jsog = false } = {}) {
open(out, work, current, view, jsog ? `"@id":"${seen.size}"` : undefined);
} else if (current instanceof Relation) {
open(out, work, current, view);
} else if (current instanceof JsonMap) {
out.push('{');
work.push('}');
for (let at = current.entries.length - 1; at >= 0; at -= 1) {
const [key, entry] = current.entries[at];
work.push({ value: entry, floating });
work.push(`${javaString(key)}:`);
if (at > 0) work.push(',');
}
} else if (Array.isArray(current)) {
out.push('[');
work.push(']');
Expand Down
Loading
Loading