Skip to content

Commit 23fa445

Browse files
authored
Merge pull request #78 from workflowhub-eu/fix-vis
Correct run script so blueprint config can be generated
2 parents c6ff9cb + 7087862 commit 23fa445

19 files changed

Lines changed: 930 additions & 11 deletions
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import oxigraph from 'oxigraph';
2+
import * as queries from './queries.js';
3+
import { prettify } from './prettyprint.js';
4+
import { runGraphQuery, parametrizeQuery, writeDataToFile, readClassesFromFile, loadTurtleFileIntoStore } from './glue.js';
5+
import chalk from 'chalk';
6+
import createDebug from 'debug';
7+
const debug_sparql = createDebug('sparql');
8+
9+
async function fetchClasses(options) {
10+
console.log(`output filename: ${options.output}`);
11+
12+
const data = await runGraphQuery(queries.constructCandidateClasses, 'constructCandidateClasses');
13+
14+
if (data) {
15+
writeDataToFile(options.output, data);
16+
}
17+
}
18+
19+
async function fetchLinks(options) {
20+
console.log(`classes filename: ${options.classes}`);
21+
console.log(`output filename: ${options.output}`);
22+
23+
const classes = await readClassesFromFile(options.classes);
24+
25+
if (classes === undefined) {
26+
return console.error(chalk.red(`Aborting`));
27+
} else if (classes.size === 0) {
28+
return console.error(chalk.red(`Aborting. No classes found in file ${options.classes}`));
29+
}
30+
31+
const query = parametrizeQuery(queries.constructCandidateLinks, ["%%values-cls%%", "%%values-linktype%%"], classes);
32+
const data = await runGraphQuery(query, 'constructCandidateLinks');
33+
const prettyTurtle = await prettify(data);
34+
35+
if (prettyTurtle) {
36+
writeDataToFile(options.output, prettyTurtle);
37+
}
38+
}
39+
40+
async function fetchDetails(options) {
41+
console.log(`classes filename: ${options.classes}`);
42+
console.log(`output filename: ${options.output}`);
43+
44+
const classes = await readClassesFromFile(options.classes);
45+
46+
if (classes === undefined) {
47+
return console.error(chalk.red(`Aborting`));
48+
} else if (classes.size === 0) {
49+
return console.error(chalk.red(`Aborting. No classes found in file ${options.classes}`));
50+
}
51+
52+
const query = parametrizeQuery(queries.constructCandidateDetails, ["%%values-cls%%"], classes);
53+
const data = await runGraphQuery(query, 'constructCandidateDetails');
54+
const prettyTurtle = await prettify(data);
55+
56+
if (prettyTurtle) {
57+
writeDataToFile(options.output, prettyTurtle);
58+
}
59+
}
60+
61+
async function generateConfig(options) {
62+
console.log(`classes filename: ${options.classes}`);
63+
console.log(`links filename: ${options.links}`);
64+
console.log(`details filename: ${options.details}`);
65+
console.log(`output filename: ${options.output}`);
66+
67+
console.log(chalk.blue(`Combining the files and generating the config ...`));
68+
69+
const inMemoryStore = new oxigraph.Store();
70+
let loadingOK = true;
71+
loadingOK &= loadTurtleFileIntoStore(options.classes, inMemoryStore);
72+
loadingOK &= loadTurtleFileIntoStore(options.links, inMemoryStore);
73+
loadingOK &= loadTurtleFileIntoStore(options.details, inMemoryStore);
74+
75+
if (!loadingOK) {
76+
return console.error(chalk.red(`Aborting`));
77+
}
78+
79+
const query = queries.constructConfig;
80+
debug_sparql(query);
81+
const blueprintConfig = new oxigraph.Store(inMemoryStore.query(query));
82+
const data = blueprintConfig.dump({format: "application/trig"});
83+
const prettyTurtle = await prettify(data);
84+
85+
if (prettyTurtle) {
86+
writeDataToFile(options.output, prettyTurtle);
87+
}
88+
}
89+
90+
export {
91+
fetchClasses,
92+
fetchLinks,
93+
fetchDetails,
94+
generateConfig
95+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import SparqlClient from 'sparql-http-client/SimpleClient.js';
2+
import fs from 'fs';
3+
import rdfEnv from './rdf-env.js'
4+
import chalk from 'chalk';
5+
import createDebug from 'debug';
6+
const debug_sparql = createDebug('sparql');
7+
8+
const clientOptions = {
9+
endpointUrl: process.env.SPARQL_ENDPOINT,
10+
user: process.env.SPARQL_USER,
11+
password: process.env.SPARQL_PASS
12+
};
13+
14+
function createSparqlClient() {
15+
logENV();
16+
return new SparqlClient(clientOptions);
17+
}
18+
19+
const queryOptions = { operation: 'postDirect', headers: { 'Accept': 'text/turtle' } };
20+
21+
function logENV() {
22+
console.log(`SPARQL_ENDPOINT: ${process.env.SPARQL_ENDPOINT}`);
23+
console.log(`SPARQL_USER: ${process.env.SPARQL_USER}`);
24+
console.log(`SPARQL_PASS: ${process.env.SPARQL_PASS !== undefined ? '********' : undefined}`);
25+
}
26+
27+
function parametrizeQuery(queryTemplate, placeholders, classes) {
28+
if (classes === undefined) {
29+
return undefined;
30+
}
31+
32+
const valuesString = Array.from(classes)
33+
.map(iri => `\n<${iri}>`)
34+
.sort()
35+
.join('');
36+
37+
let query = queryTemplate;
38+
placeholders.forEach(x => {
39+
query = query.replaceAll(x, valuesString);
40+
});
41+
42+
return query;
43+
}
44+
45+
async function runGraphQuery(query, queryId) {
46+
const client = createSparqlClient();
47+
48+
console.log(chalk.blue(`Running graph query '${queryId}' ...`));
49+
debug_sparql(query);
50+
51+
try {
52+
const res = await client.query.construct(query, queryOptions);
53+
54+
if (!res.ok) {
55+
return console.error(chalk.red(`Failed to run graph query '${queryId}': HTTP status ${res.status} ${res.statusText}`));
56+
}
57+
58+
return await res.text();
59+
} catch (err) {
60+
return console.error(chalk.red(`Failed to run graph query: ${err}`));
61+
}
62+
}
63+
64+
function writeDataToFile(file, data) {
65+
try {
66+
fs.writeFileSync(file, data, 'utf8');
67+
console.log(chalk.green(`File ${file} has been written`));
68+
} catch (err) {
69+
console.error(chalk.red(`Failed to write file ${file}: ${err}`));
70+
}
71+
}
72+
73+
async function readClassesFromFile(file) {
74+
try {
75+
const dataset = await rdfEnv.dataset().import(rdfEnv.fromFile(file));
76+
77+
const classes = new Set();
78+
dataset.filter(matchClassDeclaration)
79+
.forEach(quad => classes.add(quad.subject.value));
80+
81+
return classes;
82+
83+
} catch (err) {
84+
return console.error(chalk.red(`Failed to read classes from file ${file}: ${err}`));
85+
}
86+
}
87+
88+
function matchClassDeclaration(quad) {
89+
return quad.predicate.value === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type'
90+
&& quad.object.value === 'http://schema.example.org/blueprint-ui-config-initializer/Class'
91+
}
92+
93+
function loadTurtleFileIntoStore(file, store) {
94+
try {
95+
const ttlString = fs.readFileSync(file, 'utf8');
96+
store.load(ttlString, { format: "text/turtle" });
97+
return true;
98+
99+
} catch (err) {
100+
return console.error(chalk.red(`Failed to load turtle from file ${file} into in-memory-store: ${err}`));
101+
}
102+
}
103+
104+
export {
105+
runGraphQuery,
106+
parametrizeQuery,
107+
writeDataToFile,
108+
readClassesFromFile,
109+
loadTurtleFileIntoStore
110+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import rdfEnv from './rdf-env.js'
2+
import formats from '@rdfjs-elements/formats-pretty'
3+
import { TurtleSerializer } from '@rdfjs-elements/formats-pretty'
4+
import { Readable } from 'readable-stream'
5+
import getStream from 'get-stream'
6+
7+
const sink = new TurtleSerializer({
8+
prefixes: {
9+
'': 'http://schema.example.org/blueprint-ui-config-initializer/',
10+
rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
11+
rdfs: 'http://www.w3.org/2000/01/rdf-schema#',
12+
sh: 'http://www.w3.org/ns/shacl#',
13+
blueprintMetaShapes: 'https://ld.flux.zazuko.com/shapes/metadata/',
14+
blueprintMetaLink: 'https://ld.flux.zazuko.com/link/metadata/',
15+
blueprint: 'https://flux.described.at/',
16+
}
17+
})
18+
19+
async function prettify(uglyTurtle) {
20+
if (uglyTurtle === undefined) {
21+
return undefined;
22+
}
23+
24+
const inQuadStream = formats.parsers.import('text/turtle', Readable.from(uglyTurtle))
25+
26+
const dataset = await rdfEnv.dataset().import(inQuadStream)
27+
28+
const outStream = sink.import(dataset.toStream())
29+
const result = await getStream(outStream)
30+
31+
return result
32+
}
33+
34+
export {
35+
prettify
36+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
PREFIX schema: <http://schema.org/>
2+
PREFIX : <http://schema.example.org/blueprint-ui-config-initializer/>
3+
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
4+
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
5+
PREFIX owl: <http://www.w3.org/2002/07/owl#>
6+
7+
CONSTRUCT {
8+
?cls a :Class ;
9+
:label ?localName ;
10+
:icon ?icon;
11+
:colorIndex ?colorIndex ;
12+
:searchPrio 1 .
13+
}
14+
WHERE {
15+
{
16+
SELECT DISTINCT ?cls ?localName ?icon ?colorIndex WHERE {
17+
?subject a ?cls .
18+
bind(replace(str(?cls), "^.*[/#]", "") AS ?localName)
19+
20+
# Assigning an icon and color to the type
21+
BIND(IF(STR(?cls) = "http://schema.org/Person", "fas fa-user", "fas fa-circle") AS ?icon)
22+
BIND(IF(STR(?cls) = "http://schema.org/Person", 2, 1) AS ?colorIndex)
23+
24+
# Filtering out well-known types that are less relevant
25+
MINUS {
26+
VALUES ?cls {
27+
schema:Property
28+
schema:URL
29+
schema:Thing
30+
schema:DefinedTerm
31+
# -----------------------------------
32+
rdf:Property
33+
owl:TransitiveProperty
34+
owl:SymmetricProperty
35+
rdf:List
36+
rdfs:Class
37+
rdfs:Datatype
38+
rdfs:ContainerMembershipProperty
39+
# -----------------------------------
40+
<https://flux.described.at/Detail>
41+
<https://flux.described.at/Link>
42+
<https://flux.described.at/CompositionToNodeLink>
43+
<https://flux.described.at/Aggregate>
44+
<https://flux.described.at/Composition>
45+
<https://flux.described.at/Hierarchy>
46+
<https://flux.described.at/Container>
47+
<https://flux.described.at/ConnectionPoint>
48+
# -----------------------------------
49+
<https://ld.flux.zazuko.com/shapes/metadata/ClassShape>
50+
<https://ld.flux.zazuko.com/shapes/metadata/ClassMetadataShape>
51+
<https://ld.flux.zazuko.com/shapes/metadata/ClassDetailShape>
52+
}
53+
}
54+
}
55+
ORDER BY ?localName ?cls
56+
}
57+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
PREFIX schema: <http://schema.org/>
2+
PREFIX : <http://schema.example.org/blueprint-ui-config-initializer/>
3+
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
4+
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
5+
6+
CONSTRUCT {
7+
?cls :detailGroup ?detailGroup .
8+
9+
?detailGroup :label "Info" ;
10+
:order 0 ;
11+
:detail [
12+
:label ?label ;
13+
:path ?property ;
14+
:order 0
15+
] .
16+
}
17+
WHERE {
18+
{
19+
SELECT DISTINCT ?detailGroup ?cls ?label ?property WHERE {
20+
?subject a ?cls .
21+
?subject ?property ?object .
22+
FILTER(isLiteral(?object))
23+
24+
# Only consider schema.org types and properties
25+
#FILTER (?cls IN (schema:Person, schema:Organization, schema:Event))
26+
#FILTER (?property IN (schema:familyName, schema:givenName, schema:jobTitle))
27+
28+
# Bind the local names and detail group URIs
29+
BIND(REPLACE(STR(?property), "^.*[/#]", "") AS ?propertyLocalName)
30+
BIND(REPLACE(STR(?cls), "^.*[/#]", "") AS ?clsLocalName)
31+
BIND(CONCAT(?clsLocalName, "-info-", ENCODE_FOR_URI(STR(?cls))) AS ?localDetailGroupId)
32+
BIND(IRI(CONCAT("http://data.example.org/blueprint-ui-config-initializer/detailgroup/", ?localDetailGroupId)) AS ?detailGroup)
33+
34+
# Bind the label
35+
OPTIONAL {
36+
?property rdfs:label ?propertyLabel
37+
}
38+
39+
# Otherwise, generate a label from the property's local name
40+
BIND(COALESCE(?propertyLabel, ?propertyLocalName) AS ?label)
41+
}
42+
ORDER BY ?cls ?label
43+
}
44+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
PREFIX schema: <http://schema.org/>
2+
PREFIX : <http://schema.example.org/blueprint-ui-config-initializer/>
3+
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
4+
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
5+
6+
CONSTRUCT {
7+
?cls :link [
8+
:label ?label ;
9+
:to ?linkType ;
10+
:path ?property
11+
]
12+
}
13+
WHERE {
14+
{
15+
SELECT DISTINCT ?cls ?property ?linkType ?label WHERE {
16+
?subject a ?cls .
17+
?subject ?property ?object .
18+
?object a ?linkType .
19+
20+
# Bind local names for better readability
21+
BIND(REPLACE(STR(?cls), "^.*[/#]", "") AS ?clsLocalName)
22+
BIND(REPLACE(STR(?property), "^.*[/#]", "") AS ?propertyLocalName)
23+
24+
# Filter to only include schema.org types and properties
25+
# FILTER (?cls IN (schema:Person, schema:Organization, schema:Event))
26+
# FILTER (?linkType IN (schema:Person, schema:Organization, schema:Event))
27+
28+
# Optional: Get labels for properties if available
29+
OPTIONAL {
30+
?property rdfs:label ?propertyLabel
31+
}
32+
33+
# Otherwise, generate a label from the property's local name
34+
BIND(COALESCE(?propertyLabel, ?propertyLocalName) AS ?label)
35+
}
36+
order by ?clsLocalName ?cls ?propertyLocalName ?property ?linkType
37+
}
38+
}

0 commit comments

Comments
 (0)