refactor: queries loading order and wrapper order (#3603)

* Added loading page

* Removed unused loadings and changed loading to LoadingPage

* Refactored ComponentsComponent to receive info from parent

* refactored headerTabsComponent to receive loading from parent

* Added loading of folders into MyCollectionComponent

* removed unused loading and folderSelected

* updated get config api call to update everything

* Make app wait for autoLogin to be set to execute everything else

* changed API type to not contain params if its undefined

* Updated get autologin to do all logic regarding autologin

* Updated other queries with the new useQueryFunctionType type

* Updated App.tsx with new gets and configurations and added a loader before loading the router

* Made ProtectedRoute refresh on authentication change

* Fixed order of wrappers in order for Auth and  API context to have access to router

* Made loading only exist in one place

* 📝 (folders.spec.ts): remove unused test for adding folder by drag and drop to improve test suite cleanliness and maintainability.

* Fixed flow dropping to another folder

* ✨ (folders.spec.ts): add test for adding folder by drag and drop functionality
🔧 (auto-save-off.spec.ts): add click event for "Save And Exit" button
🔧 (dragAndDrop.spec.ts): change dispatchEvent to getByTestId and add assertions for specific text visibility
🔧 (store-shard-3.spec.ts): increase timeout for page.waitForTimeout to improve test reliability

* ✅ (folders.spec.ts): update test description to be more descriptive and accurate

* test: improve timeout for page.waitForSelector in auto-save-off.spec.ts

* feat: add replace button functionality to main page

The code changes include adding the functionality for the replace button on the main page. This allows users to replace a flow or a component. The replace button is now visible on the page, and clicking on it triggers the appropriate action.

Recent user commits:
- test: improve timeout for page.waitForSelector in auto-save-off.spec.ts
- ✅ (folders.spec.ts): update test description to be more descriptive and accurate
- ✨ (folders.spec.ts): add test for adding folder by drag and drop functionality
- 🔧 (auto-save-off.spec.ts): add click event for "Save And Exit" button
- 🔧 (dragAndDrop.spec.ts): change dispatchEvent to getByTestId and add assertions for specific text visibility
- 🔧 (store-shard-3.spec.ts): increase timeout for page.waitForTimeout to improve test reliability

Recent repository commits:
- test: improve timeout for page.waitForSelector in auto-save-off.spec.ts
- ✅ (folders.spec.ts): update test description to be more descriptive and accurate
- ✨ (folders.spec.ts): add test for adding folder by drag and drop functionality
- 🔧 (auto-save-off.spec.ts): add click event for "Save And Exit" button
- 🔧 (dragAndDrop.spec.ts): change dispatchEvent to getByTestId and add assertions for specific text visibility
- 🔧 (store-shard-3.spec.ts): increase timeout for page.waitForTimeout to improve test reliability
- Fixed flow dropping to another folder
- 📝 (folders.spec.ts): remove unused test for adding folder by drag and drop to improve test suite cleanliness and maintainability.
- Made loading only exist in one place
- Fixed order of wrappers in order for Auth and API context to have access to router
- Made ProtectedRoute refresh on authentication change
- Updated App.tsx with new gets and configurations and added a loader before loading the router
- Updated other queries with the new useQueryFunctionType type

---------

Co-authored-by: cristhianzl <cristhian.lousa@gmail.com>
This commit is contained in:
Lucas Oliveira 2024-08-29 09:10:26 -03:00 • committed by GitHub
commit af052285ec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 393 additions and 371 deletions

View file

@ -1,90 +1,12 @@
import { Suspense, useContext, useEffect } from "react"; import { Suspense } from "react";
import { Cookies } from "react-cookie";
import { RouterProvider } from "react-router-dom"; import { RouterProvider } from "react-router-dom";
import "reactflow/dist/style.css"; import "reactflow/dist/style.css";
import LoadingComponent from "./components/loadingComponent"; import { LoadingPage } from "./pages/LoadingPage";
import { AuthContext } from "./contexts/authContext";
import {
useAutoLogin,
useRefreshAccessToken,
} from "./controllers/API/queries/auth";
import { useGetVersionQuery } from "./controllers/API/queries/version";
import useSaveConfig from "./hooks/use-save-config";
import router from "./routes"; import router from "./routes";
import useAlertStore from "./stores/alertStore";
import useAuthStore from "./stores/authStore";
import { useDarkStore } from "./stores/darkStore";
import useFlowsManagerStore from "./stores/flowsManagerStore";
export default function App() { export default function App() {
const { login, setUserData, getUser } = useContext(AuthContext);
const setAutoLogin = useAuthStore((state) => state.setAutoLogin);
const setLoading = useAlertStore((state) => state.setLoading);
const refreshStars = useDarkStore((state) => state.refreshStars);
const dark = useDarkStore((state) => state.dark);
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
const cookies = new Cookies();
const logout = useAuthStore((state) => state.logout);
const refreshToken = cookies.get("refresh_token");
const { mutate: mutateAutoLogin } = useAutoLogin();
const { mutate: mutateRefresh } = useRefreshAccessToken();
const isLoginPage = location.pathname.includes("login");
useEffect(() => {
if (!dark) {
document.getElementById("body")!.classList.remove("dark");
} else {
document.getElementById("body")!.classList.add("dark");
}
}, [dark]);
useEffect(() => {
mutateAutoLogin(undefined, {
onSuccess: async (user) => {
if (user && user["access_token"]) {
user["refresh_token"] = "auto";
login(user["access_token"], "auto");
setUserData(user);
setAutoLogin(true);
refreshStars();
// mutateRefresh({ refresh_token: refreshToken });
}
},
onError: (error) => {
if (error.name !== "CanceledError") {
setAutoLogin(false);
if (!isLoginPage) {
if (!isAuthenticated) {
setLoading(false);
useFlowsManagerStore.setState({ isLoading: false });
logout();
} else {
mutateRefresh({ refresh_token: refreshToken });
refreshStars();
getUser();
}
}
}
},
});
}, []);
useGetVersionQuery();
useSaveConfig();
return ( return (
//need parent component with width and height <Suspense fallback={<LoadingPage />}>
<Suspense
fallback={
<div className="loading-page-panel">
<LoadingComponent remSize={50} />
</div>
}
>
<RouterProvider router={router} /> <RouterProvider router={router} />
</Suspense> </Suspense>
); );

View file

@ -1,8 +1,8 @@
import { LoadingPage } from "@/pages/LoadingPage";
import useAuthStore from "@/stores/authStore"; import useAuthStore from "@/stores/authStore";
import { useContext } from "react"; import { useContext } from "react";
import { Navigate } from "react-router-dom"; import { Navigate } from "react-router-dom";
import { AuthContext } from "../../contexts/authContext"; import { AuthContext } from "../../contexts/authContext";
import LoadingComponent from "../loadingComponent";
export const ProtectedAdminRoute = ({ children }) => { export const ProtectedAdminRoute = ({ children }) => {
const { userData } = useContext(AuthContext); const { userData } = useContext(AuthContext);
@ -11,11 +11,7 @@ export const ProtectedAdminRoute = ({ children }) => {
const isAdmin = useAuthStore((state) => state.isAdmin); const isAdmin = useAuthStore((state) => state.isAdmin);
if (!isAuthenticated) { if (!isAuthenticated) {
return ( return <LoadingPage />;
<div className="flex h-screen w-screen items-center justify-center">
<LoadingComponent remSize={30} />
</div>
);
} else if ((userData && !isAdmin) || autoLogin) { } else if ((userData && !isAdmin) || autoLogin) {
return <Navigate to="/" replace />; return <Navigate to="/" replace />;
} else { } else {

View file

@ -25,14 +25,17 @@ export const ProtectedRoute = ({ children }) => {
? automaticRefreshTime ? automaticRefreshTime
: envRefreshTime; : envRefreshTime;
const intervalId = setInterval(() => { const intervalFunction = () => {
if (isAuthenticated) { if (isAuthenticated) {
mutateRefresh({ refresh_token: refreshToken }); mutateRefresh({ refresh_token: refreshToken });
} }
}, accessTokenTimer * 1000); };
const intervalId = setInterval(intervalFunction, accessTokenTimer * 1000);
intervalFunction();
return () => clearInterval(intervalId); return () => clearInterval(intervalId);
}, []); }, [isAuthenticated]);
if (!isAuthenticated && hasToken) { if (!isAuthenticated && hasToken) {
logout(); logout();

View file

@ -19,12 +19,12 @@ const useFileDrop = (folderId: string) => {
const flows = useFlowsManagerStore((state) => state.flows); const flows = useFlowsManagerStore((state) => state.flows);
const saveFlow = useSaveFlow(); const saveFlow = useSaveFlow();
const { mutate: uploadFlowToFolder } = usePostUploadFlowToFolder(); const { mutate: uploadFlowToFolder } = usePostUploadFlowToFolder();
const handleFileDrop = async (e) => { const handleFileDrop = async (e, folderId) => {
if (e.dataTransfer.types.some((type) => type === "Files")) { if (e.dataTransfer.types.some((type) => type === "Files")) {
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
const firstFile = e.dataTransfer.files[0]; const firstFile = e.dataTransfer.files[0];
if (firstFile.type === "application/json") { if (firstFile.type === "application/json") {
uploadFormData(firstFile); uploadFormData(firstFile, folderId);
} else { } else {
setErrorData({ setErrorData({
title: WRONG_FILE_ERROR_ALERT, title: WRONG_FILE_ERROR_ALERT,
@ -94,7 +94,7 @@ const useFileDrop = (folderId: string) => {
} }
e.preventDefault(); e.preventDefault();
handleFileDrop(e); handleFileDrop(e, folderId);
}; };
const uploadFromDragCard = (flowId, folderId) => { const uploadFromDragCard = (flowId, folderId) => {
@ -115,7 +115,7 @@ const useFileDrop = (folderId: string) => {
saveFlow(updatedFlow); saveFlow(updatedFlow);
}; };
const uploadFormData = (data) => { const uploadFormData = (data, folderId) => {
const formData = new FormData(); const formData = new FormData();
formData.append("file", data); formData.append("file", data);
setFolderDragging(false); setFolderDragging(false);

View file

@ -8,7 +8,6 @@ import { useGetUserData } from "@/controllers/API/queries/auth";
import useAuthStore from "@/stores/authStore"; import useAuthStore from "@/stores/authStore";
import { createContext, useEffect, useState } from "react"; import { createContext, useEffect, useState } from "react";
import Cookies from "universal-cookie"; import Cookies from "universal-cookie";
import useAlertStore from "../stores/alertStore";
import { useStoreStore } from "../stores/storeStore"; import { useStoreStore } from "../stores/storeStore";
import { Users } from "../types/api"; import { Users } from "../types/api";
import { AuthContextType } from "../types/contexts/auth"; import { AuthContextType } from "../types/contexts/auth";
@ -33,7 +32,6 @@ export function AuthProvider({ children }): React.ReactElement {
cookies.get(LANGFLOW_ACCESS_TOKEN) ?? null, cookies.get(LANGFLOW_ACCESS_TOKEN) ?? null,
); );
const [userData, setUserData] = useState<Users | null>(null); const [userData, setUserData] = useState<Users | null>(null);
const setLoading = useAlertStore((state) => state.setLoading);
const [apiKey, setApiKey] = useState<string | null>( const [apiKey, setApiKey] = useState<string | null>(
cookies.get(LANGFLOW_API_TOKEN), cookies.get(LANGFLOW_API_TOKEN),
); );
@ -71,7 +69,6 @@ export function AuthProvider({ children }): React.ReactElement {
}, },
onError: () => { onError: () => {
setUserData(null); setUserData(null);
setLoading(false);
}, },
}, },
); );

View file

@ -1,4 +1,3 @@
import { useDarkStore } from "@/stores/darkStore";
import { useQueryFunctionType } from "@/types/api"; import { useQueryFunctionType } from "@/types/api";
import { api } from "../../api"; import { api } from "../../api";
import { getURL } from "../../helpers/constants"; import { getURL } from "../../helpers/constants";
@ -24,7 +23,7 @@ interface IApiQueryResponse {
export const useGetApiKeysQuery: useQueryFunctionType< export const useGetApiKeysQuery: useQueryFunctionType<
undefined, undefined,
IApiQueryResponse IApiQueryResponse
> = (_, options) => { > = (options) => {
const { query } = UseRequestProcessor(); const { query } = UseRequestProcessor();
const getApiKeysFn = async () => { const getApiKeysFn = async () => {

View file

@ -1,24 +1,57 @@
import { UseMutationResult } from "@tanstack/react-query"; import { AuthContext } from "@/contexts/authContext";
import { useMutationFunctionType } from "../../../../types/api"; import useAuthStore from "@/stores/authStore";
import { AxiosError } from "axios";
import { useContext } from "react";
import { useQueryFunctionType, Users } from "../../../../types/api";
import { api } from "../../api"; import { api } from "../../api";
import { getURL } from "../../helpers/constants"; import { getURL } from "../../helpers/constants";
import { UseRequestProcessor } from "../../services/request-processor"; import { UseRequestProcessor } from "../../services/request-processor";
export const useAutoLogin: useMutationFunctionType<undefined, any> = ( export interface AutoLoginResponse {
options?, frontend_timeout: number;
auto_saving: boolean;
auto_saving_interval: number;
health_check_max_retries: number;
}
export const useGetAutoLogin: useQueryFunctionType<undefined, undefined> = (
options,
) => { ) => {
const { mutate } = UseRequestProcessor(); const { query } = UseRequestProcessor();
const { login, setUserData, getUser } = useContext(AuthContext);
const setAutoLogin = useAuthStore((state) => state.setAutoLogin);
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
const logout = useAuthStore((state) => state.logout);
const isLoginPage = location.pathname.includes("login");
const autoLoginFn = async (): Promise<any> => { async function getAutoLoginFn(): Promise<null> {
const res = await api.get(`${getURL("AUTOLOGIN")}`); try {
return res.data; const response = await api.get<Users>(`${getURL("AUTOLOGIN")}`);
}; const user = response.data;
if (user && user["access_token"]) {
user["refresh_token"] = "auto";
login(user["access_token"], "auto");
setUserData(user);
setAutoLogin(true);
}
} catch (e) {
const error = e as AxiosError;
if (error.name !== "CanceledError") {
setAutoLogin(false);
if (!isLoginPage) {
if (!isAuthenticated) {
await logout();
throw new Error("Unauthorized");
} else {
getUser();
}
}
}
}
return null;
}
const mutation: UseMutationResult = mutate( const queryResult = query(["useGetAutoLogin"], getAutoLoginFn, options);
["useAutoLogin"],
autoLoginFn,
options,
);
return mutation; return queryResult;
}; };

View file

@ -1,3 +1,5 @@
import useFlowsManagerStore from "@/stores/flowsManagerStore";
import axios from "axios";
import { useQueryFunctionType } from "../../../../types/api"; import { useQueryFunctionType } from "../../../../types/api";
import { api } from "../../api"; import { api } from "../../api";
import { getURL } from "../../helpers/constants"; import { getURL } from "../../helpers/constants";
@ -10,18 +12,35 @@ export interface ConfigResponse {
health_check_max_retries: number; health_check_max_retries: number;
} }
export const useGetConfigQuery: useQueryFunctionType< export const useGetConfig: useQueryFunctionType<undefined, undefined> = (
undefined, options,
ConfigResponse ) => {
> = (options) => { const setAutoSaving = useFlowsManagerStore((state) => state.setAutoSaving);
const setAutoSavingInterval = useFlowsManagerStore(
(state) => state.setAutoSavingInterval,
);
const setHealthCheckMaxRetries = useFlowsManagerStore(
(state) => state.setHealthCheckMaxRetries,
);
const { query } = UseRequestProcessor(); const { query } = UseRequestProcessor();
const getConfigFn = async () => { const getConfigFn = async () => {
const response = await api.get<ConfigResponse>(`${getURL("CONFIG")}`); const response = await api.get<ConfigResponse>(`${getURL("CONFIG")}`);
return response["data"]; const data = response["data"];
if (data) {
const timeoutInMilliseconds = data.frontend_timeout
? data.frontend_timeout * 1000
: 30000;
axios.defaults.baseURL = "";
axios.defaults.timeout = timeoutInMilliseconds;
setAutoSaving(data.auto_saving);
setAutoSavingInterval(data.auto_saving_interval);
setHealthCheckMaxRetries(data.health_check_max_retries);
}
}; };
const queryResult = query(["useGetConfigQuery"], getConfigFn, options); const queryResult = query(["useGetConfig"], getConfigFn, options);
return queryResult; return queryResult;
}; };

View file

@ -5,7 +5,7 @@ import {
import { useUtilityStore } from "@/stores/utilityStore"; import { useUtilityStore } from "@/stores/utilityStore";
import { createNewError503 } from "@/types/factory/axios-error-503"; import { createNewError503 } from "@/types/factory/axios-error-503";
import { keepPreviousData } from "@tanstack/react-query"; import { keepPreviousData } from "@tanstack/react-query";
import { AxiosError, AxiosHeaders } from "axios"; import { AxiosError } from "axios";
import { useQueryFunctionType } from "../../../../types/api"; import { useQueryFunctionType } from "../../../../types/api";
import { api } from "../../api"; import { api } from "../../api";
import { UseRequestProcessor } from "../../services/request-processor"; import { UseRequestProcessor } from "../../services/request-processor";
@ -21,7 +21,7 @@ interface getHealthResponse {
export const useGetHealthQuery: useQueryFunctionType< export const useGetHealthQuery: useQueryFunctionType<
undefined, undefined,
getHealthResponse getHealthResponse
> = (_, options) => { > = (options) => {
const { query } = UseRequestProcessor(); const { query } = UseRequestProcessor();
const setHealthCheckTimeout = useUtilityStore( const setHealthCheckTimeout = useUtilityStore(
(state) => state.setHealthCheckTimeout, (state) => state.setHealthCheckTimeout,

View file

@ -13,7 +13,7 @@ type tagsQueryResponse = Array<ITagsDataArray>;
export const useGetTagsQuery: useQueryFunctionType< export const useGetTagsQuery: useQueryFunctionType<
undefined, undefined,
tagsQueryResponse tagsQueryResponse
> = (_, options) => { > = (options) => {
const { query } = UseRequestProcessor(); const { query } = UseRequestProcessor();
const getTagsFn = async () => { const getTagsFn = async () => {

View file

@ -12,7 +12,7 @@ interface versionQueryResponse {
export const useGetVersionQuery: useQueryFunctionType< export const useGetVersionQuery: useQueryFunctionType<
undefined, undefined,
versionQueryResponse versionQueryResponse
> = (_, options) => { > = (options) => {
const { query } = UseRequestProcessor(); const { query } = UseRequestProcessor();
const getVersionFn = async () => { const getVersionFn = async () => {

View file

@ -1,30 +0,0 @@
import useFlowsManagerStore from "@/stores/flowsManagerStore";
import axios from "axios";
import { useEffect } from "react";
import { useGetConfigQuery } from "../controllers/API/queries/config/use-get-config";
function useSaveConfig() {
const { data } = useGetConfigQuery();
const setAutoSaving = useFlowsManagerStore((state) => state.setAutoSaving);
const setAutoSavingInterval = useFlowsManagerStore(
(state) => state.setAutoSavingInterval,
);
const setHealthCheckMaxRetries = useFlowsManagerStore(
(state) => state.setHealthCheckMaxRetries,
);
useEffect(() => {
if (data) {
const timeoutInMilliseconds = data.frontend_timeout
? data.frontend_timeout * 1000
: 30000;
axios.defaults.baseURL = "";
axios.defaults.timeout = timeoutInMilliseconds;
setAutoSaving(data.auto_saving);
setAutoSavingInterval(data.auto_saving_interval);
setHealthCheckMaxRetries(data.health_check_max_retries);
}
}, [data]);
}
export default useSaveConfig;

View file

@ -1,5 +1,4 @@
import ReactDOM from "react-dom/client"; import ReactDOM from "react-dom/client";
import ContextWrapper from "./contexts";
import reportWebVitals from "./reportWebVitals"; import reportWebVitals from "./reportWebVitals";
import "./style/classes.css"; import "./style/classes.css";
@ -16,9 +15,5 @@ const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement, document.getElementById("root") as HTMLElement,
); );
root.render( root.render(<App />);
<ContextWrapper>
<App />
</ContextWrapper>,
);
reportWebVitals(); reportWebVitals();

View file

@ -1,5 +1,4 @@
import { useLoginUser } from "@/controllers/API/queries/auth"; import { useLoginUser } from "@/controllers/API/queries/auth";
import { useFolderStore } from "@/stores/foldersStore";
import { useContext, useState } from "react"; import { useContext, useState } from "react";
import { Button } from "../../../components/ui/button"; import { Button } from "../../../components/ui/button";
import { Input } from "../../../components/ui/input"; import { Input } from "../../../components/ui/input";
@ -17,8 +16,6 @@ export default function LoginAdminPage() {
const [inputState, setInputState] = const [inputState, setInputState] =
useState<loginInputStateType>(CONTROL_LOGIN_STATE); useState<loginInputStateType>(CONTROL_LOGIN_STATE);
const { login } = useContext(AuthContext); const { login } = useContext(AuthContext);
const setLoading = useAlertStore((state) => state.setLoading);
const setSelectedFolder = useFolderStore((state) => state.setSelectedFolder);
const { password, username } = inputState; const { password, username } = inputState;
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
@ -38,9 +35,6 @@ export default function LoginAdminPage() {
mutate(user, { mutate(user, {
onSuccess: (res) => { onSuccess: (res) => {
setSelectedFolder(null);
setLoading(true);
login(res.access_token, "login", res.refresh_token); login(res.access_token, "login", res.refresh_token);
}, },
onError: (error) => { onError: (error) => {

View file

@ -0,0 +1,40 @@
import { useGetAutoLogin } from "@/controllers/API/queries/auth";
import { useGetConfig } from "@/controllers/API/queries/config/use-get-config";
import { useGetVersionQuery } from "@/controllers/API/queries/version";
import { useDarkStore } from "@/stores/darkStore";
import useFlowsManagerStore from "@/stores/flowsManagerStore";
import { useEffect } from "react";
import { Outlet } from "react-router-dom";
import { LoadingPage } from "../LoadingPage";
export function AppInitPage() {
const dark = useDarkStore((state) => state.dark);
const refreshStars = useDarkStore((state) => state.refreshStars);
const isLoading = useFlowsManagerStore((state) => state.isLoading);
const { isFetched } = useGetAutoLogin();
useGetVersionQuery({ enabled: isFetched });
useGetConfig({ enabled: isFetched });
useEffect(() => {
if (isFetched) {
refreshStars();
}
}, [isFetched]);
useEffect(() => {
if (!dark) {
document.getElementById("body")!.classList.remove("dark");
} else {
document.getElementById("body")!.classList.add("dark");
}
}, [dark]);
return (
//need parent component with width and height
<>
{(isLoading || !isFetched) && <LoadingPage overlay />}
{isFetched && <Outlet />}
</>
);
}

View file

@ -1,7 +1,6 @@
import AlertDisplayArea from "@/alerts/displayArea"; import AlertDisplayArea from "@/alerts/displayArea";
import CrashErrorComponent from "@/components/crashErrorComponent"; import CrashErrorComponent from "@/components/crashErrorComponent";
import FetchErrorComponent from "@/components/fetchErrorComponent"; import FetchErrorComponent from "@/components/fetchErrorComponent";
import LoadingComponent from "@/components/loadingComponent";
import TimeoutErrorComponent from "@/components/timeoutErrorComponent"; import TimeoutErrorComponent from "@/components/timeoutErrorComponent";
import { import {
FETCH_ERROR_DESCRIPION, FETCH_ERROR_DESCRIPION,
@ -12,15 +11,12 @@ import {
import { useGetHealthQuery } from "@/controllers/API/queries/health"; import { useGetHealthQuery } from "@/controllers/API/queries/health";
import useFlowsManagerStore from "@/stores/flowsManagerStore"; import useFlowsManagerStore from "@/stores/flowsManagerStore";
import { useUtilityStore } from "@/stores/utilityStore"; import { useUtilityStore } from "@/stores/utilityStore";
import { cn } from "@/utils/utils";
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { ErrorBoundary } from "react-error-boundary"; import { ErrorBoundary } from "react-error-boundary";
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router-dom";
export function AppWrapperPage() { export function AppWrapperPage() {
const isLoading = useFlowsManagerStore((state) => state.isLoading);
const healthCheckMaxRetries = useFlowsManagerStore( const healthCheckMaxRetries = useFlowsManagerStore(
(state) => state.healthCheckMaxRetries, (state) => state.healthCheckMaxRetries,
); );
@ -108,15 +104,6 @@ export function AppWrapperPage() {
> >
<> <>
{modalErrorComponent} {modalErrorComponent}
<div
className={cn(
"loading-page-panel absolute left-0 top-0 z-[999]",
isLoading ? "" : "hidden",
)}
>
<LoadingComponent remSize={50} />
</div>
<Outlet /> <Outlet />
</> </>
</ErrorBoundary> </ErrorBoundary>

View file

@ -0,0 +1,15 @@
import LoadingComponent from "@/components/loadingComponent";
import { cn } from "@/utils/utils";
export function LoadingPage({ overlay = false }: { overlay?: boolean }) {
return (
<div
className={cn(
"flex h-screen w-screen items-center justify-center bg-background",
overlay && "fixed left-0 top-0 z-[999]",
)}
>
<LoadingComponent remSize={50} />
</div>
);
}

View file

@ -1,5 +1,4 @@
import { useLoginUser } from "@/controllers/API/queries/auth"; import { useLoginUser } from "@/controllers/API/queries/auth";
import { useFolderStore } from "@/stores/foldersStore";
import * as Form from "@radix-ui/react-form"; import * as Form from "@radix-ui/react-form";
import { useContext, useState } from "react"; import { useContext, useState } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
@ -23,7 +22,6 @@ export default function LoginPage(): JSX.Element {
const { password, username } = inputState; const { password, username } = inputState;
const { login } = useContext(AuthContext); const { login } = useContext(AuthContext);
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const setSelectedFolder = useFolderStore((state) => state.setSelectedFolder);
function handleInput({ function handleInput({
target: { name, value }, target: { name, value },
@ -41,8 +39,6 @@ export default function LoginPage(): JSX.Element {
mutate(user, { mutate(user, {
onSuccess: (data) => { onSuccess: (data) => {
setSelectedFolder(null);
login(data.access_token, "login", data.refresh_token); login(data.access_token, "login", data.refresh_token);
}, },
onError: (error) => { onError: (error) => {

View file

@ -1,6 +1,4 @@
import { usePostDownloadMultipleFlows } from "@/controllers/API/queries/flows"; import { usePostDownloadMultipleFlows } from "@/controllers/API/queries/flows";
import { useGetFolderQuery } from "@/controllers/API/queries/folders/use-get-folder";
import { useGetFoldersQuery } from "@/controllers/API/queries/folders/use-get-folders";
import useDeleteFlow from "@/hooks/flows/use-delete-flow"; import useDeleteFlow from "@/hooks/flows/use-delete-flow";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { FormProvider, useForm, useWatch } from "react-hook-form"; import { FormProvider, useForm, useWatch } from "react-hook-form";
@ -13,6 +11,7 @@ import useAlertStore from "../../../../stores/alertStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { useFolderStore } from "../../../../stores/foldersStore"; import { useFolderStore } from "../../../../stores/foldersStore";
import { FlowType } from "../../../../types/flow"; import { FlowType } from "../../../../types/flow";
import { FolderType } from "../../entities";
import useFileDrop from "../../hooks/use-on-file-drop"; import useFileDrop from "../../hooks/use-on-file-drop";
import { getNameByType } from "../../utils/get-name-by-type"; import { getNameByType } from "../../utils/get-name-by-type";
import { sortFlows } from "../../utils/sort-flows"; import { sortFlows } from "../../utils/sort-flows";
@ -28,11 +27,13 @@ import useSelectedFlows from "./hooks/use-selected-flows";
export default function ComponentsComponent({ export default function ComponentsComponent({
type = "all", type = "all",
currentFolder,
isLoading,
}: { }: {
type?: string; type?: string;
currentFolder?: FolderType;
isLoading: boolean;
}) { }) {
const isLoading = useFlowsManagerStore((state) => state.isLoading);
const { folderId } = useParams(); const { folderId } = useParams();
const setSuccessData = useAlertStore((state) => state.setSuccessData); const setSuccessData = useAlertStore((state) => state.setSuccessData);
@ -51,10 +52,6 @@ export default function ComponentsComponent({
); );
const myCollectionId = useFolderStore((state) => state.myCollectionId); const myCollectionId = useFolderStore((state) => state.myCollectionId);
const { data: currentFolder, isLoading: isLoadingCurrentFolder } =
useGetFolderQuery({
id: folderId ?? myCollectionId ?? "",
});
const flowsFromFolder = currentFolder?.flows ?? []; const flowsFromFolder = currentFolder?.flows ?? [];
const [filteredFlows, setFilteredFlows] = const [filteredFlows, setFilteredFlows] =
@ -71,10 +68,6 @@ export default function ComponentsComponent({
const name = getNameByType(type); const name = getNameByType(type);
const setSelectedFolder = useFolderStore((state) => state.setSelectedFolder);
const { isLoading: isLoadingFolders } = useGetFoldersQuery();
const [shouldSelectAll, setShouldSelectAll] = useState(true); const [shouldSelectAll, setShouldSelectAll] = useState(true);
const cardTypes = useMemo(() => { const cardTypes = useMemo(() => {
@ -188,7 +181,6 @@ export default function ComponentsComponent({
const handleDeleteMultiple = () => { const handleDeleteMultiple = () => {
deleteFlow({ id: selectedFlowsComponentsCards }) deleteFlow({ id: selectedFlowsComponentsCards })
.then(() => { .then(() => {
setSelectedFolder(null);
resetFilter(); resetFilter();
setSelectedFlowsComponentsCards([]); setSelectedFlowsComponentsCards([]);
handleSelectAll(false); handleSelectAll(false);
@ -218,12 +210,7 @@ export default function ComponentsComponent({
<> <>
<div className="flex w-full gap-4 pb-5"> <div className="flex w-full gap-4 pb-5">
<HeaderComponent <HeaderComponent
disabled={ disabled={isLoading || data?.length === 0}
isLoading ||
isLoadingFolders ||
isLoadingCurrentFolder ||
data?.length === 0
}
shouldSelectAll={shouldSelectAll} shouldSelectAll={shouldSelectAll}
setShouldSelectAll={setShouldSelectAll} setShouldSelectAll={setShouldSelectAll}
handleDelete={() => handleSelectOptionsChange("delete")} handleDelete={() => handleSelectOptionsChange("delete")}
@ -243,16 +230,11 @@ export default function ComponentsComponent({
data-testid="cards-wrapper" data-testid="cards-wrapper"
> >
<div className="flex w-full flex-col gap-4"> <div className="flex w-full flex-col gap-4">
{!isLoading && {!isLoading && data?.length === 0 ? (
!isLoadingFolders &&
!isLoadingCurrentFolder &&
data?.length === 0 ? (
<EmptyComponent /> <EmptyComponent />
) : ( ) : (
<div className="grid w-full gap-4 md:grid-cols-2 lg:grid-cols-2"> <div className="grid w-full gap-4 md:grid-cols-2 lg:grid-cols-2">
{data?.length > 0 && {data?.length > 0 && isLoading === false ? (
isLoadingFolders === false &&
isLoadingCurrentFolder === false ? (
<> <>
{data?.map((item) => ( {data?.map((item) => (
<FormProvider {...methods} key={item.id}> <FormProvider {...methods} key={item.id}>

View file

@ -3,10 +3,13 @@ import useFlowsManagerStore from "../../../../../../stores/flowsManagerStore";
import InputSearchComponent from "../inputSearchComponent"; import InputSearchComponent from "../inputSearchComponent";
import TabsSearchComponent from "../tabsComponent"; import TabsSearchComponent from "../tabsComponent";
type HeaderTabsSearchComponentProps = {}; type HeaderTabsSearchComponentProps = {
loading: boolean;
};
const HeaderTabsSearchComponent = ({}: HeaderTabsSearchComponentProps) => { const HeaderTabsSearchComponent = ({
const isLoading = useFlowsManagerStore((state) => state.isLoading); loading,
}: HeaderTabsSearchComponentProps) => {
const [tabActive, setTabActive] = useState("Flows"); const [tabActive, setTabActive] = useState("Flows");
const [inputValue, setInputValue] = useState(""); const [inputValue, setInputValue] = useState("");
@ -18,7 +21,7 @@ const HeaderTabsSearchComponent = ({}: HeaderTabsSearchComponentProps) => {
<> <>
<div className="relative flex items-end gap-4"> <div className="relative flex items-end gap-4">
<InputSearchComponent <InputSearchComponent
loading={isLoading} loading={loading}
value={inputValue} value={inputValue}
onChange={(e) => { onChange={(e) => {
setSearchFlowsComponents(e.target.value); setSearchFlowsComponents(e.target.value);
@ -33,7 +36,7 @@ const HeaderTabsSearchComponent = ({}: HeaderTabsSearchComponentProps) => {
<TabsSearchComponent <TabsSearchComponent
tabsOptions={["All", "Flows", "Components"]} tabsOptions={["All", "Flows", "Components"]}
setActiveTab={setTabActive} setActiveTab={setTabActive}
loading={isLoading} loading={loading}
tabActive={tabActive} tabActive={tabActive}
/> />
</div> </div>

View file

@ -1,3 +1,7 @@
import { useGetFolderQuery } from "@/controllers/API/queries/folders/use-get-folder";
import { useGetFoldersQuery } from "@/controllers/API/queries/folders/use-get-folders";
import { useFolderStore } from "@/stores/foldersStore";
import { useParams } from "react-router-dom";
import ComponentsComponent from "../componentsComponent"; import ComponentsComponent from "../componentsComponent";
import HeaderTabsSearchComponent from "./components/headerTabsSearchComponent"; import HeaderTabsSearchComponent from "./components/headerTabsSearchComponent";
@ -6,11 +10,24 @@ type MyCollectionComponentProps = {
}; };
const MyCollectionComponent = ({ type }: MyCollectionComponentProps) => { const MyCollectionComponent = ({ type }: MyCollectionComponentProps) => {
const { folderId } = useParams();
const myCollectionId = useFolderStore((state) => state.myCollectionId);
const { data, isLoading } = useGetFolderQuery({
id: folderId ?? myCollectionId ?? "",
});
const { isLoading: isLoadingFolders } = useGetFoldersQuery();
return ( return (
<> <>
<HeaderTabsSearchComponent /> <HeaderTabsSearchComponent loading={isLoading || isLoadingFolders} />
<div className="mt-5 flex h-full flex-col"> <div className="mt-5 flex h-full flex-col">
<ComponentsComponent key={type} type={type} /> <ComponentsComponent
key={type}
type={type}
currentFolder={data}
isLoading={isLoading || isLoadingFolders}
/>
</div> </div>
</> </>
); );

View file

@ -4,7 +4,6 @@ import { useStoreStore } from "@/stores/storeStore";
import { useTypesStore } from "@/stores/typesStore"; import { useTypesStore } from "@/stores/typesStore";
import { useEffect } from "react"; import { useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import LoadingComponent from "../../components/loadingComponent";
import { getComponent } from "../../controllers/API"; import { getComponent } from "../../controllers/API";
import IOModal from "../../modals/IOModal"; import IOModal from "../../modals/IOModal";
import useFlowsManagerStore from "../../stores/flowsManagerStore"; import useFlowsManagerStore from "../../stores/flowsManagerStore";
@ -59,11 +58,7 @@ export default function PlaygroundPage() {
return ( return (
<div className="flex h-full w-full flex-col items-center justify-center align-middle"> <div className="flex h-full w-full flex-col items-center justify-center align-middle">
{!currentSavedFlow ? ( {currentSavedFlow && (
<div>
<LoadingComponent remSize={24}></LoadingComponent>
</div>
) : (
<IOModal open={true} setOpen={() => {}} isPlayground> <IOModal open={true} setOpen={() => {}} isPlayground>
<></> <></>
</IOModal> </IOModal>

View file

@ -11,6 +11,8 @@ import { ProtectedRoute } from "./components/authGuard";
import { ProtectedLoginRoute } from "./components/authLoginGuard"; import { ProtectedLoginRoute } from "./components/authLoginGuard";
import { AuthSettingsGuard } from "./components/authSettingsGuard"; import { AuthSettingsGuard } from "./components/authSettingsGuard";
import { StoreGuard } from "./components/storeGuard"; import { StoreGuard } from "./components/storeGuard";
import ContextWrapper from "./contexts";
import { AppInitPage } from "./pages/AppInitPage";
import { AppWrapperPage } from "./pages/AppWrapperPage"; import { AppWrapperPage } from "./pages/AppWrapperPage";
import { DashboardWrapperPage } from "./pages/DashboardWrapperPage"; import { DashboardWrapperPage } from "./pages/DashboardWrapperPage";
import FlowPage from "./pages/FlowPage"; import FlowPage from "./pages/FlowPage";
@ -35,129 +37,146 @@ const PlaygroundPage = lazy(() => import("./pages/Playground"));
const SignUp = lazy(() => import("./pages/SignUpPage")); const SignUp = lazy(() => import("./pages/SignUpPage"));
const router = createBrowserRouter( const router = createBrowserRouter(
createRoutesFromElements([ createRoutesFromElements([
<Route path="/" element={<AppWrapperPage />}> <Route
<Route path="/"
path="" element={
element={ <ContextWrapper>
<ProtectedRoute> <Outlet />
<Outlet /> </ContextWrapper>
</ProtectedRoute> }
} >
> <Route path="" element={<AppInitPage />}>
<Route path="" element={<DashboardWrapperPage />}> <Route path="" element={<AppWrapperPage />}>
<Route path="" element={<HomePage />}> <Route
<Route index element={<Navigate replace to={"all"} />} /> path=""
<Route element={
path="flows/" <ProtectedRoute>
element={<MyCollectionComponent key="flows" type="flow" />} <Outlet />
> </ProtectedRoute>
}
>
<Route path="" element={<DashboardWrapperPage />}>
<Route path="" element={<HomePage />}>
<Route index element={<Navigate replace to={"all"} />} />
<Route
path="flows/"
element={<MyCollectionComponent key="flows" type="flow" />}
>
<Route
path="folder/:folderId"
element={<MyCollectionComponent key="flows" type="flow" />}
/>
</Route>
<Route
path="components/"
element={
<MyCollectionComponent key="components" type="component" />
}
>
<Route
path="folder/:folderId"
element={
<MyCollectionComponent
key="components"
type="component"
/>
}
/>
</Route>
<Route
path="all/"
element={<MyCollectionComponent key="all" type="all" />}
>
<Route
path="folder/:folderId"
element={<MyCollectionComponent key="all" type="all" />}
/>
</Route>
</Route>
<Route path="/settings" element={<SettingsPage />}>
<Route index element={<Navigate replace to={"general"} />} />
<Route
path="global-variables"
element={<GlobalVariablesPage />}
/>
<Route path="api-keys" element={<ApiKeysPage />} />
<Route
path="general/:scrollId?"
element={
<AuthSettingsGuard>
<GeneralPage />
</AuthSettingsGuard>
}
/>
<Route path="shortcuts" element={<ShortcutsPage />} />
<Route path="messages" element={<MessagesPage />} />
</Route>
<Route <Route
path="folder/:folderId" path="/store"
element={<MyCollectionComponent key="flows" type="flow" />}
/>
</Route>
<Route
path="components/"
element={
<MyCollectionComponent key="components" type="component" />
}
>
<Route
path="folder/:folderId"
element={ element={
<MyCollectionComponent key="components" type="component" /> <StoreGuard>
<StorePage />
</StoreGuard>
}
/>
<Route
path="/store/:id/"
element={
<StoreGuard>
<StorePage />
</StoreGuard>
}
/>
<Route path="/account">
<Route path="delete" element={<DeleteAccountPage />}></Route>
</Route>
<Route
path="/admin"
element={
<ProtectedAdminRoute>
<AdminPage />
</ProtectedAdminRoute>
} }
/> />
</Route> </Route>
<Route <Route path="/flow/:id/">
path="all/" <Route path="" element={<DashboardWrapperPage />}>
element={<MyCollectionComponent key="all" type="all" />} <Route path="folder/:folderId/" element={<FlowPage />} />
> <Route path="" element={<FlowPage />} />
<Route </Route>
path="folder/:folderId" <Route path="view" element={<ViewPage />} />
element={<MyCollectionComponent key="all" type="all" />} </Route>
/> <Route path="/playground/:id/">
<Route path="" element={<PlaygroundPage />} />
</Route> </Route>
</Route> </Route>
<Route path="/settings" element={<SettingsPage />}>
<Route index element={<Navigate replace to={"general"} />} />
<Route path="global-variables" element={<GlobalVariablesPage />} />
<Route path="api-keys" element={<ApiKeysPage />} />
<Route
path="general/:scrollId?"
element={
<AuthSettingsGuard>
<GeneralPage />
</AuthSettingsGuard>
}
/>
<Route path="shortcuts" element={<ShortcutsPage />} />
<Route path="messages" element={<MessagesPage />} />
</Route>
<Route <Route
path="/store" path="/login"
element={ element={
<StoreGuard> <ProtectedLoginRoute>
<StorePage /> <LoginPage />
</StoreGuard> </ProtectedLoginRoute>
} }
/> />
<Route <Route
path="/store/:id/" path="/signup"
element={ element={
<StoreGuard> <ProtectedLoginRoute>
<StorePage /> <SignUp />
</StoreGuard> </ProtectedLoginRoute>
} }
/> />
<Route path="/account">
<Route path="delete" element={<DeleteAccountPage />}></Route>
</Route>
<Route <Route
path="/admin" path="/login/admin"
element={ element={
<ProtectedAdminRoute> <ProtectedLoginRoute>
<AdminPage /> <LoginAdminPage />
</ProtectedAdminRoute> </ProtectedLoginRoute>
} }
/> />
</Route> <Route path="*" element={<Navigate replace to="/" />} />
<Route path="/flow/:id/">
<Route path="" element={<DashboardWrapperPage />}>
<Route path="folder/:folderId/" element={<FlowPage />} />
<Route path="" element={<FlowPage />} />
</Route>
<Route path="view" element={<ViewPage />} />
</Route>
<Route path="/playground/:id/">
<Route path="" element={<PlaygroundPage />} />
</Route> </Route>
</Route> </Route>
<Route
path="/login"
element={
<ProtectedLoginRoute>
<LoginPage />
</ProtectedLoginRoute>
}
/>
<Route
path="/signup"
element={
<ProtectedLoginRoute>
<SignUp />
</ProtectedLoginRoute>
}
/>
<Route
path="/login/admin"
element={
<ProtectedLoginRoute>
<LoginAdminPage />
</ProtectedLoginRoute>
}
/>
<Route path="*" element={<Navigate replace to="/" />} />
</Route>, </Route>,
]), ]),
); );

View file

@ -19,7 +19,6 @@ const useAlertStore = create<AlertStoreType>((set, get) => ({
notificationCenter: false, notificationCenter: false,
notificationList: [], notificationList: [],
tempNotificationList: [], tempNotificationList: [],
loading: true,
setErrorData: (newState: { title: string; list?: Array<string> }) => { setErrorData: (newState: { title: string; list?: Array<string> }) => {
if (newState.title && newState.title !== "") { if (newState.title && newState.title !== "") {
set({ set({
@ -151,9 +150,6 @@ const useAlertStore = create<AlertStoreType>((set, get) => ({
), ),
}); });
}, },
setLoading: (newState: boolean) => {
set({ loading: newState });
},
clearTempNotificationList: () => { clearTempNotificationList: () => {
set({ tempNotificationList: [] }); set({ tempNotificationList: [] });
}, },

View file

@ -10,7 +10,7 @@ const useAuthStore = create<AuthStoreType>((set, get) => ({
isAuthenticated: !!cookies.get(LANGFLOW_ACCESS_TOKEN), isAuthenticated: !!cookies.get(LANGFLOW_ACCESS_TOKEN),
accessToken: cookies.get(LANGFLOW_ACCESS_TOKEN) ?? null, accessToken: cookies.get(LANGFLOW_ACCESS_TOKEN) ?? null,
userData: null, userData: null,
autoLogin: false, autoLogin: null,
apiKey: cookies.get("apikey_tkn_lflw"), apiKey: cookies.get("apikey_tkn_lflw"),
authenticationErrorCount: 0, authenticationErrorCount: 0,

View file

@ -2,8 +2,6 @@ import { create } from "zustand";
import { FoldersStoreType } from "../types/zustand/folders"; import { FoldersStoreType } from "../types/zustand/folders";
export const useFolderStore = create<FoldersStoreType>((set, get) => ({ export const useFolderStore = create<FoldersStoreType>((set, get) => ({
selectedFolder: null,
setSelectedFolder: (folder) => set(() => ({ selectedFolder: folder })),
loadingById: false, loadingById: false,
setMyCollectionId: (myCollectionId) => { setMyCollectionId: (myCollectionId) => {
set({ myCollectionId }); set({ myCollectionId });

View file

@ -248,7 +248,6 @@ export type ResponseErrorDetailAPI = {
}; };
export type useQueryFunctionType<T = undefined, R = any> = T extends undefined export type useQueryFunctionType<T = undefined, R = any> = T extends undefined
? ( ? (
params?: T,
options?: Omit<UseQueryOptions, "queryFn" | "queryKey">, options?: Omit<UseQueryOptions, "queryFn" | "queryKey">,
) => UseQueryResult<R> ) => UseQueryResult<R>
: ( : (

View file

@ -15,6 +15,4 @@ export type AlertStoreType = {
removeFromTempNotificationList: (index: string) => void; removeFromTempNotificationList: (index: string) => void;
clearNotificationList: () => void; clearNotificationList: () => void;
removeFromNotificationList: (index: string) => void; removeFromNotificationList: (index: string) => void;
loading: boolean;
setLoading: (newState: boolean) => void;
}; };

View file

@ -5,7 +5,7 @@ export interface AuthStoreType {
isAuthenticated: boolean; isAuthenticated: boolean;
accessToken: string | null; accessToken: string | null;
userData: Users | null; userData: Users | null;
autoLogin: boolean; autoLogin: boolean | null;
apiKey: string | null; apiKey: string | null;
authenticationErrorCount: number; authenticationErrorCount: number;

View file

@ -1,8 +1,6 @@
import { FolderType } from "../../../pages/MainPage/entities"; import { FolderType } from "../../../pages/MainPage/entities";
export type FoldersStoreType = { export type FoldersStoreType = {
selectedFolder: FolderType | null;
setSelectedFolder: (folder: FolderType | null) => void;
myCollectionId: string | null; myCollectionId: string | null;
setMyCollectionId: (value: string) => void; setMyCollectionId: (value: string) => void;
folderToEdit: FolderType | null; folderToEdit: FolderType | null;

View file

@ -1,4 +1,4 @@
import { test } from "@playwright/test"; import { expect, test } from "@playwright/test";
import { readFileSync } from "fs"; import { readFileSync } from "fs";
test("CRUD folders", async ({ page }) => { test("CRUD folders", async ({ page }) => {
@ -71,7 +71,7 @@ test("CRUD folders", async ({ page }) => {
await page.getByText("Folder deleted successfully").isVisible(); await page.getByText("Folder deleted successfully").isVisible();
}); });
test("add folder by drag and drop", async ({ page }) => { test("add a flow into a folder by drag and drop", async ({ page }) => {
await page.goto("/"); await page.goto("/");
await page.waitForSelector("text=my collection", { await page.waitForSelector("text=my collection", {
@ -84,10 +84,10 @@ test("add folder by drag and drop", async ({ page }) => {
); );
// Wait for the target element to be available before evaluation // Wait for the target element to be available before evaluation
await page.waitForSelector(
'//*[@id="root"]/div/div[2]/div[2]/div[3]/aside/nav/div/div[2]',
);
await page.waitForSelector('[data-testid="sidebar-nav-My Projects"]', {
timeout: 100000,
});
// Create the DataTransfer and File // Create the DataTransfer and File
const dataTransfer = await page.evaluateHandle((data) => { const dataTransfer = await page.evaluateHandle((data) => {
const dt = new DataTransfer(); const dt = new DataTransfer();
@ -100,15 +100,34 @@ test("add folder by drag and drop", async ({ page }) => {
}, jsonContent); }, jsonContent);
// Now dispatch // Now dispatch
await page.dispatchEvent( await page.getByTestId("sidebar-nav-My Projects").dispatchEvent("drop", {
'//*[@id="root"]/div/div[2]/div[2]/div[3]/aside/nav/div/div[2]', dataTransfer,
"drop", });
{
dataTransfer,
},
);
await page.getByText("Getting Started").first().isVisible(); await page.waitForTimeout(3000);
const genericNode = page.getByTestId("div-generic-node");
const elementCount = await genericNode?.count();
if (elementCount > 0) {
expect(true).toBeTruthy();
}
await page.getByTestId("sidebar-nav-My Projects").click();
await page.waitForTimeout(1000);
expect(
await page.locator("text=Getting Started:").last().isVisible(),
).toBeTruthy();
expect(
await page.locator("text=Inquisitive Pike").last().isVisible(),
).toBeTruthy();
expect(
await page.locator("text=Dreamy Bassi").last().isVisible(),
).toBeTruthy();
expect(
await page.locator("text=Furious Faraday").last().isVisible(),
).toBeTruthy();
}); });
test("change flow folder", async ({ page }) => { test("change flow folder", async ({ page }) => {

View file

@ -162,6 +162,12 @@ test("user should be able to duplicate a flow or a component", async ({
await page.getByText("Exit", { exact: true }).click(); await page.getByText("Exit", { exact: true }).click();
} }
const replaceButton = await page.getByTestId("replace-button").isVisible();
if (replaceButton) {
await page.getByTestId("replace-button").click();
}
await page.getByTestId("icon-ChevronLeft").last().click(); await page.getByTestId("icon-ChevronLeft").last().click();
await page.getByRole("checkbox").nth(1).click(); await page.getByRole("checkbox").nth(1).click();

View file

@ -22,11 +22,11 @@ test("user should be able to manually save a flow when the auto_save is off", as
await page.locator("span").filter({ hasText: "My Collection" }).isVisible(); await page.locator("span").filter({ hasText: "My Collection" }).isVisible();
await page.waitForSelector('[data-testid="mainpage_title"]', { await page.waitForSelector('[data-testid="mainpage_title"]', {
timeout: 30000, timeout: 5000,
}); });
await page.waitForSelector('[id="new-project-btn"]', { await page.waitForSelector('[id="new-project-btn"]', {
timeout: 30000, timeout: 5000,
}); });
let modalCount = 0; let modalCount = 0;
@ -46,12 +46,12 @@ test("user should be able to manually save a flow when the auto_save is off", as
} }
await page.waitForSelector('[data-testid="blank-flow"]', { await page.waitForSelector('[data-testid="blank-flow"]', {
timeout: 30000, timeout: 5000,
}); });
await page.getByTestId("blank-flow").click(); await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', { await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000, timeout: 5000,
}); });
await page.getByPlaceholder("Search").click(); await page.getByPlaceholder("Search").click();
@ -66,7 +66,7 @@ test("user should be able to manually save a flow when the auto_save is off", as
await page.mouse.down(); await page.mouse.down();
await page.waitForSelector('[title="fit view"]', { await page.waitForSelector('[title="fit view"]', {
timeout: 100000, timeout: 5000,
}); });
await page.getByTitle("fit view").click(); await page.getByTitle("fit view").click();
@ -77,7 +77,7 @@ test("user should be able to manually save a flow when the auto_save is off", as
await page.waitForSelector("text=loading", { await page.waitForSelector("text=loading", {
state: "hidden", state: "hidden",
timeout: 100000, timeout: 5000,
}); });
await page.getByTestId("icon-ChevronLeft").last().click(); await page.getByTestId("icon-ChevronLeft").last().click();
@ -93,7 +93,7 @@ test("user should be able to manually save a flow when the auto_save is off", as
await page.getByText("Untitled document").first().click(); await page.getByText("Untitled document").first().click();
await page.waitForSelector('[data-testid="icon-ChevronLeft"]', { await page.waitForSelector('[data-testid="icon-ChevronLeft"]', {
timeout: 100000, timeout: 5000,
}); });
expect(await page.getByText("NVIDIA").isVisible()).toBeFalsy(); expect(await page.getByText("NVIDIA").isVisible()).toBeFalsy();
@ -110,7 +110,7 @@ test("user should be able to manually save a flow when the auto_save is off", as
await page.mouse.down(); await page.mouse.down();
await page.waitForSelector('[title="fit view"]', { await page.waitForSelector('[title="fit view"]', {
timeout: 100000, timeout: 5000,
}); });
await page.getByTitle("fit view").click(); await page.getByTitle("fit view").click();
@ -123,7 +123,7 @@ test("user should be able to manually save a flow when the auto_save is off", as
await page.waitForSelector("text=loading", { await page.waitForSelector("text=loading", {
state: "hidden", state: "hidden",
timeout: 100000, timeout: 5000,
}); });
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
@ -142,7 +142,7 @@ test("user should be able to manually save a flow when the auto_save is off", as
await page.mouse.down(); await page.mouse.down();
await page.waitForSelector('[title="fit view"]', { await page.waitForSelector('[title="fit view"]', {
timeout: 100000, timeout: 5000,
}); });
await page.getByTitle("fit view").click(); await page.getByTitle("fit view").click();
@ -150,10 +150,25 @@ test("user should be able to manually save a flow when the auto_save is off", as
await page.getByTestId("save-flow-button").click(); await page.getByTestId("save-flow-button").click();
await page.getByTestId("icon-ChevronLeft").last().click(); await page.getByTestId("icon-ChevronLeft").last().click();
const replaceButton = await page.getByTestId("replace-button").isVisible();
if (replaceButton) {
await page.getByTestId("replace-button").click();
}
const saveExitButton = await page
.getByText("Save And Exit", { exact: true })
.last()
.isVisible();
if (saveExitButton) {
await page.getByText("Save And Exit", { exact: true }).last().click();
}
await page.getByText("Untitled document").first().click(); await page.getByText("Untitled document").first().click();
await page.waitForSelector('[data-testid="icon-ChevronLeft"]', { await page.waitForSelector('[data-testid="icon-ChevronLeft"]', {
timeout: 100000, timeout: 5000,
}); });
await page.waitForTimeout(5000); await page.waitForTimeout(5000);

View file

@ -42,18 +42,29 @@ test.describe("drag and drop test", () => {
}, jsonContent); }, jsonContent);
// Now dispatch // Now dispatch
await page.dispatchEvent( await page.getByTestId("cards-wrapper").dispatchEvent("drop", {
'//*[@id="root"]/div/div[2]/div[2]/div[3]/div', dataTransfer,
"drop", });
{
dataTransfer, await page.waitForTimeout(3000);
},
);
const genericNode = page.getByTestId("div-generic-node"); const genericNode = page.getByTestId("div-generic-node");
const elementCount = await genericNode?.count(); const elementCount = await genericNode?.count();
if (elementCount > 0) { if (elementCount > 0) {
expect(true).toBeTruthy(); expect(true).toBeTruthy();
} }
expect(
await page.locator("text=Getting Started:").last().isVisible(),
).toBeTruthy();
expect(
await page.locator("text=Inquisitive Pike").last().isVisible(),
).toBeTruthy();
expect(
await page.locator("text=Dreamy Bassi").last().isVisible(),
).toBeTruthy();
expect(
await page.locator("text=Furious Faraday").last().isVisible(),
).toBeTruthy();
}); });
}); });

View file

@ -96,12 +96,12 @@ test("should filter by type", async ({ page }) => {
expect(toyBrick).not.toBe(0); expect(toyBrick).not.toBe(0);
await page.getByTestId("all-button-store").click(); await page.getByTestId("all-button-store").click();
await page.waitForTimeout(8000); await page.waitForTimeout(10000);
let iconGroupAllCount = await page.getByTestId("icon-Group")?.count(); let iconGroupAllCount = await page.getByTestId("icon-Group")?.count();
await page.waitForTimeout(1000); await page.waitForTimeout(5000);
let toyBrickAllCount = await page.getByTestId("icon-ToyBrick")?.count(); let toyBrickAllCount = await page.getByTestId("icon-ToyBrick")?.count();
await page.waitForTimeout(1000); await page.waitForTimeout(5000);
if (iconGroupAllCount === 0 || toyBrickAllCount === 0) { if (iconGroupAllCount === 0 || toyBrickAllCount === 0) {
expect(false).toBe(true); expect(false).toBe(true);