Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hierarchies from all graphs #1564

Merged
merged 4 commits into from
Dec 30, 2024
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
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 @@
}

return CONSTRUCT`
${hierarchy} ?p ?o .
?proxyUrl ?p ?o .

Check warning on line 39 in apis/shared-dimensions/lib/domain/hierarchies.ts

View check run for this annotation

Codecov / codecov/patch

apis/shared-dimensions/lib/domain/hierarchies.ts#L39

Added line #L39 was not covered by tests
`
.WHERE`
{
${select}
}

${hierarchy} ?p ?o .

BIND(IRI(CONCAT("${env.MANAGED_DIMENSIONS_API_BASE}", "dimension/_hierarchy/proxy?id=", ENCODE_FOR_URI(STR(${hierarchy})))) AS ?proxyUrl)

Check warning on line 48 in apis/shared-dimensions/lib/domain/hierarchies.ts

View check run for this annotation

Codecov / codecov/patch

apis/shared-dimensions/lib/domain/hierarchies.ts#L47-L48

Added lines #L47 - L48 were not covered by tests
`
}

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'

Check warning on line 2 in apis/shared-dimensions/lib/handlers/hierarchy.ts

View check run for this annotation

Codecov / codecov/patch

apis/shared-dimensions/lib/handlers/hierarchy.ts#L2

Added line #L2 was not covered by tests
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'

Check warning on line 11 in apis/shared-dimensions/lib/handlers/hierarchy.ts

View check run for this annotation

Codecov / codecov/patch

apis/shared-dimensions/lib/handlers/hierarchy.ts#L6-L11

Added lines #L6 - L11 were not covered by tests
import { ShouldRewrite } from '../middleware/canonicalRewrite'
import shapeToQuery from '../shapeToQuery'
import { loadShapes } from '../store/shapes'
import { parsingClient } from '../sparql'

Check warning on line 15 in apis/shared-dimensions/lib/handlers/hierarchy.ts

View check run for this annotation

Codecov / codecov/patch

apis/shared-dimensions/lib/handlers/hierarchy.ts#L13-L15

Added lines #L13 - L15 were not covered by tests

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

Check warning on line 18 in apis/shared-dimensions/lib/handlers/hierarchy.ts

View check run for this annotation

Codecov / codecov/patch

apis/shared-dimensions/lib/handlers/hierarchy.ts#L18

Added line #L18 was not covered by tests

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

Check warning on line 20 in apis/shared-dimensions/lib/handlers/hierarchy.ts

View check run for this annotation

Codecov / codecov/patch

apis/shared-dimensions/lib/handlers/hierarchy.ts#L20

Added line #L20 was not covered by tests

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

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))
})
}
}

Check warning on line 74 in apis/shared-dimensions/lib/handlers/hierarchy.ts

View check run for this annotation

Codecov / codecov/patch

apis/shared-dimensions/lib/handlers/hierarchy.ts#L36-L74

Added lines #L36 - L74 were not covered by tests
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
Loading