diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index c5ea5f6f7..f84dead6b 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -26,18 +26,24 @@ jobs: setup: runs-on: ubuntu-latest outputs: - base_tags: ${{ steps.set-vars.outputs.base_tags }} - main_tags: ${{ steps.set-vars.outputs.main_tags }} + tags: ${{ steps.set-vars.outputs.tags }} steps: - uses: actions/checkout@v4 - name: Set Dockerfile and Tags id: set-vars run: | - echo "base_tags=langflowai/langflow:base-${{ inputs.version }}" >> $GITHUB_OUTPUT - echo "main_tags=langflowai/langflow:${{ inputs.version }},langflowai/langflow:1.0-alpha" >> $GITHUB_OUTPUT + if [[ "${{ inputs.release_type }}" == "base" ]]; then + echo "tags=langflowai/langflow:base-${{ inputs.version }}" >> $GITHUB_OUTPUT + else + echo "tags=langflowai/langflow:${{ inputs.version }},langflowai/langflow:1.0-alpha" >> $GITHUB_OUTPUT + fi build_base: runs-on: ubuntu-latest needs: setup + strategy: + matrix: + platform: [linux/amd64, linux/arm64/v8] + file: [./docker/build_and_push.Dockerfile, ./docker/build_and_push_base.Dockerfile] steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx @@ -52,9 +58,9 @@ jobs: with: context: . push: true - platforms: "linux/amd64,linux/arm64/v8" - file: ./docker/build_and_push_base.Dockerfile - tags: ${{ needs.setup.outputs.base_tags }} + platforms: ${{ matrix.platform }} + file: ${{ matrix.file }} + tags: ${{ needs.setup.outputs.tags }} build_components: if: ${{ inputs.release_type == 'main' }} diff --git a/.github/workflows/lint-js.yml b/.github/workflows/lint-js.yml index 0590e8a38..84823b885 100644 --- a/.github/workflows/lint-js.yml +++ b/.github/workflows/lint-js.yml @@ -39,7 +39,7 @@ jobs: if: ${{ steps.setup-node.outputs.cache-hit != 'true' }} - name: Run linters - uses: wearerequired/lint-action@v1 + uses: wearerequired/lint-action@v2 with: github_token: ${{ secrets.github_token }} auto_fix: true diff --git a/.github/workflows/lint-py.yml b/.github/workflows/lint-py.yml index 6182f91fc..eb28b3101 100644 --- a/.github/workflows/lint-py.yml +++ b/.github/workflows/lint-py.yml @@ -41,7 +41,7 @@ jobs: ./.mypy_cache key: ${{ runner.os }}-mypy-${{ hashFiles('**/pyproject.toml') }} - name: Run linters - uses: wearerequired/lint-action@v1 + uses: wearerequired/lint-action@v2 with: github_token: ${{ secrets.github_token }} # Enable linters diff --git a/src/backend/base/langflow/custom/custom_component/custom_component.py b/src/backend/base/langflow/custom/custom_component/custom_component.py index 5c1da081b..963de3e28 100644 --- a/src/backend/base/langflow/custom/custom_component/custom_component.py +++ b/src/backend/base/langflow/custom/custom_component/custom_component.py @@ -159,11 +159,11 @@ class CustomComponent(Component): if self.repr_value == "": self.repr_value = self.status if isinstance(self.repr_value, dict): - return yaml.dump(self.repr_value) - if isinstance(self.repr_value, str): - return self.repr_value - if isinstance(self.repr_value, BaseModel) and not isinstance(self.repr_value, Record): - return str(self.repr_value) + self.repr_value = yaml.dump(self.repr_value) + if isinstance(self.repr_value, BaseModel) and not isinstance(self.repr_value, Data): + self.repr_value = str(self.repr_value) + elif hasattr(self.repr_value, "to_json"): + self.repr_value = self.repr_value.to_json() return self.repr_value def build_config(self): diff --git a/src/backend/base/langflow/main.py b/src/backend/base/langflow/main.py index c81c014e2..1227d4c62 100644 --- a/src/backend/base/langflow/main.py +++ b/src/backend/base/langflow/main.py @@ -20,6 +20,7 @@ from langflow.initial_setup.setup import ( load_flows_from_directory, ) from langflow.interface.utils import setup_llm_caching +from langflow.services.deps import get_settings_service from langflow.services.plugins.langfuse_plugin import LangfuseInstance from langflow.services.utils import initialize_services, teardown_services from langflow.utils.logger import configure @@ -78,6 +79,7 @@ def create_app(): socketio_server = socketio.AsyncServer(async_mode="asgi", cors_allowed_origins="*", logger=True) lifespan = get_lifespan(socketio_server=socketio_server, version=__version__) app = FastAPI(lifespan=lifespan, title="Langflow", version=__version__) + setup_sentry(app) origins = ["*"] app.add_middleware( @@ -115,6 +117,20 @@ def mount_socketio(app: FastAPI, socketio_server: socketio.AsyncServer): return app +def setup_sentry(app: FastAPI): + settings = get_settings_service().settings + if settings.sentry_dsn: + import sentry_sdk + from sentry_sdk.integrations.asgi import SentryAsgiMiddleware + + sentry_sdk.init( + dsn=settings.sentry_dsn, + traces_sample_rate=settings.sentry_traces_sample_rate, + profiles_sample_rate=settings.sentry_profiles_sample_rate, + ) + app.add_middleware(SentryAsgiMiddleware) + + def setup_static_files(app: FastAPI, static_files_dir: Path): """ Setup the static files directory. diff --git a/src/backend/base/langflow/services/settings/base.py b/src/backend/base/langflow/services/settings/base.py index 679d16627..e8c0cbf90 100644 --- a/src/backend/base/langflow/services/settings/base.py +++ b/src/backend/base/langflow/services/settings/base.py @@ -85,6 +85,11 @@ class Settings(BaseSettings): redis_url: Optional[str] = None redis_cache_expire: int = 3600 + # Sentry + sentry_dsn: Optional[str] = None + sentry_traces_sample_rate: Optional[float] = 1.0 + sentry_profiles_sample_rate: Optional[float] = 1.0 + # PLUGIN_DIR: Optional[str] = None langfuse_secret_key: Optional[str] = None diff --git a/src/backend/base/poetry.lock b/src/backend/base/poetry.lock index 8f57c7d7b..7f21e0c5f 100644 --- a/src/backend/base/poetry.lock +++ b/src/backend/base/poetry.lock @@ -2411,7 +2411,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -2499,6 +2498,56 @@ files = [ [package.dependencies] pyasn1 = ">=0.1.3" +[[package]] +name = "sentry-sdk" +version = "2.5.1" +description = "Python client for Sentry (https://sentry.io)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "sentry_sdk-2.5.1-py2.py3-none-any.whl", hash = "sha256:1f87acdce4a43a523ae5aa21a3fc37522d73ebd9ec04b1dbf01aa3d173852def"}, + {file = "sentry_sdk-2.5.1.tar.gz", hash = "sha256:fbc40a78a8a9c6675133031116144f0d0940376fa6e4e1acd5624c90b0aaf58b"}, +] + +[package.dependencies] +certifi = "*" +urllib3 = ">=1.26.11" + +[package.extras] +aiohttp = ["aiohttp (>=3.5)"] +anthropic = ["anthropic (>=0.16)"] +arq = ["arq (>=0.23)"] +asyncpg = ["asyncpg (>=0.23)"] +beam = ["apache-beam (>=2.12)"] +bottle = ["bottle (>=0.12.13)"] +celery = ["celery (>=3)"] +celery-redbeat = ["celery-redbeat (>=2)"] +chalice = ["chalice (>=1.16.0)"] +clickhouse-driver = ["clickhouse-driver (>=0.2.0)"] +django = ["django (>=1.8)"] +falcon = ["falcon (>=1.4)"] +fastapi = ["fastapi (>=0.79.0)"] +flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"] +grpcio = ["grpcio (>=1.21.1)", "protobuf (>=3.8.0)"] +httpx = ["httpx (>=0.16.0)"] +huey = ["huey (>=2)"] +huggingface-hub = ["huggingface-hub (>=0.22)"] +langchain = ["langchain (>=0.0.210)"] +loguru = ["loguru (>=0.5)"] +openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"] +opentelemetry = ["opentelemetry-distro (>=0.35b0)"] +opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] +pure-eval = ["asttokens", "executing", "pure-eval"] +pymongo = ["pymongo (>=3.1)"] +pyspark = ["pyspark (>=2.4.4)"] +quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] +rq = ["rq (>=0.6)"] +sanic = ["sanic (>=0.8)"] +sqlalchemy = ["sqlalchemy (>=1.2)"] +starlette = ["starlette (>=0.19.1)"] +starlite = ["starlite (>=1.48)"] +tornado = ["tornado (>=5)"] + [[package]] name = "shellingham" version = "1.5.4" @@ -3247,4 +3296,4 @@ local = [] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "3c83d086a178cebd83473758459c4f026907268dbf8c9ce45bc4bfb1f340e9a5" +content-hash = "72f05330f1e734596d160b45cb68ab2ebf7d0824314bec0566bddb5b2043f4e6" diff --git a/src/backend/base/pyproject.toml b/src/backend/base/pyproject.toml index a833470a2..1762b645d 100644 --- a/src/backend/base/pyproject.toml +++ b/src/backend/base/pyproject.toml @@ -63,6 +63,7 @@ cryptography = "^42.0.5" asyncer = "^0.0.5" pyperclip = "^1.8.2" uncurl = "^0.0.11" +sentry-sdk = "^2.5.1" [tool.poetry.extras] diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 849244999..b84c0d792 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -18,13 +18,13 @@ import { autoLogin, getGlobalVariables, getHealth } from "./controllers/API"; import { setupAxiosDefaults } from "./controllers/API/utils"; import useTrackLastVisitedPath from "./hooks/use-track-last-visited-path"; import Router from "./routes"; +import { Case } from "./shared/components/caseComponent"; import useAlertStore from "./stores/alertStore"; import { useDarkStore } from "./stores/darkStore"; import useFlowsManagerStore from "./stores/flowsManagerStore"; import { useFolderStore } from "./stores/foldersStore"; import { useGlobalVariablesStore } from "./stores/globalVariablesStore/globalVariables"; import { useStoreStore } from "./stores/storeStore"; -import { useTypesStore } from "./stores/typesStore"; export default function App() { useTrackLastVisitedPath(); @@ -43,10 +43,8 @@ export default function App() { const { isAuthenticated, login, setUserData, setAutoLogin, getUser } = useContext(AuthContext); - const refreshFlows = useFlowsManagerStore((state) => state.refreshFlows); const setLoading = useAlertStore((state) => state.setLoading); const fetchApiData = useStoreStore((state) => state.fetchApiData); - const getTypes = useTypesStore((state) => state.getTypes); const refreshVersion = useDarkStore((state) => state.refreshVersion); const refreshStars = useDarkStore((state) => state.refreshStars); const setGlobalVariables = useGlobalVariablesStore( @@ -56,8 +54,7 @@ export default function App() { const navigate = useNavigate(); const dark = useDarkStore((state) => state.dark); - const getFoldersApi = useFolderStore((state) => state.getFoldersApi); - const loadingFolders = useFolderStore((state) => state.loading); + const isLoadingFolders = useFolderStore((state) => state.isLoadingFolders); const [isLoadingHealth, setIsLoadingHealth] = useState(false); @@ -115,13 +112,13 @@ export default function App() { if (isAuthenticated) { try { await setupAxiosDefaults(); - await getFoldersApi(); - await getTypes(); - await refreshFlows(); + const res = await getGlobalVariables(); setGlobalVariables(res); + checkHasStore(); fetchApiData(); + resolve(); } catch (error) { console.error("Failed to fetch data:", error); @@ -174,6 +171,8 @@ export default function App() { } }; + const isLoadingApplication = isLoading || isLoadingFolders; + return ( //need parent component with width and height
@@ -196,15 +195,15 @@ export default function App() { > } - {isLoading || loadingFolders ? ( +
- ) : ( - <> - - - )} +
+ + + +
diff --git a/src/frontend/src/components/sidebarComponent/components/sideBarFolderButtons/index.tsx b/src/frontend/src/components/sidebarComponent/components/sideBarFolderButtons/index.tsx index 234eb9304..ff5c17a15 100644 --- a/src/frontend/src/components/sidebarComponent/components/sideBarFolderButtons/index.tsx +++ b/src/frontend/src/components/sidebarComponent/components/sideBarFolderButtons/index.tsx @@ -37,8 +37,8 @@ const SideBarFoldersButtonsComponent = ({ const currentFolder = pathname.split("/"); const urlWithoutPath = pathname.split("/").length < 4; const myCollectionId = useFolderStore((state) => state.myCollectionId); - const getFoldersApi = useFolderStore((state) => state.getFoldersApi); const folderIdDragging = useFolderStore((state) => state.folderIdDragging); + const refreshFolders = useFolderStore((state) => state.refreshFolders); const checkPathName = (itemId: string) => { if (urlWithoutPath && itemId === myCollectionId) { @@ -85,7 +85,7 @@ const SideBarFoldersButtonsComponent = ({ function addNewFolder() { addFolder({ name: "New Folder", parent_id: null, description: "" }).then( (res) => { - getFoldersApi(true); + refreshFolders(); } ); } diff --git a/src/frontend/src/components/sidebarComponent/hooks/use-on-file-drop.tsx b/src/frontend/src/components/sidebarComponent/hooks/use-on-file-drop.tsx index f10733468..ab4b9d27f 100644 --- a/src/frontend/src/components/sidebarComponent/hooks/use-on-file-drop.tsx +++ b/src/frontend/src/components/sidebarComponent/hooks/use-on-file-drop.tsx @@ -16,7 +16,7 @@ const useFileDrop = (folderId, folderChangeCallback) => { ); const setErrorData = useAlertStore((state) => state.setErrorData); - const getFoldersApi = useFolderStore((state) => state.getFoldersApi); + const refreshFolders = useFolderStore((state) => state.refreshFolders); const flows = useFlowsManagerStore((state) => state.flows); const triggerFolderChange = (folderId) => { @@ -118,7 +118,7 @@ const useFileDrop = (folderId, folderChangeCallback) => { setFolderIdDragging(""); updateFlowInDatabase(updatedFlow).then(() => { - getFoldersApi(true); + refreshFolders(); triggerFolderChange(folderId); }); }; @@ -129,7 +129,7 @@ const useFileDrop = (folderId, folderChangeCallback) => { setFolderDragging(false); setFolderIdDragging(""); uploadFlowsFromFolders(formData).then(() => { - getFoldersApi(true); + refreshFolders(); triggerFolderChange(folderId); }); }; diff --git a/src/frontend/src/components/sidebarComponent/index.tsx b/src/frontend/src/components/sidebarComponent/index.tsx index efec5da1e..8cdbe5907 100644 --- a/src/frontend/src/components/sidebarComponent/index.tsx +++ b/src/frontend/src/components/sidebarComponent/index.tsx @@ -28,7 +28,7 @@ export default function SidebarNav({ }: SidebarNavProps) { const location = useLocation(); const pathname = location.pathname; - const loadingFolders = useFolderStore((state) => state.loading); + const loadingFolders = useFolderStore((state) => state.isLoadingFolders); const folders = useFolderStore((state) => state.folders); const pathValues = ["folder", "components", "flows", "all"]; diff --git a/src/frontend/src/components/tableComponent/index.tsx b/src/frontend/src/components/tableComponent/index.tsx index 142e8f5a2..54c159856 100644 --- a/src/frontend/src/components/tableComponent/index.tsx +++ b/src/frontend/src/components/tableComponent/index.tsx @@ -104,7 +104,7 @@ const TableComponent = forwardRef< } }, 50); setTimeout(() => { - realRef.current.api.hideOverlay(); + realRef?.current?.api?.hideOverlay(); }, 1000); if (props.onGridReady) props.onGridReady(params); }; @@ -143,6 +143,7 @@ const TableComponent = forwardRef< minWidth: 100, autoHeight: true, }} + animateRows={false} columnDefs={colDef} ref={realRef} onGridReady={onGridReady} diff --git a/src/frontend/src/contexts/authContext.tsx b/src/frontend/src/contexts/authContext.tsx index 1817447f1..f6b81a235 100644 --- a/src/frontend/src/contexts/authContext.tsx +++ b/src/frontend/src/contexts/authContext.tsx @@ -3,6 +3,7 @@ import { useNavigate } from "react-router-dom"; import Cookies from "universal-cookie"; import { getLoggedUser, requestLogout } from "../controllers/API"; import useAlertStore from "../stores/alertStore"; +import { useFolderStore } from "../stores/foldersStore"; import { Users } from "../types/api"; import { AuthContextType } from "../types/contexts/auth"; @@ -42,7 +43,8 @@ export function AuthProvider({ children }): React.ReactElement { const [apiKey, setApiKey] = useState( cookies.get("apikey_tkn_lflw") ); - // const getFoldersApi = useFolderStore((state) => state.getFoldersApi); + + const getFoldersApi = useFolderStore((state) => state.getFoldersApi); useEffect(() => { const storedAccessToken = cookies.get("access_token_lf"); @@ -64,7 +66,8 @@ export function AuthProvider({ children }): React.ReactElement { setUserData(user); const isSuperUser = user!.is_superuser; setIsAdmin(isSuperUser); - // await getFoldersApi(true); + + getFoldersApi(true, true); }) .catch((error) => { setLoading(false); diff --git a/src/frontend/src/controllers/API/api.tsx b/src/frontend/src/controllers/API/api.tsx index 2a305cf5a..6bd73f5f1 100644 --- a/src/frontend/src/controllers/API/api.tsx +++ b/src/frontend/src/controllers/API/api.tsx @@ -87,6 +87,7 @@ function ApiInterceptor() { if (!checkRequest) { controller.abort("Duplicate Request"); + console.error("Duplicate Request"); } const accessToken = cookies.get("access_token_lf"); diff --git a/src/frontend/src/controllers/API/helpers/check-duplicate-requests.ts b/src/frontend/src/controllers/API/helpers/check-duplicate-requests.ts index 6486de541..ec3c74268 100644 --- a/src/frontend/src/controllers/API/helpers/check-duplicate-requests.ts +++ b/src/frontend/src/controllers/API/helpers/check-duplicate-requests.ts @@ -4,9 +4,10 @@ export function checkDuplicateRequestAndStoreRequest(config) { const lastUrl = localStorage.getItem("lastUrlCalled"); const lastMethodCalled = localStorage.getItem("lastMethodCalled"); const lastRequestTime = localStorage.getItem("lastRequestTime"); + const lastCurrentUrl = localStorage.getItem("lastCurrentUrl"); + const currentUrl = window.location.pathname; const currentTime = Date.now(); - const isContained = AUTHORIZED_DUPLICATE_REQUESTS.some((request) => config?.url!.includes(request) ); @@ -17,7 +18,8 @@ export function checkDuplicateRequestAndStoreRequest(config) { lastMethodCalled === config.method && lastMethodCalled === "get" && // Assuming you want to check only for GET requests lastRequestTime && - currentTime - parseInt(lastRequestTime, 10) < 800 + currentTime - parseInt(lastRequestTime, 10) < 300 && + lastCurrentUrl === currentUrl ) { return false; } @@ -25,6 +27,7 @@ export function checkDuplicateRequestAndStoreRequest(config) { localStorage.setItem("lastUrlCalled", config.url ?? ""); localStorage.setItem("lastMethodCalled", config.method ?? ""); localStorage.setItem("lastRequestTime", currentTime.toString()); + localStorage.setItem("lastCurrentUrl", currentUrl); return true; } diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index 07a9cd06d..481aa2135 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -1091,8 +1091,6 @@ export async function getMessagesTable( const rowsOrganized = rows.data; - console.log(rowsOrganized); - const columns = extractColumnsFromRows(rowsOrganized, mode, excludedFields); const sessions = new Set(); rowsOrganized.forEach((row) => { diff --git a/src/frontend/src/modals/flowLogsModal/index.tsx b/src/frontend/src/modals/flowLogsModal/index.tsx index 840de14ca..27af4b734 100644 --- a/src/frontend/src/modals/flowLogsModal/index.tsx +++ b/src/frontend/src/modals/flowLogsModal/index.tsx @@ -1,5 +1,5 @@ import { ColDef, ColGroupDef } from "ag-grid-community"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import IconComponent from "../../components/genericIconComponent"; import TableComponent from "../../components/tableComponent"; import { Tabs, TabsList, TabsTrigger } from "../../components/ui/tabs"; @@ -61,6 +61,21 @@ export default function FlowLogsModal({ } }, [open, activeTab]); + const tableComponentRender = useMemo(() => { + return ( + + ); + }, [activeTab]); + return ( @@ -85,16 +100,7 @@ export default function FlowLogsModal({ Messages - + {tableComponentRender} ); diff --git a/src/frontend/src/pages/AdminPage/LoginPage/index.tsx b/src/frontend/src/pages/AdminPage/LoginPage/index.tsx index b1772baa5..0c7e073df 100644 --- a/src/frontend/src/pages/AdminPage/LoginPage/index.tsx +++ b/src/frontend/src/pages/AdminPage/LoginPage/index.tsx @@ -19,6 +19,7 @@ export default function LoginAdminPage() { const [inputState, setInputState] = useState(CONTROL_LOGIN_STATE); const { login, isAuthenticated, setUserData } = useContext(AuthContext); + const setLoading = useAlertStore((state) => state.setLoading); const { password, username } = inputState; const setErrorData = useAlertStore((state) => state.setErrorData); @@ -35,6 +36,10 @@ export default function LoginAdminPage() { }; onLogin(user) .then((user) => { + console.log("admin page"); + + setLoading(true); + login(user.access_token); navigate("/admin/"); }) diff --git a/src/frontend/src/pages/LoginPage/index.tsx b/src/frontend/src/pages/LoginPage/index.tsx index 826b2a931..d25d1c2e1 100644 --- a/src/frontend/src/pages/LoginPage/index.tsx +++ b/src/frontend/src/pages/LoginPage/index.tsx @@ -9,6 +9,7 @@ import { CONTROL_LOGIN_STATE } from "../../constants/constants"; import { AuthContext } from "../../contexts/authContext"; import { onLogin } from "../../controllers/API"; import useAlertStore from "../../stores/alertStore"; +import useFlowsManagerStore from "../../stores/flowsManagerStore"; import { LoginType } from "../../types/api"; import { inputHandlerEventType, @@ -23,6 +24,7 @@ export default function LoginPage(): JSX.Element { const { login } = useContext(AuthContext); const navigate = useNavigate(); const setErrorData = useAlertStore((state) => state.setErrorData); + const setLoading = useFlowsManagerStore((state) => state.setIsLoading); function handleInput({ target: { name, value }, @@ -37,6 +39,9 @@ export default function LoginPage(): JSX.Element { }; onLogin(user) .then((user) => { + console.log("login page"); + + setLoading(true); login(user.access_token); navigate("/"); }) diff --git a/src/frontend/src/pages/MainPage/components/myCollectionComponent/components/headerTabsSearchComponent/index.tsx b/src/frontend/src/pages/MainPage/components/myCollectionComponent/components/headerTabsSearchComponent/index.tsx index 17916f05b..b023216ab 100644 --- a/src/frontend/src/pages/MainPage/components/myCollectionComponent/components/headerTabsSearchComponent/index.tsx +++ b/src/frontend/src/pages/MainPage/components/myCollectionComponent/components/headerTabsSearchComponent/index.tsx @@ -1,39 +1,19 @@ import { useState } from "react"; -import { useLocation } from "react-router-dom"; -import useAlertStore from "../../../../../../stores/alertStore"; import useFlowsManagerStore from "../../../../../../stores/flowsManagerStore"; -import { useFolderStore } from "../../../../../../stores/foldersStore"; -import { handleDownloadFolderFn } from "../../../../utils/handle-download-folder"; import InputSearchComponent from "../inputSearchComponent"; import TabsSearchComponent from "../tabsComponent"; type HeaderTabsSearchComponentProps = {}; const HeaderTabsSearchComponent = ({}: HeaderTabsSearchComponentProps) => { - const location = useLocation(); - const myCollectionId = useFolderStore((state) => state.myCollectionId); - const folderId = location?.state?.folderId || myCollectionId; const isLoading = useFlowsManagerStore((state) => state.isLoading); const [tabActive, setTabActive] = useState("Flows"); - const setErrorData = useAlertStore((state) => state.setErrorData); - const allFlows = useFlowsManagerStore((state) => state.allFlows); const [inputValue, setInputValue] = useState(""); const setSearchFlowsComponents = useFlowsManagerStore( (state) => state.setSearchFlowsComponents ); - const handleDownloadFolder = () => { - if (allFlows.length === 0) { - setErrorData({ - title: "Folder is empty", - list: [], - }); - return; - } - handleDownloadFolderFn(folderId); - }; - return ( <>
diff --git a/src/frontend/src/pages/MainPage/hooks/use-delete-folder.tsx b/src/frontend/src/pages/MainPage/hooks/use-delete-folder.tsx index 257f1b6d2..0d093bc32 100644 --- a/src/frontend/src/pages/MainPage/hooks/use-delete-folder.tsx +++ b/src/frontend/src/pages/MainPage/hooks/use-delete-folder.tsx @@ -2,11 +2,12 @@ import useAlertStore from "../../../stores/alertStore"; import { useFolderStore } from "../../../stores/foldersStore"; import { deleteFolder, getFolderById } from "../services"; -const useDeleteFolder = ({ navigate, getFoldersApi }) => { +const useDeleteFolder = ({ navigate }) => { const setSuccessData = useAlertStore((state) => state.setSuccessData); const setErrorData = useAlertStore((state) => state.setErrorData); const folderToEdit = useFolderStore((state) => state.folderToEdit); const myCollectionId = useFolderStore((state) => state.myCollectionId); + const getFoldersApi = useFolderStore((state) => state.getFoldersApi); const handleDeleteFolder = () => { deleteFolder(folderToEdit?.id!) diff --git a/src/frontend/src/pages/MainPage/pages/mainPage/index.tsx b/src/frontend/src/pages/MainPage/pages/mainPage/index.tsx index 0daa61257..39add5211 100644 --- a/src/frontend/src/pages/MainPage/pages/mainPage/index.tsx +++ b/src/frontend/src/pages/MainPage/pages/mainPage/index.tsx @@ -25,16 +25,9 @@ export default function HomePage(): JSX.Element { const [openFolderModal, setOpenFolderModal] = useState(false); const [openDeleteFolderModal, setOpenDeleteFolderModal] = useState(false); const is_component = pathname === "/components"; - const getFoldersApi = useFolderStore((state) => state.getFoldersApi); const setFolderToEdit = useFolderStore((state) => state.setFolderToEdit); const navigate = useNavigate(); - useEffect(() => { - setTimeout(() => { - getFoldersApi(); - }, 300); - }, []); - useEffect(() => { setCurrentFlowId(""); }, [pathname]); @@ -45,7 +38,7 @@ export default function HomePage(): JSX.Element { is_component, }); - const { handleDeleteFolder } = useDeleteFolder({ navigate, getFoldersApi }); + const { handleDeleteFolder } = useDeleteFolder({ navigate }); return ( <> diff --git a/src/frontend/src/stores/foldersStore.tsx b/src/frontend/src/stores/foldersStore.tsx index ce858c3be..8e42026b4 100644 --- a/src/frontend/src/stores/foldersStore.tsx +++ b/src/frontend/src/stores/foldersStore.tsx @@ -7,64 +7,100 @@ import { } from "../pages/MainPage/services"; import { FoldersStoreType } from "../types/zustand/folders"; import useFlowsManagerStore from "./flowsManagerStore"; +import { useTypesStore } from "./typesStore"; export const useFolderStore = create((set, get) => ({ folders: [], - getFoldersApi: (refetch = false) => { + getFoldersApi: (refetch = false, startupApplication: boolean = false) => { return new Promise((resolve, reject) => { if (get()?.folders.length === 0 || refetch === true) { - get().setLoading(true); + get().setIsLoadingFolders(true); getFolders().then( - (res) => { + async (res) => { const foldersWithoutStarterProjects = res?.filter( - (folder) => folder.name !== STARTER_FOLDER_NAME, + (folder) => folder.name !== STARTER_FOLDER_NAME ); const starterProjects = res?.find( - (folder) => folder.name === STARTER_FOLDER_NAME, + (folder) => folder.name === STARTER_FOLDER_NAME ); set({ starterProjectId: starterProjects!.id ?? "" }); set({ folders: foldersWithoutStarterProjects }); const myCollectionId = res?.find( - (f) => f.name === DEFAULT_FOLDER, + (f) => f.name === DEFAULT_FOLDER )?.id; set({ myCollectionId }); - if (refetch === true) { - useFlowsManagerStore.getState().refreshFlows(); - useFlowsManagerStore.getState().setAllFlows; - } + const { refreshFlows } = useFlowsManagerStore.getState(); + const { getTypes } = useTypesStore.getState(); + const { setIsLoadingFolders } = get(); + + if (refetch) { + if (startupApplication) { + await refreshFlows(); + await getTypes(); + } else { + refreshFlows(); + getTypes(); + } + } + setIsLoadingFolders(false); - get().setLoading(false); resolve(); }, (error) => { set({ folders: [] }); - get().setLoading(false); + get().setIsLoadingFolders(false); reject(error); - }, + } ); } }); }, + refreshFolders: () => { + return new Promise((resolve, reject) => { + getFolders().then( + async (res) => { + const foldersWithoutStarterProjects = res?.filter( + (folder) => folder.name !== STARTER_FOLDER_NAME + ); + + const starterProjects = res?.find( + (folder) => folder.name === STARTER_FOLDER_NAME + ); + + set({ starterProjectId: starterProjects!.id ?? "" }); + set({ folders: foldersWithoutStarterProjects }); + + const myCollectionId = res?.find( + (f) => f.name === DEFAULT_FOLDER + )?.id; + + set({ myCollectionId }); + + resolve(); + }, + (error) => { + set({ folders: [] }); + get().setIsLoadingFolders(false); + reject(error); + } + ); + }); + }, setFolders: (folders) => set(() => ({ folders: folders })), - loading: false, - setLoading: (loading) => set(() => ({ loading: loading })), + isLoadingFolders: false, + setIsLoadingFolders: (isLoadingFolders) => set(() => ({ isLoadingFolders })), getFolderById: (id) => { if (id) { - getFolderById(id).then( - (res) => { - const setAllFlows = useFlowsManagerStore.getState().setAllFlows; - setAllFlows(res.flows); - set({ selectedFolder: res }); - }, - () => { - get().getFoldersApi(true); - }, - ); + getFolderById(id).then((res) => { + const setAllFlows = useFlowsManagerStore.getState().setAllFlows; + setAllFlows(res.flows); + set({ selectedFolder: res }); + }); } }, selectedFolder: null, diff --git a/src/frontend/src/types/zustand/folders/index.ts b/src/frontend/src/types/zustand/folders/index.ts index 5e41677b2..45c1e0bd9 100644 --- a/src/frontend/src/types/zustand/folders/index.ts +++ b/src/frontend/src/types/zustand/folders/index.ts @@ -2,10 +2,13 @@ import { FolderType } from "../../../pages/MainPage/entities"; export type FoldersStoreType = { folders: FolderType[]; - getFoldersApi: (refetch?: boolean) => Promise; + getFoldersApi: ( + refetch?: boolean, + startupApplication?: boolean + ) => Promise; setFolders: (folders: FolderType[]) => void; - loading: boolean; - setLoading: (loading: boolean) => void; + isLoadingFolders: boolean; + setIsLoadingFolders: (isLoadingFolders: boolean) => void; selectedFolder: FolderType | null; getFolderById: (id: string) => void; getMyCollectionFolder: () => void; @@ -23,4 +26,5 @@ export type FoldersStoreType = { setFolderIdDragging: (id: string) => void; starterProjectId: string; setStarterProjectId: (id: string) => void; + refreshFolders: () => void; }; diff --git a/src/frontend/src/utils/utils.ts b/src/frontend/src/utils/utils.ts index 5a1f6621f..0a1f99692 100644 --- a/src/frontend/src/utils/utils.ts +++ b/src/frontend/src/utils/utils.ts @@ -56,7 +56,7 @@ export function normalCaseToSnakeCase(str: string): string { export function toTitleCase( str: string | undefined, - isNodeField?: boolean + isNodeField?: boolean, ): string { if (!str) return ""; let result = str @@ -65,7 +65,7 @@ export function toTitleCase( if (isNodeField) return word; if (index === 0) { return checkUpperWords( - word[0].toUpperCase() + word.slice(1).toLowerCase() + word[0].toUpperCase() + word.slice(1).toLowerCase(), ); } return checkUpperWords(word.toLowerCase()); @@ -78,7 +78,7 @@ export function toTitleCase( if (isNodeField) return word; if (index === 0) { return checkUpperWords( - word[0].toUpperCase() + word.slice(1).toLowerCase() + word[0].toUpperCase() + word.slice(1).toLowerCase(), ); } return checkUpperWords(word.toLowerCase()); @@ -182,7 +182,7 @@ export function checkLocalStorageKey(key: string): boolean { export function IncrementObjectKey( object: object, - key: string + key: string, ): { newKey: string; increment: number } { let count = 1; const type = removeCountFromString(key); @@ -217,7 +217,7 @@ export function groupByFamily( data: APIDataType, baseClasses: string, left: boolean, - flow?: NodeType[] + flow?: NodeType[], ): groupedObjType[] { const baseClassesSet = new Set(baseClasses.split("\n")); let arrOfPossibleInputs: Array<{ @@ -243,7 +243,7 @@ export function groupByFamily( baseClassesSet.has(template.type)) || (template?.input_types && template?.input_types.some((inputType) => - baseClassesSet.has(inputType) + baseClassesSet.has(inputType), ))) ); }; @@ -263,7 +263,7 @@ export function groupByFamily( hasBaseClassInBaseClasses: foundNode?.hasBaseClassInBaseClasses || nodeData.node!.base_classes.some((baseClass) => - baseClassesSet.has(baseClass) + baseClassesSet.has(baseClass), ), //seta como anterior ou verifica se o node tem base class displayName: nodeData.node?.display_name, }); @@ -280,10 +280,10 @@ export function groupByFamily( if (!foundNode) { foundNode = { hasBaseClassInTemplate: Object.values(node!.template).some( - checkBaseClass + checkBaseClass, ), - hasBaseClassInBaseClasses: node!.base_classes.some((baseClass) => - baseClassesSet.has(baseClass) + hasBaseClassInBaseClasses: node!.base_classes?.some((baseClass) => + baseClassesSet.has(baseClass), ), displayName: node?.display_name, }; @@ -355,7 +355,7 @@ export function isTimeStampString(str: string): boolean { export function extractColumnsFromRows( rows: object[], mode: "intersection" | "union", - excludeColumns?: Array + excludeColumns?: Array, ): (ColDef | ColGroupDef)[] { let columnsKeys: { [key: string]: ColDef | ColGroupDef } = {}; if (rows.length === 0) {