From 703198ec4cb11639e143e48e3d33c03be75abc21 Mon Sep 17 00:00:00 2001 From: Aiga115 Date: Thu, 12 Jun 2025 15:44:38 +0200 Subject: [PATCH 1/5] ILEX-124 initial bulk of changes --- .../SingleTermView/OverView/Details.jsx | 2 + .../SingleTermView/OverView/OverView.jsx | 1 + .../SingleTermView/OverView/RawDataViewer.jsx | 1 + src/components/SingleTermView/index.jsx | 48 +++++++++++-------- 4 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/components/SingleTermView/OverView/Details.jsx b/src/components/SingleTermView/OverView/Details.jsx index 8e61303f..553144d7 100644 --- a/src/components/SingleTermView/OverView/Details.jsx +++ b/src/components/SingleTermView/OverView/Details.jsx @@ -35,6 +35,8 @@ const Details = ({loading, data }) => { return
No data available
; } + console.log("data: ", data) + return ( <> diff --git a/src/components/SingleTermView/OverView/OverView.jsx b/src/components/SingleTermView/OverView/OverView.jsx index 28c36d11..21bd5d99 100644 --- a/src/components/SingleTermView/OverView/OverView.jsx +++ b/src/components/SingleTermView/OverView/OverView.jsx @@ -21,6 +21,7 @@ const OverView = ({ searchTerm, isCodeViewVisible, selectedDataFormat }) => { debounce((searchTerm) => { if (searchTerm) { getMatchTerms("base", searchTerm).then(data => { + console.log("data from api call: ", data) setData(data?.results?.[0]); setLoading(false); }); diff --git a/src/components/SingleTermView/OverView/RawDataViewer.jsx b/src/components/SingleTermView/OverView/RawDataViewer.jsx index 421d2d16..07b03d2e 100644 --- a/src/components/SingleTermView/OverView/RawDataViewer.jsx +++ b/src/components/SingleTermView/OverView/RawDataViewer.jsx @@ -36,6 +36,7 @@ const RawDataViewer = ({ dataId, dataFormat }) => { }) }, [dataId, dataFormat]); + console.log("formattedData: ", formattedData) return (
{formattedData ? ( diff --git a/src/components/SingleTermView/index.jsx b/src/components/SingleTermView/index.jsx index ecb13764..2a2972c9 100644 --- a/src/components/SingleTermView/index.jsx +++ b/src/components/SingleTermView/index.jsx @@ -16,7 +16,6 @@ import ToggleButton from '@mui/material/ToggleButton'; import CustomBreadcrumbs from "../common/CustomBreadcrumbs"; import ForkRightIcon from '@mui/icons-material/ForkRight'; import { vars } from "../../theme/variables"; -import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord'; import OntologySearch from "./OntologySearch"; import ModeEditOutlineOutlinedIcon from '@mui/icons-material/ModeEditOutlineOutlined'; import RateReviewOutlinedIcon from '@mui/icons-material/RateReviewOutlined'; @@ -45,10 +44,18 @@ import CreateForkDialog from "./CreateForkDialog"; import TermDialog from "../TermEditor/TermDialog"; import { getSelectedTermLabel } from "../../api/endpoints/apiService"; import { GlobalDataContext } from "../../contexts/DataContext"; +import { getRawData } from "../../api/endpoints"; -const { gray200, brand700, gray600 } = vars; +const { gray200, gray600 } = vars; -const dataFormats = ['JSON-LD', 'Turtle', 'N3', 'OWL', 'CSV'] +const dataFormats = ['JSON-LD', 'Turtle', 'N3', 'OWL', 'CSV']; +const formatExtensions = { + 'JSON-LD': 'jsonld', + 'Turtle': 'ttl', + 'N3': 'n3', + 'OWL': 'owl', + 'CSV': 'csv' +}; const SingleTermView = () => { const [open, setOpen] = useState(false); @@ -93,6 +100,8 @@ const SingleTermView = () => { const handleDataFormatMenuItemClick = (value) => { setSelectedDataFormat(value); setDataFormatAnchorEl(null); + + downloadFormattedData(value); }; const handleOpenRequestMergeDialog = () => { @@ -118,15 +127,28 @@ const SingleTermView = () => { setTabValue(newValue); }; + const downloadFormattedData = (dataFormat) => { + getRawData("base", searchTerm, formatExtensions[dataFormat]).then(rawResponse => { + const formattedData = JSON.stringify(rawResponse, null, 2); + const blob = new Blob([formattedData], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `data.${formatExtensions[dataFormat]}`; + a.click(); + URL.revokeObjectURL(url); + }) + } + const CodeOrTreeIcon = () => { return isCodeViewVisible ? : } const breadcrumbItems = [ { label: '', href: '/', icon: HomeOutlinedIcon }, - { label: 'Term search', href: `/search?searchTerm=${searchTerm}` }, - { label: 'My organization 1', href: '#' }, - { label: 'ILX:0101901' }, + { label: 'Term search', href: `/search?searchTerm=${storedSearchTerm}` }, + { label: 'base', href: '#' }, + { label: searchTerm.toUpperCase().replace("_", ":") }, ]; useEffect(() => { @@ -148,19 +170,7 @@ const SingleTermView = () => { - - - - - fork1 - - } - label="Not merged" - variant="outlined" - className="rounded not-merged" - /> - + Active Ontology: From f1172975466c64cc3e8edf1d105106c75b3d3e6c Mon Sep 17 00:00:00 2001 From: Aiga115 Date: Sun, 15 Jun 2025 16:50:14 +0200 Subject: [PATCH 2/5] ILEX-124 get and format json data --- .../SingleTermView/OverView/Details.jsx | 18 +++-- .../SingleTermView/OverView/OverView.jsx | 20 ++++-- .../SingleTermView/OverView/RawDataViewer.jsx | 1 - src/utils.js | 66 ++++++++++++------- 4 files changed, 69 insertions(+), 36 deletions(-) diff --git a/src/components/SingleTermView/OverView/Details.jsx b/src/components/SingleTermView/OverView/Details.jsx index 553144d7..697ebddf 100644 --- a/src/components/SingleTermView/OverView/Details.jsx +++ b/src/components/SingleTermView/OverView/Details.jsx @@ -7,11 +7,12 @@ import { } from "@mui/material"; import PropTypes from "prop-types"; import OpenInNewOutlinedIcon from '@mui/icons-material/OpenInNewOutlined'; +import { formatTimestamp } from "../../../utils"; import { vars } from "../../../theme/variables"; const { gray800, gray500 } = vars; -const Details = ({loading, data }) => { +const Details = ({ loading, data, jsonData }) => { const handleChipClick = (url) => { window.open(url, '_blank'); }; @@ -34,8 +35,10 @@ const Details = ({loading, data }) => { if (!data) { return
No data available
; } - - console.log("data: ", data) + const graphArray = jsonData["@graph"]; + const lastGraphItem = graphArray[graphArray.length - 1] + const versionIRI = lastGraphItem?.["owl:versionIRI"]?.["@id"]; + const versionInfo = formatTimestamp(lastGraphItem?.["owl:versionInfo"]); return ( <> @@ -77,7 +80,7 @@ const Details = ({loading, data }) => { Existing IDs - {data?.existingID && ( processExistingIds(data?.existingID).map((id) => + {data?.existingID && (processExistingIds(data?.existingID).map((id) => } onClick={() => handleChipClick(id)} /> ))} @@ -113,7 +116,7 @@ const Details = ({loading, data }) => { Version - {data?.versionInfo} + {versionIRI}
@@ -153,7 +156,7 @@ const Details = ({loading, data }) => { Last modify timestamp - {data?.lastModifyTimestamp} + {versionInfo}
@@ -164,7 +167,8 @@ const Details = ({loading, data }) => { Details.propTypes = { loading: PropTypes.bool.isRequired, - data: PropTypes.object + data: PropTypes.object, + jsonData: PropTypes.object }; export default Details; diff --git a/src/components/SingleTermView/OverView/OverView.jsx b/src/components/SingleTermView/OverView/OverView.jsx index 21bd5d99..62e8d0b1 100644 --- a/src/components/SingleTermView/OverView/OverView.jsx +++ b/src/components/SingleTermView/OverView/OverView.jsx @@ -9,13 +9,14 @@ import PropTypes from 'prop-types'; import Hierarchy from "./Hierarchy"; import Predicates from "./Predicates"; import RawDataViewer from "./RawDataViewer"; -import {useCallback, useEffect, useMemo, useState} from "react"; -import { getMatchTerms } from "../../../api/endpoints"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { getMatchTerms, getRawData } from "../../../api/endpoints"; const OverView = ({ searchTerm, isCodeViewVisible, selectedDataFormat }) => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); - + const [jsonData, setJsonData] = useState(null); + // eslint-disable-next-line react-hooks/exhaustive-deps const fetchTerms = useCallback( debounce((searchTerm) => { @@ -29,24 +30,31 @@ const OverView = ({ searchTerm, isCodeViewVisible, selectedDataFormat }) => { }, 300), [] ); - + + const fetchJSONFile = () => { + getRawData("base", searchTerm, 'jsonld').then(rawResponse => { + setJsonData(rawResponse); + }) + } + useEffect(() => { setLoading(true); fetchTerms(searchTerm); + fetchJSONFile(); return () => { fetchTerms.cancel(); }; }, [searchTerm, fetchTerms]); const memoData = useMemo(() => data, [data]); - + return ( {isCodeViewVisible ? : <> -
+
diff --git a/src/components/SingleTermView/OverView/RawDataViewer.jsx b/src/components/SingleTermView/OverView/RawDataViewer.jsx index 07b03d2e..421d2d16 100644 --- a/src/components/SingleTermView/OverView/RawDataViewer.jsx +++ b/src/components/SingleTermView/OverView/RawDataViewer.jsx @@ -36,7 +36,6 @@ const RawDataViewer = ({ dataId, dataFormat }) => { }) }, [dataId, dataFormat]); - console.log("formattedData: ", formattedData) return (
{formattedData ? ( diff --git a/src/utils.js b/src/utils.js index f125688f..e30361fc 100644 --- a/src/utils.js +++ b/src/utils.js @@ -45,29 +45,29 @@ export const stableSort = (array, comparator) => { export function compareArrays(originalArray, modifiedArray) { if (!Array.isArray(originalArray) || !Array.isArray(modifiedArray)) { - console.warn("compareArrays received invalid input", { originalArray, modifiedArray }); - return []; + console.warn("compareArrays received invalid input", { originalArray, modifiedArray }); + return []; } - + return originalArray.filter(item => !modifiedArray.includes(item)); } export function compareSentences(originalText, modifiedText) { - // Split texts into sentences - const originalSentences = originalText.match(/[^.!?]+[.!?]+/g) || []; - const modifiedSentences = modifiedText.match(/[^.!?]+[.!?]+/g) || []; - - // Find sentences in original that are not in modified - const uniqueSentences = originalSentences.filter(sentence => - !modifiedSentences.some(modSentence => - sentence.trim().toLowerCase() === modSentence.trim().toLowerCase() - ) - ); - return uniqueSentences; + // Split texts into sentences + const originalSentences = originalText.match(/[^.!?]+[.!?]+/g) || []; + const modifiedSentences = modifiedText.match(/[^.!?]+[.!?]+/g) || []; + + // Find sentences in original that are not in modified + const uniqueSentences = originalSentences.filter(sentence => + !modifiedSentences.some(modSentence => + sentence.trim().toLowerCase() === modSentence.trim().toLowerCase() + ) + ); + return uniqueSentences; } export function compareStrings(originalString, modifiedString) { - return originalString !== modifiedString ? [originalString] : [] + return originalString !== modifiedString ? [originalString] : [] } export function compareObjects(originalObject, modifiedObject) { @@ -75,11 +75,33 @@ export function compareObjects(originalObject, modifiedObject) { } export function getDataType(data) { - if (Array.isArray(data)) return "array" - if (typeof data === "string") return "string" - if (typeof data === "number") return "number" - if (typeof data === "boolean") return "boolean" - if (data === null) return "null" - if (typeof data === "object") return "object" - return "unknown" + if (Array.isArray(data)) return "array" + if (typeof data === "string") return "string" + if (typeof data === "number") return "number" + if (typeof data === "boolean") return "boolean" + if (data === null) return "null" + if (typeof data === "object") return "object" + return "unknown" +} + +export function formatTimestamp(rawDate) { + if (!rawDate || typeof rawDate !== 'string') return 'Invalid date'; + + try { + const cleanedDateStr = rawDate.replace(',', '.'); + const date = new Date(cleanedDateStr); + + if (isNaN(date)) return 'Invalid date'; + + const yyyy = date.getFullYear(); + const mm = String(date.getMonth() + 1).padStart(2, '0'); + const dd = String(date.getDate()).padStart(2, '0'); + const hh = String(date.getHours()).padStart(2, '0'); + const min = String(date.getMinutes()).padStart(2, '0'); + + return `${yyyy}-${mm}-${dd} ${hh}:${min}`; + } catch (e) { + return 'Invalid date'; + } } + From 3f87b3f6c98fe2630c4092a5bdbd612da2bad350 Mon Sep 17 00:00:00 2001 From: Aiga115 Date: Mon, 16 Jun 2025 17:34:09 +0200 Subject: [PATCH 3/5] ILEX-124 extract only version from version url --- src/components/SingleTermView/OverView/Details.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SingleTermView/OverView/Details.jsx b/src/components/SingleTermView/OverView/Details.jsx index 697ebddf..5ea9fad9 100644 --- a/src/components/SingleTermView/OverView/Details.jsx +++ b/src/components/SingleTermView/OverView/Details.jsx @@ -116,7 +116,7 @@ const Details = ({ loading, data, jsonData }) => { Version - {versionIRI} + {versionIRI.split('/version/')[1].split('/')[0]} From bf3eb56d5f0dee6bc2284435f15d2b3e018b335b Mon Sep 17 00:00:00 2001 From: Aiga115 Date: Mon, 16 Jun 2025 17:40:43 +0200 Subject: [PATCH 4/5] ILEX-124 add error state for downloading data --- src/components/SingleTermView/index.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/SingleTermView/index.jsx b/src/components/SingleTermView/index.jsx index 2a2972c9..51cc3423 100644 --- a/src/components/SingleTermView/index.jsx +++ b/src/components/SingleTermView/index.jsx @@ -137,7 +137,9 @@ const SingleTermView = () => { a.download = `data.${formatExtensions[dataFormat]}`; a.click(); URL.revokeObjectURL(url); - }) + }).catch(error => { + console.error('Error downloading data:', error); + }); } const CodeOrTreeIcon = () => { From 99542f1cfebdcf87a1d2db0217d24ccab25abec9 Mon Sep 17 00:00:00 2001 From: Aiga115 Date: Tue, 17 Jun 2025 15:06:23 +0200 Subject: [PATCH 5/5] ILEX-124 fix linting --- src/components/SingleTermView/OverView/OverView.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/SingleTermView/OverView/OverView.jsx b/src/components/SingleTermView/OverView/OverView.jsx index 62e8d0b1..33441efd 100644 --- a/src/components/SingleTermView/OverView/OverView.jsx +++ b/src/components/SingleTermView/OverView/OverView.jsx @@ -31,11 +31,11 @@ const OverView = ({ searchTerm, isCodeViewVisible, selectedDataFormat }) => { [] ); - const fetchJSONFile = () => { + const fetchJSONFile = useCallback(() => { getRawData("base", searchTerm, 'jsonld').then(rawResponse => { setJsonData(rawResponse); }) - } + }, [searchTerm]); useEffect(() => { setLoading(true); @@ -44,7 +44,7 @@ const OverView = ({ searchTerm, isCodeViewVisible, selectedDataFormat }) => { return () => { fetchTerms.cancel(); }; - }, [searchTerm, fetchTerms]); + }, [searchTerm, fetchTerms, fetchJSONFile]); const memoData = useMemo(() => data, [data]);