diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a340d0bd7a230524f3c983bdcfd3b9029dabf3e2..c0f2c9466999f885671d41fc7551809771df1fae 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,6 +19,8 @@ build-esap-gui: expire_in: 1 hour paths: - build + only: + - master deploy-esap-gui: stage: deploy diff --git a/src/components/basket/EmptyBasketButton.js b/src/components/basket/EmptyBasketButton.js index b4bb8be7481af6912e5b8eb2ff8627bda6a53f30..d45581cff79f83dc02995d9023e0dee2dc2a9dd8 100644 --- a/src/components/basket/EmptyBasketButton.js +++ b/src/components/basket/EmptyBasketButton.js @@ -1,23 +1,27 @@ import React, { useContext, useState } from "react"; +import { useHistory } from "react-router-dom"; import { Button, Modal } from "react-bootstrap"; import { GlobalContext } from "../../contexts/GlobalContext"; import { BasketContext } from "../../contexts/BasketContext"; import { getTrashIcon, getOKIcon } from "../../utils/styling"; +import { saveBasket } from "./SaveBasketButton" export default function EmptyBasketButton(props) { - const { api_host, isAuthenticated } = useContext(GlobalContext); + const { api_host, isAuthenticated, isTokenValid, loginAgain } = useContext(GlobalContext); const basketContext = useContext(BasketContext); const { setHasChanged } = useContext(BasketContext); - const [show, setShow] = useState(false); const handleClose = () => setShow(false); const handleShow = () => setShow(true); - function emptyBasket(){ + let history = useHistory() + + function emptyBasket(basketContext , api_host, isTokenValid, loginAgain, history){ basketContext.setDatasets([]) setHasChanged(true) setShow(false) + saveBasket(basketContext , api_host, isTokenValid, loginAgain, history) } @@ -39,7 +43,9 @@ export default function EmptyBasketButton(props) { </Modal.Header> <Modal.Body>Are you sure you want to empty the shopping basket?</Modal.Body> <Modal.Footer> - <Button variant="success" onClick={emptyBasket}> + <Button variant="success" + onClick={() => emptyBasket(basketContext, api_host, isTokenValid, loginAgain, history)} + > {getOKIcon()}{' '}OK </Button> <Button variant="warning" onClick={handleClose}> diff --git a/src/components/basket/LoadBasket.js b/src/components/basket/LoadBasket.js index 4961f25a65ba752d2518b0707ff6bb39c10bf0ba..98f747b416c78bfea9f6a45480fda0039ee7130b 100644 --- a/src/components/basket/LoadBasket.js +++ b/src/components/basket/LoadBasket.js @@ -13,9 +13,8 @@ function ResponseToDatasets(response) { let datasets = shopping_cart.map((item) => { // make item_data an object instead of a string - console.log('itemdata = '+item.item_data) + //console.log('itemdata = '+item.item_data) let o = JSON.parse(item.item_data) - //alert(o) return o //return item.item_data @@ -23,6 +22,8 @@ function ResponseToDatasets(response) { return datasets } + + export function loadBasket(basketContext, api_host, isAuthenticated){ //alert('loadBasket: authenticated = '+isAuthenticated) if (!isAuthenticated) { diff --git a/src/components/basket/SaveBasketButton.js b/src/components/basket/SaveBasketButton.js index 729ea79746bea102e07d69083f1e17e2d3b903f0..2d566a516232027ff2c56da02c1a149d4f7fbed8 100644 --- a/src/components/basket/SaveBasketButton.js +++ b/src/components/basket/SaveBasketButton.js @@ -1,57 +1,71 @@ import React, { useContext } from "react"; +import { useHistory } from "react-router-dom"; import { Button } from "react-bootstrap"; import { GlobalContext } from "../../contexts/GlobalContext"; import { BasketContext } from "../../contexts/BasketContext"; import axios from "axios"; import { getShoppingIcon } from "../../utils/styling"; -export default function SaveBasketButton(props) { - const { api_host, isAuthenticated } = useContext(GlobalContext); - const basketContext = useContext(BasketContext); - const { hasChanged, setHasChanged } = useContext(BasketContext); - - function saveBasket(basketData){ - const payload = {shopping_cart: basketData}; - console.log(payload); +export function saveBasket(basketContext, api_host, isTokenValid, loginAgain, history){ + const payload = {shopping_cart: basketContext.datasets}; + console.log('saveBasket()') const profileUrl = api_host + "accounts/user-profiles/"; + + // check if he token is still valid + let token_is_valid = isTokenValid() + console.log('token valid: '+token_is_valid) + + // if the token is not valid, then refresh it by logging in again + if (token_is_valid < 0) { + + console.log('token no longer valid, retrying login...') + loginAgain(history) + //saveBasket(basketContext, api_host, isTokenValid, history) + + return + } + axios - .get(profileUrl, { - withCredentials: true, - //headers : {"Access-Control-Allow-Origin": "*"} - }) - .then((response) => { - console.log(response.data) - const userProfileUrl = profileUrl + response.data.results[0].user_name + "/"; + .get(profileUrl, { + withCredentials: true, + }) + .then((response) => { + // build the userProfileUrl based on the user_name in the original id_token + console.log(response.data) + const userProfileUrl = profileUrl + response.data.results[0].user_name + "/"; - axios - .patch(userProfileUrl, payload, {withCredentials: true}) - .then((response) => { - console.log("patch", response); - basketContext.setHasChanged(false) - }) - .catch((error) => { + // send the payload to the userProfile + axios + .patch(userProfileUrl, payload, {withCredentials: true}) + .then((response) => { + console.log("patch", response); + basketContext.setHasChanged(false) + }) + .catch((error) => { + console.log(error); + }); + }) + .catch((error) => { console.log(error); - alert(error) - }); - }) - .catch((error) => { - console.log(error); - //alert(error) - }); - } + }); +} + +export default function SaveBasketButton(props) { + const { api_host, isAuthenticated, isTokenValid, loginAgain } = useContext(GlobalContext); + const basketContext = useContext(BasketContext); + const { hasChanged, setHasChanged } = useContext(BasketContext); - // fake authentication when in 'development' mode. - //let authenticated = isAuthenticated || (process.env.NODE_ENV === "development") - let authenticated = isAuthenticated + let history = useHistory() - if (authenticated) { + // only show the 'save basket' button when a user is logged in and something in the basket has changed + if (isAuthenticated) { if (hasChanged) { return ( <Button type="button" variant="primary" - onClick={() => saveBasket(basketContext.datasets)} + onClick={() => saveBasket(basketContext , api_host, isTokenValid, loginAgain, history)} {...props}> {getShoppingIcon("save_cart")} Save Basket</Button> ) @@ -60,11 +74,7 @@ export default function SaveBasketButton(props) { } } else{ - return (<> - <Button variant="warning" disabled {...props}> - Log In to Enable Data Selection - </Button> - </> - ); + return null + } } diff --git a/src/contexts/GlobalContext.js b/src/contexts/GlobalContext.js index 62c9a2650d2e59e5b56bc0b9cb520bdecffe7cfa..174f751c0da15c61cdf1d9d76c15f767e4500ed1 100644 --- a/src/contexts/GlobalContext.js +++ b/src/contexts/GlobalContext.js @@ -11,8 +11,8 @@ function setProfileState(api_host, setIdToken, setAccessToken, setTokenExpiration, - setSecondsLeft, setIsAuthenticated){ + const profileUrl = api_host + "accounts/user-profiles/"; axios .get(profileUrl, {withCredentials: true}) @@ -30,12 +30,13 @@ function setProfileState(api_host, }) .catch((error) => { - alert('GlobalContext.setProfileState:' + error) + console.log('GlobalContext.setProfileState:' + error) // when the token is no longer valid, .get with credentials will fail // mark the user as being logged out localStorage.removeItem('esap_logged_in') setIsAuthenticated(false); setLoggedInUserName(""); + }); } @@ -53,32 +54,30 @@ export function GlobalContextProvider({ children }) { const [idToken, setIdToken] = useState([]); const [accessToken, setAccessToken] = useState([]); const [tokenExpiration, setTokenExpiration] = useState([]); - const [secondsLeft, setSecondsLeft] = useState(undefined) - - useEffect(() => { - axios - .get(api_host + "query/archives-uri") - .then((response) => setArchives(response.data.results)); - }, [api_host]); useEffect(() => { - axios - .get(api_host + "query/configuration?name=navbar") - .then((response) => { - console.log("navbar response", response.data.configuration); - setNavbar(response.data.configuration); - }); - }, [api_host]); + axios + .get(api_host + "query/archives-uri") + .then((response) => setArchives(response.data.results)); + }, [api_host]); + + useEffect(() => { + axios + .get(api_host + "query/configuration?name=navbar") + .then((response) => { + console.log("navbar response", response.data.configuration); + setNavbar(response.data.configuration); + }); + }, [api_host]); // Zheng: "!!!!! Still need to look at sessionid and stuff" const [sessionid, setSessionid] = useState(getCookie("sessionid")); console.log("waah", sessionid, getCookie("sessionid"), document.cookie); - const [isAuthenticated, setIsAuthenticated] = useState( - sessionid ? true : false - ); + const [isAuthenticated, setIsAuthenticated] = useState(sessionid ? true : false); const handleLogin = ({ history }) => { + console.log('handleLogin()') setIsAuthenticated(true); setSessionid(getCookie("sessionid")); history.replace("/"); @@ -88,17 +87,29 @@ export function GlobalContextProvider({ children }) { setIdToken, setAccessToken, setTokenExpiration, - setSecondsLeft, setIsAuthenticated); return null; }; + // used when token expiration is detected before or during an axios fetch + const loginAgain = (history) => { + console.log('loginAgain()') + const loginUrl = api_host + "oidc/authenticate" + console.log('history = '+history) + + //history.replace("/login"); + window.location = loginUrl + //alert('history push') + //history.push("/login"); + } + const handleLogout = ({ history }) => { - setIsAuthenticated(false); - setSessionid(null); - history.replace("/"); - setLoggedInUserName(""); - localStorage.removeItem('esap_logged_in') + console.log('handleLogout()') + setIsAuthenticated(false); + setSessionid(null); + history.replace("/"); + setLoggedInUserName(""); + localStorage.removeItem('esap_logged_in') return null; }; @@ -112,6 +123,15 @@ export function GlobalContextProvider({ children }) { } } + // compare the tokenExpiration timestamp with the current time + // to determine if the token is still valid. + const isTokenValid = () => { + let expiration = Date.parse(tokenExpiration) + let now = Date.parse(new Date()) + let valid = (expiration - now) > 0 + return (expiration - now)/1000 + } + const handleError = (event) => { setIsAuthenticated(false); setSessionid(null); @@ -140,9 +160,9 @@ export function GlobalContextProvider({ children }) { idToken, accessToken, tokenExpiration, - secondsLeft, - setSecondsLeft, - refreshLogin + refreshLogin, + isTokenValid, + loginAgain }} > {children} diff --git a/src/contexts/QueryContext.js b/src/contexts/QueryContext.js index 47af255063e07a0db8b6681fd00aff0c146023f9..bd9d7c8a573e729407426b236a1abd5ce1a3d91e 100644 --- a/src/contexts/QueryContext.js +++ b/src/contexts/QueryContext.js @@ -16,7 +16,7 @@ export function QueryContextProvider({ children }) { const [collection, setCollection] = useState(); const [config, setConfig] = useState(); const [configName, setConfigName] = useState(defaultConf); - const { api_host } = useContext(GlobalContext); + const { api_host, loginAgain } = useContext(GlobalContext); const [preview, setPreview] = useState(false); const [ds9, setDS9] = useState(false); @@ -61,6 +61,11 @@ export function QueryContextProvider({ children }) { let description = ". Configuration not loaded. Is ESAP-API online? " + api_host console.log(error.toString() + description) //alert(description) + // frantic attempt to solve cors errors by trying to trigger a login + //const loginUrl = api_host + "oidc/authenticate" + //alert('(QueryContext) token expired, attempting login: '+loginUrl) + //window.location = loginUrl + loginAgain() return false }); @@ -95,9 +100,13 @@ export function QueryContextProvider({ children }) { setConfig(config); }) .catch((error) => { + let description = ". Configuration not loaded. Is ESAP-API online? " + api_host console.log(error.toString() + description) - //alert(description) + alert(description) + //const loginUrl = api_host + "oidc/authenticate" + // window.location = loginUrl + loginAgain() }); return true diff --git a/src/routes/Routes.js b/src/routes/Routes.js index ac421d3f8acca6bc3b63d7119f182d1c95c5ef7a..9c49d4b80f1bea83f14893b46d7bdafb36a7a22f 100644 --- a/src/routes/Routes.js +++ b/src/routes/Routes.js @@ -87,7 +87,7 @@ export default function Routes() { </Switch> - <footer><small>esap-gui version 23 aug 2021 - 15:00</small></footer> + <footer><small>esap-gui version 27 aug 2021 - 11:00</small></footer> </Router> ); } diff --git a/src/utils/ShowTimeLeft.js b/src/utils/ShowTimeLeft.js deleted file mode 100644 index 1ce85680b97650b23b15b8b1832b603f8a6e4ffb..0000000000000000000000000000000000000000 --- a/src/utils/ShowTimeLeft.js +++ /dev/null @@ -1,31 +0,0 @@ -import React, { useContext, useState, useRef, useEffect } from "react"; -import { GlobalContext } from "../contexts/GlobalContext"; - -export function ShowTimeLeft() { - const { tokenExpiration } = useContext(GlobalContext); - const [timer, setTimer] = useState(undefined) - const [secondsLeft, setSecondsLeft] = useState(undefined) - - useEffect(() => { - setTimer(setInterval(() => showTimeLeft(), 10000)) - - // this function is automatically called when the component unmounts - return function cleanup() { - clearInterval(timer); - } - },[] - ); - - function showTimeLeft() { - //console.log({tokenExpiration}) - alert(tokenExpiration) - //let expiration = new Date(tokenExpiration) - - setSecondsLeft(1) - } - - alert('showtimeleft') - - return (<h5>{secondsLeft}</h5>) - -} \ No newline at end of file