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

287 deprecated concepts #291

Merged
merged 10 commits into from
Feb 7, 2024
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 @@ -143,6 +143,7 @@ exports.onPreBootstrap = async ({ createContentDigest, actions, getNode }) => {
topConceptOf,
hasTopConcept,
member,
deprecated,
...properties
} = graph
const type = Array.isArray(properties.type)
Expand Down Expand Up @@ -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
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()
})
})
Loading