Skip to content

Commit

Permalink
Merge pull request #291 from skohub-io/287-deprecated-concepts
Browse files Browse the repository at this point in the history
287 deprecated concepts
  • Loading branch information
sroertgen authored Feb 7, 2024
2 parents 538919d + 9d3814a commit b7d07de
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 9 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions gatsby-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ exports.onPreBootstrap = async ({ createContentDigest, actions, getNode }) => {
topConceptOf,
hasTopConcept,
member,
deprecated,
...properties
} = graph
const type = Array.isArray(properties.type)
Expand Down Expand Up @@ -174,6 +175,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
Expand Down
25 changes: 20 additions & 5 deletions src/components/Concept.jsx
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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("")

Expand All @@ -20,12 +20,29 @@ const Concept = ({

return (
<div className="content block main-block" id={getDomId(concept.id)}>
<h1 style={{ color: config.colors.skoHubAction }}>
{concept.deprecated ? "Deprecated" : ""}
</h1>
<h1>
{concept.notation && <span>{concept.notation.join(",")}&nbsp;</span>}
{i18n(language)(concept.prefLabel)}
</h1>
<ConceptURI id={concept.id} />
<JsonLink to={getFilePath(concept.id, "json", customDomain)} />
{concept.isReplacedBy && concept.isReplacedBy.length > 0 && (
<div>
<h3>Is replaced by</h3>
<ul>
{concept.isReplacedBy.map((isReplacedBy) => (
<li key={isReplacedBy.id}>
<Link to={getFilePath(isReplacedBy.id, `html`, customDomain)}>
{isReplacedBy.id}
</Link>
</li>
))}
</ul>
</div>
)}
{concept.definition && (
<div className="markdown">
<h3>Definition</h3>
Expand Down Expand Up @@ -88,9 +105,7 @@ const Concept = ({
<ul>
{concept.related.map((related) => (
<li key={related.id}>
<Link
to={getFilePath(related.id, `${language}.html`, customDomain)}
>
<Link to={getFilePath(related.id, `html`, customDomain)}>
{i18n(language)(related.prefLabel) || related.id}
</Link>
</li>
Expand Down
3 changes: 3 additions & 0 deletions src/components/nestedList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,9 @@ const NestedList = ({
const renderPrefLabel = () => {
// Function for handling highlighting
function handleHighlight(text, highlight) {
text = item.deprecated
? `<span style="color: ${config.colors.skoHubAction} ">(DEPRECATED)</span> ${text}`
: text
if (highlight) {
return text.replace(highlight, (str) => `<strong>${str}</strong>`)
} else {
Expand Down
9 changes: 9 additions & 0 deletions src/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -95,6 +96,14 @@ const jsonld = {
topConceptOf: {
"@container": "@set",
},
deprecated: {
"@id": "owl:deprecated",
"@type": "xsd:boolean",
},
isReplacedBy: {
"@id": "dct:isReplacedBy",
"@container": "@set",
},
},
}

Expand Down
5 changes: 5 additions & 0 deletions src/queries.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ module.exports.allConcept = (inScheme, languages) => `
${[...languages].join(" ")}
}
}
deprecated
isReplacedBy {
id
}
}
}
}
Expand Down Expand Up @@ -178,6 +182,7 @@ module.exports.allConceptScheme = (languages) => `
example {
${[...languages].join(" ")}
}
deprecated
}
`
module.exports.tokenizer = `{
Expand Down
4 changes: 3 additions & 1 deletion src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ module.exports = (languages) => `
exactMatch: [Concept],
inScheme: [ConceptScheme] @link(from: "inScheme___NODE"),
inSchemeAll: [ConceptScheme],
hub: String
hub: String,
deprecated: Boolean,
isReplacedBy: [Concept]
}
type LanguageMap {
Expand Down
29 changes: 27 additions & 2 deletions test/concept.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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", () => {
Expand Down Expand Up @@ -226,4 +229,26 @@ 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(<Concept pageContext={ConceptPCDeprecated} />)
expect(
screen.getByRole("heading", { name: /Deprecated/i })
).toBeInTheDocument()
})

it("adds a isReplacedBy notice if concept is replaced", () => {
render(<Concept pageContext={ConceptPCDeprecated} />)
expect(
screen.getByRole("heading", { name: /is replaced by/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$/)
})
})
36 changes: 36 additions & 0 deletions test/data/pageContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ const concept2 = {
inScheme: [{ id: "http://w3id.org/" }],
}

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",
en: "Concept 1",
},
narrower: [concept2],
topConceptOf: null,
}

export const topConcept = {
id: "http://w3id.org/c1",
type: "Concept",
Expand Down Expand Up @@ -98,6 +112,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",
Expand Down
17 changes: 16 additions & 1 deletion test/nestedList.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -59,4 +59,19 @@ 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(
<NestedList
items={ConceptSchemeDeprecated.hasTopConcept}
current={"http://w3id.org/c1"}
filter={null}
highlight={null}
language={"de"}
/>
)
expect(
screen.getByRole("link", { name: "(DEPRECATED) Konzept 1" })
).toBeInTheDocument()
})
})

0 comments on commit b7d07de

Please sign in to comment.