diff --git a/src/App.jsx b/src/App.jsx index 1946418c..c3d80d0e 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,4 +1,4 @@ -import { useEffect } from "react"; +import { useEffect, useContext } from "react"; import CssBaseline from "@mui/material/CssBaseline"; import { Box, ThemeProvider } from "@mui/material"; import { @@ -26,6 +26,9 @@ import SingleOrganization from "./components/SingleOrganization"; import TermActivity from "./components/term_activity/TermActivity"; import OrganizationsCurieEditor from "./components/CurieEditor/OrganizationCurieEditor"; import { handleOrcidLogin } from "./api/endpoints"; +import { GlobalDataContext } from "./contexts/DataContext"; +import { useCookies } from 'react-cookie' +import { API_CONFIG } from "./config"; const PageContainer = ({ children }) => { return ( @@ -36,6 +39,30 @@ const PageContainer = ({ children }) => { }; function MainContent() { + const [cookies] = useCookies(['session']) + const cookiesInfo = JSON.parse(localStorage.getItem(API_CONFIG.SESSION_DATA.COOKIE)); + const { user, setUserData } = useContext(GlobalDataContext); + + // check if cookie is expired + if (!user) { + const sessionCookie = cookies.session; + const expires = new Date(cookiesInfo.expires); + const today = new Date(); + if (sessionCookie === cookiesInfo?.value && expires > today) { + const userData = JSON.parse(localStorage.getItem(API_CONFIG.SESSION_DATA.SETTINGS)); + setUserData({ + name: userData['groupname'], + id: userData['orcid'], + email: userData?.emails[0]?.email, + role: userData['own-role'], + groupname: userData['groupname'], + settings: userData + }); + } else { + setUserData({}); + } + } + return ( (API_CONFIG.REAL_API.SIGNIN, "application/x-www-form-urlencoded") + export const register = createPostRequest(API_CONFIG.REAL_API.NEWUSER_ILX, "application/x-www-form-urlencoded") + +export const getUserSettings = (group: string) => { + const endpoint = `/${group}${API_CONFIG.REAL_API.USER_SETTINGS}`; + return createGetRequest(endpoint, "application/json")(); +}; + export const createNewOrganization = ({group, data} : {group: string, data: any}) => { const endpoint = `/${group}${API_CONFIG.REAL_API.CREATE_NEW_ORGANIZATION}`; return createPostRequest(endpoint, "application/json")(data); @@ -28,4 +35,9 @@ export const createNewOrganization = ({group, data} : {group: string, data: any} export const getOrganizations = (group: string) => { const endpoint = `/${group}${API_CONFIG.REAL_API.GET_ORGANIZATIONS}`; return createGetRequest(endpoint, "application/json")(); -}; \ No newline at end of file +}; + +export const userLogout = (group: string) => { + const endpoint = `/${group}${API_CONFIG.REAL_API.LOGOUT}`; + return createGetRequest(endpoint, "application/json")(); +}; diff --git a/src/api/endpoints/index.ts b/src/api/endpoints/index.ts index 6c07e0b8..686bbdb5 100644 --- a/src/api/endpoints/index.ts +++ b/src/api/endpoints/index.ts @@ -3,7 +3,7 @@ import * as mockApi from './../../api/endpoints/swaggerMockMissingEndpoints'; import * as api from "./../../api/endpoints/interLexURIStructureAPI"; import { TERM, ONTOLOGY, ORGANIZATION } from '../../model/frontend/types' import curieParser from '../../parsers/curieParser'; -import termParser, { elasticSearhParser, getTerm } from '../../parsers/termParser'; +import termParser, { elasticSearchParser, getTerm } from '../../parsers/termParser'; import axios from 'axios'; import { API_CONFIG } from '../../config'; diff --git a/src/components/Auth/Login.jsx b/src/components/Auth/Login.jsx index 698abcdf..9d7aedec 100644 --- a/src/components/Auth/Login.jsx +++ b/src/components/Auth/Login.jsx @@ -21,6 +21,7 @@ import { API_CONFIG } from "../../config"; import { GlobalDataContext } from "../../contexts/DataContext"; import * as yup from "yup"; import { useCookies } from 'react-cookie' +import { requestUserSettings } from "./utils"; const schema = yup.object().shape({ @@ -45,22 +46,32 @@ const Login = () => { let eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; let eventer = window[eventMethod]; let messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; - eventer(messageEvent, function (e) { + eventer(messageEvent, async function (e) { if (!e.data || !e.data.orcid_meta) return; - // TODO: get the session cookie when here and add it to our domain. - // also store the user info once logged from here in the local storage for future usage. - const { code, orcid_meta, cookies } = e.data; - const _cookies = JSON.parse(cookies); - // create a cookie with the name "session" and the value of the session cookie - const sessionCookie = _cookies.find(cookie => cookie.name === "session"); - if (sessionCookie) { - let expires = new Date() - expires.setTime(expires.getTime() + (2 * 24 * 60 * 60 * 1000)); // 2 days - setCookie('session', sessionCookie.value, { path: '/', domain: '.localhost', secure: false, sameSite: false, expires, httpOnly: false }); - } - + const { code, cookies, groupname } = e.data; if (code === 200 || code === 302) { - setUserData({ name: orcid_meta.name, id: orcid_meta.orcid }); + const _cookies = JSON.parse(cookies); + const sessionCookie = _cookies.find(cookie => cookie.name === "session"); + let expires = new Date() + if (sessionCookie) { + expires.setTime(expires.getTime() + (2 * 24 * 60 * 60 * 1000)); // 2 days + setCookie('session', sessionCookie.value, { path: '/', domain: '.localhost', secure: false, sameSite: false, expires, httpOnly: false }); + } + const userData = await requestUserSettings(groupname); + localStorage.setItem(API_CONFIG.SESSION_DATA.SETTINGS, JSON.stringify(userData)); + localStorage.setItem(API_CONFIG.SESSION_DATA.COOKIE, JSON.stringify({ + name: 'session', + value: sessionCookie.value, + expires: expires + })); + setUserData({ + name: userData['groupname'], + id: userData['orcid'], + email: userData?.emails[0]?.email, + role: userData['own-role'], + groupname: userData['groupname'], + settings: userData + }); navigate("/") } else if (code === 401) { setErrors((prev) => ({ @@ -95,11 +106,12 @@ const Login = () => { if (!result.data || !result.data?.orcid_meta) { setErrors((prev) => ({ ...prev, - auth: "Interlex API is not returning the user information, please contact the support at support@interlex.org", + auth: "Interlex API is not returning the user information, reminder to ask Tom to send the groupname back so that we can query the priv/setting endpoint to get the rest of the info required", })); } else { const { code, orcid_meta } = result.data; if (code === 200 || code === 302) { + // TODO: the backend should return the groupname, for now is just returning a message. setUserData({ name: orcid_meta.name, id: orcid_meta.orcid }); } navigate("/") diff --git a/src/components/Auth/utils.ts b/src/components/Auth/utils.ts new file mode 100644 index 00000000..261a48ad --- /dev/null +++ b/src/components/Auth/utils.ts @@ -0,0 +1,18 @@ +import { API_CONFIG } from "../../config"; +import { getUser } from "../../api/endpoints"; +import { getUserSettings } from "../../api/endpoints/apiService"; + +export const requestUserSettings = async (group: string) => { + try { + const response = await getUserSettings(group); + if (response.status === 200) { + const data = response.settings; + return data; + } else { + throw new Error(`Error fetching user settings: ${response.statusText}`); + } + } catch (error) { + console.error("Error fetching user settings:", error); + throw error; + } +}; diff --git a/src/components/Dashboard/Organizations/index.jsx b/src/components/Dashboard/Organizations/index.jsx index 6490090d..444a8e91 100644 --- a/src/components/Dashboard/Organizations/index.jsx +++ b/src/components/Dashboard/Organizations/index.jsx @@ -7,15 +7,15 @@ import AddOutlinedIcon from "@mui/icons-material/AddOutlined"; import OrganizationsList from "../../common/OrganizationsList"; import CustomViewButton from "../../common/CustomViewButton"; import MessageDialog from "../../common/MessageDialog"; +import { GlobalDataContext } from "../../../contexts/DataContext"; const { gray600, gray200 } = vars; const Organizations = () => { const [open, setOpen] = useState(false); - - // TODO: change this to be dynamic when we get the response from api call - const groupname = "aigul" + const { user } = GlobalDataContext(); + const groupname = user?.groupname || "base"; const { listView, diff --git a/src/components/GraphViewer/Graph.jsx b/src/components/GraphViewer/Graph.jsx index 295a2838..eb5aeb1e 100644 --- a/src/components/GraphViewer/Graph.jsx +++ b/src/components/GraphViewer/Graph.jsx @@ -2,7 +2,9 @@ import * as d3 from "d3"; import PropTypes from "prop-types"; import { Box } from "@mui/material"; import { useMemo, useEffect } from "react"; -import { getGraphStructure , OBJECT, SUBJECT, PREDICATE, ROOT} from "./GraphStructure"; +import { getGraphStructure, PREDICATE, ROOT} from "./GraphStructure"; +import { vars } from "../../theme/variables"; +const { gray600, white } = vars; const MARGIN = { top: 60, right: 60, bottom: 60, left: 60 }; @@ -59,67 +61,72 @@ const Graph = ({ width, height, predicate }) => { const allNodes = dendrogram.descendants().map((node) => { let textOffset = 0; - if ( node.data.type === OBJECT ) { - textOffset = 40; - } else if ( node.data.type === SUBJECT ) { - textOffset = 40; - } else if ( node.data.type === PREDICATE ) { - textOffset = 40; + if (node.data.type === PREDICATE || node.data.type === ROOT) { + textOffset = -40; + } else { + textOffset = 5; } - + const truncatedName = node.data.name.length > 25 ? `${node.data.name.substring(0, 25)}...` : node.data.name; - + return ( - - {( - - {truncatedName} - - )} + + + {truncatedName} + ); }); const allEdges = dendrogram.descendants().map((node, index) => { if (!node.parent) { - return; + // Add a black circle at the root + return ( + + ); } - + const line = d3 .line() .x(d => d[0]) .y(d => d[1]) - .curve(d3.curveBundle.beta(.75)); - - const start = [boundsWidth - node.parent.y, node.parent.x] - const end = [boundsWidth - node.y, node.x] + .curve(d3.curveBundle.beta(0.75)); + + const start = [node.parent.y, node.parent.x]; + const end = [node.y, node.x]; const radius = 5; - + const points = [ start, - [start[0] + radius, end[1]], - end + [start[0] - radius, end[1]], + end, ]; - + return ( ); @@ -132,22 +139,27 @@ const Graph = ({ width, height, predicate }) => { pointerEvents: "none", opacity: 0, zIndex: 1000, + background: gray600, + color: white, + padding: "0.5rem", + borderRadius: "0.5rem", }}> - - - - - + + + + + { const handleMenuClick = (e, menu) => { if (menu.label === 'Log out') { - // TODO: call logout endpoint {group}/priv/logout also - // TODO: flush the userinfo from the localstorage - setUserData(null, null); + userLogout(); + localStorage.removeItem('session'); + localStorage.removeItem('settings'); + setUserData({}); navigate('/'); } if (menu.href) { @@ -250,7 +252,7 @@ const Header = () => { React.useEffect(() => { console.log("Stored user in context ", user) - if(user) { + if(user !== null && user?.groupname !== undefined) { setIsLoggedIn(true) } else { setIsLoggedIn(false) diff --git a/src/components/SearchResults/ListView.jsx b/src/components/SearchResults/ListView.jsx index f70a6bab..eeb6cd35 100644 --- a/src/components/SearchResults/ListView.jsx +++ b/src/components/SearchResults/ListView.jsx @@ -20,7 +20,7 @@ const TitleSection = ({ searchResult }) => { {searchResult.label || searchResult.name} - + {searchResult.ontologyIsActive ? ( { const InfoSection = ({ searchResult }) => { const infoItems = [ - { label: 'Preferred ID', value: searchResult.ilx.replace('_', ':').toUpperCase() }, - { label: 'IDs', value: searchResult.existing_ids.flatMap(item => item.curie) }, - { label: 'Type', value: searchResult.type }, - { label: 'Score', value: searchResult.status }, - { label: 'Organization', value: searchResult.organization }, + { label: 'ID', value: searchResult.ilx}, + { label: 'Preferred ID', value: searchResult.existing_ids}, + { label: 'Synonyms', value: searchResult.synonyms }, + { label: 'Score', value: searchResult.score }, ]; + const getText = (value) => { + return ({value}) + } + + const getChip = (value) => { + return () + } + + const getChips = (value) => { + return ( + + {value.map((val, index) => ( + + ))} + + ); + }; + + const getValue = (label, value) => { + if (label === 'ID') { + return getText(value.replace('_', ':').toUpperCase()); + } else if (label === 'Preferred ID') { + const id = value.find((id) => id.preferred === "1"); + return getChip(id.curie); + } else if (label === 'Synonyms') { + return getChips(value.map((synonym) => synonym.literal)); + } else if (label === 'Score') { + return getText(value); + } + } + return ( { }} > {infoItems.map(({ label, value }) => ( - + {label} - {label === 'IDs' - ? (value.map((val, index) => ( ))) - : {value} - } + {getValue(label, value)} ))} diff --git a/src/components/organizations/index.jsx b/src/components/organizations/index.jsx index 514688bb..31f21031 100644 --- a/src/components/organizations/index.jsx +++ b/src/components/organizations/index.jsx @@ -3,6 +3,7 @@ import OrganizationsList from "../common/OrganizationsList"; import { Box, Typography, CircularProgress, Stack, Button } from "@mui/material"; import { useOrganizations } from "../../helpers/useOrganizations"; import GroupAddOutlinedIcon from "@mui/icons-material/GroupAddOutlined"; +import { GlobalDataContext } from "../../contexts/DataContext"; import MessageDialog from "../common/MessageDialog"; import { vars } from "../../theme/variables"; @@ -10,10 +11,9 @@ const { gray700 } = vars; const Organizations = () => { + const { user } = GlobalDataContext(); const [open, setOpen] = useState(false); - - // TODO: change this to be dynamic when we get the response from api call - const groupname = "aigul" + const groupname = user?.groupname || "base"; const { organizations, @@ -24,8 +24,7 @@ const Organizations = () => { const handleCreateOrganization = async (e) => { e.preventDefault(); - // eslint-disable-next-line no-unused-vars - const success = await createOrganization(); + await createOrganization(); setOpen(true); }; diff --git a/src/config.js b/src/config.js index e76315a7..0b296f38 100644 --- a/src/config.js +++ b/src/config.js @@ -27,8 +27,14 @@ export const API_CONFIG = { ORCID_SIGNIN: "/u/ops/orcid-login", NEWUSER_ILX: "/u/ops/user-new", NEWUSER_ORCID: "/u/ops/orcid-new", + USER_SETTINGS: "/priv/settings", CREATE_NEW_ORGANIZATION: "/priv/org-new", - GET_ORGANIZATIONS: "/priv/role-other" + GET_ORGANIZATIONS: "/priv/role-other", + LOGOUT: "/priv/logout", + }, + SESSION_DATA: { + SETTINGS: "settings", + COOKIE: "session", }, OLYMPIAN_GODS : "https://uri.olympiangods.org", BASE_SCICRUNCH_URL: "/api/elasticsearch?apikey=", diff --git a/src/parsers/termParser.tsx b/src/parsers/termParser.tsx index 77fe3c5f..f6797438 100644 --- a/src/parsers/termParser.tsx +++ b/src/parsers/termParser.tsx @@ -155,13 +155,15 @@ export const termParser = (data, searchTerm, start?, end?) => { } }; -export const elasticSearhParser = (data) => { +export const elasticSearchParser = (data) => { + // TODO add the score to the results let terms : Terms; if ( Array.isArray(data) ){ terms = data?.map( term => { let newTerm : Term = {} as Term - term = term._source - return term + newTerm = term._source + newTerm['score'] = term._score + return newTerm }) // We are receiving an unknown amout of terms from server, we need to control @@ -177,4 +179,4 @@ export const elasticSearhParser = (data) => { } }; -export default termParser; \ No newline at end of file +export default termParser;