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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"@babel/preset-react": "7.16.7",
"@babel/register": "^7.16.0",
"@babel/runtime-corejs3": "7.16.8",
"@exabyte-io/esse.js": "2022.10.10-0",
"@exabyte-io/esse.js": "2022.10.21-0",
"crypto-js": "^4.1.1",
"json-schema-merge-allof": "^0.8.1",
"lodash": "^4.17.21",
Expand Down
55 changes: 47 additions & 8 deletions src/JSONSchemasInterface.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,43 @@ import { schemas } from "@exabyte-io/esse.js/schemas";
import mergeAllOf from "json-schema-merge-allof";

const schemasCache = new Map();

/**
* We assume that each schema in the application has its own unique schemaId
* Unfortunately, mergeAllOf keeps schemaId after merging, and this results in multiple different schemas with the same schemaId
* Hence this function
*/
function removeSchemaIdsAfterAllOf(schema, clean = false) {
if (clean) {
const { schemaId, ...restSchema } = schema;

return restSchema;
}

if (Array.isArray(schema)) {
return schema.map((item) => removeSchemaIdsAfterAllOf(item));
}

if (typeof schema !== "object") {
return schema;
}

if (schema.allOf) {
const { allOf, ...restSchema } = schema;

return {
allOf: allOf.map((innerSchema) => removeSchemaIdsAfterAllOf(innerSchema, true)),
...restSchema,
};
}

return Object.fromEntries(
Object.entries(schema).map(([key, value]) => {
return [key, removeSchemaIdsAfterAllOf(value)];
}),
);
}

export class JSONSchemasInterface {
/**
*
Expand All @@ -16,13 +53,7 @@ export class JSONSchemasInterface {
throw new Error(`Schema not found: ${schemaId}`);
}

const schema = mergeAllOf(originalSchema, {
resolvers: {
defaultResolver: mergeAllOf.options.resolvers.title,
},
});

schemasCache.set(schemaId, schema);
this.registerSchema(originalSchema);
}

return schemasCache.get(schemaId);
Expand All @@ -32,8 +63,16 @@ export class JSONSchemasInterface {
*
* @param {Object} - external schema
*/
static registerSchema(schema) {
static registerSchema(originalSchema) {
const schema = mergeAllOf(removeSchemaIdsAfterAllOf(originalSchema), {
resolvers: {
defaultResolver: mergeAllOf.options.resolvers.title,
},
});

schemasCache.set(schema.schemaId, schema);

return schema;
}

/**
Expand Down
47 changes: 14 additions & 33 deletions src/entity/in_memory.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import mergeAllOf from "json-schema-merge-allof";
import lodash from "lodash";

// import { ESSE } from "@exabyte-io/esse.js";
import { deepClone } from "../utils/clone";
import { getMixSchemasByClassName, getSchemaByClassName } from "../utils/schemas";
import { getSchemaByClassName } from "../utils/schemas";

// TODO: https://exabyte.atlassian.net/browse/SOF-5946
// const schemas = new ESSE().schemas;
Expand Down Expand Up @@ -195,42 +194,24 @@ export class InMemoryEntity {
}

/**
* Returns original ESSE schema with nested properties from customJsonSchemaProperties
* @see customJsonSchemaProperties
* @returns {Object} schema
*/
static get baseJSONSchema() {
if (!this.customJsonSchemaProperties) {
return getSchemaByClassName(this.name);
}

const { properties, ...schema } = getSchemaByClassName(this.name);

return {
...schema,
properties: {
...properties,
...this.customJsonSchemaProperties,
},
};
}

/**
* Returns resolved JSON schema with custom properties and all mixes from schemas.js
* Returns class JSON schema
* @returns {Object} schema
*/
static get jsonSchema() {
try {
return mergeAllOf(
{
allOf: [this.baseJSONSchema, ...getMixSchemasByClassName(this.name)],
},
{
resolvers: {
defaultResolver: mergeAllOf.options.resolvers.title,
},
if (!this.customJsonSchemaProperties) {
return getSchemaByClassName(this.name);
}

const { properties, ...schema } = getSchemaByClassName(this.name);

return {
...schema,
properties: {
...properties,
...this.customJsonSchemaProperties,
},
);
};
} catch (e) {
console.error(e.stack);
throw e;
Expand Down
103 changes: 10 additions & 93 deletions src/utils/schemas.js
Original file line number Diff line number Diff line change
@@ -1,104 +1,21 @@
import { JSONSchemasInterface } from "../JSONSchemasInterface";

export const baseSchemas = {
Material: "material",
Entity: "system/entity",
BankMaterial: "material",
Workflow: "workflow",
Subworkflow: "workflow/subworkflow",
BankWorkflow: "workflow",
Job: "job",
Application: "software/application",
Executable: "software/executable",
Flavor: "software/flavor",
Template: "software/template",
AssertionUnit: "workflow/unit/assertion",
AssignmentUnit: "workflow/unit/assignment",
ConditionUnit: "workflow/unit/condition",
ExecutionUnit: "workflow/unit/execution",
IOUnit: "workflow/unit/io",
MapUnit: "workflow/unit/map",
ProcessingUnit: "workflow/unit/processing",
ReduceUnit: "workflow/unit/reduce",
SubworkflowUnit: "workflow/unit",
Unit: "workflow/unit",
Project: "project",
};

export const entityMix = [
"system/description-object",
"system/base-entity-set",
"system/sharing",
"system/metadata",
"system/defaultable",
];

export const subWorkflowMix = ["system/system-name", "system/is-multi-material"];

export const workflowMix = ["workflow/base-flow", "system/history", "system/is-outdated"];

export const bankMaterialMix = ["material/conventional", "system/creator-account"];

export const bankWorkflowMix = ["system/creator-account"];

export const jobMix = ["system/status", "system/job-extended"];

export const unitMix = [
"system/unit-extended",
"system/status",
"workflow/unit/runtime/runtime-items",
];

export const assignmentUnitMix = ["system/scope"];

export const flavorMix = ["system/is-multi-material"];

export const systemEntityMix = ["system/entity"];

export const projectMix = ["system/status"];

export const mixSchemas = {
Entity: [...entityMix],
Material: [...entityMix],
BankMaterial: [...entityMix, ...bankMaterialMix],
Workflow: [...entityMix, ...subWorkflowMix, ...workflowMix],
Subworkflow: [...subWorkflowMix],
BankWorkflow: [...entityMix, ...subWorkflowMix, ...workflowMix, ...bankWorkflowMix],
Job: [...entityMix, ...jobMix],
Application: [...entityMix, ...systemEntityMix],
Executable: [...entityMix, ...systemEntityMix],
Flavor: [...entityMix, ...flavorMix, ...systemEntityMix],
Template: [...entityMix, ...systemEntityMix],
AssertionUnit: [...unitMix],
AssignmentUnit: [...unitMix, ...assignmentUnitMix],
ConditionUnit: [...unitMix],
ExecutionUnit: [...unitMix],
IOUnit: [...unitMix],
MapUnit: [...unitMix],
ProcessingUnit: [...unitMix],
ReduceUnit: [...unitMix],
SubworkflowUnit: [...unitMix],
Unit: [...unitMix],
Project: [...entityMix, ...systemEntityMix, ...projectMix],
};
export const schemas = {};

/**
* Returns previously registered schema for InMemoryEntity
* @param {*} className
* @returns
*/
export function getSchemaByClassName(className) {
return baseSchemas[className] ? JSONSchemasInterface.schemaById(baseSchemas[className]) : null;
}

export function getMixSchemasByClassName(className) {
return mixSchemas[className]
? mixSchemas[className].map((schemaId) => JSONSchemasInterface.schemaById(schemaId))
: [];
return schemas[className] ? JSONSchemasInterface.schemaById(schemas[className]) : null;
}

/**
* Register additional Entity classes to be resolved with jsonSchema property
* @param {String} className - class name derived from InMemoryEntity
* @param {String} classBaseSchema - base schemaId
* @param {Array} classMixSchemas - array of schemaId to mix
* @param {String} schemaId - class schemaId
*/
export function registerClassName(className, classBaseSchema, classMixSchemas) {
baseSchemas[className] = classBaseSchema;
mixSchemas[className] = classMixSchemas;
export function registerClassName(className, schemaId) {
schemas[className] = schemaId;
}
88 changes: 66 additions & 22 deletions tests/JSONSchemasInterface.tests.js
Original file line number Diff line number Diff line change
@@ -1,47 +1,91 @@
import { expect } from "chai";
import { assert, expect } from "chai";

import { JSONSchemasInterface } from "../src/JSONSchemasInterface";
import { baseSchemas, mixSchemas } from "../src/utils/schemas";

describe("JSONSchemasInterface", () => {
it("can find main schema", () => {
Object.values(baseSchemas).forEach((schemaId) => {
const schema = JSONSchemasInterface.schemaById(schemaId);
expect(schema).to.be.an("object");
});
});

it("can find mix schemas", () => {
Object.values(mixSchemas).forEach((schemaIds) => {
schemaIds.forEach((schemaId) => {
const schema = JSONSchemasInterface.schemaById(schemaId);
expect(schema).to.be.an("object");
});
});
it("can find schema", () => {
const schema = JSONSchemasInterface.schemaById("workflow");
expect(schema).to.be.an("object");
});

it("can match schemas", () => {
const schemaId = Object.values(baseSchemas)[0];
const schema = JSONSchemasInterface.matchSchema({
schemaId: {
$regex: schemaId,
$regex: "workflow",
},
});

expect(schema).to.be.an("object");
});

it("can find registered schemas", () => {
it("can find registered schemas; the schema is merged and clean", () => {
JSONSchemasInterface.registerSchema({
schemaId: "test-schema-id",
schemaId: "system/in-set",
$schema: "http://json-schema.org/draft-04/schema#",
title: "System in-set schema",
properties: {
testProp: {
inSet: {
type: "array",
items: {
allOf: [
{
schemaId: "system/entity-reference",
$schema: "http://json-schema.org/draft-04/schema#",
title: "entity reference schema",
properties: {
_id: {
description: "entity identity",
type: "string",
},
cls: {
description: "entity class",
type: "string",
},
slug: {
description: "entity slug",
type: "string",
},
},
},
{
type: "object",
properties: {
type: {
type: "string",
},
index: {
type: "number",
},
},
},
],
},
},
valueMapFunction: {
description: "Specifies the function to convert the currentValue in UI.",
type: "string",
enum: [
"toString",
"toContactUs",
"toPlusMinusSign",
"toUnlimited",
"toSupportSeverity",
],
default: "toString",
},
},
});

const schema = JSONSchemasInterface.schemaById("test-schema-id");
const schema = JSONSchemasInterface.schemaById("system/in-set");

expect(schema).to.be.an("object");
assert(schema.schemaId, "system/in-set");
expect(schema.properties.inSet.items.schemaId).to.be.an("undefined");
expect(schema.properties.inSet.items.properties).to.be.an("object");
expect(schema.properties.valueMapFunction.enum[0]).to.be.an("string");
expect(schema.properties.valueMapFunction.enum[1]).to.be.an("string");
expect(schema.properties.valueMapFunction.enum[2]).to.be.an("string");
expect(schema.properties.valueMapFunction.enum[3]).to.be.an("string");
expect(schema.properties.valueMapFunction.enum[4]).to.be.an("string");
});
});
Loading