Skip to content

Commit 69e9709

Browse files
author
cesnietor
committed
Refactor session to avoid duplicate calls to api
1 parent 9fa49b4 commit 69e9709

File tree

8 files changed

+151
-82
lines changed

8 files changed

+151
-82
lines changed

portal-ui/src/ProtectedRoutes.tsx

Lines changed: 20 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,18 @@
1414
// You should have received a copy of the GNU Affero General Public License
1515
// along with this program. If not, see <http://www.gnu.org/licenses/>.
1616

17-
import React, { useEffect, useState } from "react";
17+
import { useEffect, useState } from "react";
1818
import { Navigate, useLocation } from "react-router-dom";
1919
import useApi from "./screens/Console/Common/Hooks/useApi";
2020
import { ErrorResponseHandler } from "./common/types";
2121
import { ReplicationSite } from "./screens/Console/Configurations/SiteReplication/SiteReplication";
2222
import { useSelector } from "react-redux";
23-
import {
24-
globalSetDistributedSetup,
25-
setAnonymousMode,
26-
setOverrideStyles,
27-
setSiteReplicationInfo,
28-
userLogged,
29-
} from "./systemSlice";
3023
import { SRInfoStateType } from "./types";
3124
import { AppState, useAppDispatch } from "./store";
32-
import { saveSessionResponse } from "./screens/Console/consoleSlice";
33-
import { getOverrideColorVariants } from "./utils/stylesUtils";
3425
import LoadingComponent from "./common/LoadingComponent";
35-
import { api } from "api";
26+
import { fetchSession } from "./screens/LoginPage/sessionThunk";
27+
import { setSiteReplicationInfo } from "./systemSlice";
28+
import { SessionCallStates } from "./screens/Console/consoleSlice";
3629

3730
interface ProtectedRouteProps {
3831
Component: any;
@@ -41,8 +34,14 @@ interface ProtectedRouteProps {
4134
const ProtectedRoute = ({ Component }: ProtectedRouteProps) => {
4235
const dispatch = useAppDispatch();
4336

44-
const [sessionLoading, setSessionLoading] = useState<boolean>(true);
4537
const userLoggedIn = useSelector((state: AppState) => state.system.loggedIn);
38+
// sessionLoading is used to control rendering this page, if we only use console.sessionLoadingState,
39+
// since the state chages it triggers the reload of this component multiple times, causing duplicate queries.
40+
const [sessionLoading, setSessionLoading] = useState<boolean>(true);
41+
const sessionLoadingState = useSelector(
42+
(state: AppState) => state.console.sessionLoadingState
43+
);
44+
4645
const anonymousMode = useSelector(
4746
(state: AppState) => state.system.anonymousMode
4847
);
@@ -53,56 +52,16 @@ const ProtectedRoute = ({ Component }: ProtectedRouteProps) => {
5352
return <Navigate to={{ pathname: `login` }} />;
5453
};
5554

56-
const pathnameParts = pathname.split("/");
57-
const screen = pathnameParts.length > 2 ? pathnameParts[1] : "";
55+
// update location path and store it
56+
useEffect(() => {
57+
dispatch(fetchSession(pathname));
58+
}, [dispatch, pathname]);
5859

5960
useEffect(() => {
60-
api.session
61-
.sessionCheck()
62-
.then((res) => {
63-
dispatch(saveSessionResponse(res.data));
64-
dispatch(userLogged(true));
65-
setSessionLoading(false);
66-
dispatch(globalSetDistributedSetup(res.data?.distributedMode || false));
67-
68-
if (res.data.customStyles && res.data.customStyles !== "") {
69-
const overrideColorVariants = getOverrideColorVariants(
70-
res.data.customStyles
71-
);
72-
73-
if (overrideColorVariants !== false) {
74-
dispatch(setOverrideStyles(overrideColorVariants));
75-
}
76-
}
77-
})
78-
.catch(() => {
79-
// if we are trying to browse, probe access to the requested prefix
80-
if (screen === "browser") {
81-
const bucket = pathnameParts.length >= 3 ? pathnameParts[2] : "";
82-
// no bucket, no business
83-
if (bucket === "") {
84-
setSessionLoading(false);
85-
return;
86-
}
87-
// before marking the session as done, let's check if the bucket is publicly accessible
88-
api.buckets
89-
.listObjects(
90-
bucket,
91-
{ limit: 1 },
92-
{ headers: { "X-Anonymous": "1" } }
93-
)
94-
.then(() => {
95-
dispatch(setAnonymousMode());
96-
setSessionLoading(false);
97-
})
98-
.catch(() => {
99-
setSessionLoading(false);
100-
});
101-
} else {
102-
setSessionLoading(false);
103-
}
104-
});
105-
}, [dispatch, screen, pathnameParts]);
61+
if (sessionLoadingState === SessionCallStates.Done) {
62+
setSessionLoading(false);
63+
}
64+
}, [dispatch, sessionLoadingState]);
10665

10766
const [, invokeSRInfoApi] = useApi(
10867
(res: any) => {
@@ -142,6 +101,7 @@ const ProtectedRoute = ({ Component }: ProtectedRouteProps) => {
142101
if (sessionLoading) {
143102
return <LoadingComponent />;
144103
}
104+
145105
// redirect user to the right page based on session status
146106
return userLoggedIn ? <Component /> : <StorePathAndRedirect />;
147107
};

portal-ui/src/screens/Console/CommandBar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,9 @@ const CommandBar = () => {
152152
}, []);
153153

154154
const initialActions: Action[] = routesAsKbarActions(
155-
features,
156155
buckets,
157-
navigate
156+
navigate,
157+
features
158158
);
159159

160160
useRegisterActions(initialActions, [buckets, features]);

portal-ui/src/screens/Console/consoleSlice.ts

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,40 +15,56 @@
1515
// along with this program. If not, see <http://www.gnu.org/licenses/>.
1616

1717
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
18+
import { SessionResponse } from "../../api/consoleApi";
1819
import { AppState } from "../../store";
19-
import { SessionResponse } from "api/consoleApi";
20+
import { fetchSession } from "../../screens/LoginPage/sessionThunk";
21+
22+
export enum SessionCallStates {
23+
Idle = "idle",
24+
Loading = "loading",
25+
Done = "done",
26+
}
2027

2128
export interface ConsoleState {
2229
session: SessionResponse;
30+
sessionLoadingState: SessionCallStates;
2331
}
2432

2533
const initialState: ConsoleState = {
26-
session: {
27-
status: undefined,
28-
features: [],
29-
distributedMode: false,
30-
permissions: {},
31-
allowResources: undefined,
32-
customStyles: undefined,
33-
envConstants: undefined,
34-
serverEndPoint: "",
35-
},
34+
session: {},
35+
sessionLoadingState: SessionCallStates.Idle,
3636
};
3737

3838
export const consoleSlice = createSlice({
3939
name: "console",
4040
initialState,
4141
reducers: {
42+
setSessionLoadingState: (
43+
state,
44+
action: PayloadAction<SessionCallStates>
45+
) => {
46+
state.sessionLoadingState = action.payload;
47+
},
4248
saveSessionResponse: (state, action: PayloadAction<SessionResponse>) => {
4349
state.session = action.payload;
4450
},
4551
resetSession: (state) => {
4652
state.session = initialState.session;
4753
},
4854
},
55+
extraReducers: (builder) => {
56+
builder
57+
.addCase(fetchSession.pending, (state, action) => {
58+
state.sessionLoadingState = SessionCallStates.Loading;
59+
})
60+
.addCase(fetchSession.fulfilled, (state, action) => {
61+
state.sessionLoadingState = SessionCallStates.Done;
62+
});
63+
},
4964
});
5065

51-
export const { saveSessionResponse, resetSession } = consoleSlice.actions;
66+
export const { saveSessionResponse, resetSession, setSessionLoadingState } =
67+
consoleSlice.actions;
5268
export const selSession = (state: AppState) => state.console.session;
5369
export const selFeatures = (state: AppState) =>
5470
state.console.session ? state.console.session.features : [];

portal-ui/src/screens/Console/kbar-actions.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ import { IAM_PAGES } from "../../common/SecureComponent/permissions";
2121
import { Bucket } from "../../api/consoleApi";
2222

2323
export const routesAsKbarActions = (
24-
features: string[] | undefined,
2524
buckets: Bucket[],
26-
navigate: (url: string) => void
25+
navigate: (url: string) => void,
26+
features?: string[]
2727
) => {
2828
const initialActions: Action[] = [];
2929
const allowedMenuItems = validRoutes(features);

portal-ui/src/screens/LoginPage/Login.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ export interface LoginStrategyPayload {
3636
}
3737

3838
export const getTargetPath = () => {
39-
let targetPath = "/";
39+
// default to browser view to avoid redirecting first to "/" and then to "/browser" on login
40+
let targetPath = "/browser";
4041
if (
4142
localStorage.getItem("redirect-path") &&
4243
localStorage.getItem("redirect-path") !== ""
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// This file is part of MinIO Console Server
2+
// Copyright (c) 2023 MinIO, Inc.
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
import { createAsyncThunk } from "@reduxjs/toolkit";
18+
import { setErrorSnackMessage } from "../../systemSlice";
19+
import { api } from "api";
20+
import {
21+
Error,
22+
HttpResponse,
23+
SessionResponse,
24+
ListObjectsResponse,
25+
} from "api/consoleApi";
26+
import { errorToHandler } from "api/errors";
27+
import {
28+
saveSessionResponse,
29+
setSessionLoadingState,
30+
SessionCallStates,
31+
} from "../Console/consoleSlice";
32+
import {
33+
globalSetDistributedSetup,
34+
setOverrideStyles,
35+
setAnonymousMode,
36+
} from "../../../src/systemSlice";
37+
import { getOverrideColorVariants } from "../../utils/stylesUtils";
38+
import { userLogged } from "../../../src/systemSlice";
39+
40+
export const fetchSession = createAsyncThunk(
41+
"session/fetchSession",
42+
async (location: string, { dispatch, rejectWithValue }) => {
43+
const pathnameParts = location.split("/");
44+
const screen = pathnameParts.length > 2 ? pathnameParts[1] : "";
45+
46+
return api.session
47+
.sessionCheck()
48+
.then((res: HttpResponse<SessionResponse, Error>) => {
49+
dispatch(userLogged(true));
50+
dispatch(saveSessionResponse(res.data));
51+
dispatch(globalSetDistributedSetup(res.data.distributedMode || false));
52+
53+
if (res.data.customStyles && res.data.customStyles !== "") {
54+
const overrideColorVariants = getOverrideColorVariants(
55+
res.data.customStyles
56+
);
57+
58+
if (overrideColorVariants !== false) {
59+
dispatch(setOverrideStyles(overrideColorVariants));
60+
}
61+
}
62+
})
63+
.catch(async (res: HttpResponse<SessionResponse, Error>) => {
64+
if (screen === "browser") {
65+
const bucket = pathnameParts.length >= 3 ? pathnameParts[2] : "";
66+
// no bucket, no business
67+
if (bucket === "") {
68+
return;
69+
}
70+
// before marking the session as done, let's check if the bucket is publicly accessible (anonymous)
71+
api.buckets
72+
.listObjects(
73+
bucket,
74+
{ limit: 1 },
75+
{ headers: { "X-Anonymous": "1" } }
76+
)
77+
.then(() => {
78+
dispatch(setAnonymousMode());
79+
})
80+
.catch((res: HttpResponse<ListObjectsResponse, Error>) => {
81+
dispatch(setErrorSnackMessage(errorToHandler(res.error)));
82+
})
83+
.finally(() => {
84+
// TODO: we probably need a thunk for this api since setting the state here is hacky,
85+
// we can use a state to let the ProtectedRoutes know when to render the elements
86+
dispatch(setSessionLoadingState(SessionCallStates.Done));
87+
});
88+
} else {
89+
dispatch(setSessionLoadingState(SessionCallStates.Done));
90+
dispatch(setErrorSnackMessage(errorToHandler(res.error)));
91+
}
92+
return rejectWithValue(res.error);
93+
});
94+
}
95+
);

portal-ui/src/store.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import logReducer from "./screens/Console/Logs/logsSlice";
2323
import healthInfoReducer from "./screens/Console/HealthInfo/healthInfoSlice";
2424
import watchReducer from "./screens/Console/Watch/watchSlice";
2525
import consoleReducer from "./screens/Console/consoleSlice";
26-
import bucketsReducer from "./screens/Console/Buckets/ListBuckets/AddBucket/addBucketsSlice";
26+
import addBucketsReducer from "./screens/Console/Buckets/ListBuckets/AddBucket/addBucketsSlice";
2727
import bucketDetailsReducer from "./screens/Console/Buckets/BucketDetails/bucketDetailsSlice";
2828
import objectBrowserReducer from "./screens/Console/ObjectBrowser/objectBrowserSlice";
2929
import dashboardReducer from "./screens/Console/Dashboard/dashboardSlice";
@@ -39,7 +39,7 @@ const rootReducer = combineReducers({
3939
logs: logReducer,
4040
watch: watchReducer,
4141
console: consoleReducer,
42-
addBucket: bucketsReducer,
42+
addBucket: addBucketsReducer,
4343
bucketDetails: bucketDetailsReducer,
4444
objectBrowser: objectBrowserReducer,
4545
healthInfo: healthInfoReducer,

portal-ui/src/systemSlice.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ export interface SystemState {
2929
loggedIn: boolean;
3030
showMarketplace: boolean;
3131
sidebarOpen: boolean;
32-
session: string;
3332
userName: string;
3433
serverNeedsRestart: boolean;
3534
serverIsLoading: boolean;
@@ -51,7 +50,6 @@ const initialState: SystemState = {
5150
value: 0,
5251
loggedIn: false,
5352
showMarketplace: false,
54-
session: "",
5553
userName: "",
5654
sidebarOpen: initSideBarOpen,
5755
siteReplicationInfo: { siteName: "", curSite: false, enabled: false },
@@ -155,7 +153,6 @@ export const systemSlice = createSlice({
155153
state.licenseInfo = action.payload;
156154
},
157155
setHelpName: (state, action: PayloadAction<string>) => {
158-
console.log("setting helpName: ", action.payload);
159156
state.helpName = action.payload;
160157
},
161158
setHelpTabName: (state, action: PayloadAction<string>) => {

0 commit comments

Comments
 (0)