From 68bfb27cb816f61caeaa7817ebfc526d5288211f Mon Sep 17 00:00:00 2001 From: Jan-Marius Vatle <48485965+janmarius@users.noreply.github.com> Date: Mon, 18 Mar 2024 10:43:55 +0100 Subject: [PATCH] FIX-2270 Storing the currentUsername for server objects between refreshing (#2307) --- .../__testUtils__/testUtils.tsx | 37 +++++++------ .../components/ContentViews/ServerManager.tsx | 7 +++ .../components/TopRightCornerMenu.tsx | 7 +++ .../contexts/loggedInUsernamesContext.tsx | 53 +++++++++++++++++++ .../contexts/loggedInUsernamesReducer.tsx | 46 ++++++++++++++++ .../hooks/query/useGetServers.tsx | 27 ++++++++++ .../routes/AuthRoute.tsx | 7 +++ Src/WitsmlExplorer.Frontend/routes/Root.tsx | 37 +++++++------ 8 files changed, 187 insertions(+), 34 deletions(-) create mode 100644 Src/WitsmlExplorer.Frontend/contexts/loggedInUsernamesContext.tsx create mode 100644 Src/WitsmlExplorer.Frontend/contexts/loggedInUsernamesReducer.tsx diff --git a/Src/WitsmlExplorer.Frontend/__testUtils__/testUtils.tsx b/Src/WitsmlExplorer.Frontend/__testUtils__/testUtils.tsx index e0aae7abf..03d3c3e8a 100644 --- a/Src/WitsmlExplorer.Frontend/__testUtils__/testUtils.tsx +++ b/Src/WitsmlExplorer.Frontend/__testUtils__/testUtils.tsx @@ -4,6 +4,7 @@ import { render } from "@testing-library/react"; import { ConnectedServerProvider } from "contexts/connectedServerContext"; import { CurveThresholdProvider } from "contexts/curveThresholdContext"; import { Filter, FilterContextProvider } from "contexts/filter"; +import { LoggedInUsernamesProvider } from "contexts/loggedInUsernamesContext"; import OperationContext from "contexts/operationContext"; import { DateTimeFormat, @@ -89,23 +90,25 @@ export function renderWithContexts( - - - - - - - {children} - - - - - - + + + + + + + + {children} + + + + + + + diff --git a/Src/WitsmlExplorer.Frontend/components/ContentViews/ServerManager.tsx b/Src/WitsmlExplorer.Frontend/components/ContentViews/ServerManager.tsx index cf8166d10..46cf0d725 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContentViews/ServerManager.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContentViews/ServerManager.tsx @@ -14,6 +14,8 @@ import UserCredentialsModal, { } from "components/Modals/UserCredentialsModal"; import ProgressSpinner from "components/ProgressSpinner"; import { useConnectedServer } from "contexts/connectedServerContext"; +import { useLoggedInUsernames } from "contexts/loggedInUsernamesContext"; +import { LoggedInUsernamesActionType } from "contexts/loggedInUsernamesReducer"; import OperationContext from "contexts/operationContext"; import OperationType from "contexts/operationType"; import { useGetServers } from "hooks/query/useGetServers"; @@ -40,6 +42,7 @@ const ServerManager = (): React.ReactElement => { const editDisabled = msalEnabled && !getUserAppRoles().includes(adminRole); const navigate = useNavigate(); const { connectedServer, setConnectedServer } = useConnectedServer(); + const { dispatchLoggedInUsernames } = useLoggedInUsernames(); const connectServer = async (server: Server) => { const userCredentialsModalProps: UserCredentialsModalProps = { @@ -48,6 +51,10 @@ const ServerManager = (): React.ReactElement => { dispatchOperation({ type: OperationType.HideModal }); AuthorizationService.onAuthorized(server, username); AuthorizationService.setSelectedServer(server); + dispatchLoggedInUsernames({ + type: LoggedInUsernamesActionType.AddLoggedInUsername, + payload: { serverId: server.id, username } + }); setConnectedServer(server); navigate(getWellsViewPath(server.url)); } diff --git a/Src/WitsmlExplorer.Frontend/components/TopRightCornerMenu.tsx b/Src/WitsmlExplorer.Frontend/components/TopRightCornerMenu.tsx index ca1fe5bad..b7f35d4d8 100644 --- a/Src/WitsmlExplorer.Frontend/components/TopRightCornerMenu.tsx +++ b/Src/WitsmlExplorer.Frontend/components/TopRightCornerMenu.tsx @@ -6,6 +6,8 @@ import UserCredentialsModal, { } from "components/Modals/UserCredentialsModal"; import ServerManagerButton from "components/ServerManagerButton"; import { useConnectedServer } from "contexts/connectedServerContext"; +import { useLoggedInUsernames } from "contexts/loggedInUsernamesContext"; +import { LoggedInUsernamesActionType } from "contexts/loggedInUsernamesReducer"; import OperationContext from "contexts/operationContext"; import OperationType from "contexts/operationType"; import useDocumentDimensions from "hooks/useDocumentDimensions"; @@ -26,6 +28,7 @@ export default function TopRightCornerMenu() { const showLabels = documentWidth > 1180; const { connectedServer } = useConnectedServer(); const navigate = useNavigate(); + const { dispatchLoggedInUsernames } = useLoggedInUsernames(); const openSettingsMenu = () => { dispatchOperation({ @@ -40,6 +43,10 @@ export default function TopRightCornerMenu() { onConnectionVerified: (username) => { dispatchOperation({ type: OperationType.HideModal }); AuthorizationService.onAuthorized(connectedServer, username); + dispatchLoggedInUsernames({ + type: LoggedInUsernamesActionType.AddLoggedInUsername, + payload: { serverId: connectedServer.id, username } + }); } }; dispatchOperation({ diff --git a/Src/WitsmlExplorer.Frontend/contexts/loggedInUsernamesContext.tsx b/Src/WitsmlExplorer.Frontend/contexts/loggedInUsernamesContext.tsx new file mode 100644 index 000000000..410d0b31f --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/contexts/loggedInUsernamesContext.tsx @@ -0,0 +1,53 @@ +import { + LoggedInUsernamesAction, + loggedInUsernamesReducer +} from "contexts/loggedInUsernamesReducer"; +import { ReactNode, createContext, useContext, useReducer } from "react"; + +export interface LoggedInUsername { + serverId: string; + username: string; +} + +interface LoggedInUsernamesContextType { + loggedInUsernames: LoggedInUsername[]; + dispatchLoggedInUsernames: (action: LoggedInUsernamesAction) => void; +} + +const LoggedInUsernamesContext = + createContext(null); + +interface LoggedInUsernamesProviderProps { + children: ReactNode; +} + +/** + * This context is utilized to store the currentUsername for server objects in between refreshes for caching purposes. + * @param param0 + * @returns + */ +export function LoggedInUsernamesProvider({ + children +}: LoggedInUsernamesProviderProps) { + const [loggedInUsernames, dispatchLoggedInUsernames] = useReducer( + loggedInUsernamesReducer, + [] + ); + + return ( + + {children} + + ); +} + +export function useLoggedInUsernames() { + const context = useContext(LoggedInUsernamesContext); + if (!context) + throw new Error( + `useLoggedInUsernames() has to be used within ` + ); + return context; +} diff --git a/Src/WitsmlExplorer.Frontend/contexts/loggedInUsernamesReducer.tsx b/Src/WitsmlExplorer.Frontend/contexts/loggedInUsernamesReducer.tsx new file mode 100644 index 000000000..10e3457c6 --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/contexts/loggedInUsernamesReducer.tsx @@ -0,0 +1,46 @@ +import { LoggedInUsername } from "contexts/loggedInUsernamesContext"; + +export enum LoggedInUsernamesActionType { + AddLoggedInUsername = "AddLoggedInUsername" +} + +export interface LoggedInUsernamesAction { + type: any; + payload: any; +} + +export interface AddLoggedInUsernameAction extends LoggedInUsernamesAction { + type: LoggedInUsernamesActionType.AddLoggedInUsername; + payload: LoggedInUsername; +} + +export function loggedInUsernamesReducer( + state: LoggedInUsername[], + action: LoggedInUsernamesAction +): LoggedInUsername[] { + switch (action.type) { + case LoggedInUsernamesActionType.AddLoggedInUsername: + return addLoggedInUsername(state, action); + default: { + throw new Error(`Unsupported action type ${action.type}`); + } + } +} + +const addLoggedInUsername = ( + state: LoggedInUsername[], + { payload }: LoggedInUsernamesAction +): LoggedInUsername[] => { + const loggedInUsernames = structuredClone(state); + const newLoggedInUsername: LoggedInUsername = structuredClone(payload); + const index = loggedInUsernames.findIndex( + (loggedInUsername) => + loggedInUsername.serverId === newLoggedInUsername.serverId + ); + if (index === -1) { + loggedInUsernames.push(newLoggedInUsername); + } else if (index >= 0) { + loggedInUsernames.splice(index, 1, newLoggedInUsername); + } + return loggedInUsernames; +}; diff --git a/Src/WitsmlExplorer.Frontend/hooks/query/useGetServers.tsx b/Src/WitsmlExplorer.Frontend/hooks/query/useGetServers.tsx index 82c22d902..7a7342d14 100644 --- a/Src/WitsmlExplorer.Frontend/hooks/query/useGetServers.tsx +++ b/Src/WitsmlExplorer.Frontend/hooks/query/useGetServers.tsx @@ -1,4 +1,8 @@ import { QueryObserverResult, useQuery } from "@tanstack/react-query"; +import { + LoggedInUsername, + useLoggedInUsernames +} from "contexts/loggedInUsernamesContext"; import { Server } from "../../models/server"; import ServerService from "../../services/serverService"; import { QUERY_KEY_SERVERS } from "./queryKeys"; @@ -26,5 +30,28 @@ type ServersQueryResult = Omit< export const useGetServers = (options?: QueryOptions): ServersQueryResult => { const { data, ...state } = useQuery(serversQuery(options)); + const { loggedInUsernames } = useLoggedInUsernames(); + + // This step is necessary because the currentUsername is lost during server refresh. + data?.map((server) => setCurrentUsernames(server, loggedInUsernames)); + return { servers: data, ...state }; }; + +/** + * The following method is used to set the currentUsername on the server object. + * It's worth noting that changing objects using their reference is generally considered bad practice. + * However, since the AuthorizationService is already modifying the server object in this way, this method has been designed to do the same. + * @param server + * @param loggedInUsernames + */ +const setCurrentUsernames = ( + server: Server, + loggedInUsernames: LoggedInUsername[] +) => { + loggedInUsernames.map((loggedInUsername) => { + if (server.id === loggedInUsername.serverId) { + server.currentUsername = loggedInUsername.username; + } + }); +}; diff --git a/Src/WitsmlExplorer.Frontend/routes/AuthRoute.tsx b/Src/WitsmlExplorer.Frontend/routes/AuthRoute.tsx index fbf3a65b4..9236b67a0 100644 --- a/Src/WitsmlExplorer.Frontend/routes/AuthRoute.tsx +++ b/Src/WitsmlExplorer.Frontend/routes/AuthRoute.tsx @@ -1,4 +1,6 @@ import { useIsAuthenticated } from "@azure/msal-react"; +import { useLoggedInUsernames } from "contexts/loggedInUsernamesContext"; +import { LoggedInUsernamesActionType } from "contexts/loggedInUsernamesReducer"; import { useContext, useEffect } from "react"; import { Outlet, useNavigate, useParams } from "react-router-dom"; import { ItemNotFound } from "routes/ItemNotFound"; @@ -23,6 +25,7 @@ export default function AuthRoute() { const { serverUrl } = useParams(); const { connectedServer, setConnectedServer } = useConnectedServer(); const navigate = useNavigate(); + const { dispatchLoggedInUsernames } = useLoggedInUsernames(); useEffect(() => { const unsubscribe = @@ -68,6 +71,10 @@ export default function AuthRoute() { onConnectionVerified: (username) => { dispatchOperation({ type: OperationType.HideModal }); AuthorizationService.onAuthorized(server, username); + dispatchLoggedInUsernames({ + type: LoggedInUsernamesActionType.AddLoggedInUsername, + payload: { serverId: server.id, username } + }); if (initialLogin) { AuthorizationService.setSelectedServer(server); setConnectedServer(server); diff --git a/Src/WitsmlExplorer.Frontend/routes/Root.tsx b/Src/WitsmlExplorer.Frontend/routes/Root.tsx index 92f8e1c5f..006a32f8d 100644 --- a/Src/WitsmlExplorer.Frontend/routes/Root.tsx +++ b/Src/WitsmlExplorer.Frontend/routes/Root.tsx @@ -1,6 +1,7 @@ import { InteractionType } from "@azure/msal-browser"; import { MsalAuthenticationTemplate, MsalProvider } from "@azure/msal-react"; import { ThemeProvider } from "@material-ui/core"; +import { LoggedInUsernamesProvider } from "contexts/loggedInUsernamesContext"; import Head from "next/head"; import { SnackbarProvider } from "notistack"; import { useEffect } from "react"; @@ -124,23 +125,25 @@ export default function Root() { > - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + +