|
@@ -11,6 +11,7 @@ const SPARQL_ENDPOINT = "/SystemDesignOntology2Layers/"
|
|
|
const dropVocabularyPrefix = str => str.substring(41);
|
|
|
const dropDescriptionPrefix = str => str.substring(30);
|
|
|
const dropArtifactPrefix = str => str.substring(41);
|
|
|
+const addFormalismsPrefix = str => "http://ua.be/sdo2l/vocabulary/formalisms/" + str;
|
|
|
const addArtifactPrefix = str => "http://ua.be/sdo2l/description/artifacts/" + str;
|
|
|
|
|
|
const QUERIES = {
|
|
@@ -100,6 +101,43 @@ WHERE {
|
|
|
?ftgFormalism base:hasGUID ?formalismName .
|
|
|
}`,
|
|
|
|
|
|
+ getModels: `\
|
|
|
+PREFIX object_diagram: <http://ua.be/sdo2l/vocabulary/formalisms/object_diagram#>
|
|
|
+PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
|
+PREFIX cs_as: <http://ua.be/sdo2l/vocabulary/formalisms/cs_as#>
|
|
|
+
|
|
|
+SELECT DISTINCT ?model ?type
|
|
|
+WHERE {
|
|
|
+ ?model a object_diagram:Model .
|
|
|
+ ?model a ?type .
|
|
|
+ ?type rdfs:subClassOf object_diagram:Model .
|
|
|
+ FILTER(NOT EXISTS {
|
|
|
+ ?model a cs_as:CorrespondenceModel .
|
|
|
+ })
|
|
|
+ FILTER(NOT EXISTS {
|
|
|
+ ?more_concrete_type rdfs:subClassOf ?type .
|
|
|
+ ?model a ?more_concrete_type .
|
|
|
+ FILTER(?more_concrete_type != ?type)
|
|
|
+ })
|
|
|
+}
|
|
|
+`,
|
|
|
+
|
|
|
+ getProperty: (iri, propertyIri) => `\
|
|
|
+SELECT DISTINCT ?value
|
|
|
+WHERE {
|
|
|
+ <${iri}> <${propertyIri}> ?value .
|
|
|
+}
|
|
|
+`,
|
|
|
+
|
|
|
+ insertTraceLink: (fromIri, toIri) => `\
|
|
|
+PREFIX trace: <http://ua.be/sdo2l/vocabulary/formalisms/traceability_model#>
|
|
|
+INSERT DATA {
|
|
|
+ GRAPH <http://ua.be/sdo2l/description/traces> {
|
|
|
+ <${fromIri}> trace:traceLinkTo <${toIri}> .
|
|
|
+ <${fromIri}> trace:traceLinkFrom <${toIri}> .
|
|
|
+ }
|
|
|
+}
|
|
|
+`,
|
|
|
};
|
|
|
|
|
|
|
|
@@ -109,81 +147,123 @@ GRAPHS_NOT_TO_DELETE = [
|
|
|
"http://ua.be/sdo2l/description/traces",
|
|
|
];
|
|
|
|
|
|
+const typeToDescription = new Map([
|
|
|
+ ["ftg#Model", async (element, type, getProperty) => getProperty("object_diagram#hasName").then(modelName => `ftg "${modelName}"`)],
|
|
|
+ ["xopp#Text", async (element, type, getProperty) => getProperty("xopp#hasText").then(text => `text "${text}"`)],
|
|
|
+ ["xopp#Model", async (element, type, getProperty) => getProperty("object_diagram#hasName").then(modelName => `xournal++ model "${modelName}"`)],
|
|
|
+ ["drawio#Cell", async (element, type, getProperty) => getProperty("drawio#hasDrawioId").then(id => `cell "${id}"`)],
|
|
|
+ ["drawio#Vertex", async (element, type, getProperty) => getProperty("drawio#hasDrawioId").then(id => `vertex "${id}"`)],
|
|
|
+ ["drawio#Edge", async (element, type, getProperty) => getProperty("drawio#hasDrawioId").then(id => `edge "${id}"`)],
|
|
|
+ ["drawio#Model", async (element, type, getProperty) => getProperty("object_diagram#hasName").then(modelName => `drawio model "${modelName}"`)],
|
|
|
+ ["pm#Model", async (element, type, getProperty) => getProperty("object_diagram#hasName").then(modelName => `process model "${modelName}"`)],
|
|
|
+ ["pm#Activity", async (element, type, getProperty) => element],
|
|
|
+ ["pm#Artifact", async (element, type, getProperty) => element],
|
|
|
+ ["pm#CtrlInputPort", async (element, type, getProperty) => getProperty("pm#hasName").then(portname => `ctrl inport "${portname}"`)],
|
|
|
+ ["pm#CtrlOutputPort", async (element, type, getProperty) => getProperty("pm#hasName").then(portname => `ctrl outport "${portname}"`)],
|
|
|
+ ["pm#DataInputPort", async (element, type, getProperty) => getProperty("pm#hasName").then(portname => `data inport "${portname}"`)],
|
|
|
+ ["pm#DataOutputPort", async (element, type, getProperty) => getProperty("pm#hasName").then(portname => `data outport "${portname}"`)],
|
|
|
+])
|
|
|
+
|
|
|
// (Hardcoded) link types exposed to the user.
|
|
|
const typeToLinkType = new Map([
|
|
|
["object_diagram#Object", [
|
|
|
{
|
|
|
relation: "cs_as#parsedAs",
|
|
|
- description: (element, type) => `Is parsed as ${type}`,
|
|
|
+ description: `Is parsed as`,
|
|
|
},
|
|
|
{
|
|
|
relation: "cs_as#renderedAs",
|
|
|
- description: (element, type) => `Is rendered as ${type}`,
|
|
|
+ description: `Is rendered as`,
|
|
|
},
|
|
|
// Outcommented, because it's not an interesting relation:
|
|
|
{
|
|
|
relation: "object_diagram#inModel",
|
|
|
- description: (element, type) => `Is part of model ${type}`,
|
|
|
+ description: `Is part of`,
|
|
|
},
|
|
|
{
|
|
|
relation: "traceability_model#traceLinkTo",
|
|
|
- description: (element, type) => `Trace-link (outgoing) ${element}`,
|
|
|
+ description: `Trace-link (outgoing)`,
|
|
|
},
|
|
|
{
|
|
|
relation: "traceability_model#traceLinkFrom",
|
|
|
- description: (element, type) => `Trace-link (incoming) ${element}`,
|
|
|
+ description: `Trace-link (incoming)`,
|
|
|
},
|
|
|
]],
|
|
|
["drawio#Model", [
|
|
|
{
|
|
|
relation: "drawio#hasRootCell",
|
|
|
- description: (element, type) => `Has root ${type}`,
|
|
|
+ description: `Has root`,
|
|
|
},
|
|
|
]],
|
|
|
+ // Outcommented, blows up:
|
|
|
+ // ["drawio#Cell", [
|
|
|
+ // {
|
|
|
+ // relation: "drawio#hasChild",
|
|
|
+ // description: `Has child`,
|
|
|
+ // },
|
|
|
+ // ]],
|
|
|
["pm#Activity", [
|
|
|
{
|
|
|
relation: "pm#isTransformation",
|
|
|
- description: (element, type) => `Is typed by ${type}`,
|
|
|
+ description: `Is typed by`,
|
|
|
},
|
|
|
{
|
|
|
relation: "pm#hasPort",
|
|
|
- description: (element, type) => `Has port ${type}`,
|
|
|
+ description: `Has`,
|
|
|
},
|
|
|
]],
|
|
|
["pm#element", [
|
|
|
{
|
|
|
relation: "processtraces#relatesTo",
|
|
|
- description: (element, type) => `Enacted by ${element}`,
|
|
|
+ description: `Enacted by`,
|
|
|
reverse: true,
|
|
|
},
|
|
|
]],
|
|
|
["processtraces#element", [
|
|
|
{
|
|
|
relation: "processtraces#relatesTo",
|
|
|
- description: (element, type) => `Enactment of ${element}`,
|
|
|
+ description: `Enactment of`,
|
|
|
},
|
|
|
// pt-element is not a sub-type of 'Object', so we must repeat this:
|
|
|
{
|
|
|
relation: "cs_as#renderedAs",
|
|
|
- description: (element, type) => `Is rendered as ${type}`,
|
|
|
+ description: `Is rendered as`,
|
|
|
},
|
|
|
]],
|
|
|
["pm#Artifact", [
|
|
|
{
|
|
|
relation: "pm#hasType",
|
|
|
- description: (element, type) => `Is typed by ${type}`,
|
|
|
+ description: `Is typed by`,
|
|
|
},
|
|
|
]],
|
|
|
["ftg#Transformation", [
|
|
|
{
|
|
|
relation: "pm#occursAsActivity",
|
|
|
- description: (element, type) => `Occurs as activity ${element}`,
|
|
|
+ description: `Occurs as`,
|
|
|
}
|
|
|
]],
|
|
|
["ftg#Formalism", [
|
|
|
{
|
|
|
relation: "pm#occursAsArtifact",
|
|
|
- description: (element, type) => `Occurs as artifact ${element}`,
|
|
|
+ description: `Occurs as`,
|
|
|
+ },
|
|
|
+ ]],
|
|
|
+ ["xopp#Model", [
|
|
|
+ {
|
|
|
+ relation: "xopp#hasPage",
|
|
|
+ description: `Has page`,
|
|
|
+ },
|
|
|
+ ]],
|
|
|
+ ["xopp#Page", [
|
|
|
+ {
|
|
|
+ relation: "xopp#hasLayer",
|
|
|
+ description: `Has layer`,
|
|
|
+ },
|
|
|
+ ]],
|
|
|
+ ["xopp#Layer", [
|
|
|
+ {
|
|
|
+ relation: "xopp#hasElement",
|
|
|
+ description: `Has element`,
|
|
|
},
|
|
|
]],
|
|
|
]);
|
|
@@ -199,7 +279,7 @@ function getQueries(type) {
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
-function executeSPARQL(query) {
|
|
|
+function querySPARQL(query) {
|
|
|
const body = new URLSearchParams();
|
|
|
body.append("query", query);
|
|
|
return fetch(SPARQL_SERVER+SPARQL_ENDPOINT, {
|
|
@@ -209,13 +289,25 @@ function executeSPARQL(query) {
|
|
|
})
|
|
|
.then(res => res.json())
|
|
|
.then(json => {
|
|
|
- // console.log({query, result: json.results.bindings});
|
|
|
- console.log("Query:\n"+query);
|
|
|
- console.log("Result:\n",json.results.bindings);
|
|
|
+ console.log("Query:\n"+query+"\Result:",json.results.bindings);
|
|
|
return json.results.bindings;
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+function updateSPARQL(update) {
|
|
|
+ const body = new URLSearchParams();
|
|
|
+ body.append("update", update);
|
|
|
+ return fetch(SPARQL_SERVER+SPARQL_ENDPOINT, {
|
|
|
+ headers: new Headers({"Content-Type": "application/x-www-form-urlencoded"}),
|
|
|
+ method: "POST",
|
|
|
+ body,
|
|
|
+ })
|
|
|
+ .then(res => {
|
|
|
+ console.log("Update:\n"+update+"\Result:",res);
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+
|
|
|
const defaultSettings = {
|
|
|
dialogPosX: 100,
|
|
|
dialogPosY: 100,
|
|
@@ -239,7 +331,7 @@ fetch(BACKEND+"/version")
|
|
|
|
|
|
// Loads all 'rdfs:subClassOf' relations and stores them in a mapping.
|
|
|
function loadSuperclasses() {
|
|
|
- return executeSPARQL(QUERIES.getSubClassRelations)
|
|
|
+ return querySPARQL(QUERIES.getSubClassRelations)
|
|
|
.then(results => {
|
|
|
for (const {subclass, superclass} of results) {
|
|
|
const superclasslist = superclasses.get(subclass.value) || (() => {
|
|
@@ -257,47 +349,61 @@ fetch(BACKEND+"/version")
|
|
|
// Override context menu when right-clicking somewhere in the diagram:
|
|
|
const oldFactoryMethod = ui.editor.graph.popupMenuHandler.factoryMethod;
|
|
|
ui.editor.graph.popupMenuHandler.factoryMethod = function(menu, cell, evt) {
|
|
|
- const modelverseMenu = menu.addItem("ModelVerse", null, null);
|
|
|
+ const modelverseMenu = menu.addItem("Knowledge Graph...", null, null);
|
|
|
+ const createLinkMenu = menu.addItem("Create traceability link to...", null, null);
|
|
|
menu.addSeparator();
|
|
|
oldFactoryMethod.apply(this, arguments);
|
|
|
|
|
|
- const alreadyVisited = new Set();
|
|
|
const entry = (element, type) => element+':'+type;
|
|
|
|
|
|
- function addMenuItem(element, type, maxRecursion, parentMenuItem, description) {
|
|
|
+ function addMenuItem(element, type, maxRecursion, parentMenuItem, description, alreadyVisited, onClick) {
|
|
|
if (alreadyVisited.has(entry(element, type))) {
|
|
|
return; // don't go in circles
|
|
|
}
|
|
|
const shortType = dropVocabularyPrefix(type);
|
|
|
- const menuItemText = description(dropArtifactPrefix(element), shortType);
|
|
|
- let createdMenuItem;
|
|
|
- // If the menu item is a (subtype of a) drawio cell, then clicking on it will take us to that cell in drawio.
|
|
|
- if ((superclasses.get(type) || []).some(t => t.endsWith("drawio#Cell"))) {
|
|
|
- createdMenuItem = menu.addItem(menuItemText, null, () => goto(element), parentMenuItem);
|
|
|
- } else {
|
|
|
- createdMenuItem = menu.addItem(menuItemText, null, null, parentMenuItem);
|
|
|
- }
|
|
|
- if ((superclasses.get(type) || []).some(t => t.endsWith("processtraces#Artifact"))) {
|
|
|
- executeSPARQL(QUERIES.getArtifactFilename(element))
|
|
|
+ const itemDescriptionGen = typeToDescription.get(shortType) || (async () => shortType);
|
|
|
+ itemDescriptionGen(dropArtifactPrefix(element), shortType, property => {
|
|
|
+ return querySPARQL(QUERIES.getProperty(element, addFormalismsPrefix(property)))
|
|
|
.then(results => {
|
|
|
- for (const {filename, formalismName} of results) {
|
|
|
- menu.addItem(`File "${filename.value}"`, null, () => (createDownloadHandler(formalismName.value))(filename.value), createdMenuItem);
|
|
|
- }
|
|
|
- })
|
|
|
- }
|
|
|
- alreadyVisited.add(entry(element, type));
|
|
|
- if (maxRecursion <= 0) {
|
|
|
- return;
|
|
|
- }
|
|
|
- addSubmenuItems(element, type, createdMenuItem, maxRecursion);
|
|
|
+ if (results.length > 0) return results[0].value.value;
|
|
|
+ else return "";
|
|
|
+ });
|
|
|
+ })
|
|
|
+ .then(itemDescription => {
|
|
|
+ const menuItemText = description + ' ' + itemDescription;
|
|
|
+ let createdMenuItem;
|
|
|
+ // If the menu item is a (subtype of a) drawio cell, then clicking on it will take us to that cell in drawio.
|
|
|
+ if (onClick) {
|
|
|
+ createdMenuItem = menu.addItem(menuItemText, null, () => onClick(element), parentMenuItem);
|
|
|
+ }
|
|
|
+ else if ((superclasses.get(type) || []).some(t => t.endsWith("drawio#Cell"))) {
|
|
|
+ createdMenuItem = menu.addItem(menuItemText, null, () => goto(element), parentMenuItem);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ createdMenuItem = menu.addItem(menuItemText, null, null, parentMenuItem);
|
|
|
+ }
|
|
|
+ if ((superclasses.get(type) || []).some(t => t.endsWith("processtraces#Artifact"))) {
|
|
|
+ querySPARQL(QUERIES.getArtifactFilename(element))
|
|
|
+ .then(results => {
|
|
|
+ for (const {filename, formalismName} of results) {
|
|
|
+ menu.addItem(`File "${filename.value}"`, null, () => (createDownloadHandler(formalismName.value))(filename.value), createdMenuItem);
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ alreadyVisited.add(entry(element, type));
|
|
|
+ if (maxRecursion <= 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ addSubmenuItems(element, type, createdMenuItem, maxRecursion, alreadyVisited, onClick);
|
|
|
+ })
|
|
|
}
|
|
|
- function addSubmenuItems(element, type, parentMenuItem, maxRecursion) {
|
|
|
+ function addSubmenuItems(element, type, parentMenuItem, maxRecursion, alreadyVisited, onClick) {
|
|
|
const queries = getQueries(type);
|
|
|
for (const {relation, description, reverse} of queries) {
|
|
|
- executeSPARQL(QUERIES.getOutgoingLink(element, "http://ua.be/sdo2l/vocabulary/formalisms/"+relation, reverse))
|
|
|
+ querySPARQL(QUERIES.getOutgoingLink(element, "http://ua.be/sdo2l/vocabulary/formalisms/"+relation, reverse))
|
|
|
.then(results => {
|
|
|
for (const {element, type} of results) {
|
|
|
- addMenuItem(element.value, type.value, maxRecursion-1, parentMenuItem, description);
|
|
|
+ addMenuItem(element.value, type.value, maxRecursion-1, parentMenuItem, description, alreadyVisited, onClick);
|
|
|
}
|
|
|
})
|
|
|
}
|
|
@@ -305,11 +411,21 @@ fetch(BACKEND+"/version")
|
|
|
|
|
|
// Populate ModelVerse submenu asynchronously:
|
|
|
if (cell && cell.vertex) {
|
|
|
- executeSPARQL(QUERIES.getCellStuff(cell.id))
|
|
|
+ querySPARQL(QUERIES.getCellStuff(cell.id))
|
|
|
.then(results => {
|
|
|
for (const {element, type} of results) {
|
|
|
- alreadyVisited.add(entry(element.value, type.value));
|
|
|
- addSubmenuItems(element.value, type.value, modelverseMenu, 4);
|
|
|
+ const alreadyVisited = new Set([[element.value, type.value]]);
|
|
|
+ addSubmenuItems(element.value, type.value, modelverseMenu, 3, alreadyVisited);
|
|
|
+
|
|
|
+ querySPARQL(QUERIES.getModels)
|
|
|
+ .then(results => {
|
|
|
+ const alreadyVisited2 = new Set([[element.value, type.value]]);
|
|
|
+ for (const {model, type} of results) {
|
|
|
+ addMenuItem(model.value, type.value, 3, createLinkMenu, "Model",
|
|
|
+ alreadyVisited2,
|
|
|
+ targetIri => updateSPARQL(QUERIES.insertTraceLink(element.value, targetIri)));
|
|
|
+ }
|
|
|
+ });
|
|
|
}
|
|
|
})
|
|
|
}
|
|
@@ -361,7 +477,7 @@ fetch(BACKEND+"/version")
|
|
|
}
|
|
|
|
|
|
function rebuildOMLForced() {
|
|
|
- return executeSPARQL(QUERIES.getAllGraphs)
|
|
|
+ return querySPARQL(QUERIES.getAllGraphs)
|
|
|
.then(results => {
|
|
|
const graphsToDelete = results
|
|
|
.map(({g,count}) => g.value)
|
|
@@ -438,7 +554,7 @@ fetch(BACKEND+"/version")
|
|
|
|
|
|
// Given a cell IRI, open the diagram that contains the cell
|
|
|
function goto(cellIri) {
|
|
|
- executeSPARQL(QUERIES.getDiagramAndCellId(cellIri))
|
|
|
+ querySPARQL(QUERIES.getDiagramAndCellId(cellIri))
|
|
|
.then(([{diagramName, cellId}]) => {
|
|
|
// drop the "_drawio" at the end:
|
|
|
const actualDiagramName = diagramName.value.substring(0, diagramName.value.length-7);
|