Skip to content

Commit 253053c

Browse files
authored
Refactor session to avoid duplicate calls to apis (#2868)
Co-authored-by: cesnietor <>
1 parent 08a3ff6 commit 253053c

File tree

9 files changed

+178
-85
lines changed

9 files changed

+178
-85
lines changed

portal-ui/src/ProtectedRoutes.tsx

Lines changed: 23 additions & 63 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, setLocationPath } from "./systemSlice";
28+
import { SessionCallStates } from "./screens/Console/consoleSlice.types";
3629

3730
interface ProtectedRouteProps {
3831
Component: any;
@@ -41,8 +34,11 @@ 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+
const [componentLoading, setComponentLoading] = useState<boolean>(true);
39+
const sessionLoadingState = useSelector(
40+
(state: AppState) => state.console.sessionLoadingState
41+
);
4642
const anonymousMode = useSelector(
4743
(state: AppState) => state.system.anonymousMode
4844
);
@@ -53,56 +49,19 @@ const ProtectedRoute = ({ Component }: ProtectedRouteProps) => {
5349
return <Navigate to={{ pathname: `login` }} />;
5450
};
5551

56-
const pathnameParts = pathname.split("/");
57-
const screen = pathnameParts.length > 2 ? pathnameParts[1] : "";
52+
useEffect(() => {
53+
dispatch(setLocationPath(pathname));
54+
}, [dispatch, pathname]);
5855

5956
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]);
57+
dispatch(fetchSession());
58+
}, [dispatch]);
59+
60+
useEffect(() => {
61+
if (sessionLoadingState === SessionCallStates.Done) {
62+
setComponentLoading(false);
63+
}
64+
}, [dispatch, sessionLoadingState]);
10665

10766
const [, invokeSRInfoApi] = useApi(
10867
(res: any) => {
@@ -132,16 +91,17 @@ const ProtectedRoute = ({ Component }: ProtectedRouteProps) => {
13291
);
13392

13493
useEffect(() => {
135-
if (userLoggedIn && !sessionLoading && !anonymousMode) {
94+
if (userLoggedIn && !componentLoading && !anonymousMode) {
13695
invokeSRInfoApi("GET", `api/v1/admin/site-replication`);
13796
}
13897
// eslint-disable-next-line react-hooks/exhaustive-deps
139-
}, [userLoggedIn, sessionLoading]);
98+
}, [userLoggedIn, componentLoading]);
14099

141100
// if we're still trying to retrieve user session render nothing
142-
if (sessionLoading) {
101+
if (componentLoading) {
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: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,40 +15,51 @@
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+
import { SessionCallStates } from "./consoleSlice.types";
2022

2123
export interface ConsoleState {
2224
session: SessionResponse;
25+
sessionLoadingState: SessionCallStates;
2326
}
2427

2528
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-
},
29+
session: {},
30+
sessionLoadingState: SessionCallStates.Initial,
3631
};
3732

3833
export const consoleSlice = createSlice({
3934
name: "console",
4035
initialState,
4136
reducers: {
37+
setSessionLoadingState: (
38+
state,
39+
action: PayloadAction<SessionCallStates>
40+
) => {
41+
state.sessionLoadingState = action.payload;
42+
},
4243
saveSessionResponse: (state, action: PayloadAction<SessionResponse>) => {
4344
state.session = action.payload;
4445
},
4546
resetSession: (state) => {
4647
state.session = initialState.session;
4748
},
4849
},
50+
extraReducers: (builder) => {
51+
builder
52+
.addCase(fetchSession.pending, (state, action) => {
53+
state.sessionLoadingState = SessionCallStates.Loading;
54+
})
55+
.addCase(fetchSession.fulfilled, (state, action) => {
56+
state.sessionLoadingState = SessionCallStates.Done;
57+
});
58+
},
4959
});
5060

51-
export const { saveSessionResponse, resetSession } = consoleSlice.actions;
61+
export const { saveSessionResponse, resetSession, setSessionLoadingState } =
62+
consoleSlice.actions;
5263
export const selSession = (state: AppState) => state.console.session;
5364
export const selFeatures = (state: AppState) =>
5465
state.console.session ? state.console.session.features : [];
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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+
export enum SessionCallStates {
18+
Initial = "initial",
19+
Loading = "loading",
20+
Done = "done",
21+
}

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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export interface LoginStrategyPayload {
3636
}
3737

3838
export const getTargetPath = () => {
39-
let targetPath = "/";
39+
let targetPath = "/browser";
4040
if (
4141
localStorage.getItem("redirect-path") &&
4242
localStorage.getItem("redirect-path") !== ""
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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+
} from "../Console/consoleSlice";
31+
import { SessionCallStates } from "../Console/consoleSlice.types";
32+
33+
import {
34+
globalSetDistributedSetup,
35+
setOverrideStyles,
36+
setAnonymousMode,
37+
} from "../../../src/systemSlice";
38+
import { getOverrideColorVariants } from "../../utils/stylesUtils";
39+
import { userLogged } from "../../../src/systemSlice";
40+
import { AppState } from "../../store";
41+
42+
export const fetchSession = createAsyncThunk(
43+
"session/fetchSession",
44+
async (_, { getState, dispatch, rejectWithValue }) => {
45+
const state = getState() as AppState;
46+
const pathnameParts = state.system.locationPath.split("/");
47+
const screen = pathnameParts.length > 2 ? pathnameParts[1] : "";
48+
49+
return api.session
50+
.sessionCheck()
51+
.then((res: HttpResponse<SessionResponse, Error>) => {
52+
dispatch(userLogged(true));
53+
dispatch(saveSessionResponse(res.data));
54+
dispatch(globalSetDistributedSetup(res.data.distributedMode || false));
55+
56+
if (res.data.customStyles && res.data.customStyles !== "") {
57+
const overrideColorVariants = getOverrideColorVariants(
58+
res.data.customStyles
59+
);
60+
61+
if (overrideColorVariants !== false) {
62+
dispatch(setOverrideStyles(overrideColorVariants));
63+
}
64+
}
65+
})
66+
.catch(async (res: HttpResponse<SessionResponse, Error>) => {
67+
if (screen === "browser") {
68+
const bucket = pathnameParts.length >= 3 ? pathnameParts[2] : "";
69+
// no bucket, no business
70+
if (bucket === "") {
71+
return;
72+
}
73+
// before marking the session as done, let's check if the bucket is publicly accessible (anonymous)
74+
api.buckets
75+
.listObjects(
76+
bucket,
77+
{ limit: 1 },
78+
{ headers: { "X-Anonymous": "1" } }
79+
)
80+
.then(() => {
81+
dispatch(setAnonymousMode());
82+
})
83+
.catch((res: HttpResponse<ListObjectsResponse, Error>) => {
84+
dispatch(setErrorSnackMessage(errorToHandler(res.error)));
85+
})
86+
.finally(() => {
87+
// TODO: we probably need a thunk for this api since setting the state here is hacky,
88+
// we can use a state to let the ProtectedRoutes know when to render the elements
89+
dispatch(setSessionLoadingState(SessionCallStates.Done));
90+
});
91+
} else {
92+
dispatch(setSessionLoadingState(SessionCallStates.Done));
93+
dispatch(setErrorSnackMessage(errorToHandler(res.error)));
94+
}
95+
return rejectWithValue(res.error);
96+
});
97+
}
98+
);

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,

0 commit comments

Comments
 (0)