From ff13455b56647b9aefcac6f389929a4856c6ee2d Mon Sep 17 00:00:00 2001 From: "@s.roertgen" Date: Tue, 26 Mar 2024 16:33:14 +0100 Subject: [PATCH 1/4] WIP: Allow multiple entries for `note` and `scopeNote` #281 --- src/components/Concept.jsx | 24 +++++++++++++----------- src/context.js | 4 ++-- src/types.js | 4 ++-- test/data/pageContext.js | 4 ++-- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/components/Concept.jsx b/src/components/Concept.jsx index 58702c9..f570293 100644 --- a/src/components/Concept.jsx +++ b/src/components/Concept.jsx @@ -52,22 +52,24 @@ const Concept = ({ )} - {concept.scopeNote && ( + {concept.scopeNote && i18n(language)(concept.scopeNote) !== "" && (
-

Scope Note

- - {i18n(language)(concept.scopeNote) || - `*No scope note in language "${language}" provided.*`} - +

ScopeNote

+
)} - {concept.note && ( + {concept.note && i18n(language)(concept.note) !== "" && (

Note

- - {i18n(language)(concept.note) || - `*No note in language "${language}" provided.*`} - +
)} {concept.altLabel && i18n(language)(concept.altLabel) !== "" && ( diff --git a/src/context.js b/src/context.js index 0ae32b3..92eddf8 100644 --- a/src/context.js +++ b/src/context.js @@ -49,10 +49,10 @@ const jsonld = { "@container": "@language", }, scopeNote: { - "@container": "@language", + "@container": ["@language", "@set"], }, note: { - "@container": "@language", + "@container": ["@language", "@set"], }, notation: { "@container": "@set", diff --git a/src/types.js b/src/types.js index 911dc60..223dee9 100644 --- a/src/types.js +++ b/src/types.js @@ -20,8 +20,8 @@ module.exports = (languages) => ` altLabel: LanguageMapArray, hiddenLabel: LanguageMapArray, definition: LanguageMap, - scopeNote: LanguageMap, - note: LanguageMap, + scopeNote: LanguageMapArray, + note: LanguageMapArray, notation: [String], example: LanguageMap, topConceptOf: [ConceptScheme] @link(from: "topConceptOf___NODE"), diff --git a/test/data/pageContext.js b/test/data/pageContext.js index 116a23f..b54ad52 100644 --- a/test/data/pageContext.js +++ b/test/data/pageContext.js @@ -50,10 +50,10 @@ export const topConcept = { de: "Ein Beispiel", }, scopeNote: { - de: "Meine Scope Note", + de: ["Meine Scope Note"], }, note: { - de: "Meine Anmerkung", + de: ["Meine Anmerkung"], }, notation: ["1"], narrower: [concept2], From 828155f97c2c7862f9bcd293a59cb37e5a0423b3 Mon Sep 17 00:00:00 2001 From: "@s.roertgen" Date: Wed, 27 Mar 2024 09:13:25 +0100 Subject: [PATCH 2/4] Add changeNote, editorialNote, historyNote, add tests for notes #281 --- src/components/Concept.jsx | 51 ++++++++++++++++++++++++------ src/context.js | 13 ++++++-- src/queries.js | 13 ++++++-- src/types.js | 5 ++- test/concept.test.jsx | 65 ++++++++++++++++++++++++++++++++++++++ test/data/pageContext.js | 17 +++++++--- 6 files changed, 145 insertions(+), 19 deletions(-) diff --git a/src/components/Concept.jsx b/src/components/Concept.jsx index f570293..2834973 100644 --- a/src/components/Concept.jsx +++ b/src/components/Concept.jsx @@ -52,22 +52,53 @@ const Concept = ({ )} - {concept.scopeNote && i18n(language)(concept.scopeNote) !== "" && ( + {concept.note && i18n(language)(concept.note) !== "" && (
-

ScopeNote

-
    - {i18n(language)(concept.scopeNote).map((scopeNote, i) => ( -
  • {scopeNote}
  • +

    Note

    +
      + {i18n(language)(concept.note).map((note, i) => ( +
    • {note}
    • ))}
)} - {concept.note && i18n(language)(concept.note) !== "" && ( + {concept.changeNote && i18n(language)(concept.changeNote) !== "" && (
-

Note

-
    - {i18n(language)(concept.note).map((note, i) => ( -
  • {note}
  • +

    ChangeNote

    +
      + {i18n(language)(concept.changeNote).map((changeNote, i) => ( +
    • {changeNote}
    • + ))} +
    +
+ )} + {concept.editorialNote && + i18n(language)(concept.editorialNote) !== "" && ( +
+

EditorialNote

+
    + {i18n(language)(concept.editorialNote).map((editorialNote, i) => ( +
  • {editorialNote}
  • + ))} +
+
+ )} + {concept.historyNote && i18n(language)(concept.historyNote) !== "" && ( +
+

HistoryNote

+
    + {i18n(language)(concept.historyNote).map((historyNote, i) => ( +
  • {historyNote}
  • + ))} +
+
+ )} + {concept.scopeNote && i18n(language)(concept.scopeNote) !== "" && ( +
+

ScopeNote

+
    + {i18n(language)(concept.scopeNote).map((scopeNote, i) => ( +
  • {scopeNote}
  • ))}
diff --git a/src/context.js b/src/context.js index 92eddf8..127ced6 100644 --- a/src/context.js +++ b/src/context.js @@ -48,10 +48,19 @@ const jsonld = { definition: { "@container": "@language", }, - scopeNote: { + note: { "@container": ["@language", "@set"], }, - note: { + changeNote: { + "@container": ["@language", "@set"], + }, + editorialNote: { + "@container": ["@language", "@set"], + }, + historyNote: { + "@container": ["@language", "@set"], + }, + scopeNote: { "@container": ["@language", "@set"], }, notation: { diff --git a/src/queries.js b/src/queries.js index da97cb4..d14c8ca 100644 --- a/src/queries.js +++ b/src/queries.js @@ -49,10 +49,19 @@ module.exports.allConcept = (inScheme, languages) => ` definition { ${[...languages].join(" ")} } - scopeNote { + note { ${[...languages].join(" ")} } - note { + changeNote { + ${[...languages].join(" ")} + } + editorialNote { + ${[...languages].join(" ")} + } + historyNote { + ${[...languages].join(" ")} + } + scopeNote { ${[...languages].join(" ")} } notation diff --git a/src/types.js b/src/types.js index 223dee9..e189dc4 100644 --- a/src/types.js +++ b/src/types.js @@ -20,8 +20,11 @@ module.exports = (languages) => ` altLabel: LanguageMapArray, hiddenLabel: LanguageMapArray, definition: LanguageMap, - scopeNote: LanguageMapArray, note: LanguageMapArray, + changeNote: LanguageMapArray, + editorialNote: LanguageMapArray, + historyNote: LanguageMapArray, + scopeNote: LanguageMapArray, notation: [String], example: LanguageMap, topConceptOf: [ConceptScheme] @link(from: "topConceptOf___NODE"), diff --git a/test/concept.test.jsx b/test/concept.test.jsx index 4272965..3667f8d 100644 --- a/test/concept.test.jsx +++ b/test/concept.test.jsx @@ -125,6 +125,71 @@ describe.concurrent("Concept", () => { ).toBeInTheDocument() }) + it("renders notes", () => { + render() + + expect(screen.getByText(/Meine Anmerkung/i)).toBeInTheDocument() + + const list = screen.getByRole("list", { + name: "Note", + }) + const { getAllByRole } = within(list) + const items = getAllByRole("listitem") + expect(items.length).toBe(2) + }) + + it("renders changeNotes", () => { + render() + + expect(screen.getByText(/Meine Change Note/i)).toBeInTheDocument() + + const list = screen.getByRole("list", { + name: /changenote/i, + }) + const { getAllByRole } = within(list) + const items = getAllByRole("listitem") + expect(items.length).toBe(2) + }) + + it("renders editorialNotes", () => { + render() + + expect(screen.getByText(/Meine Editorial Note/i)).toBeInTheDocument() + + const list = screen.getByRole("list", { + name: /editorialnote/i, + }) + const { getAllByRole } = within(list) + const items = getAllByRole("listitem") + expect(items.length).toBe(2) + }) + + it("renders historyNotes", () => { + render() + + expect(screen.getByText(/Meine History Note/i)).toBeInTheDocument() + + const list = screen.getByRole("list", { + name: /historynote/i, + }) + const { getAllByRole } = within(list) + const items = getAllByRole("listitem") + expect(items.length).toBe(2) + }) + + it("renders scopeNotes", () => { + render() + + expect(screen.getByText(/Meine Scope Note/i)).toBeInTheDocument() + + const list = screen.getByRole("list", { + name: /scopenote/i, + }) + const { getAllByRole } = within(list) + const items = getAllByRole("listitem") + expect(items.length).toBe(2) + }) + it("renders related Concepts", () => { render() expect( diff --git a/test/data/pageContext.js b/test/data/pageContext.js index b54ad52..c593355 100644 --- a/test/data/pageContext.js +++ b/test/data/pageContext.js @@ -49,11 +49,20 @@ export const topConcept = { example: { de: "Ein Beispiel", }, - scopeNote: { - de: ["Meine Scope Note"], - }, note: { - de: ["Meine Anmerkung"], + de: ["Meine Anmerkung", "Noch eine Anmerkung"], + }, + changeNote: { + de: ["Meine Change Note", "Noch eine Change Note"], + }, + editorialNote: { + de: ["Meine Editorial Note", "Noch eine Editorial Note"], + }, + historyNote: { + de: ["Meine History Note", "Noch eine History Note"], + }, + scopeNote: { + de: ["Meine Scope Note", "Noch eine Scope Note"], }, notation: ["1"], narrower: [concept2], From 0f62218a81287577736b953f4c225deafa197763 Mon Sep 17 00:00:00 2001 From: "@s.roertgen" Date: Wed, 27 Mar 2024 09:49:52 +0100 Subject: [PATCH 3/4] WIP Add scopeNote to search widget #285 --- config.default.yaml | 1 + gatsby-node.js | 5 +++++ src/components/Search.jsx | 2 +- src/queries.js | 3 +++ src/templates/helpers.js | 5 +++++ 5 files changed, 15 insertions(+), 1 deletion(-) diff --git a/config.default.yaml b/config.default.yaml index 06fab5e..e1f6e95 100644 --- a/config.default.yaml +++ b/config.default.yaml @@ -10,6 +10,7 @@ searchableAttributes: - "hiddenLabel" - "example" - "definition" + - "scopeNote" ui: title: "SkoHub Vocabs" # Title is mandatory logo: "skohub-signet-color.svg" # Path diff --git a/gatsby-node.js b/gatsby-node.js index 04fa0d7..9f6c549 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -296,6 +296,7 @@ exports.createPages = async ({ graphql, actions: { createPage } }) => { "hiddenLabel", "definition", "example", + "scopeNote" ], }, }) @@ -364,6 +365,10 @@ exports.createPages = async ({ graphql, actions: { createPage } }) => { Object.hasOwn(concept.example, language) && { example: i18n(language)(concept.example), }), + ...(concept.scopeNote && + Object.hasOwn(concept.scopeNote, language) && { + scopeNote: i18n(language)(concept.scopeNote), + }), notation: concept.notation, } indexes[language].add(document) diff --git a/src/components/Search.jsx b/src/components/Search.jsx index 0a790e8..22568ba 100644 --- a/src/components/Search.jsx +++ b/src/components/Search.jsx @@ -53,7 +53,7 @@ const Search = ({ handleQueryInput, labels, onLabelClick }) => { closeModal={() => setModal(false)} id="settingsModal" > -

Which labels do you want to include in the search?

+

Which fields do you want to include in the search?

diff --git a/src/queries.js b/src/queries.js index d14c8ca..1211c14 100644 --- a/src/queries.js +++ b/src/queries.js @@ -191,6 +191,9 @@ module.exports.allConceptScheme = (languages) => ` example { ${[...languages].join(" ")} } + scopeNote { + ${[...languages].join(" ")} + } deprecated } ` diff --git a/src/templates/helpers.js b/src/templates/helpers.js index fafe5ec..6e87d6b 100644 --- a/src/templates/helpers.js +++ b/src/templates/helpers.js @@ -43,6 +43,10 @@ export const handleKeypresses = (labels, setLabels) => { e.preventDefault() Object.keys(labels).includes("hiddenLabel") && setLabels({ ...labels, ["hiddenLabel"]: !labels["hiddenLabel"] }) + } else if (e.altKey && e.which === 83) { + e.preventDefault() + Object.keys(labels).includes("scopeNote") && + setLabels({ ...labels, ["scopeNote"]: !labels["scopeNote"] }) } } document.addEventListener("keydown", handleKeyDown) @@ -75,6 +79,7 @@ export const importIndex = async ( "hiddenLabel", "definition", "example", + "scopeNote" ], }, }) From 9ebdd203c926b6564642e0bf0f8dd65b037db71a Mon Sep 17 00:00:00 2001 From: "@s.roertgen" Date: Thu, 28 Mar 2024 10:19:41 +0100 Subject: [PATCH 4/4] Add `scopeNote` as searchable field. #285 Added test for scopeNote. Added documentation on how to add searchable fields. --- README.md | 20 ++++++++++++++ cypress/config.e2e.yaml | 1 + cypress/e2e/searchAndFilter.cy.js | 14 ++++++++++ gatsby-node.js | 10 +------ src/common.js | 1 + src/hooks/configAndConceptSchemes.js | 40 ++++++++++++++++++++++++++++ src/templates/App.jsx | 2 +- src/templates/helpers.js | 18 +++++-------- 8 files changed, 84 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 7532566..5536142 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,7 @@ You can configure the following settings: - Logo - Colors - Fonts +- Searchable Fields The settings are explained in the following sections. @@ -275,6 +276,25 @@ url: [String] This will add the `url` field, being an array of strings. For other types, compare with already existing properties and just copy as you need. +## Adding Searchable Fields + +To add a field to be searchable you have to make the following adjustments: + +- Add the field in the `config.yaml` file to `searchableAttributes`, e.g. `editorialNote` +- In `src/queries.js` add it to `ConceptFields` (around line 176): +```graphql +editorialNote { + ${[...languages].join(" ")} +} +``` +- Add it to the labels to be indexed. Go to `gatsby-node.js` and add it the document object around line 341. For fields being *single* language tagged labels (e.g. `skos:prefLabel`) use `prefLabel` as an example. For fields being arrays of language tagged labels (e.g. `skos:altLabel`) use `altLabel` as an example. For `skos:editorialNote` it would be: +```js +...(concept.editorialNote && +Object.hasOwn(concept.editorialNote, language) && { + editorialNote: i18n(language)(concept.editorialNote), +}), +``` + ## Troubleshooting Depending on special circumstances you may get errors in the log files, e.g. diff --git a/cypress/config.e2e.yaml b/cypress/config.e2e.yaml index 4b2ceb4..8046a3f 100644 --- a/cypress/config.e2e.yaml +++ b/cypress/config.e2e.yaml @@ -10,6 +10,7 @@ searchableAttributes: - "hiddenLabel" - "example" - "definition" + - "scopeNote" ui: title: "SkoHub Vocabs" # Title is mandatory logo: "skohub-signet-color.svg" # Path diff --git a/cypress/e2e/searchAndFilter.cy.js b/cypress/e2e/searchAndFilter.cy.js index b64e0f0..38503dc 100644 --- a/cypress/e2e/searchAndFilter.cy.js +++ b/cypress/e2e/searchAndFilter.cy.js @@ -151,4 +151,18 @@ describe("search and filter", () => { cy.get("#closeModal").click() cy.get("span").contains("Konzept 1").should("exist") }) + + it("turning on scopeNote checkbox returns scopeNote matches", () => { + cy.visit("/w3id.org/index.html", { + onBeforeLoad(win) { + Object.defineProperty(win.navigator, "language", { value: "de-DE" }) + }, + }) + cy.findByRole("textbox").type("Scope") + cy.get("p").contains("Nothing found").should("exist") + cy.get("#settings").click() + cy.get("#scopeNoteCheckBox").click() + cy.get("#closeModal").click() + cy.get("span").contains("Konzept 1").should("exist") + }) }) diff --git a/gatsby-node.js b/gatsby-node.js index 9f6c549..e0db5a3 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -289,15 +289,7 @@ exports.createPages = async ({ graphql, actions: { createPage } }) => { document: { id: "id", // store: ["prefLabel", "altLabel"], /* not working when importing, bug in flexsearch */ - index: [ - "notation", - "prefLabel", - "altLabel", - "hiddenLabel", - "definition", - "example", - "scopeNote" - ], + index: [...config.searchableAttributes], }, }) return [l, index] diff --git a/src/common.js b/src/common.js index 54c0efd..9064e1a 100644 --- a/src/common.js +++ b/src/common.js @@ -98,6 +98,7 @@ const parseLanguages = (graph) => { * @property {string} tokenizer * @property {Object} colors * @property {string} customDomain + * @property {string[]} searchableAttributes */ /** diff --git a/src/hooks/configAndConceptSchemes.js b/src/hooks/configAndConceptSchemes.js index 2becd67..161f618 100644 --- a/src/hooks/configAndConceptSchemes.js +++ b/src/hooks/configAndConceptSchemes.js @@ -1,5 +1,45 @@ import { useStaticQuery, graphql } from "gatsby" +/** + * @returns {{ + * config: { + * colors: { + * skoHubWhite: string, + * skoHubDarkColor: string, + * skoHubMiddleColor: string, + * skoHubLightColor: string, + * skoHubThinColor: string, + * skoHubBlackColor: string, + * skoHubAction: string, + * skoHubNotice: string, + * skoHubDarkGrey: string, + * skoHubMiddleGrey: string, + * skoHubLightGrey: string + * }, + * logo: string, + * title: string, + * fonts: { + * bold: { + * font_family: string, + * font_style: string, + * font_weight: string, + * name: string + * }, + * regular: { + * font_family: string, + * font_style: string, + * font_weight: string, + * name: string + * } + * }, + * searchableAttributes: string[], + * customDomain: string, + * failOnValidation: boolean + * }, + * conceptSchemes: Object + * }} An object containing `config` and `conceptSchemes` + * + */ export const getConfigAndConceptSchemes = () => { const { site, allConceptScheme } = useStaticQuery(graphql` query Colors { diff --git a/src/templates/App.jsx b/src/templates/App.jsx index 57cd894..52d8d9e 100644 --- a/src/templates/App.jsx +++ b/src/templates/App.jsx @@ -130,7 +130,7 @@ const App = ({ pageContext, children, location }) => { labels, data.selectedLanguage, setIndex, - config.customDomain + config ) }, [data, language, labels]) diff --git a/src/templates/helpers.js b/src/templates/helpers.js index 6e87d6b..c28d1b8 100644 --- a/src/templates/helpers.js +++ b/src/templates/helpers.js @@ -1,6 +1,6 @@ import { useEffect } from "react" import Document from "flexsearch/dist/module/document.js" -import { i18n, getFilePath } from "../common" +import { getFilePath } from "../common" import { withPrefix } from "gatsby" export const handleKeypresses = (labels, setLabels) => { @@ -62,7 +62,7 @@ export const importIndex = async ( labels, language, setIndex, - customDomain + config ) => { if (!conceptSchemeId) return const idx = new Document({ @@ -72,15 +72,7 @@ export const importIndex = async ( document: { id: "id", // store: ["prefLabel", "altLabel"], /* not working flexsearchside */ - index: [ - "notation", - "prefLabel", - "altLabel", - "hiddenLabel", - "definition", - "example", - "scopeNote" - ], + index: [...config.searchableAttributes], }, }) // filter from labels object the selected entries @@ -102,7 +94,9 @@ export const importIndex = async ( try { const path = getFilePath(conceptSchemeId) + `-cs/search/${language}/${key}` - data = await fetch(withPrefix(getFilePath(path, `json`, customDomain))) + data = await fetch( + withPrefix(getFilePath(path, `json`, config.customDomain)) + ) const jsonData = await data.json() idx.import(key, jsonData ?? null) } catch (e) {