From 5224c33bc00e987e042dd2000c2b945457cb4b8a Mon Sep 17 00:00:00 2001 From: sdevalk Date: Fri, 13 Dec 2024 09:35:01 +0100 Subject: [PATCH] feat: hasParts --- .../api/src/research-guides/definitions.ts | 1 + .../fetcher.integration.test.ts | 158 ++++++------------ packages/api/src/research-guides/fetcher.ts | 44 +++-- .../src/research-guides/rdf-helpers.test.ts | 79 +++++++++ .../api/src/research-guides/rdf-helpers.ts | 12 ++ 5 files changed, 169 insertions(+), 125 deletions(-) diff --git a/packages/api/src/research-guides/definitions.ts b/packages/api/src/research-guides/definitions.ts index 7f793363..994ade11 100644 --- a/packages/api/src/research-guides/definitions.ts +++ b/packages/api/src/research-guides/definitions.ts @@ -11,5 +11,6 @@ export type ResearchGuide = Thing & { contentLocations?: Place[]; keywords?: Term[]; citations?: Citation[]; + hasParts?: ResearchGuide[]; seeAlso?: ResearchGuide[]; }; diff --git a/packages/api/src/research-guides/fetcher.integration.test.ts b/packages/api/src/research-guides/fetcher.integration.test.ts index 9413ee33..5d515432 100644 --- a/packages/api/src/research-guides/fetcher.integration.test.ts +++ b/packages/api/src/research-guides/fetcher.integration.test.ts @@ -23,73 +23,51 @@ describe('getTopLevels', () => { 'Research aides for conducting provenance research into colonial collections', text: 'On this page you find various research aides that can assist...', encodingFormat: 'text/markdown', - seeAlso: [ + hasParts: [ { - id: 'https://guides.example.org/subset3', - name: '3. Name', - seeAlso: [ + id: 'https://guides.example.org/subset2', + name: '2. Name', + hasParts: [ { - id: 'https://guides.example.org/guide6', - name: 'Royal Cabinet of Curiosities', - seeAlso: [ + id: 'https://guides.example.org/guide4', + name: 'Military and navy', + hasParts: [ { - id: 'https://guides.example.org/guide5', - name: 'Trade', - seeAlso: [ - { - id: 'https://guides.example.org/guide7', - name: 'Kunsthandel Van Lier', - }, - ], + id: 'https://guides.example.org/guide6', + name: 'Royal Cabinet of Curiosities', }, ], }, { - id: 'https://guides.example.org/guide7', - name: 'Kunsthandel Van Lier', - seeAlso: [ + id: 'https://guides.example.org/guide5', + name: 'Trade', + hasParts: [ { - id: 'https://guides.example.org/guide4', - name: 'Military and navy', - seeAlso: [ - { - id: 'https://guides.example.org/guide1', - name: 'Doing research', - }, - { - id: 'https://guides.example.org/guide6', - name: 'Royal Cabinet of Curiosities', - }, - ], + id: 'https://guides.example.org/guide7', + name: 'Kunsthandel Van Lier', }, ], }, ], }, { - id: 'https://guides.example.org/subset1', - name: '1. Name', - seeAlso: [ + id: 'https://guides.example.org/subset3', + name: '3. Name', + hasParts: [ { - id: 'https://guides.example.org/guide1', - name: 'Doing research', - seeAlso: [ - { - id: 'https://guides.example.org/guide4', - name: 'Military and navy', - seeAlso: [ - { - id: 'https://guides.example.org/guide1', - name: 'Doing research', - }, - { - id: 'https://guides.example.org/guide6', - name: 'Royal Cabinet of Curiosities', - }, - ], - }, - ], + id: 'https://guides.example.org/guide6', + name: 'Royal Cabinet of Curiosities', + }, + { + id: 'https://guides.example.org/guide7', + name: 'Kunsthandel Van Lier', }, + ], + }, + { + id: 'https://guides.example.org/subset1', + name: '1. Name', + hasParts: [ { id: 'https://guides.example.org/guide2', name: 'How can I use the data hub for my research?', @@ -97,66 +75,10 @@ describe('getTopLevels', () => { { id: 'https://guides.example.org/guide3', name: 'Sources', - seeAlso: [ - { - id: 'https://guides.example.org/guide5', - name: 'Trade', - seeAlso: [ - { - id: 'https://guides.example.org/guide7', - name: 'Kunsthandel Van Lier', - }, - ], - }, - ], - }, - ], - }, - { - id: 'https://guides.example.org/subset2', - name: '2. Name', - seeAlso: [ - { - id: 'https://guides.example.org/guide4', - name: 'Military and navy', - seeAlso: [ - { - id: 'https://guides.example.org/guide1', - name: 'Doing research', - seeAlso: [ - { - id: 'https://guides.example.org/guide4', - name: 'Military and navy', - }, - ], - }, - { - id: 'https://guides.example.org/guide6', - name: 'Royal Cabinet of Curiosities', - seeAlso: [ - { - id: 'https://guides.example.org/guide5', - name: 'Trade', - }, - ], - }, - ], }, { - id: 'https://guides.example.org/guide5', - name: 'Trade', - seeAlso: [ - { - id: 'https://guides.example.org/guide7', - name: 'Kunsthandel Van Lier', - seeAlso: [ - { - id: 'https://guides.example.org/guide4', - name: 'Military and navy', - }, - ], - }, - ], + id: 'https://guides.example.org/guide1', + name: 'Doing research', }, ], }, @@ -244,6 +166,12 @@ describe('getById', () => { }, }, ]), + hasParts: expect.arrayContaining([ + { + id: 'https://guides.example.org/guide6', + name: 'Royal Cabinet of Curiosities', + }, + ]), seeAlso: expect.arrayContaining([ { id: 'https://guides.example.org/guide6', @@ -301,6 +229,12 @@ describe('get with localized names', () => { abstract: 'Army and Navy personnel who operated in colonized territories collected objects in various ways during the colonial era.', text: 'Dutch authority in the [Dutch East Indies](https://www.geonames.org/1643084/republic-of-indonesia.html), [Suriname](https://www.geonames.org/3382998/republic-of-suriname.html) and on the [Caribbean Islands](https://www.geonames.org/8505032/netherlands-antilles.html) relied heavily on the use of the military...', + hasParts: expect.arrayContaining([ + { + id: 'https://guides.example.org/guide6', + name: 'Royal Cabinet of Curiosities', + }, + ]), seeAlso: expect.arrayContaining([ { id: 'https://guides.example.org/guide6', @@ -344,6 +278,12 @@ describe('get with localized names', () => { abstract: 'Leger- en marinepersoneel dat actief was in gekoloniseerde gebieden, verzamelde op verschillende manieren objecten tijdens het koloniale tijdperk.', text: 'Het Nederlandse gezag in [Nederlands-Indiƫ](https://www.geonames.org/1643084/republic-of-indonesia.html), [Suriname](https://www.geonames.org/3382998/republic-of-suriname.html) en op de [Caribische eilanden](https://www.geonames.org/8505032/netherlands-antilles.html) steunde in belangrijke mate op de inzet van het leger.', + hasParts: expect.arrayContaining([ + { + id: 'https://guides.example.org/guide6', + name: 'Koninklijk Kabinet van Zeldzaamheden', + }, + ]), seeAlso: expect.arrayContaining([ { id: 'https://guides.example.org/guide6', diff --git a/packages/api/src/research-guides/fetcher.ts b/packages/api/src/research-guides/fetcher.ts index d77c6edc..8ba4e693 100644 --- a/packages/api/src/research-guides/fetcher.ts +++ b/packages/api/src/research-guides/fetcher.ts @@ -67,18 +67,18 @@ export class ResearchGuideFetcher { ex:abstract ?topSetAbstract ; ex:text ?topSetText ; ex:encodingFormat ?topSetEncodingFormat ; - ex:seeAlso ?subSet . + ex:hasPart ?subSet . ?subSet a ex:CreativeWork ; ex:name ?subSetName ; - ex:seeAlso ?guide . + ex:hasPart ?level1Guide . - ?guide a ex:CreativeWork ; - ex:name ?guideName ; - ex:seeAlso ?relatedGuide . + ?level1Guide a ex:CreativeWork ; + ex:name ?level1GuideName ; + ex:hasPart ?level2Guide . - ?relatedGuide a ex:CreativeWork ; - ex:name ?relatedGuideName . + ?level2Guide a ex:CreativeWork ; + ex:name ?level2GuideName . } WHERE { VALUES ?topSet { @@ -111,15 +111,15 @@ export class ResearchGuideFetcher { # Get a selection of information from member guides, if any OPTIONAL { - ?subSet la:has_member ?guide . - ?guide schema:name ?guideName - FILTER(LANG(?guideName) = "${options.locale}") + ?subSet la:has_member ?level1Guide . + ?level1Guide schema:name ?level1GuideName + FILTER(LANG(?level1GuideName) = "${options.locale}") - # Get a selection of information from related guides, if any + # Get a selection of information from member guides, if any OPTIONAL { - ?guide rdfs:seeAlso ?relatedGuide . - ?relatedGuide schema:name ?relatedGuideName - FILTER(LANG(?relatedGuideName) = "${options.locale}") + ?level1Guide la:has_member ?level2Guide . + ?level2Guide schema:name ?level2GuideName + FILTER(LANG(?level2GuideName) = "${options.locale}") } } } @@ -137,7 +137,7 @@ export class ResearchGuideFetcher { WHERE { ?this a la:Set . FILTER NOT EXISTS { - ?this la:member_of [] + [] la:has_member ?this } } `; @@ -163,6 +163,7 @@ export class ResearchGuideFetcher { const query = ` PREFIX ex: + PREFIX la: PREFIX rdfs: PREFIX schema: @@ -173,12 +174,16 @@ export class ResearchGuideFetcher { ex:abstract ?abstract ; ex:text ?text ; ex:encodingFormat ?encodingFormat ; + ex:hasPart ?memberGuide ; ex:seeAlso ?relatedGuide ; ex:contentLocation ?spatial ; ex:keyword ?keyword ; ex:citation ?citation ; ex:contentReferenceTime ?contentReferenceTime . + ?memberGuide a ex:CreativeWork ; + ex:name ?memberGuideName . + ?relatedGuide a ex:CreativeWork ; ex:name ?relatedGuideName . @@ -231,10 +236,17 @@ export class ResearchGuideFetcher { ?this schema:encodingFormat ?encodingFormat } + # Get a selection of information from member guides, if any + OPTIONAL { + ?this la:has_member ?memberGuide . + ?memberGuide schema:name ?memberGuideName + FILTER(LANG(?memberGuideName) = "${options.locale}") + } + # Get a selection of information from related guides, if any OPTIONAL { ?this rdfs:seeAlso ?relatedGuide . - ?relatedGuide schema:name ?relatedGuideName . + ?relatedGuide schema:name ?relatedGuideName FILTER(LANG(?relatedGuideName) = "${options.locale}") } diff --git a/packages/api/src/research-guides/rdf-helpers.test.ts b/packages/api/src/research-guides/rdf-helpers.test.ts index ef04b277..6d044bec 100644 --- a/packages/api/src/research-guides/rdf-helpers.test.ts +++ b/packages/api/src/research-guides/rdf-helpers.test.ts @@ -46,15 +46,18 @@ beforeAll(async () => { ex:description "Citation Description" ; ex:url ; ] ; + ex:hasPart ex:researchGuide3 ; ex:seeAlso ex:researchGuide3 . ex:researchGuide3 a ex:CreativeWork ; ex:name "Name 3" ; + ex:hasPart ex:researchGuide5 ; ex:seeAlso ex:researchGuide5 . ex:researchGuide5 a ex:CreativeWork ; ex:name "Name 5" ; ex:abstract "Abstract 5" ; + ex:hasPart ex:researchGuide6 ; ex:seeAlso ex:researchGuide6 . ex:researchGuide6 a ex:CreativeWork ; @@ -162,15 +165,91 @@ describe('createResearchGuide', () => { }, }, ], + hasParts: [ + { + id: 'https://example.org/researchGuide3', + name: 'Name 3', + hasParts: [ + { + id: 'https://example.org/researchGuide5', + name: 'Name 5', + abstract: 'Abstract 5', + hasParts: [ + { + id: 'https://example.org/researchGuide6', + name: 'Name 6', + abstract: 'Abstract 6', + }, + ], + seeAlso: [ + { + id: 'https://example.org/researchGuide6', + name: 'Name 6', + abstract: 'Abstract 6', + }, + ], + }, + ], + seeAlso: [ + { + id: 'https://example.org/researchGuide5', + name: 'Name 5', + abstract: 'Abstract 5', + hasParts: [ + { + id: 'https://example.org/researchGuide6', + name: 'Name 6', + abstract: 'Abstract 6', + }, + ], + seeAlso: [ + { + id: 'https://example.org/researchGuide6', + name: 'Name 6', + abstract: 'Abstract 6', + }, + ], + }, + ], + }, + ], seeAlso: [ { id: 'https://example.org/researchGuide3', name: 'Name 3', + hasParts: [ + { + id: 'https://example.org/researchGuide5', + name: 'Name 5', + abstract: 'Abstract 5', + hasParts: [ + { + id: 'https://example.org/researchGuide6', + name: 'Name 6', + abstract: 'Abstract 6', + }, + ], + seeAlso: [ + { + id: 'https://example.org/researchGuide6', + name: 'Name 6', + abstract: 'Abstract 6', + }, + ], + }, + ], seeAlso: [ { id: 'https://example.org/researchGuide5', name: 'Name 5', abstract: 'Abstract 5', + hasParts: [ + { + id: 'https://example.org/researchGuide6', + name: 'Name 6', + abstract: 'Abstract 6', + }, + ], seeAlso: [ { id: 'https://example.org/researchGuide6', diff --git a/packages/api/src/research-guides/rdf-helpers.ts b/packages/api/src/research-guides/rdf-helpers.ts index 32e0050b..375166db 100644 --- a/packages/api/src/research-guides/rdf-helpers.ts +++ b/packages/api/src/research-guides/rdf-helpers.ts @@ -82,6 +82,17 @@ export function createResearchGuide( getPropertyValues(researchGuideResource, 'ex:encodingFormat') ); + let hasParts: ResearchGuide[] | undefined = undefined; + + // Prevent infinite recursion + if (stackSize < 5) { + hasParts = createResearchGuides( + researchGuideResource, + 'ex:hasPart', + stackSize + 1 + ); + } + let seeAlso: ResearchGuide[] | undefined = undefined; // Prevent infinite recursion @@ -112,6 +123,7 @@ export function createResearchGuide( text, encodingFormat, contentReferenceTimes, + hasParts, seeAlso, contentLocations, keywords,