Skip to content

Commit

Permalink
Merge pull request #1564 from zazuko/hierarchies-all-graphs
Browse files Browse the repository at this point in the history
  • Loading branch information
tpluscode authored Dec 30, 2024
2 parents cdfe35e + 1b8f302 commit 3e4acc3
Show file tree
Hide file tree
Showing 14 changed files with 171 additions and 21 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
6 changes: 0 additions & 6 deletions .github/workflows/setup-env/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,6 @@ runs:
cache: "yarn"
- run: yarn install --ci
shell: bash
- name: Set up Docker
uses: docker-practice/actions-setup-docker@master
with:
docker_version: 20.10.23
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Start site
uses: tpluscode/[email protected]
with:
Expand Down
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
60 changes: 51 additions & 9 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 { md, 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, md.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))
})
}
}
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
}
}
16 changes: 16 additions & 0 deletions fuseki/hierarchies.trig
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,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
4 changes: 4 additions & 0 deletions ui/src/api/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { rdf, schema } from '@tpluscode/rdf-ns-builders'
import { Actions } from '@/api/mixins/ApiResource'

export function findOperation (resource: RdfResource, idOrType: NamedNode): RuntimeOperation | null {
if (!resource.id.value.includes(window.APP_CONFIG.apiCoreBase)) {
return null
}

const matches = resource.operations.filter(op => op.pointer.has(rdf.type, idOrType).values.length)

if (matches.length > 1) {
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
6 changes: 3 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,7 @@ const getters: GetterTree<HierarchyState, RootState> = {}

const actions: ActionTree<HierarchyState, RootState> = {
async fetchHierarchy (context, id) {
context.commit('storeHierarchy', await api.fetchResource(id))
context.commit('storeHierarchy', await api.fetchResource(id.replaceAll('!!', '/')))
},

reset (context) {
Expand Down

0 comments on commit 3e4acc3

Please sign in to comment.