Skip to content

Commit 8af916b

Browse files
committed
Merge branch '287-deprecated-concepts' into dev
2 parents 0c80242 + 1d97e81 commit 8af916b

10 files changed

+129
-7
lines changed

README.md

+12
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,18 @@ In general we advise the use of slash URIs for SKOS vocabularies.
1717

1818
If you would like more support of hash URIs for SkoHub Vocabs, [please open an issue](https://github.com/skohub-io/skohub-vocabs/issues/new/choose).
1919

20+
## Internationalization
21+
22+
To determine the language displayed of the vocabulary the browser language is used.
23+
If the browser language is not present in the vocabulary a default language is chosen.
24+
If you want to link to a specific language, you can use a URL parameter: `?lang=de`.
25+
26+
## Deprecation of Concepts
27+
28+
To mark a concept as deprecated you can mark it with `owl:deprecated true`.
29+
To point to a successor add `dct:isReplacedBy`.
30+
The information will be available in the machine readable version as well as in the html page.
31+
2032
## Set up
2133

2234
### Install Node.js

gatsby-node.js

+2
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ exports.onPreBootstrap = async ({ createContentDigest, actions, getNode }) => {
143143
topConceptOf,
144144
hasTopConcept,
145145
member,
146+
deprecated,
146147
...properties
147148
} = graph
148149
const type = Array.isArray(properties.type)
@@ -173,6 +174,7 @@ exports.onPreBootstrap = async ({ createContentDigest, actions, getNode }) => {
173174
* a concept scheme not present in the graphql data layer would not be found and not
174175
* be shown on the concepts page.
175176
*/
177+
deprecated: Boolean(deprecated) || null,
176178
inSchemeAll:
177179
inSchemeFiltered.map((inScheme) => ({ id: inScheme.id })) || null,
178180
// topConceptOf nodes are also set to inScheme to facilitate parsing and filtering later

src/components/Concept.jsx

+16-3
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,27 @@ const Concept = ({
2020

2121
return (
2222
<div className="content block main-block" id={getDomId(concept.id)}>
23+
<h1 style={{ color: "red" }}>{concept.deprecated ? "Deprecated" : ""}</h1>
2324
<h1>
2425
{concept.notation && <span>{concept.notation.join(",")}&nbsp;</span>}
2526
{i18n(language)(concept.prefLabel)}
2627
</h1>
2728
<ConceptURI id={concept.id} />
2829
<JsonLink to={getFilePath(concept.id, "json", customDomain)} />
30+
{concept.isReplacedBy && concept.isReplacedBy.length > 0 && (
31+
<div>
32+
<h3>Is replaced by</h3>
33+
<ul>
34+
{concept.isReplacedBy.map((isReplacedBy) => (
35+
<li key={isReplacedBy.id}>
36+
<Link to={getFilePath(isReplacedBy.id, `html`, customDomain)}>
37+
{isReplacedBy.id}
38+
</Link>
39+
</li>
40+
))}
41+
</ul>
42+
</div>
43+
)}
2944
{concept.definition && (
3045
<div className="markdown">
3146
<h3>Definition</h3>
@@ -88,9 +103,7 @@ const Concept = ({
88103
<ul>
89104
{concept.related.map((related) => (
90105
<li key={related.id}>
91-
<Link
92-
to={getFilePath(related.id, `${language}.html`, customDomain)}
93-
>
106+
<Link to={getFilePath(related.id, `html`, customDomain)}>
94107
{i18n(language)(related.prefLabel) || related.id}
95108
</Link>
96109
</li>

src/components/nestedList.jsx

+3
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@ const NestedList = ({
191191
const renderPrefLabel = () => {
192192
// Function for handling highlighting
193193
function handleHighlight(text, highlight) {
194+
text = item.deprecated
195+
? `<span style="color: red">(DEPRECATED)</span> ${text}`
196+
: text
194197
if (highlight) {
195198
return text.replace(highlight, (str) => `<strong>${str}</strong>`)
196199
} else {

src/context.js

+9
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const jsonld = {
99
schema: "https://schema.org/",
1010
vann: "http://purl.org/vocab/vann/",
1111
ldp: "http://www.w3.org/ns/ldp#",
12+
owl: "http://www.w3.org/2002/07/owl#",
1213
title: {
1314
"@id": "dct:title",
1415
"@container": "@language",
@@ -95,6 +96,14 @@ const jsonld = {
9596
topConceptOf: {
9697
"@container": "@set",
9798
},
99+
deprecated: {
100+
"@id": "owl:deprecated",
101+
"@type": "xsd:boolean",
102+
},
103+
isReplacedBy: {
104+
"@id": "dct:isReplacedBy",
105+
"@container": "@set",
106+
},
98107
},
99108
}
100109

src/queries.js

+5
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ module.exports.allConcept = (inScheme, languages) => `
119119
${[...languages].join(" ")}
120120
}
121121
}
122+
deprecated
123+
isReplacedBy {
124+
id
125+
}
122126
}
123127
}
124128
}
@@ -178,6 +182,7 @@ module.exports.allConceptScheme = (languages) => `
178182
example {
179183
${[...languages].join(" ")}
180184
}
185+
deprecated
181186
}
182187
`
183188
module.exports.tokenizer = `{

src/types.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ module.exports = (languages) => `
3737
exactMatch: [Concept],
3838
inScheme: [ConceptScheme] @link(from: "inScheme___NODE"),
3939
inSchemeAll: [ConceptScheme],
40-
hub: String
40+
hub: String,
41+
deprecated: Boolean,
42+
isReplacedBy: [Concept]
4143
}
4244
4345
type LanguageMap {

test/concept.test.jsx

+27-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as Gatsby from "gatsby"
44

55
import React from "react"
66
import Concept from "../src/components/Concept.jsx"
7-
import { ConceptPC } from "./data/pageContext"
7+
import { ConceptPC, ConceptPCDeprecated } from "./data/pageContext"
88
import mockFetch from "./mocks/mockFetch"
99
import { mockConfig } from "./mocks/mockConfig"
1010
import { useSkoHubContext } from "../src/context/Context.jsx"
@@ -130,7 +130,10 @@ describe.concurrent("Concept", () => {
130130
expect(
131131
screen.getByRole("heading", { name: /^related$/i })
132132
).toBeInTheDocument()
133-
expect(screen.getByRole("link", { name: /konzept 4/i })).toBeInTheDocument()
133+
const href = screen.getByRole("link", { name: /konzept 4/i })
134+
expect(href).toBeInTheDocument()
135+
// ensure there is no language tag in the link
136+
expect(href.getAttribute("href")).not.toMatch(/\..{2}\.html$/)
134137
})
135138

136139
it("renders narrow matches", () => {
@@ -226,4 +229,26 @@ describe.concurrent("Concept", () => {
226229
screen.getByRole("link", { name: /just-another-scheme/i })
227230
).toHaveAttribute("href", "http://just-another-scheme.org/")
228231
})
232+
233+
it("renders deprecated notice, if concept is deprecaed", () => {
234+
render(<Concept pageContext={ConceptPCDeprecated} />)
235+
expect(
236+
screen.getByRole("heading", { name: /Deprecated/i })
237+
).toBeInTheDocument()
238+
})
239+
240+
it("adds a isReplacedBy notice if concept is replaced", () => {
241+
render(<Concept pageContext={ConceptPCDeprecated} />)
242+
expect(
243+
screen.getByRole("heading", { name: /isReplacedBy/i })
244+
).toBeInTheDocument()
245+
const linkElement = screen.getByRole("link", {
246+
name: "http://w3id.org/replacement",
247+
}) // Adjust the query to match your link
248+
const href = linkElement.getAttribute("href")
249+
250+
// Assert the URL ends with .html but not .xx.html
251+
expect(href).toMatch(/\.html$/)
252+
expect(href).not.toMatch(/\..{2}\.html$/)
253+
})
229254
})

test/data/pageContext.js

+36
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,20 @@ const concept2 = {
1414
inScheme: [{ id: "http://w3id.org/" }],
1515
}
1616

17+
const topConceptDeprecated = {
18+
id: "http://w3id.org/c1",
19+
type: "Concept",
20+
deprecated: true,
21+
isReplacedBy: [{ id: "http://w3id.org/replacement" }],
22+
hub: "https://test.skohub.io/hub",
23+
prefLabel: {
24+
de: "Konzept 1",
25+
en: "Concept 1",
26+
},
27+
narrower: [concept2],
28+
topConceptOf: null,
29+
}
30+
1731
export const topConcept = {
1832
id: "http://w3id.org/c1",
1933
type: "Concept",
@@ -98,6 +112,28 @@ export const ConceptPC = {
98112
],
99113
}
100114

115+
export const ConceptPCDeprecated = {
116+
node: topConceptDeprecated,
117+
language: "de",
118+
collections: [
119+
{
120+
id: "http://w3id.org/collection",
121+
prefLabel: { de: "Meine Collection", en: "My Collection" },
122+
member: [topConcept, concept2],
123+
},
124+
],
125+
}
126+
127+
export const ConceptSchemeDeprecated = {
128+
id: "http://w3id.org/",
129+
type: "ConceptScheme",
130+
title: {
131+
de: "Test Vokabular",
132+
en: "Test Vocabulary",
133+
},
134+
hasTopConcept: [topConceptDeprecated],
135+
}
136+
101137
export const ConceptScheme = {
102138
id: "http://w3id.org/",
103139
type: "ConceptScheme",

test/nestedList.test.jsx

+16-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"
22
import { render, screen } from "@testing-library/react"
33
import React from "react"
44
import NestedList from "../src/components/nestedList"
5-
import { ConceptScheme } from "./data/pageContext"
5+
import { ConceptScheme, ConceptSchemeDeprecated } from "./data/pageContext"
66
import userEvent from "@testing-library/user-event"
77
import * as Gatsby from "gatsby"
88
import { mockConfig } from "./mocks/mockConfig"
@@ -59,4 +59,19 @@ describe("Nested List", () => {
5959
await user.click(screen.getByRole("button", { expanded: true }))
6060
expect(screen.getByRole("button", { expanded: false }))
6161
})
62+
63+
it("shows deprecation notice for deprecated concepts", () => {
64+
render(
65+
<NestedList
66+
items={ConceptSchemeDeprecated.hasTopConcept}
67+
current={"http://w3id.org/c1"}
68+
filter={null}
69+
highlight={null}
70+
language={"de"}
71+
/>
72+
)
73+
expect(
74+
screen.getByRole("link", { name: "(DEPRECATED) Konzept 1" })
75+
).toBeInTheDocument()
76+
})
6277
})

0 commit comments

Comments
 (0)