From af66e5c3ce803cfde8ef3deed49effd7a579e02a Mon Sep 17 00:00:00 2001 From: "@s.roertgen" Date: Tue, 6 Feb 2024 16:12:35 +0100 Subject: [PATCH 01/10] Add owl:deprecated #287 --- gatsby-node.js | 2 ++ src/context.js | 5 +++++ src/queries.js | 1 + src/types.js | 3 ++- 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/gatsby-node.js b/gatsby-node.js index 1d99546..76fd46b 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -143,6 +143,7 @@ exports.onPreBootstrap = async ({ createContentDigest, actions, getNode }) => { topConceptOf, hasTopConcept, member, + deprecated, ...properties } = graph const type = Array.isArray(properties.type) @@ -173,6 +174,7 @@ exports.onPreBootstrap = async ({ createContentDigest, actions, getNode }) => { * a concept scheme not present in the graphql data layer would not be found and not * be shown on the concepts page. */ + deprecated: Boolean(deprecated) || null, inSchemeAll: inSchemeFiltered.map((inScheme) => ({ id: inScheme.id })) || null, // topConceptOf nodes are also set to inScheme to facilitate parsing and filtering later diff --git a/src/context.js b/src/context.js index dab1450..a5fbc58 100644 --- a/src/context.js +++ b/src/context.js @@ -9,6 +9,7 @@ const jsonld = { schema: "https://schema.org/", vann: "http://purl.org/vocab/vann/", ldp: "http://www.w3.org/ns/ldp#", + owl: "http://www.w3.org/2002/07/owl#", title: { "@id": "dct:title", "@container": "@language", @@ -95,6 +96,10 @@ const jsonld = { topConceptOf: { "@container": "@set", }, + deprecated: { + "@id": "owl:deprecated", + "@type": "xsd:boolean", + }, }, } diff --git a/src/queries.js b/src/queries.js index b70d33b..a15333b 100644 --- a/src/queries.js +++ b/src/queries.js @@ -119,6 +119,7 @@ module.exports.allConcept = (inScheme, languages) => ` ${[...languages].join(" ")} } } + deprecated } } } diff --git a/src/types.js b/src/types.js index e295921..2d5bcd9 100644 --- a/src/types.js +++ b/src/types.js @@ -37,7 +37,8 @@ module.exports = (languages) => ` exactMatch: [Concept], inScheme: [ConceptScheme] @link(from: "inScheme___NODE"), inSchemeAll: [ConceptScheme], - hub: String + hub: String, + deprecated: Boolean } type LanguageMap { From a5180c11b20359203ad3e943182e2a021bf5eda4 Mon Sep 17 00:00:00 2001 From: "@s.roertgen" Date: Tue, 6 Feb 2024 16:54:35 +0100 Subject: [PATCH 02/10] Add markup in tree view and concept page #287 --- src/components/Concept.jsx | 1 + src/components/nestedList.jsx | 3 +++ src/queries.js | 1 + 3 files changed, 5 insertions(+) diff --git a/src/components/Concept.jsx b/src/components/Concept.jsx index 4d3aaf9..41cce31 100644 --- a/src/components/Concept.jsx +++ b/src/components/Concept.jsx @@ -20,6 +20,7 @@ const Concept = ({ return (
+

{concept.deprecated ? "Deprecated" : ""}

{concept.notation && {concept.notation.join(",")} } {i18n(language)(concept.prefLabel)} diff --git a/src/components/nestedList.jsx b/src/components/nestedList.jsx index 058ed56..581f38b 100644 --- a/src/components/nestedList.jsx +++ b/src/components/nestedList.jsx @@ -191,6 +191,9 @@ const NestedList = ({ const renderPrefLabel = () => { // Function for handling highlighting function handleHighlight(text, highlight) { + text = item.deprecated + ? `(DEPRECATED) ${text}` + : text if (highlight) { return text.replace(highlight, (str) => `${str}`) } else { diff --git a/src/queries.js b/src/queries.js index a15333b..cbac592 100644 --- a/src/queries.js +++ b/src/queries.js @@ -179,6 +179,7 @@ module.exports.allConceptScheme = (languages) => ` example { ${[...languages].join(" ")} } + deprecated } ` module.exports.tokenizer = `{ From e6ae03b9ed5864054af5f489e1774bd1bb3f20ae Mon Sep 17 00:00:00 2001 From: "@s.roertgen" Date: Tue, 6 Feb 2024 17:20:22 +0100 Subject: [PATCH 03/10] Add tests for deprecation notice #287 --- test/concept.test.jsx | 9 ++++++++- test/data/pageContext.js | 35 +++++++++++++++++++++++++++++++++++ test/nestedList.test.jsx | 18 +++++++++++++++++- 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/test/concept.test.jsx b/test/concept.test.jsx index a90d85e..59d39d9 100644 --- a/test/concept.test.jsx +++ b/test/concept.test.jsx @@ -4,7 +4,7 @@ import * as Gatsby from "gatsby" import React from "react" import Concept from "../src/components/Concept.jsx" -import { ConceptPC } from "./data/pageContext" +import { ConceptPC, ConceptPCDeprecated } from "./data/pageContext" import mockFetch from "./mocks/mockFetch" import { mockConfig } from "./mocks/mockConfig" import { useSkoHubContext } from "../src/context/Context.jsx" @@ -226,4 +226,11 @@ describe.concurrent("Concept", () => { screen.getByRole("link", { name: /just-another-scheme/i }) ).toHaveAttribute("href", "http://just-another-scheme.org/") }) + + it("renders deprecated notice, if concept is deprecaed", () => { + render() + expect( + screen.getByRole("heading", { name: /Deprecated/i }) + ).toBeInTheDocument() + }) }) diff --git a/test/data/pageContext.js b/test/data/pageContext.js index e033f1d..34e877e 100644 --- a/test/data/pageContext.js +++ b/test/data/pageContext.js @@ -14,6 +14,19 @@ const concept2 = { inScheme: [{ id: "http://w3id.org/" }], } +const topConceptDeprecated = { + id: "http://w3id.org/c1", + type: "Concept", + deprecated: true, + hub: "https://test.skohub.io/hub", + prefLabel: { + de: "Konzept 1", + en: "Concept 1", + }, + narrower: [concept2], + topConceptOf: null, +} + export const topConcept = { id: "http://w3id.org/c1", type: "Concept", @@ -98,6 +111,28 @@ export const ConceptPC = { ], } +export const ConceptPCDeprecated = { + node: topConceptDeprecated, + language: "de", + collections: [ + { + id: "http://w3id.org/collection", + prefLabel: { de: "Meine Collection", en: "My Collection" }, + member: [topConcept, concept2], + }, + ], +} + +export const ConceptSchemeDeprecated = { + id: "http://w3id.org/", + type: "ConceptScheme", + title: { + de: "Test Vokabular", + en: "Test Vocabulary", + }, + hasTopConcept: [topConceptDeprecated], +} + export const ConceptScheme = { id: "http://w3id.org/", type: "ConceptScheme", diff --git a/test/nestedList.test.jsx b/test/nestedList.test.jsx index 3eb33c7..f402546 100644 --- a/test/nestedList.test.jsx +++ b/test/nestedList.test.jsx @@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest" import { render, screen } from "@testing-library/react" import React from "react" import NestedList from "../src/components/nestedList" -import { ConceptScheme } from "./data/pageContext" +import { ConceptScheme, ConceptSchemeDeprecated } from "./data/pageContext" import userEvent from "@testing-library/user-event" import * as Gatsby from "gatsby" import { mockConfig } from "./mocks/mockConfig" @@ -59,4 +59,20 @@ describe("Nested List", () => { await user.click(screen.getByRole("button", { expanded: true })) expect(screen.getByRole("button", { expanded: false })) }) + + it("shows deprecation notice for deprecated concepts", () => { + render( + + ) + screen.debug() + expect( + screen.getByRole("link", { name: "(DEPRECATED) Konzept 1" }) + ).toBeInTheDocument() + }) }) From 10537f55e8ed81e1853df32b5afba54e348e89b0 Mon Sep 17 00:00:00 2001 From: "@s.roertgen" Date: Wed, 7 Feb 2024 08:27:44 +0100 Subject: [PATCH 04/10] Add `dct:isReplacedBy` #287 --- src/components/Concept.jsx | 14 ++++++++++++++ src/context.js | 4 ++++ src/queries.js | 3 +++ src/types.js | 3 ++- 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/components/Concept.jsx b/src/components/Concept.jsx index 41cce31..056283f 100644 --- a/src/components/Concept.jsx +++ b/src/components/Concept.jsx @@ -27,6 +27,20 @@ const Concept = ({

+ {concept.isReplacedBy && concept.isReplacedBy.length > 0 && ( +
+

isReplacedBy

+
    + {concept.isReplacedBy.map((isReplacedBy) => ( +
  • + + {isReplacedBy.id} + +
  • + ))} +
+
+ )} {concept.definition && (

Definition

diff --git a/src/context.js b/src/context.js index a5fbc58..0ae32b3 100644 --- a/src/context.js +++ b/src/context.js @@ -100,6 +100,10 @@ const jsonld = { "@id": "owl:deprecated", "@type": "xsd:boolean", }, + isReplacedBy: { + "@id": "dct:isReplacedBy", + "@container": "@set", + }, }, } diff --git a/src/queries.js b/src/queries.js index cbac592..da97cb4 100644 --- a/src/queries.js +++ b/src/queries.js @@ -120,6 +120,9 @@ module.exports.allConcept = (inScheme, languages) => ` } } deprecated + isReplacedBy { + id + } } } } diff --git a/src/types.js b/src/types.js index 2d5bcd9..911dc60 100644 --- a/src/types.js +++ b/src/types.js @@ -38,7 +38,8 @@ module.exports = (languages) => ` inScheme: [ConceptScheme] @link(from: "inScheme___NODE"), inSchemeAll: [ConceptScheme], hub: String, - deprecated: Boolean + deprecated: Boolean, + isReplacedBy: [Concept] } type LanguageMap { From 003afad56f2b07bfb4cdb37f3291412636646c83 Mon Sep 17 00:00:00 2001 From: "@s.roertgen" Date: Wed, 7 Feb 2024 08:32:01 +0100 Subject: [PATCH 05/10] Remove screen.debug #287 --- test/nestedList.test.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/test/nestedList.test.jsx b/test/nestedList.test.jsx index f402546..5f6e810 100644 --- a/test/nestedList.test.jsx +++ b/test/nestedList.test.jsx @@ -70,7 +70,6 @@ describe("Nested List", () => { language={"de"} /> ) - screen.debug() expect( screen.getByRole("link", { name: "(DEPRECATED) Konzept 1" }) ).toBeInTheDocument() From b5f9073617a854290fcf0e4341d6bb14b4b487a0 Mon Sep 17 00:00:00 2001 From: "@s.roertgen" Date: Wed, 7 Feb 2024 09:00:26 +0100 Subject: [PATCH 06/10] Add test for `isReplacedBy` #287 --- src/components/Concept.jsx | 4 +--- test/concept.test.jsx | 20 +++++++++++++++++++- test/data/pageContext.js | 1 + 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/components/Concept.jsx b/src/components/Concept.jsx index 056283f..6eb2b2c 100644 --- a/src/components/Concept.jsx +++ b/src/components/Concept.jsx @@ -103,9 +103,7 @@ const Concept = ({
    {concept.related.map((related) => (
  • - + {i18n(language)(related.prefLabel) || related.id}
  • diff --git a/test/concept.test.jsx b/test/concept.test.jsx index 59d39d9..4298d29 100644 --- a/test/concept.test.jsx +++ b/test/concept.test.jsx @@ -130,7 +130,10 @@ describe.concurrent("Concept", () => { expect( screen.getByRole("heading", { name: /^related$/i }) ).toBeInTheDocument() - expect(screen.getByRole("link", { name: /konzept 4/i })).toBeInTheDocument() + const href = screen.getByRole("link", { name: /konzept 4/i }) + expect(href).toBeInTheDocument() + // ensure there is no language tag in the link + expect(href.getAttribute("href")).not.toMatch(/\..{2}\.html$/) }) it("renders narrow matches", () => { @@ -233,4 +236,19 @@ describe.concurrent("Concept", () => { screen.getByRole("heading", { name: /Deprecated/i }) ).toBeInTheDocument() }) + + it("adds a isReplacedBy notice if concept is replaced", () => { + render() + expect( + screen.getByRole("heading", { name: /isReplacedBy/i }) + ).toBeInTheDocument() + const linkElement = screen.getByRole("link", { + name: "http://w3id.org/replacement", + }) // Adjust the query to match your link + const href = linkElement.getAttribute("href") + + // Assert the URL ends with .html but not .xx.html + expect(href).toMatch(/\.html$/) + expect(href).not.toMatch(/\..{2}\.html$/) + }) }) diff --git a/test/data/pageContext.js b/test/data/pageContext.js index 34e877e..116a23f 100644 --- a/test/data/pageContext.js +++ b/test/data/pageContext.js @@ -18,6 +18,7 @@ const topConceptDeprecated = { id: "http://w3id.org/c1", type: "Concept", deprecated: true, + isReplacedBy: [{ id: "http://w3id.org/replacement" }], hub: "https://test.skohub.io/hub", prefLabel: { de: "Konzept 1", From 2ba201dcea784f5032c1c9c76e80a374b15a39d2 Mon Sep 17 00:00:00 2001 From: "@s.roertgen" Date: Wed, 7 Feb 2024 09:19:51 +0100 Subject: [PATCH 07/10] Add info about marking concepts as deprecated. #287 --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index aef41d9..7532566 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,12 @@ To determine the language displayed of the vocabulary the browser language is us If the browser language is not present in the vocabulary a default language is chosen. If you want to link to a specific language, you can use a URL parameter: `?lang=de`. +## Deprecation of Concepts + +To mark a concept as deprecated you can mark it with `owl:deprecated true`. +To point to a successor add `dct:isReplacedBy`. +The information will be available in the machine readable version as well as in the html page. + ## Set up ### Install Node.js From 1d97e81ed16d648d909370b3be4f25e115b2b5c9 Mon Sep 17 00:00:00 2001 From: "@s.roertgen" Date: Wed, 7 Feb 2024 10:03:10 +0100 Subject: [PATCH 08/10] Align headline #287 --- src/components/Concept.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Concept.jsx b/src/components/Concept.jsx index 6eb2b2c..0cf5c65 100644 --- a/src/components/Concept.jsx +++ b/src/components/Concept.jsx @@ -29,7 +29,7 @@ const Concept = ({ {concept.isReplacedBy && concept.isReplacedBy.length > 0 && (
    -

    isReplacedBy

    +

    Is replaced by

      {concept.isReplacedBy.map((isReplacedBy) => (
    • From 1e9bdc80a964c454eaae9ef47c096c0608fc6ed0 Mon Sep 17 00:00:00 2001 From: "@s.roertgen" Date: Wed, 7 Feb 2024 10:06:22 +0100 Subject: [PATCH 09/10] Fix test for aligned headline #287 --- test/concept.test.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/concept.test.jsx b/test/concept.test.jsx index 4298d29..4272965 100644 --- a/test/concept.test.jsx +++ b/test/concept.test.jsx @@ -240,7 +240,7 @@ describe.concurrent("Concept", () => { it("adds a isReplacedBy notice if concept is replaced", () => { render() expect( - screen.getByRole("heading", { name: /isReplacedBy/i }) + screen.getByRole("heading", { name: /is replaced by/i }) ).toBeInTheDocument() const linkElement = screen.getByRole("link", { name: "http://w3id.org/replacement", From 9d3814adcedac7053919077133e10cdd771ffa4f Mon Sep 17 00:00:00 2001 From: "@s.roertgen" Date: Wed, 7 Feb 2024 14:55:50 +0100 Subject: [PATCH 10/10] Use brand colors for deprecation notice --- src/components/Concept.jsx | 8 +++++--- src/components/nestedList.jsx | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/Concept.jsx b/src/components/Concept.jsx index 0cf5c65..58702c9 100644 --- a/src/components/Concept.jsx +++ b/src/components/Concept.jsx @@ -1,7 +1,7 @@ import Markdown from "markdown-to-jsx" import { Link } from "gatsby" import JsonLink from "./JsonLink.jsx" -import { getConceptSchemes } from "../hooks/getConceptSchemes" +import { getConfigAndConceptSchemes } from "../hooks/configAndConceptSchemes.js" import { useSkoHubContext } from "../context/Context.jsx" import { i18n, getDomId, getFilePath } from "../common" import ConceptURI from "./ConceptURI.jsx" @@ -10,7 +10,7 @@ import { useEffect, useState } from "react" const Concept = ({ pageContext: { node: concept, collections, customDomain }, }) => { - const conceptSchemes = getConceptSchemes() + const { config, conceptSchemes } = getConfigAndConceptSchemes() const { data } = useSkoHubContext() const [language, setLanguage] = useState("") @@ -20,7 +20,9 @@ const Concept = ({ return (
      -

      {concept.deprecated ? "Deprecated" : ""}

      +

      + {concept.deprecated ? "Deprecated" : ""} +

      {concept.notation && {concept.notation.join(",")} } {i18n(language)(concept.prefLabel)} diff --git a/src/components/nestedList.jsx b/src/components/nestedList.jsx index 581f38b..7a9e15f 100644 --- a/src/components/nestedList.jsx +++ b/src/components/nestedList.jsx @@ -192,7 +192,7 @@ const NestedList = ({ // Function for handling highlighting function handleHighlight(text, highlight) { text = item.deprecated - ? `(DEPRECATED) ${text}` + ? `(DEPRECATED) ${text}` : text if (highlight) { return text.replace(highlight, (str) => `${str}`)