diff --git a/src/api/SwaggerApi.js b/src/api/SwaggerApi.js index ca80c3923f21d847fe76d9f4428a16ddb4c21b95..9e29a25489fb8fcfa8891a2c4865f74b6b51862d 100644 --- a/src/api/SwaggerApi.js +++ b/src/api/SwaggerApi.js @@ -350,110 +350,6 @@ const emptyRecordListResponse = { recordList: [] }; -export function unpackLogin(loginResponse) { - return { ...loginResponse }; -} - -export function useLogin(onError) { - const api = useContext(apiContext); - const method = useCallAndUnpack( - (username, password) => - api.apis.Authentication.login( - {}, - { - requestBody: { - userName: username, - password: password - } - } - ), - unpackLogin - ); - return useAsync({ fcn: method, call: false, onError: onError }); -} - -export function unpackUser(user) { - if (user?.length > 0) { - return { ...user[0] }; - } else { - return {}; - } -} - -export function useUser() { - const onError = useCallback((message) => { - console.warn("useUser error: ", message); - }, []); - - const [userInfo, getUserInfo, resetUserInfo, userInfoLoading] = useUserInfo( - null, - onError - ); - const [userRoles, getUserRoles, resetUserRoles, userRolesLoading] = - useUserRoles(onError); - const [user, setUser] = useState(); - const [loading, setLoading] = useState(true); - - const dataLoading = userInfoLoading || userRolesLoading; - - useEffect(() => { - // userInfo and user Roles should: - // - both be defined when logged in - // - both be undefined when logged out - const loginStateConsistent = Boolean(userInfo) === Boolean(userRoles); - if (loginStateConsistent && !dataLoading) { - setUser({ userInfo, userRoles }); - setLoading(false); - } - }, [dataLoading, userInfo, userRoles]); - - const getUser = useCallback(() => { - setLoading(true); - getUserInfo(); - getUserRoles(); - }, [getUserInfo, getUserRoles]); - - const resetUser = useCallback(() => { - setLoading(true); - resetUserInfo(); - resetUserRoles(); - }, [resetUserInfo, resetUserRoles]); - - return [user, getUser, resetUser, loading]; -} - -export function useUserInfo(userName, onError) { - const api = useContext(apiContext); - const method = useCallAndUnpack(api.apis.Git.infoFromUserName, unpackUser); - const boundMethod = useCallback(method.bind(null, { user_name: userName }), [ - userName - ]); - return useAsync({ fcn: boundMethod, call: false, onError: onError }); -} - -export function unpackUserRoles(roles) { - return roles; -} - -export function useUserRoles(onError) { - const api = useContext(apiContext); - const method = useCallAndUnpack( - api.apis.Authentication.getUserRoles, - unpackUserRoles - ); - return useAsync({ fcn: method, call: false, onError: onError }); -} - -export function useLogout() { - const api = useContext(apiContext); - const method = useCallAndUnpack(api.apis.Authentication.logout); - const [, /* value*/ logout /* reset*/, , loading] = useAsync({ - fcn: method, - call: false - }); - return [logout, loading]; -} - export function useStartIOC(id, onError) { const api = useContext(apiContext); const method = useCallAndUnpack(api.apis.IOCs.startIoc); diff --git a/src/api/UserProvider.js b/src/api/UserProvider.js index 1ac4a6a9cfa7be16310650a3d5d15fc2d162b633..f0ba41b79679b119c7778e5ffb99f0bd543a801e 100644 --- a/src/api/UserProvider.js +++ b/src/api/UserProvider.js @@ -1,58 +1,108 @@ import React from "react"; -import { useCallback, useEffect, useState } from "react"; -import { useLogin, useLogout, useUser } from "./SwaggerApi"; -import { userContext } from "@ess-ics/ce-ui-common"; +import { useCallback, useEffect, useState, useContext } from "react"; +import { userContext, useAPIMethod } from "@ess-ics/ce-ui-common"; +import { apiContext } from "./SwaggerApi"; -export function UserProvider({ children }) { - function onLoginError(message) { - setLoginError(message); +function loginRequest(username, password) { + return { + userName: username, // fyi on template it is username, but on deploy it is userName + password: password + }; +} + +function unpackUser(user) { + if (user?.length > 0) { + return { ...user[0] }; + } else { + return {}; } +} - const [user, getUser, resetUser, userLoading] = useUser(); - const [loginError, setLoginError] = useState(); - const [loginResponse, loginFcn /* reset*/, , loginLoading] = - useLogin(onLoginError); - const [logoutFcn, logoutLoading] = useLogout(); +export function UserProvider({ children }) { const [initialized, setInitialized] = useState(false); + const [user, setUser] = useState(null); + const [userRoles, setUserRoles] = useState([]); + const [loginErrorMsg, setLoginErrorMsg] = useState(); - const resetLoginError = useCallback( - () => setLoginError(null), - [setLoginError] - ); - - useEffect(getUser, [loginResponse, getUser]); - - const logout = useCallback(async () => { - resetUser(); - logoutFcn(); - }, [resetUser, logoutFcn]); + const client = useContext(apiContext); + const { + error: loginError, + wrapper: callLoginAPI, + value: loginResponse, + loading: loginLoading + } = useAPIMethod({ fcn: client.apis.Authentication.login, call: false }); + const { + wrapper: callUserAPI, + value: userResponse, + loading: userLoading + } = useAPIMethod({ + fcn: client.apis.Git.infoFromUserName, + call: true, + unpacker: unpackUser + }); + const { wrapper: callLogoutAPI, loading: logoutLoading } = useAPIMethod({ + fcn: client.apis.Authentication.logout, + call: false + }); + const { + wrapper: callUserRolesAPI, + value: userRolesResponse, + loading: userRolesLoading + } = useAPIMethod({ + fcn: client.apis.Authentication.getUserRoles, + call: true + }); const login = useCallback( (username, password) => { - loginFcn(username, password); + callLoginAPI({}, { requestBody: loginRequest(username, password) }); }, - [loginFcn] + [callLoginAPI] ); - const loading = Boolean(loginLoading || userLoading || logoutLoading); + const logout = useCallback(() => { + callLogoutAPI({}, {}); + setUser(null); + }, [callLogoutAPI]); + + useEffect(() => { + if (loginResponse) { + callUserAPI(); + callUserRolesAPI(); + } + }, [loginResponse, callUserAPI, callUserRolesAPI]); + + useEffect(() => { + setUser(userResponse); + }, [userResponse, setUser]); + + useEffect(() => { + setUserRoles(userRolesResponse); + }, [userRolesResponse, setUserRoles]); + + const loading = Boolean( + loginLoading || userLoading || userRolesLoading || logoutLoading + ); + + useEffect(() => { + setLoginErrorMsg(loginError?.response?.body?.description); + }, [loginError]); + + const resetLoginError = useCallback( + () => setLoginErrorMsg(null), + [setLoginErrorMsg] + ); const createValue = useCallback(() => { return { - user: user?.userInfo, - userRoles: user?.userRoles, + user: user, + userRoles: userRoles, login, - loginError, - resetLoginError, - logout + loginError: loginErrorMsg, + logout, + resetLoginError }; - }, [ - user?.userInfo, - user?.userRoles, - login, - loginError, - resetLoginError, - logout - ]); + }, [user, userRoles, login, loginErrorMsg, logout, resetLoginError]); const [value, setValue] = useState(createValue()); diff --git a/src/components/IOC/IOCTable/IOCDescription.js b/src/components/IOC/IOCTable/IOCDescription.js index 68fdf85abbfd3a84e025cceb34d1e9581f040362..a61e06b043205cfd1e272feb1e4db62c50cdadc6 100644 --- a/src/components/IOC/IOCTable/IOCDescription.js +++ b/src/components/IOC/IOCTable/IOCDescription.js @@ -1,5 +1,5 @@ import React, { useContext, useMemo } from "react"; -import { apiContext } from "../../../api/SwaggerApi"; +import { apiContext } from "../../../api/DeployApi"; import { Skeleton, Tooltip, Typography } from "@mui/material"; import { useAPIMethod } from "@ess-ics/ce-ui-common"; diff --git a/src/components/navigation/NavigationMenu/LoginControls.js b/src/components/navigation/NavigationMenu/LoginControls.js index 8e00b7cf3127998cbb0c156d6b30807c7935e8f2..8998e226db161871b5d13f91fe2d6d4bd9936e34 100644 --- a/src/components/navigation/NavigationMenu/LoginControls.js +++ b/src/components/navigation/NavigationMenu/LoginControls.js @@ -24,7 +24,7 @@ export function ProfileMenu({ user }) { const { userRoles, logout } = useContext(userContext); const navigate = useNavigate(); - const objUserRoles = userRoles + const objUserRoles = (userRoles ?? []) ?.filter((role) => role.includes("DeploymentTool")) ?.map((role) => ({ text: ( diff --git a/src/views/UserPage/UserDetailsContainer.js b/src/views/UserPage/UserDetailsContainer.js index 868c4c84783f4649b0e77beae93c712bfa9ac668..aa6900cf9a21b8e490dc69994f91664ca1574bda 100644 --- a/src/views/UserPage/UserDetailsContainer.js +++ b/src/views/UserPage/UserDetailsContainer.js @@ -1,68 +1,88 @@ -import React, { useContext, useEffect, useState } from "react"; +import React, { useContext, useEffect, useMemo, useState } from "react"; import { UserPageView } from "./UserPageView"; import { LinearProgress } from "@mui/material"; import NotFoundView from "../../components/navigation/NotFoundView/NotFoundView"; -import { useUserInfo } from "../../api/SwaggerApi"; import { useParams } from "react-router-dom"; -import { userContext } from "@ess-ics/ce-ui-common/dist/contexts/User"; +import { userContext, useAPIMethod } from "@ess-ics/ce-ui-common"; +import { apiContext } from "../../api/DeployApi"; + +function unpackUser(user) { + if (user?.length > 0) { + return { ...user[0] }; + } else { + return {}; + } +} export function UserDetailsContainer() { const { userName } = useParams(); const { user } = useContext(userContext); const [error, setError] = useState(null); - const [userInfo, getUserInfo, ,] = useUserInfo(userName, (m, s) => { - // log error message for userInfo - console.warn(m); + const client = useContext(apiContext); - // user not found - if (s === 404) { - setError({ message: "Page not found", status: `${s}` }); - // do not show snackbar error - return false; - } + const params = useMemo( + () => ({ + user_name: userName + }), + [userName] + ); - // user doesn't have permission to fetch userInfo - if (s === 401) { - setError({ message: "Unauthorized", status: `${s}` }); - // do not show snackbar error - return false; - } + const { + value: userInfo, + wrapper: getUserInfo, + error: userInfoResponseError + } = useAPIMethod({ + fcn: client.apis.Git.infoFromUserName, + call: true, + params, + unpacker: unpackUser }); useEffect(() => { - // user logs in => clear error message, and try to re-request userInfo - if (user) { - setError(null); - } else { - // user is not logged in => show a message - setError({ message: "Unauthorized", status: "401" }); + if (userInfoResponseError) { + const { status } = userInfoResponseError; + if (status === 404) { + setError({ message: "Page not found", status: `${status}` }); + } + + // user doesn't have permission to fetch userInfo + if (status === 401) { + setError({ message: "Unauthorized", status: `${status}` }); + } } - }, [user]); + }, [userInfoResponseError]); useEffect(() => { - // request userInfo only if user is logged in + // user logs in => clear error message, and try to re-request userInfo if (user) { + setError(null); getUserInfo(); } - }, [getUserInfo, userName, user]); + }, [user, userName, getUserInfo]); - return ( - <> - {error ? ( - <NotFoundView - status={error?.status ?? "404"} - message={error?.message} - /> - ) : user ? ( - <UserPageView - userName={userName} - user={user} - userInfo={userInfo} - /> - ) : ( - <LinearProgress color="primary" /> - )} - </> - ); + // If there is an error, show it; highest priority + if (error) { + return ( + <NotFoundView + status={error?.status ?? "404"} + message={error?.message} + /> + ); + } + + // If the user is logged in and the userInfo is available + // Then show the user page + if (user && userInfo) { + return ( + <UserPageView + userName={userName} + user={user} + userInfo={userInfo} + /> + ); + } + + // Otherwise assume loading (smoothest experience / least flickering) + return <LinearProgress color="primary" />; }