Skip to content

Commit

Permalink
feat(hierarchies): find hierarchies in all graphs
Browse files Browse the repository at this point in the history
  • Loading branch information
tpluscode committed Dec 19, 2024
1 parent cdfe35e commit 7d28c55
Show file tree
Hide file tree
Showing 13 changed files with 176 additions and 15 deletions.
6 changes: 6 additions & 0 deletions .changeset/rude-toes-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@cube-creator/shared-dimensions-api": minor
"@cube-creator/ui": minor
---

Hierarchies can now exist in any graph in Lindas
3 changes: 3 additions & 0 deletions apis/shared-dimensions/bootstrap/hierarchies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ import type { BootstrappedResourceFactory } from './index'

export const hierarchies = (ptr: BootstrappedResourceFactory) =>
ptr('_hierarchies').addOut(rdf.type, md.Hierarchies)

export const externalHierarchy = (ptr: BootstrappedResourceFactory) =>
ptr('_hierarchy/proxy').addOut(rdf.type, md.HierarchyProxy)
3 changes: 2 additions & 1 deletion apis/shared-dimensions/bootstrap/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { store } from '../lib/store'
import { terms, termSets, exportSet } from './termSetCollections'
import { entrypoint } from './entrypoint'
import shapes from './shapes'
import { hierarchies } from './hierarchies'
import { hierarchies, externalHierarchy } from './hierarchies'

export interface BootstrappedResourceFactory {
(term: string): GraphPointer<NamedNode>
Expand All @@ -21,6 +21,7 @@ const resources = [
terms(pointerFactory),
termSets(pointerFactory),
hierarchies(pointerFactory),
externalHierarchy(pointerFactory),
exportSet(pointerFactory),
entrypoint(pointerFactory, ns),
...shapes,
Expand Down
27 changes: 27 additions & 0 deletions apis/shared-dimensions/hydra/index.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,33 @@ md:Hierarchy
] ;
.

md:HierarchyProxy
a hydra:Class ;
hydra:supportedOperation
[
a hydra:Operation ;
hydra:method "GET" ;
code:implementedBy
[
a code:EcmaScript ;
code:link <file:handlers/hierarchy#getExternal> ;
] ;
hydra-box:variables
[
a hydra:IriTemplate ;
hydra:template "/_hierarchy/proxy{?id}" ;
hydra:variableRepresentation hydra:ExplicitRepresentation ;
hydra:mapping
[
a hydra:IriTemplateMapping ;
hydra:property schema:identifier ;
hydra:required true ;
hydra:variable "id" ;
] ;
] ;
] ;
.

<dimension/_shape/hierarchy> a sh:Shape .
<dimension/_shape/hierarchy-create> a sh:Shape .

Expand Down
4 changes: 3 additions & 1 deletion apis/shared-dimensions/lib/domain/hierarchies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,16 @@ export function getHierarchies({ freetextQuery, limit, offset }: GetHierarchies)
}

return CONSTRUCT`
${hierarchy} ?p ?o .
?proxyUrl ?p ?o .
`
.WHERE`
{
${select}
}
${hierarchy} ?p ?o .
BIND(IRI(CONCAT("${env.MANAGED_DIMENSIONS_API_BASE}", "dimension/_hierarchy/proxy?id=", ENCODE_FOR_URI(STR(${hierarchy})))) AS ?proxyUrl)
`
}

Expand Down
58 changes: 50 additions & 8 deletions apis/shared-dimensions/lib/handlers/hierarchy.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import type { Quad } from '@rdfjs/types'
import { dcterms, sd } from '@tpluscode/rdf-ns-builders'
import { dcterms, schema, sd } from '@tpluscode/rdf-ns-builders'
import { asyncMiddleware } from 'middleware-async'
import $rdf from 'rdf-ext'
import env from '@cube-creator/core/env'
import { meta } from '@cube-creator/core/namespace'
import onetime from 'onetime'
import { sh } from '@tpluscode/rdf-ns-builders/strict'
import { isGraphPointer, isNamedNode } from 'is-graph-pointer'
import clownface, { AnyPointer, GraphPointer } from 'clownface'
import sharedDimensionsEnv from '../env'
import { ShouldRewrite } from '../middleware/canonicalRewrite'
import shapeToQuery from '../shapeToQuery'
import { loadShapes } from '../store/shapes'
import { parsingClient } from '../sparql'

export const get = asyncMiddleware(async (req, res) => {
const hierarchy = await req.hydra.resource.clownface()
const hierarchy: any = await req.hydra.resource.clownface()

if (!hierarchy.out(dcterms.source).terms.length) {
hierarchy.addOut(dcterms.source, source => {
source
.addOut(sd.endpoint, $rdf.namedNode(env.PUBLIC_QUERY_ENDPOINT))
})
}
ensureEndpoint(hierarchy)

const noRewriteRoots: ShouldRewrite = (quad: Quad) => {
if (quad.predicate.equals(meta.hierarchyRoot)) {
Expand All @@ -30,3 +33,42 @@ export const get = asyncMiddleware(async (req, res) => {

return res.dataset(hierarchy.dataset)
})

const loadShapesOnce = onetime(loadShapes)

export const getExternal = asyncMiddleware(async (req, res) => {
const shape: AnyPointer = (await loadShapesOnce()).has(sh.targetClass, meta.Hierarchy)

if (!isGraphPointer(shape)) {
throw new Error('Shape not found')
}

const queryParams = clownface({ dataset: await req.dataset!() })
const focusNode = queryParams.out(schema.identifier)
if (!isNamedNode(focusNode)) {
throw new Error('Missing or invalid id param')
}

const url = new URL(focusNode.value, sharedDimensionsEnv.MANAGED_DIMENSIONS_BASE).toString()
const { constructQuery } = await shapeToQuery()
const query = constructQuery(shape, {
focusNode: $rdf.namedNode(url),
})

const hierarchy = clownface({
dataset: $rdf.dataset(await query.execute(parsingClient)),
}).namedNode(url)
ensureEndpoint(hierarchy)

res.setLink(url, 'canonical')
return res.dataset(hierarchy.dataset)
})

function ensureEndpoint(hierarchy: GraphPointer) {
if (!hierarchy.out(dcterms.source).terms.length) {
hierarchy.addOut(dcterms.source, source => {
source
.addOut(sd.endpoint, $rdf.namedNode(env.PUBLIC_QUERY_ENDPOINT))
})
}
}
2 changes: 1 addition & 1 deletion apis/shared-dimensions/lib/store/shapes.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ PREFIX sh: <http://www.w3.org/ns/shacl#>
] .

[
sh:targetClass md:Hierarchy ;
sh:targetClass meta:Hierarchy ;
sh:property
[
sh:path rdf:type ;
Expand Down
23 changes: 23 additions & 0 deletions e2e-tests/hierarchies/external-hierarchy.hydra
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
PREFIX md: <https://cube-creator.zazuko.com/shared-dimensions/vocab#>
PREFIX hydra: <http://www.w3.org/ns/hydra/core#>
PREFIX schema: <http://schema.org/>
PREFIX qudt: <http://qudt.org/schema/qudt/>
PREFIX meta: <https://cube.link/meta/>
PREFIX sh: <http://www.w3.org/ns/shacl#>
PREFIX dcterms: <http://purl.org/dc/terms/>

ENTRYPOINT "dimension/_hierarchy/proxy?id=http://example.com/hierarchy/de-bundesland"

HEADERS {
x-user "john-doe"
x-permission "pipelines:write"
x-email "[email protected]"
}

With Class md:Hierarchy {
Expect Property schema:name "DE - Bundesland"
Expect Property meta:nextInHierarchy {
Expect Property schema:name "Bundesland"
Expect Property sh:path
}
}
20 changes: 20 additions & 0 deletions fuseki/hierarchies.trig
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ base <https://ld.admin.ch/cube/dimension/hierarchy/>

<ch-canton-station> a void:rootResource .

<hierarchies> a void:Dataset .
<https://lindas.admin.ch/cube/dimension> void:inDataset <hierarchies> .
<http://example.com/external-hierarchies> void:inDataset <hierarchies> .

graph <https://lindas.admin.ch/cube/dimension> {
<ch-canton-station> a meta:Hierarchy, hydra:Resource, md:Hierarchy ;
schema:name "CH - Canton - District" ;
Expand All @@ -29,3 +33,19 @@ graph <https://lindas.admin.ch/cube/dimension> {
] ;
.
}

graph <http://example.com/external-hierarchies> {
<http://example.com/hierarchy/de-bundesland> a meta:Hierarchy, hydra:Resource, md:Hierarchy ;
schema:name "DE - Bundesland" ;
md:sharedDimension <http://example.com/dimension/countries> ;
meta:hierarchyRoot <http://example.com/dimension/countries/Germany> ;
meta:nextInHierarchy
[
schema:name "Bundesland" ;
sh:path
[
sh:inversePath schema:containedInPlace ;
] ;
] ;
.
}
31 changes: 31 additions & 0 deletions fuseki/shared-dimensions.trig
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,29 @@ graph <http://example.com/dimension/cantons> {
schema:inDefinedTermSet <http://example.com/dimension/cantons> ;
}

<http://example.com/dimension/bundeslander> void:inDataset <shared-dimensions> .
graph <http://example.com/dimension/bundeslander> {
<http://example.com/dimension/bundeslander>
a schema:DefinedTermSet, meta:SharedDimension ;
schema:name "Bundesländer"@de, "Federal states"@en ;
.

<http://example.com/dimension/bundesland/BW>
a schema:DefinedTerm, <http://example.com/vocab#Bundesland> ;
schema:identifier "BW" ;
schema:containedInPlace <http://example.com/dimension/countries/Germany> ;
schema:name "Baden-Württemberg"@de, "Baden-Württemberg"@en ;
schema:inDefinedTermSet <http://example.com/dimension/bundeslander> ;
.

<http://example.com/dimension/bundesland/BY>
a schema:DefinedTerm, <http://example.com/vocab#Bundesland> ;
schema:identifier "BY" ;
schema:containedInPlace <http://example.com/dimension/countries/Germany> ;
schema:name "Bayern"@de, "Bavaria"@en ;
schema:inDefinedTermSet <http://example.com/dimension/bundeslander> ;
}

<http://example.com/dimension/districts> void:inDataset <shared-dimensions> .
graph <http://example.com/dimension/districts> {
<http://example.com/dimension/districts>
Expand Down Expand Up @@ -107,6 +130,14 @@ graph <http://example.com/dimension/countries> {
schema:name "Poland"@en, "Polen"@de, "Pologne"@fr, "Polonia"@it ;
schema:inDefinedTermSet <http://example.com/dimension/countries> ;
.

<http://example.com/dimension/countries/Germany>
a schema:DefinedTerm ;
schema:validFrom "2021-01-20T23:59:59Z"^^xsd:dateTime ;
schema:identifier "DE" ;
schema:name "Germany"@en, "Deutschland"@de, "Allemagne"@fr, "Germania"@it ;
schema:inDefinedTermSet <http://example.com/dimension/countries> ;
.
}

<http://example.com/dimension/colors> void:inDataset <shared-dimensions> .
Expand Down
1 change: 1 addition & 0 deletions packages/core/namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ type SharedDimensionsTerms =
'hierarchies' |
'Hierarchies' |
'Hierarchy' |
'HierarchyProxy' |
'Entrypoint' |
'FreeTextSearchConstraintComponent'

Expand Down
2 changes: 1 addition & 1 deletion ui/src/forms/plugins/dimensionMetaHierarchySynchronizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { SetObjectParams } from '@hydrofoil/shaperone-core/models/forms/reducers
import { PropertyState } from '@hydrofoil/shaperone-core/models/forms'

function copyGraph (from: GraphPointer, to: GraphPointer) {
const quads = from.dataset.match(null, null, null, from.term)
const quads = from.dataset.match(null, null, null, from._context[0].graph)

function replace (term: Term) {
return term.equals(from.term) ? to.term : term
Expand Down
11 changes: 8 additions & 3 deletions ui/src/store/modules/hierarchy.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ActionTree, MutationTree, GetterTree } from 'vuex'
import { ActionTree, GetterTree, MutationTree } from 'vuex'
import { api } from '@/api'
import { RootState, Hierarchy } from '../types'
import { Hierarchy, RootState } from '../types'

export interface HierarchyState {
hierarchy: null | Hierarchy
Expand All @@ -14,7 +14,12 @@ const getters: GetterTree<HierarchyState, RootState> = {}

const actions: ActionTree<HierarchyState, RootState> = {
async fetchHierarchy (context, id) {
context.commit('storeHierarchy', await api.fetchResource(id))
if (!context.rootState.hierarchies.collection) {
await context.dispatch('sharedDimensions/fetchEntrypoint', {}, { root: true })
await context.dispatch('hierarchies/fetchCollection', {}, { root: true })
}

context.commit('storeHierarchy', await api.fetchResource(id.replaceAll('!!', '/')))
},

reset (context) {
Expand Down

0 comments on commit 7d28c55

Please sign in to comment.