Skip to content

Commit

Permalink
refactor: routes and back button (#3600)
Browse files Browse the repository at this point in the history
* Changed ProtectedRoute to handle refresh token

* Created DashboardWrapperPage to insert header into elements

* Changed routes to be contained by only one ProtectedRoute

* Removed refresh and get version query of App.tsx

* Added loading if user not authenticated in ProtectedAdminRoute

* Changed page layout to not contain header

* Changed AdminPage and FlowPage to not have headers

* Removed unused variables

* Refactored redirectToLastLocation of headerComponent

* Removed unused track last visited path

* changed viewPage to not set onFlowPage since it's used only on header

* Added flow fetching into Playground page

* Fixed back button not working between flows

* Changed duplicate requests to show which request failed

* Refactored useGetBuilds to remove duplicated request

* Re-added get version query and config query

* [autofix.ci] apply automated fixes

* Fix tests that rely on autosave delay

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
  • Loading branch information
lucaseduoli and autofix-ci[bot] authored Aug 28, 2024
1 parent ffff572 commit d6537b7
Show file tree
Hide file tree
Showing 19 changed files with 218 additions and 279 deletions.
24 changes: 1 addition & 23 deletions src/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ import { Cookies } from "react-cookie";
import { RouterProvider } from "react-router-dom";
import "reactflow/dist/style.css";
import LoadingComponent from "./components/loadingComponent";
import {
LANGFLOW_ACCESS_TOKEN_EXPIRE_SECONDS,
LANGFLOW_ACCESS_TOKEN_EXPIRE_SECONDS_ENV,
} from "./constants/constants";
import { AuthContext } from "./contexts/authContext";
import {
useAutoLogin,
Expand Down Expand Up @@ -34,8 +30,6 @@ export default function App() {

const { mutate: mutateAutoLogin } = useAutoLogin();

useGetVersionQuery();

const { mutate: mutateRefresh } = useRefreshAccessToken();

const isLoginPage = location.pathname.includes("login");
Expand Down Expand Up @@ -79,23 +73,7 @@ export default function App() {
});
}, []);

useEffect(() => {
const envRefreshTime = LANGFLOW_ACCESS_TOKEN_EXPIRE_SECONDS_ENV;
const automaticRefreshTime = LANGFLOW_ACCESS_TOKEN_EXPIRE_SECONDS;

const accessTokenTimer = isNaN(envRefreshTime)
? automaticRefreshTime
: envRefreshTime;

const intervalId = setInterval(() => {
if (isAuthenticated && !isLoginPage) {
mutateRefresh({ refresh_token: refreshToken });
}
}, accessTokenTimer * 1000);

return () => clearInterval(intervalId);
}, [isLoginPage]);

useGetVersionQuery();
useSaveConfig();

return (
Expand Down
11 changes: 7 additions & 4 deletions src/frontend/src/components/authAdminGuard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@ import useAuthStore from "@/stores/authStore";
import { useContext } from "react";
import { Navigate } from "react-router-dom";
import { AuthContext } from "../../contexts/authContext";
import LoadingComponent from "../loadingComponent";

export const ProtectedAdminRoute = ({ children }) => {
const { userData } = useContext(AuthContext);
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
const autoLogin = useAuthStore((state) => state.autoLogin);
const isAdmin = useAuthStore((state) => state.isAdmin);
const isLoginPage = location.pathname.includes("login");
const logout = useAuthStore((state) => state.logout);

if (!isAuthenticated && !isLoginPage) {
logout();
if (!isAuthenticated) {
return (
<div className="flex h-screen w-screen items-center justify-center">
<LoadingComponent remSize={30} />
</div>
);
} else if ((userData && !isAdmin) || autoLogin) {
return <Navigate to="/" replace />;
} else {
Expand Down
33 changes: 30 additions & 3 deletions src/frontend/src/components/authGuard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,40 @@
import { LANGFLOW_AUTO_LOGIN_OPTION } from "@/constants/constants";
import {
LANGFLOW_ACCESS_TOKEN_EXPIRE_SECONDS,
LANGFLOW_ACCESS_TOKEN_EXPIRE_SECONDS_ENV,
LANGFLOW_AUTO_LOGIN_OPTION,
} from "@/constants/constants";
import { useRefreshAccessToken } from "@/controllers/API/queries/auth";
import useAuthStore from "@/stores/authStore";
import { useEffect } from "react";
import { Cookies } from "react-cookie";

export const ProtectedRoute = ({ children }) => {
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
const hasToken = !!localStorage.getItem(LANGFLOW_AUTO_LOGIN_OPTION);
const isLoginPage = location.pathname.includes("login");
const logout = useAuthStore((state) => state.logout);

if (!isAuthenticated && hasToken && !isLoginPage) {
const cookies = new Cookies();
const refreshToken = cookies.get("refresh_token");
const { mutate: mutateRefresh } = useRefreshAccessToken();

useEffect(() => {
const envRefreshTime = LANGFLOW_ACCESS_TOKEN_EXPIRE_SECONDS_ENV;
const automaticRefreshTime = LANGFLOW_ACCESS_TOKEN_EXPIRE_SECONDS;

const accessTokenTimer = isNaN(envRefreshTime)
? automaticRefreshTime
: envRefreshTime;

const intervalId = setInterval(() => {
if (isAuthenticated) {
mutateRefresh({ refresh_token: refreshToken });
}
}, accessTokenTimer * 1000);

return () => clearInterval(intervalId);
}, []);

if (!isAuthenticated && hasToken) {
logout();
} else {
return children;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,6 @@ export const MenuBar = ({}: {}): JSX.Element => {
const addFlow = useAddFlow();
const setErrorData = useAlertStore((state) => state.setErrorData);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setLockChat = useFlowStore((state) => state.setLockChat);
const setIsBuilding = useFlowStore((state) => state.setIsBuilding);
const revertBuiltStatusFromBuilding = useFlowStore(
(state) => state.revertBuiltStatusFromBuilding,
);
const undo = useFlowsManagerStore((state) => state.undo);
const redo = useFlowsManagerStore((state) => state.redo);
const saveLoading = useFlowsManagerStore((state) => state.saveLoading);
Expand Down
26 changes: 9 additions & 17 deletions src/frontend/src/components/headerComponent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { useLogout } from "@/controllers/API/queries/auth";
import useAuthStore from "@/stores/authStore";
import useAlertStore from "../../stores/alertStore";
import { useDarkStore } from "../../stores/darkStore";
import { useLocationStore } from "../../stores/locationStore";
import { useStoreStore } from "../../stores/storeStore";
import IconComponent, { ForwardedIconComponent } from "../genericIconComponent";
import { Button } from "../ui/button";
Expand Down Expand Up @@ -48,29 +47,22 @@ export default function Header(): JSX.Element {
const setDark = useDarkStore((state) => state.setDark);
const stars = useDarkStore((state) => state.stars);

const routeHistory = useLocationStore((state) => state.routeHistory);

const profileImageUrl = `${BASE_URL_API}files/profile_pictures/${
userData?.profile_image ?? "Space/046-rocket.svg"
}`;

const redirectToLastLocation = () => {
const lastVisitedIndex = routeHistory
.reverse()
.findIndex((path) => path !== location.pathname);

const lastFlowVisited = routeHistory[lastVisitedIndex];
lastFlowVisited ? navigate(lastFlowVisited) : navigate("/all");
const canGoBack = location.key !== "default";
if (canGoBack) {
navigate(-1);
} else {
navigate("/", { replace: true });
}
};

const visitedFlowPathBefore = () => {
const last100VisitedPaths = routeHistory.slice(-99);
return last100VisitedPaths.some((path) => path.includes("/flow/"));
};

const showArrowReturnIcon =
LOCATIONS_TO_RETURN.some((path) => location.pathname.includes(path)) &&
visitedFlowPathBefore();
const showArrowReturnIcon = LOCATIONS_TO_RETURN.some((path) =>
location.pathname.includes(path),
);

const handleLogout = () => {
mutationLogout(undefined, {
Expand Down
32 changes: 14 additions & 18 deletions src/frontend/src/components/pageLayout/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Header from "../headerComponent";
import { Separator } from "../ui/separator";

export default function PageLayout({
Expand All @@ -15,25 +14,22 @@ export default function PageLayout({
betaIcon?: boolean;
}) {
return (
<div className="flex h-screen w-full flex-col">
<Header />
<div className="flex h-full w-full flex-col justify-between overflow-auto bg-background px-16">
<div className="flex w-full items-center justify-between gap-4 space-y-0.5 py-8 pb-2">
<div className="flex w-full flex-col">
<h2
className="text-2xl font-bold tracking-tight"
data-testid="mainpage_title"
>
{title}
{betaIcon && <span className="store-beta-icon">BETA</span>}
</h2>
<p className="text-muted-foreground">{description}</p>
</div>
<div className="flex-shrink-0">{button && button}</div>
<div className="flex h-full w-full flex-col justify-between overflow-auto bg-background px-16">
<div className="flex w-full items-center justify-between gap-4 space-y-0.5 py-8 pb-2">
<div className="flex w-full flex-col">
<h2
className="text-2xl font-bold tracking-tight"
data-testid="mainpage_title"
>
{title}
{betaIcon && <span className="store-beta-icon">BETA</span>}
</h2>
<p className="text-muted-foreground">{description}</p>
</div>
<Separator className="my-6 flex" />
{children}
<div className="flex-shrink-0">{button && button}</div>
</div>
<Separator className="my-6 flex" />
{children}
</div>
);
}
13 changes: 6 additions & 7 deletions src/frontend/src/controllers/API/api.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { LANGFLOW_ACCESS_TOKEN } from "@/constants/constants";
import useAuthStore from "@/stores/authStore";
import { useUtilityStore } from "@/stores/utilityStore";
import axios, { AxiosError, AxiosInstance, AxiosRequestConfig } from "axios";
import { useContext, useEffect } from "react";
import { Cookies } from "react-cookie";
Expand Down Expand Up @@ -89,13 +88,13 @@ function ApiInterceptor() {
// Request interceptor to add access token to every request
const requestInterceptor = api.interceptors.request.use(
(config) => {
const checkRequest = checkDuplicateRequestAndStoreRequest(config);

const controller = new AbortController();

if (!checkRequest) {
controller.abort("Duplicate Request");
console.error("Duplicate Request");
try {
checkDuplicateRequestAndStoreRequest(config);
} catch (e) {
const error = e as Error;
controller.abort(error.message);
console.error(error.message);
}

const accessToken = cookies.get(LANGFLOW_ACCESS_TOKEN);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,11 @@ export function checkDuplicateRequestAndStoreRequest(config) {
currentTime - parseInt(lastRequestTime, 10) < 300 &&
lastCurrentUrl === currentUrl
) {
return false;
throw new Error("Duplicate request: " + lastUrl);
}

localStorage.setItem("lastUrlCalled", config.url ?? "");
localStorage.setItem("lastMethodCalled", config.method ?? "");
localStorage.setItem("lastRequestTime", currentTime.toString());
localStorage.setItem("lastCurrentUrl", currentUrl);

return true;
}
31 changes: 11 additions & 20 deletions src/frontend/src/controllers/API/queries/_builds/use-get-builds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,22 @@ import { UseRequestProcessor } from "../../services/request-processor";

interface BuildsQueryParams {
flowId?: string;
nodeId?: string;
}

export const useGetBuildsQuery: useQueryFunctionType<
BuildsQueryParams,
AxiosResponse<{ vertex_builds: FlowPoolType }>
> = ({}) => {
> = (params) => {
const { query } = UseRequestProcessor();

const setFlowPool = useFlowStore((state) => state.setFlowPool);
const currentFlow = useFlowStore((state) => state.currentFlow);

const getBuildsFn = async (
params: BuildsQueryParams,
): Promise<AxiosResponse<{ vertex_builds: FlowPoolType }>> => {
const responseFn = async () => {
const config = {};
config["params"] = { flow_id: params.flowId };

if (params.nodeId) {
config["params"] = { nodeId: params.nodeId };
}

return await api.get<any>(`${getURL("BUILDS")}`, config);
};

const responseFn = async () => {
const response = await getBuildsFn({
flowId: currentFlow!.id,
});
const response = await api.get<any>(`${getURL("BUILDS")}`, config);

if (currentFlow) {
const flowPool = response.data.vertex_builds;
Expand All @@ -47,10 +34,14 @@ export const useGetBuildsQuery: useQueryFunctionType<
return response;
};

const queryResult = query(["useGetBuildsQuery"], responseFn, {
placeholderData: keepPreviousData,
refetchOnWindowFocus: false,
});
const queryResult = query(
["useGetBuildsQuery", { key: params.flowId }],
responseFn,
{
placeholderData: keepPreviousData,
refetchOnWindowFocus: false,
},
);

return queryResult;
};
14 changes: 0 additions & 14 deletions src/frontend/src/hooks/use-track-last-visited-path.tsx

This file was deleted.

2 changes: 0 additions & 2 deletions src/frontend/src/pages/AdminPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
import { cloneDeep } from "lodash";
import { useContext, useEffect, useRef, useState } from "react";
import IconComponent from "../../components/genericIconComponent";
import Header from "../../components/headerComponent";
import LoadingComponent from "../../components/loadingComponent";
import PaginatorComponent from "../../components/paginatorComponent";
import ShadTooltip from "../../components/shadTooltipComponent";
Expand Down Expand Up @@ -246,7 +245,6 @@ export default function AdminPage() {

return (
<>
<Header />
{userData && (
<div className="admin-page-panel flex h-full flex-col pb-8">
<div className="main-page-nav-arrangement">
Expand Down
3 changes: 0 additions & 3 deletions src/frontend/src/pages/AppWrapperPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
TIMEOUT_ERROR_MESSAGE,
} from "@/constants/constants";
import { useGetHealthQuery } from "@/controllers/API/queries/health";
import useTrackLastVisitedPath from "@/hooks/use-track-last-visited-path";
import useFlowsManagerStore from "@/stores/flowsManagerStore";
import { useUtilityStore } from "@/stores/utilityStore";
import { cn } from "@/utils/utils";
Expand All @@ -20,8 +19,6 @@ import { ErrorBoundary } from "react-error-boundary";
import { Outlet } from "react-router-dom";

export function AppWrapperPage() {
useTrackLastVisitedPath();

const isLoading = useFlowsManagerStore((state) => state.isLoading);

const healthCheckMaxRetries = useFlowsManagerStore(
Expand Down
11 changes: 11 additions & 0 deletions src/frontend/src/pages/DashboardWrapperPage/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Header from "@/components/headerComponent";
import { Outlet } from "react-router-dom";

export function DashboardWrapperPage() {
return (
<div className="flex h-screen w-full flex-col">
<Header />
<Outlet />
</div>
);
}
Loading

0 comments on commit d6537b7

Please sign in to comment.