feat: Add flow deployment status and caching support#7948
feat: Add flow deployment status and caching support#7948ogabrielluiz wants to merge 66 commits into
Conversation
…ing Flow Graph instances Added a new FlowCacheService for in-memory caching of Flow Graph instances to enhance performance. Introduced FlowCacheServiceFactory for creating instances of FlowCacheService. This implementation includes methods for adding, removing, and retrieving cached graphs, with appropriate logging for error handling and debugging.
Introduced a new asynchronous function, get_flow_by_id_or_endpoint_name_from_cache, to enhance flow retrieval by utilizing the FlowCacheService. This function checks for cached flows and raises a 404 error if not found, improving performance and user experience when accessing flow data.
…enum Implemented the get_flow_cache_service function to retrieve the FlowCacheService instance from the service manager. Updated the ServiceType enum to include FLOW_CACHE_SERVICE, enhancing the service management capabilities for flow caching.
Updated the update_flow function to integrate FlowCacheService for managing flow caching. Added background tasks to handle flow addition and removal from the cache based on deployment state, improving performance and state management during flow updates.
…w retrieval Modified the simple_run_flow and simplified_run_flow functions to accept Graph instances alongside Flow objects. Updated the flow dependency in simplified_run_flow to utilize the new get_flow_by_id_or_endpoint_name_from_cache function, enhancing flow retrieval with caching support. This change improves flexibility and performance in handling flow data.
Added support for creating and instantiating custom component classes within the Vertex class. Introduced methods to create class objects and reset components, improving flexibility in component management. Updated the instantiate_class function to accept class objects and custom parameters, ensuring robust component initialization.
…sses Added a method to reset components in vertices within the Graph class, enhancing component management. Introduced a reset_component method in the Vertex class to clear custom components, improving the flexibility and robustness of component handling in the graph structure.
…tion Updated the assignment of the custom_component in the Vertex class to ensure proper instantiation. This change enhances the clarity and correctness of component initialization, contributing to more robust component management.
…om_cache function Changed the default value of the use_cache parameter in the get_flow_by_id_or_endpoint_name_from_cache function from True to False. This adjustment improves the function's behavior by preventing unintended cache usage, enhancing the robustness of flow retrieval.
Implemented the handleDeployedSwitch function to manage the deployment status of flows asynchronously. This includes updating the flow state on success and handling errors gracefully. Enhanced the UI to reflect the deployed status with appropriate tooltips and switch functionality.
Modified the Alembic migration script to correctly reference the new deployment state enum for the status column in the flow table. Adjusted the down_revision identifier and improved type hints for better clarity. Ensured the deployment state enum is created and dropped appropriately during the upgrade and downgrade processes.
Cleaned up the endpoints.py file by removing unnecessary imports related to Graph and RunOutputs, enhancing code clarity and maintainability.
There was a problem hiding this comment.
Actionable comments posted: 17
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/backend/base/langflow/api/v1/flows.py (2)
401-419: Evict from cache on deleteDeleting a flow doesn’t evict cached entries; stale graphs remain runnable by id/endpoint.
Apply:
-@router.delete("/{flow_id}", status_code=200) -async def delete_flow( +@router.delete("/{flow_id}", status_code=200) +async def delete_flow( *, session: DbSession, flow_id: UUID, current_user: CurrentActiveUser, + flow_cache_service: Annotated["FlowCacheService", Depends(get_flow_cache_service)], ): @@ await cascade_delete_flow(session, flow.id) await session.commit() + try: + await flow_cache_service.remove_flow_from_cache(flow, silent=True) + except Exception: + # best-effort eviction + pass return {"message": "Flow deleted successfully"}
486-514: Evict cached entries on batch deleteSame eviction gap for batch deletion; remove each from cache.
Apply:
-async def delete_multiple_flows( +async def delete_multiple_flows( flow_ids: list[UUID], user: CurrentActiveUser, db: DbSession, + flow_cache_service: Annotated["FlowCacheService", Depends(get_flow_cache_service)], ): @@ for flow in flows_to_delete: await cascade_delete_flow(db, flow.id) + try: + await flow_cache_service.remove_flow_from_cache(flow, silent=True) + except Exception: + passsrc/backend/base/langflow/api/v1/endpoints.py (1)
146-177: Handle Graph inputs in simple_run_flow
Graph objects don’t have.idor.to_payload; usegraph.flow_idand skip—or error on—tweaks. For example:if isinstance(flow, Graph): flow_id_str = flow.flow_id if input_request.tweaks: raise ValueError("Tweak support for cached Graph not implemented; clear cache first.") graph = flow else: flow_id_str = str(flow.id) if flow.data is None: raise ValueError(f"Flow {flow_id_str} has no data") graph_data = process_tweaks(flow.data.copy(), input_request.tweaks or {}, stream=stream) graph = Graph.from_payload( graph_data, flow_id_str, str(user_id), flow.name, context )Also update
simple_run_flow,run_flow_generator, andsimple_run_flow_tasktype hints toFlow | Graph.
♻️ Duplicate comments (1)
src/backend/base/langflow/services/flow_cache/factory.py (1)
5-14: LGTM! Singleton pattern already implemented.The factory correctly implements the singleton pattern by caching the FlowCacheService instance and reusing it on subsequent calls, which is exactly what the past review comment suggested.
Note: The codeflash-ai optimization suggestion from a previous review has already been addressed by this implementation.
🧹 Nitpick comments (11)
src/lfx/tests/unit/services/cache/test_cache_miss.py (1)
55-62: Reconsider the!= Nonecomparison for consistency.Line 58 uses
!= Nonewith a noqa comment to suppress the linter warning. While this explicitly tests the!=operator behavior, it's inconsistent with Python best practices. Unless there's a specific reason to verify!=operator behavior (e.g., ifCacheMissimplements__ne__), preferis not Nonefor clarity and consistency.Apply this diff if no specific need to test
!=operator:- assert CACHE_MISS != None # noqa: E711 + assert CACHE_MISS is not Nonesrc/frontend/tests/core/features/deploy-flow.spec.ts (2)
5-95: Add test coverage for error scenarios and edge cases.The test suite covers happy paths but misses important error and edge cases that could occur during deployment operations.
Consider adding tests for:
- Deployment failure handling: What happens if the backend deployment fails?
- Network interruption: Test behavior when deployment API call is interrupted
- Permission errors: Test unauthorized deployment attempts
- Concurrent modifications: Test race conditions if flow is modified during deployment
- Stale state recovery: Test UI recovery if deployment state becomes inconsistent
Example test structure:
test( "deployment failure shows error message", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { await awaitBootstrapTest(page); await page.getByTestId("blank-flow").click(); // Mock deployment API to return error await page.route('**/api/v1/flows/*/deploy', route => route.fulfill({ status: 500, body: 'Deployment failed' }) ); await page.getByTestId("publish-button").click(); await page.getByTestId("deploy-switch").click(); // Verify error message is displayed await expect(page.getByRole('alert')).toContainText('Deployment failed'); // Verify switch reverts to unchecked state await expect(page.getByTestId("deploy-switch")).toBeChecked({ checked: false }); } );As per coding guidelines: "Test error handling and edge cases in frontend test files."
Also applies to: 97-166, 168-199, 201-277
51-60: Consider removing or simplifying the try-catch block.The try-catch logs the error but immediately rethrows it, which doesn't add value in test execution. Playwright's built-in error messages are already descriptive.
Either remove the try-catch entirely:
-try { - await page.waitForTimeout(2000); - - await expect(page.getByTestId("deploy-switch")).toBeVisible({ - timeout: 10000, - }); -} catch (error) { - console.error("Error waiting for deploy operation:", error); - throw error; -} +await expect(page.getByTestId("deploy-switch")).toBeVisible({ + timeout: 10000, +});Or add meaningful recovery/diagnostic logic:
try { - await page.waitForTimeout(2000); - await expect(page.getByTestId("deploy-switch")).toBeVisible({ timeout: 10000, }); } catch (error) { - console.error("Error waiting for deploy operation:", error); + // Capture screenshot for debugging + await page.screenshot({ path: 'deploy-switch-failure.png' }); + console.error("Deploy switch not visible. Current page state:", { + url: page.url(), + dropdownOpen: await page.getByTestId("publish-button").isVisible(), + }); throw error; }src/backend/base/langflow/api/v1/schemas.py (1)
447-447: Consider strengthening the type annotation.The
statusfield is typed asstr | Nonebut the comment indicates onlyDRAFTorDEPLOYEDare valid values. Consider using aLiteraltype for better type safety and automatic validation:- status: str | None = None # DRAFT or DEPLOYED + status: Literal["DRAFT", "DEPLOYED"] | None = NoneDon't forget to add
Literalto the imports fromtypingat the top of the file if it's not already imported.src/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsx (3)
367-384: Test doesn’t assert tooltip contentThe test name mentions tooltip content, but it only checks presence of the menu item. Assert the actual tooltip text after hover/focus.
Example:
- await waitFor(() => { - const deployedStatus = screen.getByTestId("deployed-status"); - expect(deployedStatus).toBeInTheDocument(); - }); + const deployedStatus = await screen.findByTestId("deployed-status"); + await user.hover(deployedStatus); + expect(await screen.findByText(/Deploy to make this flow available via API/i)).toBeInTheDocument();
1-3: Unused importfireEvent is imported but unused; lint will flag it.
118-141: Add coverage for publish switch toggling (potential bug)There’s no test for the publish switch. In deploy-dropdown.tsx, handlePublishedSwitch receives the current isPublished value (not toggled), which likely flips logic. Add a test that toggles publish and asserts mutateAsync is called with PUBLIC/PRIVATE as expected.
Based on learnings
Also applies to: 386-403
src/backend/base/langflow/services/flow_cache/service.py (2)
44-49: user_id type to Graph.from_payloadGraph.from_payload likely expects str for user_id (simple_run_flow uses str(user_id)). Pass str to avoid type inconsistencies.
Apply:
- graph = Graph.from_payload(graph_data, flow_id=flow_id_str, user_id=flow.user_id, flow_name=flow.name) + graph = Graph.from_payload( + graph_data, flow_id=flow_id_str, user_id=str(flow.user_id) if flow.user_id else None, flow_name=flow.name + )
123-178: get_cache_stats OK but heavy; consider delegating endpoint to this methodPrefer using this locked method from API endpoints (/cache) rather than reimplementing stats in endpoints.py.
src/backend/base/langflow/api/v1/flows.py (1)
367-375: Endpoint name change leaves old alias cachedWhen DEPLOYED and endpoint_name changes, refresh_flow_in_cache adds the new alias but remove_flow_from_cache doesn’t clear the old alias; stale key remains.
Consider tracking aliases per flow in FlowCacheService (id→set of keys) to remove previous endpoint aliases on refresh. Based on learnings
src/backend/base/langflow/api/v1/endpoints.py (1)
404-406: CACHE_MISS check here is redundantget_flow_by_id_or_endpoint_name_from_cache returns Graph or Flow object; sentinel won’t escape. You can simplify to “if flow is None”.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (36)
src/backend/base/langflow/alembic/versions/ea8c52f13171_add_status_column_in_flow.py(1 hunks)src/backend/base/langflow/api/v1/endpoints.py(6 hunks)src/backend/base/langflow/api/v1/flows.py(7 hunks)src/backend/base/langflow/api/v1/mcp_projects.py(2 hunks)src/backend/base/langflow/api/v1/mcp_utils.py(3 hunks)src/backend/base/langflow/api/v1/schemas.py(1 hunks)src/backend/base/langflow/helpers/flow.py(2 hunks)src/backend/base/langflow/interface/initialize/loading.py(1 hunks)src/backend/base/langflow/services/database/models/flow/model.py(3 hunks)src/backend/base/langflow/services/database/models/flow/utils.py(1 hunks)src/backend/base/langflow/services/database/service.py(1 hunks)src/backend/base/langflow/services/deps.py(2 hunks)src/backend/base/langflow/services/flow_cache/factory.py(1 hunks)src/backend/base/langflow/services/flow_cache/service.py(1 hunks)src/backend/base/langflow/services/schema.py(1 hunks)src/backend/base/langflow/services/socket/utils.py(1 hunks)src/backend/base/langflow/services/utils.py(4 hunks)src/backend/tests/unit/api/v1/test_flows.py(3 hunks)src/backend/tests/unit/api/v1/test_mcp_projects.py(3 hunks)src/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsx(1 hunks)src/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsx(4 hunks)src/frontend/src/constants/flows.ts(1 hunks)src/frontend/src/controllers/API/queries/flows/use-patch-update-flow.ts(1 hunks)src/frontend/src/hooks/flows/use-save-flow.ts(2 hunks)src/frontend/src/modals/apiModal/index.tsx(3 hunks)src/frontend/src/modals/toolsModal/components/toolsTable/__tests__/deployment-status.test.tsx(1 hunks)src/frontend/src/modals/toolsModal/components/toolsTable/index.tsx(3 hunks)src/frontend/src/pages/FlowPage/components/PageComponent/MemoizedComponents.tsx(2 hunks)src/frontend/src/types/flow/index.ts(1 hunks)src/frontend/src/types/mcp/index.ts(1 hunks)src/frontend/tests/core/features/deploy-flow.spec.ts(1 hunks)src/frontend/tests/core/features/mcp-deployment.spec.ts(1 hunks)src/lfx/src/lfx/graph/graph/base.py(2 hunks)src/lfx/src/lfx/graph/vertex/base.py(3 hunks)src/lfx/src/lfx/interface/initialize/loading.py(1 hunks)src/lfx/tests/unit/services/cache/test_cache_miss.py(1 hunks)
🧰 Additional context used
📓 Path-based instructions (15)
{src/backend/**/*.py,tests/**/*.py,Makefile}
📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc)
{src/backend/**/*.py,tests/**/*.py,Makefile}: Run make format_backend to format Python code before linting or committing changes
Run make lint to perform linting checks on backend Python code
Files:
src/backend/base/langflow/api/v1/schemas.pysrc/backend/base/langflow/services/schema.pysrc/backend/base/langflow/services/database/models/flow/model.pysrc/backend/base/langflow/services/flow_cache/factory.pysrc/backend/tests/unit/api/v1/test_mcp_projects.pysrc/backend/tests/unit/api/v1/test_flows.pysrc/backend/base/langflow/api/v1/mcp_projects.pysrc/backend/base/langflow/api/v1/mcp_utils.pysrc/backend/base/langflow/services/deps.pysrc/backend/base/langflow/api/v1/flows.pysrc/backend/base/langflow/helpers/flow.pysrc/backend/base/langflow/services/database/service.pysrc/backend/base/langflow/services/socket/utils.pysrc/backend/base/langflow/alembic/versions/ea8c52f13171_add_status_column_in_flow.pysrc/backend/base/langflow/services/database/models/flow/utils.pysrc/backend/base/langflow/api/v1/endpoints.pysrc/backend/base/langflow/services/utils.pysrc/backend/base/langflow/interface/initialize/loading.pysrc/backend/base/langflow/services/flow_cache/service.py
src/frontend/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
src/frontend/src/**/*.{ts,tsx,js,jsx}: All frontend TypeScript and JavaScript code should be located under src/frontend/src/ and organized into components, pages, icons, stores, types, utils, hooks, services, and assets directories as per the specified directory layout.
Use React 18 with TypeScript for all UI components in the frontend.
Format all TypeScript and JavaScript code using the make format_frontend command.
Lint all TypeScript and JavaScript code using the make lint command.
Files:
src/frontend/src/hooks/flows/use-save-flow.tssrc/frontend/src/controllers/API/queries/flows/use-patch-update-flow.tssrc/frontend/src/constants/flows.tssrc/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsxsrc/frontend/src/modals/apiModal/index.tsxsrc/frontend/src/pages/FlowPage/components/PageComponent/MemoizedComponents.tsxsrc/frontend/src/modals/toolsModal/components/toolsTable/index.tsxsrc/frontend/src/types/flow/index.tssrc/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsxsrc/frontend/src/types/mcp/index.tssrc/frontend/src/modals/toolsModal/components/toolsTable/__tests__/deployment-status.test.tsx
src/frontend/src/@(hooks|services)/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
All API calls must implement proper error handling, such as using try/catch and setting error state.
Files:
src/frontend/src/hooks/flows/use-save-flow.ts
src/frontend/src/@(components|hooks)/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
Implement dark mode support in components and hooks where needed.
Files:
src/frontend/src/hooks/flows/use-save-flow.tssrc/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsxsrc/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsx
src/frontend/src/hooks/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
All custom React hooks should be placed in the hooks directory.
Files:
src/frontend/src/hooks/flows/use-save-flow.ts
src/backend/base/langflow/services/database/models/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc)
Place database models in src/backend/base/langflow/services/database/models/
Files:
src/backend/base/langflow/services/database/models/flow/model.pysrc/backend/base/langflow/services/database/models/flow/utils.py
src/frontend/src/components/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
All components should be styled using Tailwind CSS utility classes.
Files:
src/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsxsrc/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsx
src/frontend/**/*.@(test|spec).{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)
src/frontend/**/*.@(test|spec).{ts,tsx,js,jsx}: Frontend test files should be located in 'src/frontend/' and use '.test.{ts,tsx,js,jsx}' or '.spec.{ts,tsx,js,jsx}' extensions.
Test both sync and async code paths in frontend test files.
Mock external dependencies appropriately in frontend test files to isolate unit tests from external services.
Test error handling and edge cases in frontend test files.
Validate input/output behavior and test component initialization and configuration in frontend test files.
Each frontend test should have a clear description or comment explaining its purpose, especially for complex setups or mocks.
Files:
src/frontend/tests/core/features/mcp-deployment.spec.tssrc/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsxsrc/frontend/tests/core/features/deploy-flow.spec.tssrc/frontend/src/modals/toolsModal/components/toolsTable/__tests__/deployment-status.test.tsx
**/{test_*.py,*.test.ts,*.test.tsx}
📄 CodeRabbit inference engine (coderabbit-custom-pre-merge-checks-unique-id-file-non-traceable-F7F2B60C-1728-4C9A-8889-4F2235E186CA.txt)
**/{test_*.py,*.test.ts,*.test.tsx}: Check test files for excessive use of mocks that obscure what's actually being tested
Warn when mocks replace testing real behavior/interactions
Suggest using real objects or simpler test doubles when mocks become excessive
Use mocks primarily for external dependencies, not core logic
Recommend integration tests when unit tests are overly mocked
Verify tests actually exercise the new/changed functionality (not placeholders)
Test files should use descriptive test names explaining what is being tested
Organize tests logically with proper setup/teardown
Include edge cases and error conditions for comprehensive coverage
Cover both positive and negative scenarios where appropriate
Tests should cover the main functionality being implemented
Tests should not be mere smoke tests; they must validate behavior
Files:
src/lfx/tests/unit/services/cache/test_cache_miss.pysrc/backend/tests/unit/api/v1/test_mcp_projects.pysrc/backend/tests/unit/api/v1/test_flows.pysrc/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsxsrc/frontend/src/modals/toolsModal/components/toolsTable/__tests__/deployment-status.test.tsx
**/test_*.py
📄 CodeRabbit inference engine (coderabbit-custom-pre-merge-checks-unique-id-file-non-traceable-F7F2B60C-1728-4C9A-8889-4F2235E186CA.txt)
**/test_*.py: Backend test files must follow naming convention test_*.py
Backend tests should use proper pytest structure
For async backend functions, use proper async testing patterns with pytest
Backend tests should follow pytest conventions
For API endpoints, verify both success and error response tests are present
Files:
src/lfx/tests/unit/services/cache/test_cache_miss.pysrc/backend/tests/unit/api/v1/test_mcp_projects.pysrc/backend/tests/unit/api/v1/test_flows.py
src/backend/tests/unit/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc)
Test component integration within flows using create_flow, build_flow, and get_build_events utilities
Files:
src/backend/tests/unit/api/v1/test_mcp_projects.pysrc/backend/tests/unit/api/v1/test_flows.py
src/backend/tests/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)
src/backend/tests/**/*.py: Unit tests for backend code must be located in the 'src/backend/tests/' directory, with component tests organized by component subdirectory under 'src/backend/tests/unit/components/'.
Test files should use the same filename as the component under test, with an appropriate test prefix or suffix (e.g., 'my_component.py' → 'test_my_component.py').
Use the 'client' fixture (an async httpx.AsyncClient) for API tests in backend Python tests, as defined in 'src/backend/tests/conftest.py'.
When writing component tests, inherit from the appropriate base class in 'src/backend/tests/base.py' (ComponentTestBase, ComponentTestBaseWithClient, or ComponentTestBaseWithoutClient) and provide the required fixtures: 'component_class', 'default_kwargs', and 'file_names_mapping'.
Each test in backend Python test files should have a clear docstring explaining its purpose, and complex setups or mocks should be well-commented.
Test both sync and async code paths in backend Python tests, using '@pytest.mark.asyncio' for async tests.
Mock external dependencies appropriately in backend Python tests to isolate unit tests from external services.
Test error handling and edge cases in backend Python tests, including using 'pytest.raises' and asserting error messages.
Validate input/output behavior and test component initialization and configuration in backend Python tests.
Use the 'no_blockbuster' pytest marker to skip the blockbuster plugin in tests when necessary.
Be aware of ContextVar propagation in async tests; test both direct event loop execution and 'asyncio.to_thread' scenarios to ensure proper context isolation.
Test error handling by mocking internal functions using monkeypatch in backend Python tests.
Test resource cleanup in backend Python tests by using fixtures that ensure proper initialization and cleanup of resources.
Test timeout and performance constraints in backend Python tests using 'asyncio.wait_for' and timing assertions.
Test Langflow's Messag...
Files:
src/backend/tests/unit/api/v1/test_mcp_projects.pysrc/backend/tests/unit/api/v1/test_flows.py
src/frontend/src/types/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
All TypeScript type definitions should be placed in the types directory.
Files:
src/frontend/src/types/flow/index.tssrc/frontend/src/types/mcp/index.ts
src/frontend/src/**/__tests__/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
All frontend code should be tested using appropriate component and integration tests.
Files:
src/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsxsrc/frontend/src/modals/toolsModal/components/toolsTable/__tests__/deployment-status.test.tsx
**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (coderabbit-custom-pre-merge-checks-unique-id-file-non-traceable-F7F2B60C-1728-4C9A-8889-4F2235E186CA.txt)
**/*.test.{ts,tsx}: Frontend test files must follow naming convention *.test.ts or *.test.tsx
Frontend tests should use Playwright
Frontend tests should follow Playwright conventions
Files:
src/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsxsrc/frontend/src/modals/toolsModal/components/toolsTable/__tests__/deployment-status.test.tsx
🧠 Learnings (3)
📚 Learning: 2025-06-23T12:46:42.048Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/frontend_development.mdc:0-0
Timestamp: 2025-06-23T12:46:42.048Z
Learning: Custom React Flow node types should be implemented as memoized components, using Handle components for connection points and supporting optional icons and labels.
Applied to files:
src/frontend/src/pages/FlowPage/components/PageComponent/MemoizedComponents.tsx
📚 Learning: 2025-07-18T18:25:54.487Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/backend_development.mdc:0-0
Timestamp: 2025-07-18T18:25:54.487Z
Learning: Applies to src/backend/tests/unit/**/*.py : Test component integration within flows using create_flow, build_flow, and get_build_events utilities
Applied to files:
src/backend/tests/unit/api/v1/test_flows.py
📚 Learning: 2025-07-21T14:16:14.125Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-21T14:16:14.125Z
Learning: Applies to src/backend/tests/**/*.py : Test flows using predefined JSON data and utility functions such as 'create_flow', 'build_flow', 'get_build_events', and 'consume_and_assert_stream' in backend Python tests.
Applied to files:
src/backend/tests/unit/api/v1/test_flows.py
🧬 Code graph analysis (25)
src/backend/base/langflow/api/v1/schemas.py (1)
src/backend/base/langflow/services/task/backends/anyio.py (1)
status(24-27)
src/lfx/src/lfx/graph/vertex/base.py (1)
src/lfx/src/lfx/interface/initialize/loading.py (1)
instantiate_class(28-54)
src/backend/base/langflow/services/flow_cache/factory.py (1)
src/backend/base/langflow/services/flow_cache/service.py (1)
FlowCacheService(17-178)
src/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsx (2)
src/frontend/src/constants/flows.ts (2)
ACCESS_TYPE(15-19)DEPLOYMENT_STATUS(4-7)src/frontend/src/utils/utils.ts (1)
cn(38-40)
src/frontend/src/modals/apiModal/index.tsx (1)
src/frontend/src/constants/flows.ts (1)
DEPLOYMENT_STATUS(4-7)
src/frontend/tests/core/features/mcp-deployment.spec.ts (1)
src/frontend/tests/utils/await-bootstrap-test.ts (1)
awaitBootstrapTest(4-49)
src/frontend/src/pages/FlowPage/components/PageComponent/MemoizedComponents.tsx (3)
src/frontend/src/constants/flows.ts (1)
DEPLOYMENT_STATUS(4-7)src/frontend/src/components/common/genericIconComponent/index.tsx (1)
ForwardedIconComponent(15-146)src/frontend/src/utils/utils.ts (1)
cn(38-40)
src/frontend/src/modals/toolsModal/components/toolsTable/index.tsx (4)
src/frontend/src/controllers/API/queries/flows/use-patch-update-flow.ts (1)
usePatchUpdateFlow(20-49)src/frontend/src/constants/flows.ts (1)
DEPLOYMENT_STATUS(4-7)src/frontend/src/components/common/genericIconComponent/index.tsx (1)
ForwardedIconComponent(15-146)src/frontend/src/utils/utils.ts (1)
cn(38-40)
src/backend/tests/unit/api/v1/test_mcp_projects.py (5)
src/backend/base/langflow/services/deps.py (1)
session_scope(152-174)src/backend/base/langflow/services/auth/utils.py (1)
get_password_hash(344-346)src/backend/base/langflow/services/database/models/folder/model.py (1)
Folder(21-36)src/backend/base/langflow/services/database/models/flow/model.py (1)
Flow(204-230)src/backend/base/langflow/api/v1/mcp_projects.py (1)
list_project_tools(199-283)
src/backend/tests/unit/api/v1/test_flows.py (2)
src/backend/base/langflow/services/database/models/flow/model.py (1)
Flow(204-230)src/backend/tests/conftest.py (2)
logged_in_headers(495-501)flow(544-560)
src/backend/base/langflow/api/v1/mcp_projects.py (2)
src/backend/base/langflow/services/database/models/flow/model.py (1)
Flow(204-230)src/backend/tests/conftest.py (1)
flow(544-560)
src/backend/base/langflow/api/v1/mcp_utils.py (2)
src/backend/base/langflow/services/deps.py (1)
get_flow_cache_service(247-251)src/backend/base/langflow/services/flow_cache/service.py (1)
get_cached_graph(79-102)
src/backend/base/langflow/services/deps.py (3)
src/backend/base/langflow/services/flow_cache/service.py (1)
FlowCacheService(17-178)src/backend/base/langflow/services/flow_cache/factory.py (1)
FlowCacheServiceFactory(5-14)src/backend/base/langflow/services/schema.py (1)
ServiceType(4-23)
src/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsx (1)
src/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsx (1)
PublishDropdown(32-285)
src/backend/base/langflow/api/v1/flows.py (3)
src/backend/base/langflow/services/database/models/flow/model.py (1)
DeploymentStateEnum(32-34)src/backend/base/langflow/services/deps.py (2)
get_flow_cache_service(247-251)get_settings_service(112-125)src/backend/base/langflow/services/flow_cache/service.py (5)
FlowCacheService(17-178)add_flow_to_cache(26-59)refresh_flow_in_cache(104-121)remove_flow_from_cache(61-77)get_cache_stats(123-178)
src/backend/base/langflow/helpers/flow.py (3)
src/backend/base/langflow/services/database/models/flow/model.py (2)
Flow(204-230)FlowRead(239-243)src/backend/base/langflow/services/deps.py (1)
get_flow_cache_service(247-251)src/backend/base/langflow/services/flow_cache/service.py (1)
get_cached_graph(79-102)
src/backend/base/langflow/services/database/service.py (5)
src/backend/base/langflow/services/deps.py (1)
get_settings_service(112-125)src/backend/base/langflow/services/database/models/flow/model.py (1)
Flow(204-230)src/backend/base/langflow/services/database/models/folder/model.py (1)
Folder(21-36)src/backend/base/langflow/services/database/session.py (4)
exec(51-62)all(56-57)add(18-19)commit(21-22)src/backend/base/langflow/services/database/models/user/crud.py (1)
get_user_by_username(14-16)
src/backend/base/langflow/services/socket/utils.py (3)
src/lfx/src/lfx/graph/vertex/base.py (1)
Vertex(46-831)src/backend/base/langflow/api/utils/core.py (1)
format_elapsed_time(129-150)src/backend/base/langflow/services/database/models/vertex_builds/crud.py (1)
log_vertex_build(52-130)
src/backend/base/langflow/services/database/models/flow/utils.py (1)
src/backend/tests/conftest.py (1)
flow(544-560)
src/frontend/tests/core/features/deploy-flow.spec.ts (3)
src/frontend/tests/utils/await-bootstrap-test.ts (1)
awaitBootstrapTest(4-49)src/frontend/tests/utils/adjust-screen-view.ts (1)
adjustScreenView(3-36)src/frontend/tests/fixtures.ts (1)
expect(61-61)
src/backend/base/langflow/api/v1/endpoints.py (3)
src/backend/base/langflow/helpers/flow.py (2)
get_flow_by_id_or_endpoint_name(282-297)get_flow_by_id_or_endpoint_name_from_cache(300-323)src/backend/base/langflow/services/deps.py (1)
get_flow_cache_service(247-251)src/backend/base/langflow/services/flow_cache/service.py (1)
FlowCacheService(17-178)
src/backend/base/langflow/services/utils.py (4)
src/backend/base/langflow/services/deps.py (5)
session_scope(152-174)get_db_service(128-137)get_flow_cache_service(247-251)get_service(31-53)get_settings_service(112-125)src/backend/base/langflow/services/database/models/flow/model.py (2)
DeploymentStateEnum(32-34)Flow(204-230)src/backend/base/langflow/services/flow_cache/service.py (2)
FlowCacheService(17-178)add_flow_to_cache(26-59)src/backend/base/langflow/services/database/service.py (1)
assign_orphaned_flows_to_superuser(200-246)
src/backend/base/langflow/interface/initialize/loading.py (1)
src/lfx/src/lfx/interface/initialize/loading.py (9)
build_component(263-271)build_custom_component(274-317)convert_kwargs(97-111)convert_params_to_sets(88-94)get_instance_results(57-78)get_params(81-85)instantiate_class(28-54)update_params_with_load_from_db_fields(211-260)update_table_params_with_load_from_db_fields(129-208)
src/backend/base/langflow/services/flow_cache/service.py (2)
src/backend/base/langflow/services/cache/service.py (1)
AsyncInMemoryCache(294-355)src/backend/base/langflow/services/database/models/flow/model.py (1)
Flow(204-230)
src/frontend/src/modals/toolsModal/components/toolsTable/__tests__/deployment-status.test.tsx (1)
src/frontend/src/modals/toolsModal/components/toolsTable/index.tsx (1)
ToolsTable(28-515)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Optimize new Python code in this PR
- GitHub Check: Update Starter Projects
🔇 Additional comments (22)
src/backend/base/langflow/services/socket/utils.py (1)
5-7: Confirm lfx workspace dependency and API compatibility
pyproject.toml already includes lfx as a workspace package. Verify that the external Graph, layered_topological_sort, and Vertex APIs remain compatible with their previous internal implementations (e.g., Graph.from_payload, Vertex.build) to prevent runtime errors.src/lfx/tests/unit/services/cache/test_cache_miss.py (1)
1-94: Comprehensive test coverage for sentinel behavior.The test suite thoroughly validates
CacheMissbehavior across common Python patterns:
- Boolean evaluation and control flow (if, if not, ternary)
- Identity checks and None distinction
- String representations
- Practical usage patterns (or defaults, list comprehension filtering)
The tests are well-structured, follow pytest conventions, and validate actual behavior rather than being mere smoke tests. Good work on covering edge cases and multiple interaction scenarios.
src/frontend/tests/core/features/deploy-flow.spec.ts (1)
1-3: LGTM! Test structure and organization are solid.The test file follows good practices:
- Clear test descriptions with appropriate tags
- Proper use of shared utilities (
awaitBootstrapTest,adjustScreenView)- Structured setup-action-assertion flow in each test
- Data-testid attributes for reliable element selection
src/frontend/src/controllers/API/queries/flows/use-patch-update-flow.ts (1)
17-17: LGTM!The addition of the optional
statusfield is clean and type-safe. The literal union type"DRAFT" | "DEPLOYED"aligns with the backend'sDeploymentStateEnum, and the field is properly included in the mutation payload via the spread operator.src/frontend/src/types/flow/index.ts (1)
36-36: LGTM!The addition of the optional
statusfield with a properly typed literal union"DRAFT" | "DEPLOYED"is clean and maintains type safety across the frontend.src/backend/base/langflow/services/schema.py (1)
22-22: LGTM!The addition of the
FLOW_CACHE_SERVICEenum member is clean and follows the existing naming convention for service types.src/frontend/src/types/mcp/index.ts (1)
23-23: LGTM!The addition of the optional
statusfield with a properly typed literal union maintains consistency with the Flow type and provides type safety for MCP settings.src/frontend/src/hooks/flows/use-save-flow.ts (1)
72-72: LGTM!The destructuring and inclusion of
statusin the mutation payload ensures the deployment status is properly preserved when saving flows.Also applies to: 84-84
src/frontend/src/modals/apiModal/index.tsx (2)
56-60: LGTM!The deployment status check is correctly implemented, comparing against
DEPLOYMENT_STATUS.DEPLOYED. The logic properly defaults to not deployed when the status is undefined.
162-180: LGTM!The deployment warning banner provides clear, actionable feedback to users when a flow is not deployed. The non-blocking approach (warning instead of preventing access) is appropriate, allowing users to view API details while being informed about the deployment requirement.
src/lfx/src/lfx/graph/graph/base.py (1)
812-812: LGTM! Proper placement of component reset.Calling
_reset_components_in_vertices()after assembling vertex outputs (line 811) is the correct location, ensuring components are cleaned up after the run completes but before returning results.src/backend/base/langflow/services/deps.py (1)
19-19: LGTM! Consistent service accessor pattern.The implementation follows the established pattern for service getters, with proper TYPE_CHECKING guards for imports and consistent factory usage.
Also applies to: 247-251
src/backend/base/langflow/api/v1/mcp_utils.py (1)
195-200: LGTM! Cache-aware flow retrieval with graceful fallback.The implementation properly attempts to use the cached graph for better performance while maintaining backward compatibility by falling back to the database flow on cache miss.
Also applies to: 240-240
src/lfx/src/lfx/graph/vertex/base.py (2)
72-72: LGTM! Component class tracking added.The implementation properly captures and stores the component class object during instantiation, enabling components to be traced back to their class definitions for caching and reconstruction purposes.
Also applies to: 380-383, 404-409
385-386: Verify if class object should also be reset.The
reset_componentmethod clearscustom_componentbut leaves_custom_component_classintact. Ensure this is intentional—if the class object should be preserved for component reconstruction, this is correct. Otherwise, consider also settingself._custom_component_class = None.src/backend/base/langflow/api/v1/mcp_projects.py (1)
222-227: LGTM! Clear documentation and status field integration.The comments effectively clarify that all flows are accessible via MCP regardless of deployment status, with deployed flows benefiting from caching. The status field addition properly exposes deployment state to the frontend.
Also applies to: 256-256
src/frontend/src/pages/FlowPage/components/PageComponent/MemoizedComponents.tsx (2)
38-61: LGTM! Proper state management with cleanup.The status display effects correctly show text briefly when state changes and properly clean up timers to prevent memory leaks.
65-92: LGTM! Polished status indicators with smooth transitions.The deployment and lock status buttons provide clear visual feedback with smooth CSS transitions, proper hover states, and accessibility attributes.
Also applies to: 98-119
src/backend/base/langflow/services/utils.py (2)
230-237: LGTM! Straightforward cache loading on startup.The function correctly filters for deployed flows and loads them into cache during service initialization.
301-307: LGTM! Proper integration into service initialization.The cache loading is appropriately placed after cleanup operations and correctly reuses the session context. The try-except around
assign_orphaned_flows_to_superuserprovides resilience against integrity errors.src/frontend/src/constants/flows.ts (1)
1-21: LGTM! Well-structured constants with proper TypeScript typing.The constants are properly defined using the
as constpattern, enabling type-safe usage throughout the frontend. The derived types ensure compile-time checking while maintaining flexibility.src/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsx (1)
339-365: Mock override persistence risk (flowStore)Same persistence issue here when overriding flowStore; ensure cleanup as above to avoid affecting subsequent tests.
| # Fetch orphaned flows | ||
| stmt = ( | ||
| select(models.Flow) | ||
| .join(models.Folder) | ||
| .where( | ||
| models.Flow.user_id == None, # noqa: E711 | ||
| models.Folder.name != STARTER_FOLDER_NAME, | ||
| ) | ||
| orphaned_flows = (await session.exec(stmt)).all() | ||
| ) | ||
| orphaned_flows = (await session.exec(stmt)).all() | ||
|
|
||
| if not orphaned_flows: | ||
| return | ||
| if not orphaned_flows: | ||
| return | ||
|
|
||
| await logger.adebug("Assigning orphaned flows to the default superuser") | ||
| await logger.adebug("Assigning orphaned flows to the default superuser") | ||
|
|
||
| # Retrieve superuser | ||
| superuser_username = settings_service.auth_settings.SUPERUSER | ||
| superuser = await get_user_by_username(session, superuser_username) | ||
| # Retrieve superuser | ||
| superuser_username = settings_service.auth_settings.SUPERUSER | ||
| superuser = await get_user_by_username(session, superuser_username) | ||
|
|
||
| if not superuser: | ||
| error_message = "Default superuser not found" | ||
| await logger.aerror(error_message) | ||
| raise RuntimeError(error_message) | ||
| if not superuser: | ||
| error_message = "Default superuser not found" | ||
| await logger.aerror(error_message) | ||
| raise RuntimeError(error_message) | ||
|
|
||
| # Get existing flow names for the superuser | ||
| existing_names: set[str] = set( | ||
| (await session.exec(select(models.Flow.name).where(models.Flow.user_id == superuser.id))).all() | ||
| ) | ||
| # Get existing flow names for the superuser | ||
| existing_names: set[str] = set( | ||
| (await session.exec(select(models.Flow.name).where(models.Flow.user_id == superuser.id))).all() | ||
| ) | ||
|
|
||
| # Process orphaned flows | ||
| for flow in orphaned_flows: | ||
| flow.user_id = superuser.id | ||
| flow.name = self._generate_unique_flow_name(flow.name, existing_names) | ||
| existing_names.add(flow.name) | ||
| session.add(flow) | ||
| # Process orphaned flows | ||
| for flow in orphaned_flows: | ||
| flow.user_id = superuser.id | ||
| flow.name = self._generate_unique_flow_name(flow.name, existing_names) | ||
| existing_names.add(flow.name) | ||
| session.add(flow) | ||
|
|
||
| # Commit changes | ||
| await session.commit() | ||
| await logger.adebug("Successfully assigned orphaned flows to the default superuser") | ||
| # Commit changes | ||
| await session.commit() |
There was a problem hiding this comment.
Use an outer join so folder-less orphaned flows are reassigned.
Switching to an inner join(models.Folder) means any orphaned flow without a folder (i.e., folder_id is NULL—the common case) no longer appears in orphaned_flows, so we silently skip reassigning exactly the flows we’re supposed to fix. Please revert to an outer join (or add an or_(Folder.name.is_(None)) guard) so folderless flows remain eligible while still excluding the starter folder.
- stmt = (
- select(models.Flow)
- .join(models.Folder)
- .where(
- models.Flow.user_id == None,
- models.Folder.name != STARTER_FOLDER_NAME,
- )
- )
+ stmt = (
+ select(models.Flow)
+ .outerjoin(models.Folder)
+ .where(
+ models.Flow.user_id == None,
+ sa.or_(models.Folder.name != STARTER_FOLDER_NAME, models.Folder.name.is_(None)),
+ )
+ )📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Fetch orphaned flows | |
| stmt = ( | |
| select(models.Flow) | |
| .join(models.Folder) | |
| .where( | |
| models.Flow.user_id == None, # noqa: E711 | |
| models.Folder.name != STARTER_FOLDER_NAME, | |
| ) | |
| orphaned_flows = (await session.exec(stmt)).all() | |
| ) | |
| orphaned_flows = (await session.exec(stmt)).all() | |
| if not orphaned_flows: | |
| return | |
| if not orphaned_flows: | |
| return | |
| await logger.adebug("Assigning orphaned flows to the default superuser") | |
| await logger.adebug("Assigning orphaned flows to the default superuser") | |
| # Retrieve superuser | |
| superuser_username = settings_service.auth_settings.SUPERUSER | |
| superuser = await get_user_by_username(session, superuser_username) | |
| # Retrieve superuser | |
| superuser_username = settings_service.auth_settings.SUPERUSER | |
| superuser = await get_user_by_username(session, superuser_username) | |
| if not superuser: | |
| error_message = "Default superuser not found" | |
| await logger.aerror(error_message) | |
| raise RuntimeError(error_message) | |
| if not superuser: | |
| error_message = "Default superuser not found" | |
| await logger.aerror(error_message) | |
| raise RuntimeError(error_message) | |
| # Get existing flow names for the superuser | |
| existing_names: set[str] = set( | |
| (await session.exec(select(models.Flow.name).where(models.Flow.user_id == superuser.id))).all() | |
| ) | |
| # Get existing flow names for the superuser | |
| existing_names: set[str] = set( | |
| (await session.exec(select(models.Flow.name).where(models.Flow.user_id == superuser.id))).all() | |
| ) | |
| # Process orphaned flows | |
| for flow in orphaned_flows: | |
| flow.user_id = superuser.id | |
| flow.name = self._generate_unique_flow_name(flow.name, existing_names) | |
| existing_names.add(flow.name) | |
| session.add(flow) | |
| # Process orphaned flows | |
| for flow in orphaned_flows: | |
| flow.user_id = superuser.id | |
| flow.name = self._generate_unique_flow_name(flow.name, existing_names) | |
| existing_names.add(flow.name) | |
| session.add(flow) | |
| # Commit changes | |
| await session.commit() | |
| await logger.adebug("Successfully assigned orphaned flows to the default superuser") | |
| # Commit changes | |
| await session.commit() | |
| # Fetch orphaned flows | |
| stmt = ( | |
| select(models.Flow) | |
| .outerjoin(models.Folder) | |
| .where( | |
| models.Flow.user_id == None, # noqa: E711 | |
| sa.or_(models.Folder.name != STARTER_FOLDER_NAME, models.Folder.name.is_(None)), | |
| ) | |
| ) | |
| orphaned_flows = (await session.exec(stmt)).all() | |
| if not orphaned_flows: | |
| return | |
| await logger.adebug("Assigning orphaned flows to the default superuser") | |
| # Retrieve superuser | |
| superuser_username = settings_service.auth_settings.SUPERUSER | |
| superuser = await get_user_by_username(session, superuser_username) | |
| if not superuser: | |
| error_message = "Default superuser not found" | |
| await logger.aerror(error_message) | |
| raise RuntimeError(error_message) | |
| # Get existing flow names for the superuser | |
| existing_names: set[str] = set( | |
| (await session.exec(select(models.Flow.name).where(models.Flow.user_id == superuser.id))).all() | |
| ) | |
| # Process orphaned flows | |
| for flow in orphaned_flows: | |
| flow.user_id = superuser.id | |
| flow.name = self._generate_unique_flow_name(flow.name, existing_names) | |
| existing_names.add(flow.name) | |
| session.add(flow) | |
| # Commit changes | |
| await session.commit() |
🤖 Prompt for AI Agents
In src/backend/base/langflow/services/database/service.py around lines 207 to
245, the current inner join against models.Folder excludes flows with no folder
(folder_id IS NULL) so those orphaned flows are skipped; change the query to use
an outer join (e.g., join(models.Folder, isouter=True) or
outerjoin(models.Folder)) and update the WHERE clause to exclude the starter
folder while still allowing Folder.name IS NULL (alternatively add an
or_(models.Folder.name.is_(None), models.Folder.name != STARTER_FOLDER_NAME)
condition) so folderless flows are included and reassigned to the superuser.
| async def remove_flow_from_cache(self, flow: Flow, *, silent: bool = False) -> None: | ||
| """Remove a flow's Graph instance from the cache. | ||
|
|
||
| Args: | ||
| flow (Flow): The flow to remove from cache | ||
| silent (bool): If True, suppress debug logging (used during refresh) | ||
| """ | ||
| flow_id_str = str(flow.id) | ||
| try: | ||
| await self.delete(flow_id_str) | ||
| if not silent: | ||
| logger.debug(f"Removed flow {flow_id_str} from cache") | ||
| except KeyError as e: | ||
| if not silent: | ||
| logger.error(f"Cache key not found when removing flow {flow_id_str}: {e!s}") | ||
| except RuntimeError as e: | ||
| logger.error(f"Error removing flow {flow_id_str} from cache: {e!s}") | ||
|
|
There was a problem hiding this comment.
Alias key not removed; stale endpoint_name entries
remove_flow_from_cache deletes only by id. If flows were cached also under endpoint_name, alias remains stale after status change or deletion.
Apply:
@@
async def remove_flow_from_cache(self, flow: Flow, *, silent: bool = False) -> None:
@@
- try:
- await self.delete(flow_id_str)
+ try:
+ await self.delete(flow_id_str)
+ if getattr(flow, "endpoint_name", None):
+ await self.delete(flow.endpoint_name)Longer-term: maintain an id→aliases index to remove old aliases when endpoint_name changes.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async def remove_flow_from_cache(self, flow: Flow, *, silent: bool = False) -> None: | |
| """Remove a flow's Graph instance from the cache. | |
| Args: | |
| flow (Flow): The flow to remove from cache | |
| silent (bool): If True, suppress debug logging (used during refresh) | |
| """ | |
| flow_id_str = str(flow.id) | |
| try: | |
| await self.delete(flow_id_str) | |
| if not silent: | |
| logger.debug(f"Removed flow {flow_id_str} from cache") | |
| except KeyError as e: | |
| if not silent: | |
| logger.error(f"Cache key not found when removing flow {flow_id_str}: {e!s}") | |
| except RuntimeError as e: | |
| logger.error(f"Error removing flow {flow_id_str} from cache: {e!s}") | |
| async def remove_flow_from_cache(self, flow: Flow, *, silent: bool = False) -> None: | |
| """Remove a flow's Graph instance from the cache. | |
| Args: | |
| flow (Flow): The flow to remove from cache | |
| silent (bool): If True, suppress debug logging (used during refresh) | |
| """ | |
| flow_id_str = str(flow.id) | |
| try: | |
| await self.delete(flow_id_str) | |
| if getattr(flow, "endpoint_name", None): | |
| await self.delete(flow.endpoint_name) | |
| if not silent: | |
| logger.debug(f"Removed flow {flow_id_str} from cache") | |
| except KeyError as e: | |
| if not silent: | |
| logger.error(f"Cache key not found when removing flow {flow_id_str}: {e!s}") | |
| except RuntimeError as e: | |
| logger.error(f"Error removing flow {flow_id_str} from cache: {e!s}") |
🤖 Prompt for AI Agents
In src/backend/base/langflow/services/flow_cache/service.py around lines 61 to
78, remove_flow_from_cache currently deletes only the flow ID key which leaves
any endpoint_name alias keys stale; update this method to also remove any alias
keys (such as endpoint_name) associated with the flow by resolving the alias
names and deleting them from the cache alongside the ID, handling missing keys
and runtime errors the same way as the current ID deletion (suppressing logs
when silent), and ensure atomicity where possible (e.g., perform both deletions
in the same try/except block or transaction); longer-term, add/maintain an
id→aliases index so when a flow's endpoint_name changes you can look up and
remove all previous aliases during updates or deletion.
| // Try to edit - flow should be locked | ||
| // Check if lock indicator is visible (assuming there's a lock icon or similar) | ||
| await page.waitForTimeout(1000); | ||
|
|
||
| // Attempt to drag component (should be prevented if locked) | ||
| const chatInputNode = page.locator('[data-testid*="title-Chat Input"]'); | ||
| await expect(chatInputNode).toBeVisible(); |
There was a problem hiding this comment.
Add actual lock verification logic.
The test claims "deployed flows should be locked" but only verifies that the Chat Input node is visible. It doesn't actually test whether the flow is locked or attempt any edit operations to verify lock enforcement.
Add assertions that verify lock behavior:
// Try to edit - flow should be locked
-// Check if lock indicator is visible (assuming there's a lock icon or similar)
-await page.waitForTimeout(1000);
+// Verify lock indicator is visible
+await expect(page.getByTestId("flow-locked-indicator")).toBeVisible();
// Attempt to drag component (should be prevented if locked)
const chatInputNode = page.locator('[data-testid*="title-Chat Input"]');
await expect(chatInputNode).toBeVisible();
+
+// Verify node cannot be dragged when locked
+const initialPosition = await chatInputNode.boundingBox();
+await chatInputNode.dragTo(page.locator('[data-testid="rf__wrapper"]'), {
+ targetPosition: { x: 200, y: 200 }
+});
+const finalPosition = await chatInputNode.boundingBox();
+expect(initialPosition).toEqual(finalPosition); // Position should not change when locked
+
+// Verify component settings cannot be opened
+await chatInputNode.click();
+await expect(page.getByTestId("component-settings-modal")).not.toBeVisible();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Try to edit - flow should be locked | |
| // Check if lock indicator is visible (assuming there's a lock icon or similar) | |
| await page.waitForTimeout(1000); | |
| // Attempt to drag component (should be prevented if locked) | |
| const chatInputNode = page.locator('[data-testid*="title-Chat Input"]'); | |
| await expect(chatInputNode).toBeVisible(); | |
| // Try to edit - flow should be locked | |
| // Verify lock indicator is visible | |
| await expect(page.getByTestId("flow-locked-indicator")).toBeVisible(); | |
| // Attempt to drag component (should be prevented if locked) | |
| const chatInputNode = page.locator('[data-testid*="title-Chat Input"]'); | |
| await expect(chatInputNode).toBeVisible(); | |
| // Verify node cannot be dragged when locked | |
| const initialPosition = await chatInputNode.boundingBox(); | |
| await chatInputNode.dragTo(page.locator('[data-testid="rf__wrapper"]'), { | |
| targetPosition: { x: 200, y: 200 } | |
| }); | |
| const finalPosition = await chatInputNode.boundingBox(); | |
| expect(initialPosition).toEqual(finalPosition); // Position should not change when locked | |
| // Verify component settings cannot be opened | |
| await chatInputNode.click(); | |
| await expect(page.getByTestId("component-settings-modal")).not.toBeVisible(); |
🤖 Prompt for AI Agents
In src/frontend/tests/core/features/deploy-flow.spec.ts around lines 145 to 151,
the test only checks visibility of the Chat Input node but does not verify the
flow is locked; update the test to assert lock behavior by (1) checking for a
lock indicator element or a UI state class (e.g. data-testid or CSS class
indicating locked), (2) attempting an edit operation such as initiating a
drag/drop or double-click to open the editor and asserting the action is blocked
(no movement, editor does not open, or an error/toast appears), and (3)
asserting interactive controls are disabled (e.g. inputs/buttons have disabled
attribute or aria-disabled). Use appropriate Playwright expectations
(toBeVisible, toHaveClass, toBeDisabled, or verify position unchanged after drag
attempt) and include waits where needed to stabilize the assertions.
| // Test passes if we got this far | ||
| expect(true).toBe(true); | ||
| } catch (error) { |
There was a problem hiding this comment.
Replace the dummy assertion with real toggle verification.
This test never checks that the Deploy Flow toggle is present or changes state—it just calls expect(true).toBe(true), so it will succeed even if the toggle is missing or broken. Please assert the actual outcome (e.g., locate the toggle, click it, and confirm its state or related UI/DB effect).
- // Test passes if we got this far
- expect(true).toBe(true);
+ const toggle = page.getByRole("switch", { name: "Deploy Flow" });
+ await expect(toggle).toBeVisible();
+ const initial = await toggle.getAttribute("aria-checked");
+ await toggle.click();
+ await expect(toggle).not.toHaveAttribute("aria-checked", initial ?? "");📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Test passes if we got this far | |
| expect(true).toBe(true); | |
| } catch (error) { | |
| const toggle = page.getByRole("switch", { name: "Deploy Flow" }); | |
| await expect(toggle).toBeVisible(); | |
| const initial = await toggle.getAttribute("aria-checked"); | |
| await toggle.click(); | |
| await expect(toggle).not.toHaveAttribute("aria-checked", initial ?? ""); | |
| } catch (error) { |
🤖 Prompt for AI Agents
In src/frontend/tests/core/features/mcp-deployment.spec.ts around lines 70-72,
the test currently uses a dummy assertion expect(true).toBe(true) instead of
verifying the Deploy Flow toggle; replace the dummy assertion with real
verification: find the toggle element (by label text, role="switch"/checkbox, or
test-id), assert it exists, simulate a user click to change its state, then
assert the expected post-click state (checked/unchecked or visible UI/DB change)
and any side effects (e.g., presence/absence of related UI elements or a mock
API call). Ensure the test awaits any async updates and cleans up any mocks if
needed.
| // Verify warning appears for non-deployed flow (or doesn't if deployed) | ||
| const warning = page.getByText("Flow Not Deployed"); | ||
| const warningVisible = await warning | ||
| .isVisible({ timeout: 2000 }) | ||
| .catch(() => false); | ||
|
|
||
| // Test passes if modal opened - warning may or may not show depending on deployment status | ||
| expect(true).toBe(true); | ||
| }, |
There was a problem hiding this comment.
Assert the warning banner instead of a placeholder check.
The test title promises that the API modal warns on non‑deployed flows, yet the body ignores warningVisible and finishes with expect(true).toBe(true). The test will succeed even if the warning never renders. Please assert the banner’s visibility (or absence when deployed) so regressions are caught.
- // Test passes if modal opened - warning may or may not show depending on deployment status
- expect(true).toBe(true);
+ if (flowStatus === "DEPLOYED") {
+ await expect(page.getByText("Flow Not Deployed")).toBeHidden();
+ } else {
+ await expect(page.getByText("Flow Not Deployed")).toBeVisible();
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Verify warning appears for non-deployed flow (or doesn't if deployed) | |
| const warning = page.getByText("Flow Not Deployed"); | |
| const warningVisible = await warning | |
| .isVisible({ timeout: 2000 }) | |
| .catch(() => false); | |
| // Test passes if modal opened - warning may or may not show depending on deployment status | |
| expect(true).toBe(true); | |
| }, | |
| // Verify warning appears for non-deployed flow (or doesn't if deployed) | |
| const warning = page.getByText("Flow Not Deployed"); | |
| const warningVisible = await warning | |
| .isVisible({ timeout: 2000 }) | |
| .catch(() => false); | |
| if (flowStatus === "DEPLOYED") { | |
| await expect(page.getByText("Flow Not Deployed")).toBeHidden(); | |
| } else { | |
| await expect(page.getByText("Flow Not Deployed")).toBeVisible(); | |
| } | |
| }, |
🤖 Prompt for AI Agents
In src/frontend/tests/core/features/mcp-deployment.spec.ts around lines 156 to
164, the test currently ignores the computed warningVisible and ends with a
placeholder expect(true).toBe(true); replace that placeholder with an assertion
that checks the warning banner visibility: await the isVisible() result (or use
the caught false) and assert expect(warningVisible).toBe(true) when testing the
non‑deployed flow (or expect(...).toBe(false) for deployed cases), or derive the
expected boolean from the test setup and assert accordingly so the test fails if
the banner does not render as intended.
| def _reset_components_in_vertices(self) -> None: | ||
| """Resets the components in the vertices.""" | ||
| for vertex in self.vertices: | ||
| vertex.reset_component() |
There was a problem hiding this comment.
🧩 Analysis chain
Verify frozen vertex handling in reset logic.
The new _reset_components_in_vertices() method resets components for all vertices. Ensure that vertex.reset_component() properly respects the frozen state of vertices (since frozen vertices should retain their state across runs for caching purposes).
Run the following script to check the implementation of reset_component():
🏁 Script executed:
#!/bin/bash
# Description: Verify reset_component() implementation handles frozen vertices
# Search for the reset_component method implementation
ast-grep --pattern $'class Vertex {
$$$
def reset_component($$$):
$$$
$$$
}'
# Also check for any frozen state checks in the method
rg -A 10 'def reset_component' src/lfx/src/lfx/graph/vertex/Length of output: 740
Skip resetting components for frozen vertices in reset_component
In src/lfx/src/lfx/graph/vertex/base.py at def reset_component, guard the reset with a frozen-state check (e.g., if self.frozen: return) so cached components on frozen vertices remain intact.
🤖 Prompt for AI Agents
In src/lfx/src/lfx/graph/vertex/base.py around the method def reset_component
(the reviewer flagged interaction with src/lfx/src/lfx/graph/graph/base.py lines
1345-1348 where vertices are iterated and reset), the reset_component method
should be a no-op when a vertex is frozen; add an early guard like "if
self.frozen: return" at the start of reset_component so cached components on
frozen vertices are preserved, leaving callers (including
_reset_components_in_vertices) unchanged.
| def test_cache_miss_singleton_pattern(self): | ||
| """Test that CACHE_MISS is a singleton.""" | ||
| # Creating a new instance should give us a different object | ||
| # but CACHE_MISS itself should be the same everywhere | ||
| new_instance = CacheMiss() | ||
| assert new_instance is not CACHE_MISS # Different instances | ||
| assert not new_instance # But same falsy behavior | ||
| assert repr(new_instance) == "<CACHE_MISS>" # Same repr |
There was a problem hiding this comment.
Misleading test name contradicts actual behavior.
The test name test_cache_miss_singleton_pattern implies that CACHE_MISS follows the singleton pattern, but the test explicitly verifies that new_instance is not CACHE_MISS, demonstrating that multiple distinct instances can be created. This contradicts singleton behavior, where only one instance should exist.
The test is actually validating that multiple CacheMiss instances share the same behavioral characteristics (falsy evaluation, repr), not singleton identity.
Choose one of the following solutions:
Option 1 (rename test): Rename to reflect what's actually being tested:
- def test_cache_miss_singleton_pattern(self):
- """Test that CACHE_MISS is a singleton."""
- # Creating a new instance should give us a different object
- # but CACHE_MISS itself should be the same everywhere
+ def test_cache_miss_multiple_instances_same_behavior(self):
+ """Test that multiple CacheMiss instances have consistent behavior."""
+ # Creating a new instance gives a different object
+ # but all instances share the same falsy behavior and reprOption 2 (enforce singleton): If singleton behavior is desired, implement it in the CacheMiss class (using __new__ to return the same instance) and update the test assertions accordingly:
new_instance = CacheMiss()
- assert new_instance is not CACHE_MISS # Different instances
+ assert new_instance is CACHE_MISS # Same instance (singleton)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def test_cache_miss_singleton_pattern(self): | |
| """Test that CACHE_MISS is a singleton.""" | |
| # Creating a new instance should give us a different object | |
| # but CACHE_MISS itself should be the same everywhere | |
| new_instance = CacheMiss() | |
| assert new_instance is not CACHE_MISS # Different instances | |
| assert not new_instance # But same falsy behavior | |
| assert repr(new_instance) == "<CACHE_MISS>" # Same repr | |
| def test_cache_miss_multiple_instances_same_behavior(self): | |
| """Test that multiple CacheMiss instances have consistent behavior.""" | |
| # Creating a new instance gives a different object | |
| # but all instances share the same falsy behavior and repr | |
| new_instance = CacheMiss() | |
| assert new_instance is not CACHE_MISS # Different instances | |
| assert not new_instance # Same falsy behavior | |
| assert repr(new_instance) == "<CACHE_MISS>" # Same repr |
🤖 Prompt for AI Agents
In src/lfx/tests/unit/services/cache/test_cache_miss.py around lines 64 to 71,
the test name implies singleton behavior but the assertions verify distinct
instances that merely share falsy/repr behavior; rename the test and its
docstring to reflect that it's validating behavioral equivalence rather than
singleton identity (e.g., test_cache_miss_behavior_and_repr_not_singleton),
update the docstring/comment to state that multiple CacheMiss instances can
exist but they evaluate falsy and have the same repr, and keep the existing
assertions as-is.
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/backend/base/langflow/api/v1/endpoints.py (1)
146-197: CachedGraphobjects trigger crashes
simplified_run_flownow injects cachedGraphinstances, butsimple_run_flowstill assumes it received a ORMFlow(flow.data,flow.name, copy of dict, reparse viaGraph.from_payload). With a cached graph we hitAttributeErrorimmediately. Guard onisinstance(flow, Graph)(reuse the object, pick up metadata likeflow.flow_id,flow.flow_name) and only rebuild when you’re handed aFlow/FlowRead. Without that branch, every cache hit breaks the run path.Suggested shape:
- flow_id_str = str(flow.id) - if flow.data is None: - ... - graph_data = flow.data.copy() - graph_data = process_tweaks(...) - graph = Graph.from_payload(...) + if isinstance(flow, Graph): + graph = deepcopy(flow) + flow_id_str = getattr(flow, "flow_id", None) or getattr(flow, "id") + flow_name = getattr(flow, "flow_name", None) + else: + flow_id_str = str(flow.id) + if flow.data is None: + ... + graph_data = flow.data.copy() + graph_data = process_tweaks(...) + graph = Graph.from_payload( + graph_data, + flow_id=flow_id_str, + user_id=str(user_id), + flow_name=flow.name, + context=context, + ) + flow_name = flow.nameThen use
flow_namewhere needed. Importdeepcopyif required.
🧹 Nitpick comments (8)
src/backend/base/langflow/api/v1/schemas.py (1)
447-447: Consider usingLiteraltype annotation for stronger type safety.The
statusfield usesstr | None, which allows any string value. For better type safety and IDE support, consider usingLiteral["DRAFT", "DEPLOYED"] | Noneto restrict the field to only valid values.Apply this diff:
+from typing import Any, Literal -from typing import Any- status: str | None = None # DRAFT or DEPLOYED + status: Literal["DRAFT", "DEPLOYED"] | None = Nonesrc/lfx/src/lfx/graph/graph/base.py (1)
1345-1348: No special vertex types to skip—reset is safe. AllVertexsubclasses inherit the basereset_component, so calling it on every vertex is correct. Optionally wrapvertex.reset_component()in atry-exceptto isolate any unexpected errors during reset.src/lfx/src/lfx/interface/initialize/loading.py (1)
28-54: Consider more specific type annotation.The function return type is currently
-> Any, but it returns a specific 3-tuple. Consider updating to:def instantiate_class( vertex: Vertex, user_id=None, event_manager: EventManager | None = None, ) -> tuple[CustomComponent | Component, dict, type[CustomComponent | Component]]:This improves type safety and makes the return structure explicit.
src/frontend/src/modals/toolsModal/components/toolsTable/__tests__/deployment-status.test.tsx (1)
10-40: Consider reducing mock complexity or adding integration tests.The test setup includes extensive mocking of UI components (Sidebar, TableComponent), which obscures what's actually being tested. The current tests only verify that the component doesn't crash on render, not that the deployment toggle behavior works correctly.
Consider:
- Testing the actual deployment toggle interaction using
userEvent- Verifying that
mockMutateAsyncis called with correct arguments- Adding integration tests that use real components instead of mocks for the core behavior
As per coding guidelines: Excessive mocks that replace core logic reduce test value. Consider integration tests when unit tests become overly mocked.
src/frontend/src/modals/apiModal/index.tsx (1)
56-60: Guard undefined flow status before showing the warning
The panel at lines 163–171 renders wheneverisDeployedis false, which includes the initial undefined state. Wrap it in a check thatcurrentFlowStatusis not undefined (or null) so the warning only appears once the status has loaded:{open && currentFlowStatus != null && currentFlowStatus !== DEPLOYMENT_STATUS.DEPLOYED && ( <div className="mb-4 rounded-md border border-warning bg-warning/10 p-3"> …Flow Not Deployed… </div> )}src/backend/base/langflow/services/database/models/flow/utils.py (1)
33-36: Avoid variable shadowing for readabilityInside the loop, data shadows the outer data. Use node_data to reduce confusion.
- for node in nodes: - data = node.get("data", {}) - data_node = data.get("node", {}) + for node in nodes: + node_data = node.get("data", {}) + data_node = node_data.get("node", {})src/frontend/tests/core/features/mcp-deployment.spec.ts (1)
35-77: Test name and assertions mismatchThe spec says “allows toggling deployment status”, but we never toggle or assert anything—we just click through UI and end with
expect(true).toBe(true). Either assert the switch actually flips (e.g., check thearia-checkedstate or deployment badge) or rename/scope the test so it matches the behavior exercised.src/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsx (1)
194-308: Reset store mock implementations between testsTests that call
jest.mocked(...).mockImplementation(...)(e.g., Lines 202‑210, 274‑282) never restore the original implementation.jest.clearAllMocks()clears call history but keeps the lastmockImplementation, so later tests inherit the altered store state. Please reset the mock (e.g., viamockImplementationinbeforeEachormockRestore) to avoid cross-test coupling/flakes.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (36)
src/backend/base/langflow/alembic/versions/ea8c52f13171_add_status_column_in_flow.py(1 hunks)src/backend/base/langflow/api/v1/endpoints.py(6 hunks)src/backend/base/langflow/api/v1/flows.py(7 hunks)src/backend/base/langflow/api/v1/mcp_projects.py(2 hunks)src/backend/base/langflow/api/v1/mcp_utils.py(3 hunks)src/backend/base/langflow/api/v1/schemas.py(1 hunks)src/backend/base/langflow/helpers/flow.py(2 hunks)src/backend/base/langflow/interface/initialize/loading.py(1 hunks)src/backend/base/langflow/services/database/models/flow/model.py(3 hunks)src/backend/base/langflow/services/database/models/flow/utils.py(1 hunks)src/backend/base/langflow/services/database/service.py(1 hunks)src/backend/base/langflow/services/deps.py(2 hunks)src/backend/base/langflow/services/flow_cache/factory.py(1 hunks)src/backend/base/langflow/services/flow_cache/service.py(1 hunks)src/backend/base/langflow/services/schema.py(1 hunks)src/backend/base/langflow/services/socket/utils.py(1 hunks)src/backend/base/langflow/services/utils.py(4 hunks)src/backend/tests/unit/api/v1/test_flows.py(3 hunks)src/backend/tests/unit/api/v1/test_mcp_projects.py(3 hunks)src/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsx(1 hunks)src/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsx(4 hunks)src/frontend/src/constants/flows.ts(1 hunks)src/frontend/src/controllers/API/queries/flows/use-patch-update-flow.ts(1 hunks)src/frontend/src/hooks/flows/use-save-flow.ts(2 hunks)src/frontend/src/modals/apiModal/index.tsx(3 hunks)src/frontend/src/modals/toolsModal/components/toolsTable/__tests__/deployment-status.test.tsx(1 hunks)src/frontend/src/modals/toolsModal/components/toolsTable/index.tsx(3 hunks)src/frontend/src/pages/FlowPage/components/PageComponent/MemoizedComponents.tsx(2 hunks)src/frontend/src/types/flow/index.ts(1 hunks)src/frontend/src/types/mcp/index.ts(1 hunks)src/frontend/tests/core/features/deploy-flow.spec.ts(1 hunks)src/frontend/tests/core/features/mcp-deployment.spec.ts(1 hunks)src/lfx/src/lfx/graph/graph/base.py(2 hunks)src/lfx/src/lfx/graph/vertex/base.py(3 hunks)src/lfx/src/lfx/interface/initialize/loading.py(1 hunks)src/lfx/tests/unit/services/cache/test_cache_miss.py(1 hunks)
🧰 Additional context used
📓 Path-based instructions (15)
{src/backend/**/*.py,tests/**/*.py,Makefile}
📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc)
{src/backend/**/*.py,tests/**/*.py,Makefile}: Run make format_backend to format Python code before linting or committing changes
Run make lint to perform linting checks on backend Python code
Files:
src/backend/base/langflow/services/schema.pysrc/backend/base/langflow/api/v1/schemas.pysrc/backend/base/langflow/services/flow_cache/factory.pysrc/backend/base/langflow/services/database/models/flow/model.pysrc/backend/base/langflow/interface/initialize/loading.pysrc/backend/base/langflow/api/v1/endpoints.pysrc/backend/base/langflow/services/flow_cache/service.pysrc/backend/base/langflow/alembic/versions/ea8c52f13171_add_status_column_in_flow.pysrc/backend/base/langflow/services/database/models/flow/utils.pysrc/backend/base/langflow/services/deps.pysrc/backend/base/langflow/api/v1/mcp_projects.pysrc/backend/base/langflow/api/v1/flows.pysrc/backend/base/langflow/services/database/service.pysrc/backend/base/langflow/api/v1/mcp_utils.pysrc/backend/tests/unit/api/v1/test_flows.pysrc/backend/tests/unit/api/v1/test_mcp_projects.pysrc/backend/base/langflow/helpers/flow.pysrc/backend/base/langflow/services/socket/utils.pysrc/backend/base/langflow/services/utils.py
src/frontend/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
src/frontend/src/**/*.{ts,tsx,js,jsx}: All frontend TypeScript and JavaScript code should be located under src/frontend/src/ and organized into components, pages, icons, stores, types, utils, hooks, services, and assets directories as per the specified directory layout.
Use React 18 with TypeScript for all UI components in the frontend.
Format all TypeScript and JavaScript code using the make format_frontend command.
Lint all TypeScript and JavaScript code using the make lint command.
Files:
src/frontend/src/types/flow/index.tssrc/frontend/src/controllers/API/queries/flows/use-patch-update-flow.tssrc/frontend/src/modals/toolsModal/components/toolsTable/index.tsxsrc/frontend/src/types/mcp/index.tssrc/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsxsrc/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsxsrc/frontend/src/modals/apiModal/index.tsxsrc/frontend/src/constants/flows.tssrc/frontend/src/pages/FlowPage/components/PageComponent/MemoizedComponents.tsxsrc/frontend/src/hooks/flows/use-save-flow.tssrc/frontend/src/modals/toolsModal/components/toolsTable/__tests__/deployment-status.test.tsx
src/frontend/src/types/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
All TypeScript type definitions should be placed in the types directory.
Files:
src/frontend/src/types/flow/index.tssrc/frontend/src/types/mcp/index.ts
src/backend/base/langflow/services/database/models/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc)
Place database models in src/backend/base/langflow/services/database/models/
Files:
src/backend/base/langflow/services/database/models/flow/model.pysrc/backend/base/langflow/services/database/models/flow/utils.py
src/frontend/**/*.@(test|spec).{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)
src/frontend/**/*.@(test|spec).{ts,tsx,js,jsx}: Frontend test files should be located in 'src/frontend/' and use '.test.{ts,tsx,js,jsx}' or '.spec.{ts,tsx,js,jsx}' extensions.
Test both sync and async code paths in frontend test files.
Mock external dependencies appropriately in frontend test files to isolate unit tests from external services.
Test error handling and edge cases in frontend test files.
Validate input/output behavior and test component initialization and configuration in frontend test files.
Each frontend test should have a clear description or comment explaining its purpose, especially for complex setups or mocks.
Files:
src/frontend/tests/core/features/deploy-flow.spec.tssrc/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsxsrc/frontend/src/modals/toolsModal/components/toolsTable/__tests__/deployment-status.test.tsxsrc/frontend/tests/core/features/mcp-deployment.spec.ts
src/frontend/src/components/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
All components should be styled using Tailwind CSS utility classes.
Files:
src/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsxsrc/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsx
src/frontend/src/@(components|hooks)/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
Implement dark mode support in components and hooks where needed.
Files:
src/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsxsrc/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsxsrc/frontend/src/hooks/flows/use-save-flow.ts
src/frontend/src/**/__tests__/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
All frontend code should be tested using appropriate component and integration tests.
Files:
src/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsxsrc/frontend/src/modals/toolsModal/components/toolsTable/__tests__/deployment-status.test.tsx
**/{test_*.py,*.test.ts,*.test.tsx}
📄 CodeRabbit inference engine (coderabbit-custom-pre-merge-checks-unique-id-file-non-traceable-F7F2B60C-1728-4C9A-8889-4F2235E186CA.txt)
**/{test_*.py,*.test.ts,*.test.tsx}: Check test files for excessive use of mocks that obscure what's actually being tested
Warn when mocks replace testing real behavior/interactions
Suggest using real objects or simpler test doubles when mocks become excessive
Use mocks primarily for external dependencies, not core logic
Recommend integration tests when unit tests are overly mocked
Verify tests actually exercise the new/changed functionality (not placeholders)
Test files should use descriptive test names explaining what is being tested
Organize tests logically with proper setup/teardown
Include edge cases and error conditions for comprehensive coverage
Cover both positive and negative scenarios where appropriate
Tests should cover the main functionality being implemented
Tests should not be mere smoke tests; they must validate behavior
Files:
src/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsxsrc/backend/tests/unit/api/v1/test_flows.pysrc/backend/tests/unit/api/v1/test_mcp_projects.pysrc/frontend/src/modals/toolsModal/components/toolsTable/__tests__/deployment-status.test.tsxsrc/lfx/tests/unit/services/cache/test_cache_miss.py
**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (coderabbit-custom-pre-merge-checks-unique-id-file-non-traceable-F7F2B60C-1728-4C9A-8889-4F2235E186CA.txt)
**/*.test.{ts,tsx}: Frontend test files must follow naming convention *.test.ts or *.test.tsx
Frontend tests should use Playwright
Frontend tests should follow Playwright conventions
Files:
src/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsxsrc/frontend/src/modals/toolsModal/components/toolsTable/__tests__/deployment-status.test.tsx
src/backend/tests/unit/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc)
Test component integration within flows using create_flow, build_flow, and get_build_events utilities
Files:
src/backend/tests/unit/api/v1/test_flows.pysrc/backend/tests/unit/api/v1/test_mcp_projects.py
src/backend/tests/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)
src/backend/tests/**/*.py: Unit tests for backend code must be located in the 'src/backend/tests/' directory, with component tests organized by component subdirectory under 'src/backend/tests/unit/components/'.
Test files should use the same filename as the component under test, with an appropriate test prefix or suffix (e.g., 'my_component.py' → 'test_my_component.py').
Use the 'client' fixture (an async httpx.AsyncClient) for API tests in backend Python tests, as defined in 'src/backend/tests/conftest.py'.
When writing component tests, inherit from the appropriate base class in 'src/backend/tests/base.py' (ComponentTestBase, ComponentTestBaseWithClient, or ComponentTestBaseWithoutClient) and provide the required fixtures: 'component_class', 'default_kwargs', and 'file_names_mapping'.
Each test in backend Python test files should have a clear docstring explaining its purpose, and complex setups or mocks should be well-commented.
Test both sync and async code paths in backend Python tests, using '@pytest.mark.asyncio' for async tests.
Mock external dependencies appropriately in backend Python tests to isolate unit tests from external services.
Test error handling and edge cases in backend Python tests, including using 'pytest.raises' and asserting error messages.
Validate input/output behavior and test component initialization and configuration in backend Python tests.
Use the 'no_blockbuster' pytest marker to skip the blockbuster plugin in tests when necessary.
Be aware of ContextVar propagation in async tests; test both direct event loop execution and 'asyncio.to_thread' scenarios to ensure proper context isolation.
Test error handling by mocking internal functions using monkeypatch in backend Python tests.
Test resource cleanup in backend Python tests by using fixtures that ensure proper initialization and cleanup of resources.
Test timeout and performance constraints in backend Python tests using 'asyncio.wait_for' and timing assertions.
Test Langflow's Messag...
Files:
src/backend/tests/unit/api/v1/test_flows.pysrc/backend/tests/unit/api/v1/test_mcp_projects.py
**/test_*.py
📄 CodeRabbit inference engine (coderabbit-custom-pre-merge-checks-unique-id-file-non-traceable-F7F2B60C-1728-4C9A-8889-4F2235E186CA.txt)
**/test_*.py: Backend test files must follow naming convention test_*.py
Backend tests should use proper pytest structure
For async backend functions, use proper async testing patterns with pytest
Backend tests should follow pytest conventions
For API endpoints, verify both success and error response tests are present
Files:
src/backend/tests/unit/api/v1/test_flows.pysrc/backend/tests/unit/api/v1/test_mcp_projects.pysrc/lfx/tests/unit/services/cache/test_cache_miss.py
src/frontend/src/@(hooks|services)/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
All API calls must implement proper error handling, such as using try/catch and setting error state.
Files:
src/frontend/src/hooks/flows/use-save-flow.ts
src/frontend/src/hooks/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
All custom React hooks should be placed in the hooks directory.
Files:
src/frontend/src/hooks/flows/use-save-flow.ts
🧠 Learnings (4)
📚 Learning: 2025-07-18T18:25:54.487Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/backend_development.mdc:0-0
Timestamp: 2025-07-18T18:25:54.487Z
Learning: Applies to src/backend/tests/unit/**/*.py : Test component integration within flows using create_flow, build_flow, and get_build_events utilities
Applied to files:
src/backend/tests/unit/api/v1/test_flows.py
📚 Learning: 2025-07-21T14:16:14.125Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-21T14:16:14.125Z
Learning: Applies to src/backend/tests/**/*.py : Test flows using predefined JSON data and utility functions such as 'create_flow', 'build_flow', 'get_build_events', and 'consume_and_assert_stream' in backend Python tests.
Applied to files:
src/backend/tests/unit/api/v1/test_flows.py
📚 Learning: 2025-07-21T14:16:14.125Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-21T14:16:14.125Z
Learning: Applies to src/backend/tests/**/*.py : Test Langflow's REST API endpoints in backend Python tests using the async client fixture and asserting response codes and payloads.
Applied to files:
src/backend/tests/unit/api/v1/test_flows.py
📚 Learning: 2025-06-23T12:46:42.048Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/frontend_development.mdc:0-0
Timestamp: 2025-06-23T12:46:42.048Z
Learning: Custom React Flow node types should be implemented as memoized components, using Handle components for connection points and supporting optional icons and labels.
Applied to files:
src/frontend/src/pages/FlowPage/components/PageComponent/MemoizedComponents.tsx
🧬 Code graph analysis (25)
src/backend/base/langflow/api/v1/schemas.py (1)
src/backend/base/langflow/services/task/backends/anyio.py (1)
status(24-27)
src/backend/base/langflow/services/flow_cache/factory.py (1)
src/backend/base/langflow/services/flow_cache/service.py (1)
FlowCacheService(17-178)
src/backend/base/langflow/interface/initialize/loading.py (1)
src/lfx/src/lfx/interface/initialize/loading.py (9)
build_component(263-271)build_custom_component(274-317)convert_kwargs(97-111)convert_params_to_sets(88-94)get_instance_results(57-78)get_params(81-85)instantiate_class(28-54)update_params_with_load_from_db_fields(211-260)update_table_params_with_load_from_db_fields(129-208)
src/frontend/src/modals/toolsModal/components/toolsTable/index.tsx (4)
src/frontend/src/controllers/API/queries/flows/use-patch-update-flow.ts (1)
usePatchUpdateFlow(20-49)src/frontend/src/constants/flows.ts (1)
DEPLOYMENT_STATUS(4-7)src/frontend/src/components/common/genericIconComponent/index.tsx (1)
ForwardedIconComponent(15-146)src/frontend/src/utils/utils.ts (1)
cn(38-40)
src/backend/base/langflow/api/v1/endpoints.py (3)
src/backend/base/langflow/helpers/flow.py (2)
get_flow_by_id_or_endpoint_name(282-297)get_flow_by_id_or_endpoint_name_from_cache(300-323)src/backend/base/langflow/services/deps.py (1)
get_flow_cache_service(247-251)src/backend/base/langflow/services/flow_cache/service.py (1)
FlowCacheService(17-178)
src/backend/base/langflow/services/flow_cache/service.py (3)
src/backend/base/langflow/services/cache/service.py (1)
AsyncInMemoryCache(294-355)src/backend/tests/conftest.py (1)
flow(544-560)src/backend/base/langflow/services/database/models/flow/model.py (1)
Flow(204-230)
src/backend/base/langflow/services/database/models/flow/utils.py (1)
src/backend/tests/conftest.py (1)
flow(544-560)
src/frontend/tests/core/features/deploy-flow.spec.ts (2)
src/frontend/tests/utils/await-bootstrap-test.ts (1)
awaitBootstrapTest(4-49)src/frontend/tests/utils/adjust-screen-view.ts (1)
adjustScreenView(3-36)
src/backend/base/langflow/services/deps.py (3)
src/backend/base/langflow/services/flow_cache/service.py (1)
FlowCacheService(17-178)src/backend/base/langflow/services/flow_cache/factory.py (1)
FlowCacheServiceFactory(5-14)src/backend/base/langflow/services/schema.py (1)
ServiceType(4-23)
src/backend/base/langflow/api/v1/mcp_projects.py (2)
src/backend/base/langflow/services/database/models/flow/model.py (1)
Flow(204-230)src/backend/tests/conftest.py (1)
flow(544-560)
src/backend/base/langflow/api/v1/flows.py (3)
src/backend/base/langflow/services/database/models/flow/model.py (1)
DeploymentStateEnum(32-34)src/backend/base/langflow/services/deps.py (2)
get_flow_cache_service(247-251)get_settings_service(112-125)src/backend/base/langflow/services/flow_cache/service.py (5)
FlowCacheService(17-178)add_flow_to_cache(26-59)refresh_flow_in_cache(104-121)remove_flow_from_cache(61-77)get_cache_stats(123-178)
src/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsx (2)
src/frontend/src/constants/flows.ts (2)
ACCESS_TYPE(15-19)DEPLOYMENT_STATUS(4-7)src/frontend/src/utils/utils.ts (1)
cn(38-40)
src/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsx (1)
src/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsx (1)
PublishDropdown(32-285)
src/backend/base/langflow/services/database/service.py (6)
src/backend/base/langflow/services/deps.py (1)
get_settings_service(112-125)src/backend/base/langflow/services/database/models/flow/model.py (1)
Flow(204-230)src/backend/base/langflow/services/database/models/folder/model.py (1)
Folder(21-36)src/backend/base/langflow/services/database/session.py (4)
exec(51-62)all(56-57)add(18-19)commit(21-22)src/backend/base/langflow/services/database/models/user/crud.py (1)
get_user_by_username(14-16)src/backend/tests/conftest.py (1)
flow(544-560)
src/backend/base/langflow/api/v1/mcp_utils.py (2)
src/backend/base/langflow/services/deps.py (2)
get_flow_cache_service(247-251)session_scope(152-174)src/backend/base/langflow/services/flow_cache/service.py (1)
get_cached_graph(79-102)
src/lfx/src/lfx/graph/vertex/base.py (1)
src/lfx/src/lfx/interface/initialize/loading.py (1)
instantiate_class(28-54)
src/frontend/src/modals/apiModal/index.tsx (1)
src/frontend/src/constants/flows.ts (1)
DEPLOYMENT_STATUS(4-7)
src/backend/tests/unit/api/v1/test_flows.py (2)
src/backend/base/langflow/services/database/models/flow/model.py (1)
Flow(204-230)src/backend/tests/conftest.py (2)
logged_in_headers(495-501)flow(544-560)
src/backend/tests/unit/api/v1/test_mcp_projects.py (5)
src/backend/base/langflow/services/deps.py (1)
session_scope(152-174)src/backend/base/langflow/services/auth/utils.py (1)
get_password_hash(344-346)src/backend/base/langflow/services/database/models/folder/model.py (1)
Folder(21-36)src/backend/base/langflow/services/database/models/flow/model.py (1)
Flow(204-230)src/backend/base/langflow/api/v1/mcp_projects.py (1)
list_project_tools(199-283)
src/backend/base/langflow/helpers/flow.py (3)
src/backend/base/langflow/services/database/models/flow/model.py (2)
Flow(204-230)FlowRead(239-243)src/backend/base/langflow/services/deps.py (1)
get_flow_cache_service(247-251)src/backend/base/langflow/services/flow_cache/service.py (1)
get_cached_graph(79-102)
src/frontend/src/pages/FlowPage/components/PageComponent/MemoizedComponents.tsx (3)
src/frontend/src/constants/flows.ts (1)
DEPLOYMENT_STATUS(4-7)src/frontend/src/components/common/genericIconComponent/index.tsx (1)
ForwardedIconComponent(15-146)src/frontend/src/utils/utils.ts (1)
cn(38-40)
src/backend/base/langflow/services/socket/utils.py (5)
src/lfx/src/lfx/graph/vertex/base.py (1)
Vertex(46-831)src/backend/base/langflow/api/utils/core.py (1)
format_elapsed_time(129-150)src/backend/base/langflow/api/v1/schemas.py (2)
ResultDataResponse(266-304)VertexBuildResponse(307-331)src/backend/base/langflow/services/database/models/flow/model.py (1)
Flow(204-230)src/backend/base/langflow/services/database/models/vertex_builds/crud.py (1)
log_vertex_build(52-130)
src/frontend/src/modals/toolsModal/components/toolsTable/__tests__/deployment-status.test.tsx (1)
src/frontend/src/modals/toolsModal/components/toolsTable/index.tsx (1)
ToolsTable(28-515)
src/backend/base/langflow/services/utils.py (4)
src/backend/base/langflow/services/deps.py (5)
session_scope(152-174)get_db_service(128-137)get_flow_cache_service(247-251)get_service(31-53)get_settings_service(112-125)src/backend/base/langflow/services/database/models/flow/model.py (2)
DeploymentStateEnum(32-34)Flow(204-230)src/backend/base/langflow/services/flow_cache/service.py (2)
FlowCacheService(17-178)add_flow_to_cache(26-59)src/backend/base/langflow/services/database/service.py (1)
assign_orphaned_flows_to_superuser(200-246)
src/frontend/tests/core/features/mcp-deployment.spec.ts (1)
src/frontend/tests/utils/await-bootstrap-test.ts (1)
awaitBootstrapTest(4-49)
🔇 Additional comments (23)
src/backend/base/langflow/services/schema.py (1)
22-22: LGTM!The enum addition follows the existing naming pattern and integrates cleanly with the service infrastructure.
src/backend/base/langflow/api/v1/mcp_utils.py (2)
195-199: LGTM! Good defensive coding with clear fallback behavior.The implementation correctly attempts to use the cached graph for better performance while gracefully falling back to the database flow when the cache is unavailable. The inline comments clearly explain the behavior.
240-240: LGTM! Correct usage of cached flow.The flow execution correctly uses
flow_to_run, which will be either the cached graph or the original database flow depending on cache availability.src/frontend/src/controllers/API/queries/flows/use-patch-update-flow.ts (1)
17-17: LGTM! Proper use of TypeScript literal types.The status field correctly uses a literal union type (
"DRAFT" | "DEPLOYED"), providing compile-time type safety and preventing invalid values.src/frontend/src/hooks/flows/use-save-flow.ts (1)
72-72: LGTM! Status field correctly preserved during save.The implementation correctly destructures the
statusfield from the flow object and includes it in the mutation payload, ensuring deployment state is preserved when saving flows.Also applies to: 84-84
src/frontend/src/types/flow/index.ts (1)
36-36: LGTM! Proper TypeScript type definition.The status field correctly uses a literal union type, providing compile-time type safety consistent with the rest of the FlowType definition.
src/frontend/src/types/mcp/index.ts (1)
23-23: LGTM! Proper TypeScript type definition.The status field correctly uses a literal union type (
"DRAFT" | "DEPLOYED"), providing compile-time type safety and maintaining consistency with other type definitions in the codebase.src/backend/base/langflow/services/socket/utils.py (1)
5-7: LGTM! Import path updates align with package reorganization.The imports have been updated to reflect the new package structure (lfx.graph.*), and all imported symbols are properly used in the file. The move of
log_vertex_buildto the.crudsubmodule provides better organization.Also applies to: 14-14
src/frontend/src/modals/apiModal/index.tsx (1)
162-180: LGTM! Clear deployment warning enhances user experience.The warning panel provides clear feedback when a flow is not deployed, with an actionable message directing users to the Share menu. The UI structure and styling are consistent with the codebase.
src/backend/base/langflow/alembic/versions/ea8c52f13171_add_status_column_in_flow.py (2)
23-34: LGTM! Migration is idempotent and safe.The migration properly uses:
checkfirst=Trueto avoid duplicate enum creationcolumn_existscheck to prevent duplicate column addition- Correct
server_defaultsyntax with quoted string for enum valuenullable=Falsewith a default valueThese safeguards make the migration safe for re-running.
37-46: LGTM! Downgrade path is safe and complete.The downgrade properly:
- Checks column existence before dropping
- Drops the enum type with
checkfirst=True- Correctly reverses all changes from the upgrade
src/backend/tests/unit/api/v1/test_mcp_projects.py (4)
518-518: LGTM! Pragma comments correctly suppress false positives.The
pragma: allowlist secretmarkers are appropriately placed on test secret values to prevent security scanners from flagging them as issues.Also applies to: 567-567
730-783: LGTM! Test ensures backward compatibility.This test validates that both DRAFT and DEPLOYED flows are accessible via MCP, confirming no breaking changes were introduced. The test aligns with the documented behavior that deployment is for caching optimization, not access control.
785-835: LGTM! Test validates state transitions correctly.This test effectively verifies that the status field in MCP tools accurately reflects the flow's deployment state, including state transitions from DRAFT to DEPLOYED. The assertions are clear and comprehensive.
685-727: Confirm database cleanup strategyThe test correctly validates that deployment status is exposed via
list_project_tools, but it commits real DB records without explicit teardown. Confirm that your test suite resets or isolates the database between tests to avoid data pollution.src/backend/base/langflow/services/flow_cache/factory.py (1)
5-14: LGTM! Singleton pattern appropriate for cache service.The singleton implementation is correct for a cache service that should be shared across the application. The FlowCacheService itself has internal async locking for cache operations (from
AsyncInMemoryCache), which provides the necessary concurrency safety.Based on learnings: The optimization provides a 43% speedup by avoiding repeated instantiation of the FlowCacheService.
src/backend/base/langflow/api/v1/mcp_projects.py (2)
222-227: LGTM! Helpful comment clarifies deployment behavior.The query reformatting improves readability, and the comment clearly documents that:
- All flows are accessible via MCP regardless of deployment status
- Deployed flows benefit from caching for performance
This aligns with the test coverage in
test_mcp_projects.pythat verifies both DRAFT and DEPLOYED flows are included.
248-257: MCPSettings schema correctly includes the status fieldThe
statusattribute is defined onMCPSettings(str | None), sostatus=flow.statusis accepted.src/lfx/src/lfx/interface/initialize/loading.py (1)
54-54: All instantiate_class call sites correctly unpack three values. No further changes needed.src/backend/base/langflow/services/deps.py (1)
247-251: Flow cache accessor LGTMFactory-based accessor matches existing patterns and ServiceType. No issues.
src/lfx/tests/unit/services/cache/test_cache_miss.py (1)
1-94: Solid sentinel behavior coverageCovers truthiness, identity, repr, control-flow, and list filtering. Looks good.
src/backend/base/langflow/services/database/models/flow/model.py (1)
74-86: Naming risk: status field may collide with existing frontend usageBackend status holds deployment state, but some UI paths (e.g., ToolsTable) already use status as a boolean for selection. This leads to breakage/confusion. Consider either:
- Renaming the API field to deployment_status (with alias="status" if keeping wire format), or
- Coordinating FE to use a distinct selected flag and a separate deploymentStatus field.
Please verify all FE and API consumers are updated accordingly; the current UI shows a concrete collision.
src/backend/base/langflow/services/utils.py (1)
302-307: Old endpoint aliases remain cached after updates
Right beforerefresh_flow_in_cache/remove_flow_from_cacheyou overwritedb_flow.endpoint_name. When the endpoint changes, the cache eviction only sees the new name, so the previous endpoint key stays live and still returns the old graph. Capture the prior endpoint value and pass it (see proposedextra_keys) so both old and new aliases are purged.Example:
- for key, value in update_data.items(): - setattr(db_flow, key, value) + previous_endpoint = db_flow.endpoint_name + for key, value in update_data.items(): + setattr(db_flow, key, value) ... - await flow_cache_service.refresh_flow_in_cache(db_flow) + await flow_cache_service.refresh_flow_in_cache( + db_flow, extra_keys=[previous_endpoint] + ) ... - await flow_cache_service.remove_flow_from_cache(db_flow) + await flow_cache_service.remove_flow_from_cache( + db_flow, extra_keys=[previous_endpoint] + )Coordinate with the updated cache service to accept the
extra_keys.Likely an incorrect or invalid review comment.
| stmt = ( | ||
| select(models.Flow) | ||
| .join(models.Folder) | ||
| .where( | ||
| models.Flow.user_id == None, # noqa: E711 | ||
| models.Folder.name != STARTER_FOLDER_NAME, | ||
| ) | ||
| orphaned_flows = (await session.exec(stmt)).all() | ||
| ) | ||
| orphaned_flows = (await session.exec(stmt)).all() | ||
|
|
||
| if not orphaned_flows: | ||
| return | ||
| if not orphaned_flows: | ||
| return | ||
|
|
||
| await logger.adebug("Assigning orphaned flows to the default superuser") | ||
| await logger.adebug("Assigning orphaned flows to the default superuser") | ||
|
|
||
| # Retrieve superuser | ||
| superuser_username = settings_service.auth_settings.SUPERUSER | ||
| superuser = await get_user_by_username(session, superuser_username) | ||
| # Retrieve superuser | ||
| superuser_username = settings_service.auth_settings.SUPERUSER | ||
| superuser = await get_user_by_username(session, superuser_username) | ||
|
|
||
| if not superuser: | ||
| error_message = "Default superuser not found" | ||
| await logger.aerror(error_message) | ||
| raise RuntimeError(error_message) | ||
| if not superuser: | ||
| error_message = "Default superuser not found" | ||
| await logger.aerror(error_message) | ||
| raise RuntimeError(error_message) | ||
|
|
||
| # Get existing flow names for the superuser | ||
| existing_names: set[str] = set( | ||
| (await session.exec(select(models.Flow.name).where(models.Flow.user_id == superuser.id))).all() | ||
| ) | ||
| # Get existing flow names for the superuser | ||
| existing_names: set[str] = set( | ||
| (await session.exec(select(models.Flow.name).where(models.Flow.user_id == superuser.id))).all() | ||
| ) | ||
|
|
||
| # Process orphaned flows | ||
| for flow in orphaned_flows: | ||
| flow.user_id = superuser.id | ||
| flow.name = self._generate_unique_flow_name(flow.name, existing_names) | ||
| existing_names.add(flow.name) | ||
| session.add(flow) | ||
| # Process orphaned flows | ||
| for flow in orphaned_flows: | ||
| flow.user_id = superuser.id | ||
| flow.name = self._generate_unique_flow_name(flow.name, existing_names) | ||
| existing_names.add(flow.name) | ||
| session.add(flow) | ||
|
|
||
| # Commit changes | ||
| await session.commit() | ||
| await logger.adebug("Successfully assigned orphaned flows to the default superuser") | ||
| # Commit changes | ||
| await session.commit() | ||
| await logger.adebug("Successfully assigned orphaned flows to the default superuser") |
There was a problem hiding this comment.
Restore orphaned-flow handling for flows without folders
Switching to an inner join on models.Folder means any orphaned flow whose folder_id is NULL is now filtered out, so we never reassign it to the superuser. That regresses the original behavior (these flows remain orphaned). Update the query to keep folder-less flows—use an outer join and guard the starter-folder check accordingly.
- stmt = (
- select(models.Flow)
- .join(models.Folder)
- .where(
- models.Flow.user_id == None, # noqa: E711
- models.Folder.name != STARTER_FOLDER_NAME,
- )
- )
+ stmt = (
+ select(models.Flow)
+ .join(models.Folder, isouter=True)
+ .where(
+ models.Flow.user_id == None, # noqa: E711
+ sa.or_(
+ models.Folder.name != STARTER_FOLDER_NAME,
+ models.Folder.id == None, # noqa: E711
+ ),
+ )
+ )📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| stmt = ( | |
| select(models.Flow) | |
| .join(models.Folder) | |
| .where( | |
| models.Flow.user_id == None, # noqa: E711 | |
| models.Folder.name != STARTER_FOLDER_NAME, | |
| ) | |
| orphaned_flows = (await session.exec(stmt)).all() | |
| ) | |
| orphaned_flows = (await session.exec(stmt)).all() | |
| if not orphaned_flows: | |
| return | |
| if not orphaned_flows: | |
| return | |
| await logger.adebug("Assigning orphaned flows to the default superuser") | |
| await logger.adebug("Assigning orphaned flows to the default superuser") | |
| # Retrieve superuser | |
| superuser_username = settings_service.auth_settings.SUPERUSER | |
| superuser = await get_user_by_username(session, superuser_username) | |
| # Retrieve superuser | |
| superuser_username = settings_service.auth_settings.SUPERUSER | |
| superuser = await get_user_by_username(session, superuser_username) | |
| if not superuser: | |
| error_message = "Default superuser not found" | |
| await logger.aerror(error_message) | |
| raise RuntimeError(error_message) | |
| if not superuser: | |
| error_message = "Default superuser not found" | |
| await logger.aerror(error_message) | |
| raise RuntimeError(error_message) | |
| # Get existing flow names for the superuser | |
| existing_names: set[str] = set( | |
| (await session.exec(select(models.Flow.name).where(models.Flow.user_id == superuser.id))).all() | |
| ) | |
| # Get existing flow names for the superuser | |
| existing_names: set[str] = set( | |
| (await session.exec(select(models.Flow.name).where(models.Flow.user_id == superuser.id))).all() | |
| ) | |
| # Process orphaned flows | |
| for flow in orphaned_flows: | |
| flow.user_id = superuser.id | |
| flow.name = self._generate_unique_flow_name(flow.name, existing_names) | |
| existing_names.add(flow.name) | |
| session.add(flow) | |
| # Process orphaned flows | |
| for flow in orphaned_flows: | |
| flow.user_id = superuser.id | |
| flow.name = self._generate_unique_flow_name(flow.name, existing_names) | |
| existing_names.add(flow.name) | |
| session.add(flow) | |
| # Commit changes | |
| await session.commit() | |
| await logger.adebug("Successfully assigned orphaned flows to the default superuser") | |
| # Commit changes | |
| await session.commit() | |
| await logger.adebug("Successfully assigned orphaned flows to the default superuser") | |
| stmt = ( | |
| select(models.Flow) | |
| .join(models.Folder, isouter=True) | |
| .where( | |
| models.Flow.user_id == None, # noqa: E711 | |
| sa.or_( | |
| models.Folder.name != STARTER_FOLDER_NAME, | |
| models.Folder.id == None, # noqa: E711 | |
| ), | |
| ) | |
| ) |
🤖 Prompt for AI Agents
In src/backend/base/langflow/services/database/service.py around lines 208-246,
the query uses an inner join to models.Folder which excludes flows with
folder_id NULL; change the join to an outer/left join and change the folder
filter so it does not exclude NULL folders (e.g. check that Folder.name !=
STARTER_FOLDER_NAME OR Folder.id IS NULL) while keeping the existing user_id IS
NULL predicate; then run the query, process the returned orphaned flows as
before and commit.
| flow_id_str = str(flow.id) | ||
| try: | ||
| await self.delete(flow_id_str) | ||
| if not silent: | ||
| logger.debug(f"Removed flow {flow_id_str} from cache") | ||
| except KeyError as e: | ||
| if not silent: | ||
| logger.error(f"Cache key not found when removing flow {flow_id_str}: {e!s}") | ||
| except RuntimeError as e: | ||
| logger.error(f"Error removing flow {flow_id_str} from cache: {e!s}") |
There was a problem hiding this comment.
Endpoint aliases linger in cache after undeploy/rename
We cache flows under both UUID and endpoint_name, but remove_flow_from_cache only deletes the UUID. When a flow is undeployed or its endpoint is changed, the stale endpoint key keeps serving the old graph, so GET /run/{endpoint} still executes a supposedly DRAFT flow. Please delete every alias (current endpoint plus any extra keys you pass in) when evicting.
One way to address it:
- async def remove_flow_from_cache(self, flow: Flow, *, silent: bool = False) -> None:
+ async def remove_flow_from_cache(
+ self,
+ flow: Flow,
+ *,
+ silent: bool = False,
+ extra_keys: Iterable[str] | None = None,
+ ) -> None:
flow_id_str = str(flow.id)
- try:
- await self.delete(flow_id_str)
+ keys_to_purge: set[str] = {flow_id_str}
+ if flow.endpoint_name:
+ keys_to_purge.add(flow.endpoint_name)
+ if extra_keys:
+ keys_to_purge.update(k for k in extra_keys if k)
+ try:
+ for key in keys_to_purge:
+ await self.delete(key)Callers (e.g., in update_flow) should pass the previous endpoint name so old aliases disappear.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| flow_id_str = str(flow.id) | |
| try: | |
| await self.delete(flow_id_str) | |
| if not silent: | |
| logger.debug(f"Removed flow {flow_id_str} from cache") | |
| except KeyError as e: | |
| if not silent: | |
| logger.error(f"Cache key not found when removing flow {flow_id_str}: {e!s}") | |
| except RuntimeError as e: | |
| logger.error(f"Error removing flow {flow_id_str} from cache: {e!s}") | |
| async def remove_flow_from_cache( | |
| self, | |
| flow: Flow, | |
| *, | |
| silent: bool = False, | |
| extra_keys: Iterable[str] | None = None, | |
| ) -> None: | |
| flow_id_str = str(flow.id) | |
| keys_to_purge: set[str] = {flow_id_str} | |
| if flow.endpoint_name: | |
| keys_to_purge.add(flow.endpoint_name) | |
| if extra_keys: | |
| keys_to_purge.update(k for k in extra_keys if k) | |
| try: | |
| for key in keys_to_purge: | |
| await self.delete(key) | |
| if not silent: | |
| logger.debug(f"Removed flow {flow_id_str} from cache") | |
| except KeyError as e: | |
| if not silent: | |
| logger.error(f"Cache key not found when removing flow {flow_id_str}: {e!s}") | |
| except RuntimeError as e: | |
| logger.error(f"Error removing flow {flow_id_str} from cache: {e!s}") |
🤖 Prompt for AI Agents
In src/backend/base/langflow/services/flow_cache/service.py around lines 68 to
77, remove_flow_from_cache currently deletes only the UUID key so endpoint-name
aliases linger; update the method to accept an optional list (or varargs) of
alias keys (e.g., current endpoint and previous endpoint names) and iterate over
the UUID plus every alias, attempting to delete each key from the cache and
logging per-key success/error (preserving silent behavior). Update callers (such
as update_flow) to pass the previous endpoint name when renaming/undeploying so
old endpoint aliases are included and removed.
| cached = await self.get(flow_id) | ||
| # Check for cache miss sentinel | ||
| if not cached: | ||
| return None | ||
| # Return a deep copy to prevent concurrent requests from sharing mutable state | ||
| return deepcopy(cached) | ||
|
|
There was a problem hiding this comment.
Cache miss check misclassifies valid graphs
if not cached treats any falsy Graph instance as a miss. Graphs with no vertices (e.g., an empty {"nodes": [], "edges": []}) evaluate to False, so cached deployments of empty flows are dropped and the caller gets None. Explicitly compare against the cache miss sentinel instead of relying on truthiness.
Apply this diff:
- if not cached:
+ if cached is None or cached is CACHE_MISS:
return None(Import CACHE_MISS from langflow.services.cache.utils at the top if it’s not already available.)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| cached = await self.get(flow_id) | |
| # Check for cache miss sentinel | |
| if not cached: | |
| return None | |
| # Return a deep copy to prevent concurrent requests from sharing mutable state | |
| return deepcopy(cached) | |
| cached = await self.get(flow_id) | |
| # Check for cache miss sentinel | |
| if cached is None or cached is CACHE_MISS: | |
| return None | |
| # Return a deep copy to prevent concurrent requests from sharing mutable state | |
| return deepcopy(cached) |
🤖 Prompt for AI Agents
In src/backend/base/langflow/services/flow_cache/service.py around lines 91-97,
the code uses a truthiness check (if not cached) which misclassifies valid falsy
Graph instances (e.g., empty {"nodes": [], "edges": []}) as cache misses;
replace that check with an explicit sentinel comparison (if cached is
CACHE_MISS) and keep the rest of the logic the same, and add an import for
CACHE_MISS from langflow.services.cache.utils at the top of the file if it isn’t
already present.
| <Switch | ||
| data-testid="publish-switch" | ||
| className="scale-[85%]" | ||
| checked={isPublished} | ||
| disabled={!hasIO} | ||
| onClick={(e) => { | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| handlePublishedSwitch(isPublished); | ||
| }} | ||
| /> | ||
| </div> | ||
| </DropdownMenuItem> | ||
|
|
||
| {isPublished ? ( | ||
| <CustomLink | ||
| className="flex-1" | ||
| to={`/playground/${flowId}`} | ||
| target="_blank" | ||
| > | ||
| <span>Shareable Playground</span> | ||
| </CustomLink> | ||
| ) : ( | ||
| <span className={cn(!isPublished && "opacity-50")}> | ||
| Shareable Playground | ||
| <DropdownMenuItem | ||
| className="deploy-dropdown-item group" | ||
| onClick={() => {}} | ||
| data-testid="deployed-status" | ||
| > | ||
| <div className="flex w-full items-center justify-between"> | ||
| <div className="flex items-center"> | ||
| <ShadTooltipComponent | ||
| styleClasses="truncate" | ||
| side="left" | ||
| content={ | ||
| isDeployed | ||
| ? "Deployed and ready to use" | ||
| : "Deploy to make this flow available via API" | ||
| } | ||
| > | ||
| <div className="flex items-center"> | ||
| <IconComponent | ||
| name="Rocket" | ||
| className={cn( | ||
| `icon-size mr-2`, | ||
| !isDeployed && "opacity-50", | ||
| )} | ||
| /> | ||
| <span className={cn(!isDeployed && "opacity-50")}> | ||
| Deploy Flow | ||
| </span> | ||
| )} | ||
| </div> | ||
| </ShadTooltipComponent> | ||
| </div> | ||
| </ShadTooltipComponent> | ||
| </div> | ||
| <Switch | ||
| data-testid="deploy-switch" | ||
| className="scale-[85%]" | ||
| checked={isDeployed} | ||
| onClick={(e) => { | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| handleDeployedSwitch(!isDeployed); | ||
| }} | ||
| /> |
There was a problem hiding this comment.
Fix deploy/publish toggles to use the next checked state
Both switches derive the next status from isPublished/isDeployed captured at render time. If the user clicks twice before the store re-renders (e.g., disable and immediately re-enable), we keep sending the old value and leave the flow in the wrong state (confirmed when starting in DEPLOYED, clicking to draft, then immediately clicking again — we ship DEPLOYED → DRAFT twice and the flow stays drafted). Read the final checked value from the switch instead of recomputing from stale props.
- const handlePublishedSwitch = async (checked: boolean) => {
- await handleFlowUpdate({
- access_type: checked ? ACCESS_TYPE.PRIVATE : ACCESS_TYPE.PUBLIC,
- });
- };
+ const handlePublishedSwitch = async (checked: boolean) => {
+ await handleFlowUpdate({
+ access_type: checked ? ACCESS_TYPE.PUBLIC : ACCESS_TYPE.PRIVATE,
+ });
+ };
…
- <Switch
+ <Switch
data-testid="publish-switch"
className="scale-[85%]"
checked={isPublished}
disabled={!hasIO}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
- handlePublishedSwitch(isPublished);
}}
+ onCheckedChange={(checked) => {
+ void handlePublishedSwitch(checked);
+ }}
/>
…
- <Switch
+ <Switch
data-testid="deploy-switch"
className="scale-[85%]"
checked={isDeployed}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
- handleDeployedSwitch(!isDeployed);
}}
+ onCheckedChange={(checked) => {
+ void handleDeployedSwitch(checked);
+ }}
/>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <Switch | |
| data-testid="publish-switch" | |
| className="scale-[85%]" | |
| checked={isPublished} | |
| disabled={!hasIO} | |
| onClick={(e) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| handlePublishedSwitch(isPublished); | |
| }} | |
| /> | |
| </div> | |
| </DropdownMenuItem> | |
| {isPublished ? ( | |
| <CustomLink | |
| className="flex-1" | |
| to={`/playground/${flowId}`} | |
| target="_blank" | |
| > | |
| <span>Shareable Playground</span> | |
| </CustomLink> | |
| ) : ( | |
| <span className={cn(!isPublished && "opacity-50")}> | |
| Shareable Playground | |
| <DropdownMenuItem | |
| className="deploy-dropdown-item group" | |
| onClick={() => {}} | |
| data-testid="deployed-status" | |
| > | |
| <div className="flex w-full items-center justify-between"> | |
| <div className="flex items-center"> | |
| <ShadTooltipComponent | |
| styleClasses="truncate" | |
| side="left" | |
| content={ | |
| isDeployed | |
| ? "Deployed and ready to use" | |
| : "Deploy to make this flow available via API" | |
| } | |
| > | |
| <div className="flex items-center"> | |
| <IconComponent | |
| name="Rocket" | |
| className={cn( | |
| `icon-size mr-2`, | |
| !isDeployed && "opacity-50", | |
| )} | |
| /> | |
| <span className={cn(!isDeployed && "opacity-50")}> | |
| Deploy Flow | |
| </span> | |
| )} | |
| </div> | |
| </ShadTooltipComponent> | |
| </div> | |
| </ShadTooltipComponent> | |
| </div> | |
| <Switch | |
| data-testid="deploy-switch" | |
| className="scale-[85%]" | |
| checked={isDeployed} | |
| onClick={(e) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| handleDeployedSwitch(!isDeployed); | |
| }} | |
| /> | |
| // … elsewhere in this component … | |
| // Update the publish handler to use the new checked state | |
| const handlePublishedSwitch = async (checked: boolean) => { | |
| await handleFlowUpdate({ | |
| access_type: checked ? ACCESS_TYPE.PUBLIC : ACCESS_TYPE.PRIVATE, | |
| }); | |
| }; | |
| return ( | |
| <> | |
| {/* … */} | |
| <DropdownMenuItem className="deploy-dropdown-item group"> | |
| <div className="flex w-full items-center justify-between"> | |
| {/* … */} | |
| <Switch | |
| data-testid="publish-switch" | |
| className="scale-[85%]" | |
| checked={isPublished} | |
| disabled={!hasIO} | |
| onCheckedChange={(checked) => { | |
| void handlePublishedSwitch(checked); | |
| }} | |
| /> | |
| </div> | |
| </DropdownMenuItem> | |
| {/* … */} | |
| <DropdownMenuItem | |
| className="deploy-dropdown-item group" | |
| onClick={() => {}} | |
| data-testid="deployed-status" | |
| > | |
| <div className="flex w-full items-center justify-between"> | |
| {/* … */} | |
| <Switch | |
| data-testid="deploy-switch" | |
| className="scale-[85%]" | |
| checked={isDeployed} | |
| onCheckedChange={(checked) => { | |
| void handleDeployedSwitch(checked); | |
| }} | |
| /> | |
| </div> | |
| </DropdownMenuItem> | |
| {/* … */} | |
| </> | |
| ); |
🤖 Prompt for AI Agents
In
src/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsx
around lines 210 to 263, the onClick handlers read the next state from the stale
isPublished/isDeployed props; change them to read the actual toggled value from
the switch element (use event.currentTarget.checked or event.target.checked) and
pass that boolean into handlePublishedSwitch/handleDeployedSwitch so rapid
double-clicks use the real new checked state instead of the captured prop value.
| it("displays deployment status for deployed flow", () => { | ||
| const { container } = render( | ||
| <ToolsTable | ||
| rows={[mockDeployedFlow]} | ||
| data={[mockDeployedFlow]} | ||
| setData={jest.fn()} | ||
| isAction={true} | ||
| placeholder="Search" | ||
| open={true} | ||
| handleOnNewValue={jest.fn()} | ||
| />, | ||
| ); | ||
|
|
||
| // The component uses ag-grid which needs special handling | ||
| // Just verify it renders without crashing | ||
| expect(container).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it("displays deployment status for draft flow", () => { | ||
| const { container } = render( | ||
| <ToolsTable | ||
| rows={[mockDraftFlow]} | ||
| data={[mockDraftFlow]} | ||
| setData={jest.fn()} | ||
| isAction={true} | ||
| placeholder="Search" | ||
| open={true} | ||
| handleOnNewValue={jest.fn()} | ||
| />, | ||
| ); | ||
|
|
||
| expect(container).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it("includes status field in flow data", () => { | ||
| const mockSetData = jest.fn(); | ||
| render( | ||
| <ToolsTable | ||
| rows={[mockDeployedFlow, mockDraftFlow]} | ||
| data={[mockDeployedFlow, mockDraftFlow]} | ||
| setData={mockSetData} | ||
| isAction={true} | ||
| placeholder="Search" | ||
| open={true} | ||
| handleOnNewValue={jest.fn()} | ||
| />, | ||
| ); | ||
|
|
||
| // Verify the component can handle flows with status field | ||
| expect(mockDeployedFlow.status).toBe("DEPLOYED"); | ||
| expect(mockDraftFlow.status).toBe("DRAFT"); | ||
| }); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Tests lack behavioral verification.
The current tests are smoke tests that only verify the component renders without crashing. They don't validate the actual deployment toggle behavior, UI state changes, or error handling.
Suggested additions:
- Test that clicking the deploy toggle calls
mutateAsyncwith correct parameters - Verify UI updates after successful deployment status change
- Test error handling when deployment toggle fails
- Verify the deployment status indicator displays correctly
Example test structure:
it("calls mutateAsync when deploy toggle is clicked", async () => {
const user = userEvent.setup();
render(<ToolsTable {...mockProps} />);
const deployToggle = screen.getByRole("switch");
await user.click(deployToggle);
expect(mockMutateAsync).toHaveBeenCalledWith({
id: "test-flow-id",
status: "DEPLOYED"
});
});As per coding guidelines: Tests must validate behavior and cover error conditions, not just render without crashing.
🤖 Prompt for AI Agents
In
src/frontend/src/modals/toolsModal/components/toolsTable/__tests__/deployment-status.test.tsx
around lines 65 to 116 the tests are only smoke tests and do not assert the
deploy toggle behavior; add behavioral tests that (1) render ToolsTable with a
mocked mutation hook returning a jest.fn() for mutateAsync, use userEvent to
click the deploy switch (get by role "switch" or a test id), and assert
mutateAsync was called with the expected payload (id and new status), (2) mock
mutateAsync to resolve and assert the UI updates to show the new status (use
waitFor/screen assertions to verify the status indicator text/class changes),
and (3) mock mutateAsync to reject and assert error handling path (an error
toast/message is shown and the UI remains in prior state); ensure to cleanup
mocks and use await/async and proper waitFor expectations to avoid flakiness.
| const handleDeployToggle = async (flowId: string, currentStatus: string) => { | ||
| const newStatus = | ||
| currentStatus === DEPLOYMENT_STATUS.DEPLOYED | ||
| ? DEPLOYMENT_STATUS.DRAFT | ||
| : DEPLOYMENT_STATUS.DEPLOYED; | ||
| try { | ||
| await mutateAsync({ | ||
| id: flowId, | ||
| status: newStatus, | ||
| }); | ||
| // Update the focused row to reflect the change | ||
| if (focusedRow && focusedRow.id === flowId) { | ||
| setFocusedRow({ ...focusedRow, status: newStatus }); | ||
| } | ||
| // Also update the data array | ||
| setData( | ||
| data.map((row) => | ||
| row.id === flowId ? { ...row, status: newStatus } : row, | ||
| ), | ||
| ); | ||
| } catch (error: any) { | ||
| setErrorData({ | ||
| title: "Failed to update deployment status", | ||
| list: [error.message], | ||
| }); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Critical: status collision (boolean selection vs deployment state) + stale setData + error handling
status is already used elsewhere here as a boolean (selection). This new usage sets it to a string (DRAFT/DEPLOYED), causing logic breakage (e.g., Line 94 and 101 filters, Lines 154–170 mapping). Store deployment state under a separate key (e.g., deploymentStatus) and use functional setState. Also extract error messages robustly.
Apply within this block:
- const handleDeployToggle = async (flowId: string, currentStatus: string) => {
+ const handleDeployToggle = async (flowId: string, currentStatus: string) => {
const newStatus =
currentStatus === DEPLOYMENT_STATUS.DEPLOYED
? DEPLOYMENT_STATUS.DRAFT
: DEPLOYMENT_STATUS.DEPLOYED;
try {
await mutateAsync({
id: flowId,
status: newStatus,
});
// Update the focused row to reflect the change
- if (focusedRow && focusedRow.id === flowId) {
- setFocusedRow({ ...focusedRow, status: newStatus });
- }
+ if (focusedRow && focusedRow.id === flowId) {
+ setFocusedRow({ ...focusedRow, deploymentStatus: newStatus });
+ }
// Also update the data array
- setData(
- data.map((row) =>
- row.id === flowId ? { ...row, status: newStatus } : row,
- ),
- );
- } catch (error: any) {
- setErrorData({
- title: "Failed to update deployment status",
- list: [error.message],
- });
+ setData((prev: any[]) =>
+ prev.map((row) =>
+ row.id === flowId ? { ...row, deploymentStatus: newStatus } : row,
+ ),
+ );
+ } catch (error: unknown) {
+ const message =
+ (error as any)?.response?.data?.detail ||
+ (error as Error)?.message ||
+ "Unknown error";
+ setErrorData({
+ title: "Failed to update deployment status",
+ list: [message],
+ });
}
};Also update other usages in this component to use deploymentStatus for deployment state and a separate selected boolean for selection (see snippet in another comment).
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handleDeployToggle = async (flowId: string, currentStatus: string) => { | |
| const newStatus = | |
| currentStatus === DEPLOYMENT_STATUS.DEPLOYED | |
| ? DEPLOYMENT_STATUS.DRAFT | |
| : DEPLOYMENT_STATUS.DEPLOYED; | |
| try { | |
| await mutateAsync({ | |
| id: flowId, | |
| status: newStatus, | |
| }); | |
| // Update the focused row to reflect the change | |
| if (focusedRow && focusedRow.id === flowId) { | |
| setFocusedRow({ ...focusedRow, status: newStatus }); | |
| } | |
| // Also update the data array | |
| setData( | |
| data.map((row) => | |
| row.id === flowId ? { ...row, status: newStatus } : row, | |
| ), | |
| ); | |
| } catch (error: any) { | |
| setErrorData({ | |
| title: "Failed to update deployment status", | |
| list: [error.message], | |
| }); | |
| } | |
| }; | |
| const handleDeployToggle = async (flowId: string, currentStatus: string) => { | |
| const newStatus = | |
| currentStatus === DEPLOYMENT_STATUS.DEPLOYED | |
| ? DEPLOYMENT_STATUS.DRAFT | |
| : DEPLOYMENT_STATUS.DEPLOYED; | |
| try { | |
| await mutateAsync({ | |
| id: flowId, | |
| status: newStatus, | |
| }); | |
| // Update the focused row to reflect the change | |
| if (focusedRow && focusedRow.id === flowId) { | |
| setFocusedRow({ ...focusedRow, deploymentStatus: newStatus }); | |
| } | |
| // Also update the data array | |
| setData((prev: any[]) => | |
| prev.map((row) => | |
| row.id === flowId ? { ...row, deploymentStatus: newStatus } : row, | |
| ), | |
| ); | |
| } catch (error: unknown) { | |
| const message = | |
| (error as any)?.response?.data?.detail || | |
| (error as Error)?.message || | |
| "Unknown error"; | |
| setErrorData({ | |
| title: "Failed to update deployment status", | |
| list: [message], | |
| }); | |
| } | |
| }; |
| {isAction && focusedRow?.status !== undefined && ( | ||
| <div className="flex flex-col gap-2"> | ||
| <div className="flex items-center justify-between"> | ||
| <div className="flex items-center gap-2"> | ||
| <ForwardedIconComponent | ||
| name="Rocket" | ||
| className={cn( | ||
| "h-4 w-4", | ||
| focusedRow.status === DEPLOYMENT_STATUS.DEPLOYED | ||
| ? "text-success" | ||
| : "text-muted-foreground opacity-50", | ||
| )} | ||
| /> | ||
| <span className="text-mmd font-medium"> | ||
| Deploy Flow | ||
| </span> | ||
| </div> | ||
| <Switch | ||
| checked={ | ||
| focusedRow.status === DEPLOYMENT_STATUS.DEPLOYED | ||
| } | ||
| aria-label="Deploy Flow" | ||
| onClick={(e) => { | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| handleDeployToggle( | ||
| focusedRow.id, | ||
| focusedRow.status || DEPLOYMENT_STATUS.DRAFT, | ||
| ); | ||
| }} | ||
| /> | ||
| </div> | ||
| <div className="text-xs text-muted-foreground"> | ||
| {focusedRow.status === DEPLOYMENT_STATUS.DEPLOYED | ||
| ? "Deployed and cached for optimal performance" | ||
| : "Deploy to cache this flow for faster execution"} | ||
| </div> | ||
| </div> | ||
| )} |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Use deploymentStatus and onCheckedChange for Switch
Prevent collision with selection status and use the checked boolean directly from the Switch handler.
- {isAction && focusedRow?.status !== undefined && (
+ {isAction && (focusedRow?.deploymentStatus ?? focusedRow?.status) !== undefined && (
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<ForwardedIconComponent
name="Rocket"
className={cn(
"h-4 w-4",
- focusedRow.status === DEPLOYMENT_STATUS.DEPLOYED
+ (focusedRow.deploymentStatus ?? focusedRow.status) === DEPLOYMENT_STATUS.DEPLOYED
? "text-success"
: "text-muted-foreground opacity-50",
)}
/>
<span className="text-mmd font-medium">
Deploy Flow
</span>
</div>
<Switch
- checked={
- focusedRow.status === DEPLOYMENT_STATUS.DEPLOYED
- }
+ checked={(focusedRow.deploymentStatus ?? focusedRow.status) === DEPLOYMENT_STATUS.DEPLOYED}
aria-label="Deploy Flow"
- onClick={(e) => {
- e.preventDefault();
- e.stopPropagation();
- handleDeployToggle(
- focusedRow.id,
- focusedRow.status || DEPLOYMENT_STATUS.DRAFT,
- );
- }}
+ onCheckedChange={(checked) => {
+ handleDeployToggle(
+ focusedRow.id,
+ checked ? DEPLOYMENT_STATUS.DEPLOYED : DEPLOYMENT_STATUS.DRAFT,
+ );
+ }}
/>
</div>
<div className="text-xs text-muted-foreground">
- {focusedRow.status === DEPLOYMENT_STATUS.DEPLOYED
+ {(focusedRow.deploymentStatus ?? focusedRow.status) === DEPLOYMENT_STATUS.DEPLOYED
? "Deployed and cached for optimal performance"
: "Deploy to cache this flow for faster execution"}
</div>
</div>
)}Outside these ranges, update selection logic to use a separate selected boolean to avoid clobbering the deployment status:
// On mount
const initialData = cloneDeep(rows).map((r) => ({
...r,
deploymentStatus:
typeof r.status === "string"
? r.status
: r.deploymentStatus ?? DEPLOYMENT_STATUS.DRAFT,
selected:
typeof r.status === "boolean" ? r.status : r.selected ?? false,
}));
setData(initialData);
// Filters
const filter = initialData.filter((row) => row.selected === true);
// In applyInitialSelection
const filter = cloneDeep(rows).filter((row) => row.selected === true);
// When emitting value on close, keep original contract (status as selection boolean)
// but avoid mutating internal deploymentStatus in data state.
status: selectedRows?.some(...) ? true : false🤖 Prompt for AI Agents
In src/frontend/src/modals/toolsModal/components/toolsTable/index.tsx around
lines 397-435, the Switch is currently using the row's status field and onClick
which collides with selection semantics; change the component and state usage to
use a separate deploymentStatus field for deployment state and a selected
boolean for selection, use the Switch's onCheckedChange (or onChange boolean
handler) instead of onClick, bind checked to row.deploymentStatus ===
DEPLOYMENT_STATUS.DEPLOYED, update the handler to accept the boolean checked and
set deploymentStatus accordingly (without mutating the original
status/selection), and update initialization, filters, applyInitialSelection and
the final emitted payload so internal data uses deploymentStatus/selected while
keeping the external contract that status is a selection boolean when emitting
on close.
| if (not outputs and vertex.is_output) or (vertex.display_name in outputs or vertex.id in outputs): | ||
| vertex_outputs.append(vertex.result) | ||
|
|
||
| self._reset_components_in_vertices() |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
Verify placement of component reset in execution flow.
The _reset_components_in_vertices() call is positioned after collecting outputs but before returning. This looks correct for cleanup purposes, but verify that:
- The reset doesn't affect the collected
vertex_outputs(likely fine since outputs are collected first) - This is the right lifecycle point for all execution paths (normal completion, errors, etc.)
🏁 Script executed:
#!/bin/bash
# Check if there are other return paths or error handlers that should also call reset
rg -n -C5 'return vertex_outputs' --type=pyLength of output: 1752
Add missing component reset to arun return path
- Insert
self._reset_components_in_vertices()before thereturn vertex_outputsat line 877 inasync def arunto ensure cleanup runs on all execution paths. - Consider wrapping the method body in a
try/finallyso reset occurs even on errors.
🤖 Prompt for AI Agents
In src/lfx/src/lfx/graph/graph/base.py at lines 812-877, the async def arun path
is missing a call to self._reset_components_in_vertices() on the normal return
path and should ensure cleanup on exceptions; add
self._reset_components_in_vertices() immediately before the return
vertex_outputs at line ~877 and refactor the method body to use a try/finally
where the finally block calls self._reset_components_in_vertices() so the reset
runs on both successful returns and on errors.
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/backend/base/langflow/api/v1/endpoints.py (1)
146-177: Fix cachedGraphhandling insimple_run_flow.
simplified_run_flownow injects a cachedGraph(fromget_flow_by_id_or_endpoint_name_from_cache), butsimple_run_flowstill assumes a Flow/FlowRead object and accessesflow.id,flow.name, andflow.data. For cached requests this explodes withAttributeErrorbefore execution, breaking the new cache path entirely. Please branch onisinstance(flow, Graph)(or adjust the dependency to always supply a Flow-like object) so the cached Graph can run without touching Flow-only attributes. Until this is fixed, every cache hit will 500.
♻️ Duplicate comments (2)
src/frontend/src/types/mcp/index.ts (1)
23-23: Duplicate: Extract status type to shared constant.Same as the comment on
src/frontend/src/controllers/API/queries/flows/use-patch-update-flow.ts, this inline"DRAFT" | "DEPLOYED"type should be extracted to a shared constant or type definition to avoid duplication and ensure consistency.src/frontend/src/types/flow/index.ts (1)
36-36: Duplicate: Extract status type to shared constant.Same as previous comments on
use-patch-update-flow.tsandmcp/index.ts. This is the third instance of the inline"DRAFT" | "DEPLOYED"type. Consolidating these into a shared constant is increasingly important.
🧹 Nitpick comments (13)
src/frontend/src/controllers/API/queries/flows/use-patch-update-flow.ts (1)
17-17: Consider extracting the status type to a shared constant.The
"DRAFT" | "DEPLOYED"type is duplicated across multiple files (this file,src/frontend/src/types/mcp/index.ts, andsrc/frontend/src/types/flow/index.ts). Extract it to a shared constant to improve maintainability and ensure consistency.For example, create a constant in a shared types file:
// src/frontend/src/constants/flows.ts or types file export const DEPLOYMENT_STATUS = { DRAFT: "DRAFT", DEPLOYED: "DEPLOYED", } as const; export type DeploymentStatus = typeof DEPLOYMENT_STATUS[keyof typeof DEPLOYMENT_STATUS];Then use it as:
interface IPatchUpdateFlow { id: string; name?: string; data?: ReactFlowJsonObject; description?: string; folder_id?: string | null | undefined; endpoint_name?: string | null | undefined; locked?: boolean | null | undefined; access_type?: "PUBLIC" | "PRIVATE" | "PROTECTED"; - status?: "DRAFT" | "DEPLOYED"; + status?: DeploymentStatus; }src/backend/base/langflow/services/flow_cache/service.py (1)
123-178: Memory calculation is approximate.The
get_cache_statsmethod usessys.getsizeofwhich provides only shallow size estimates, not deep object sizes. This is acknowledged in the comments (lines 139-141), but consider whether this approximation is sufficient for production monitoring. If precise memory tracking is needed, consider using libraries likepympleror implementing recursive size calculation.src/backend/tests/unit/api/v1/test_mcp_projects.py (1)
685-835: Add teardown for created users/projects/flows; prefer enum over magic strings
- These tests create DB rows without cleanup. Add teardown (or use fixtures with finalizers) to avoid cross-test interference.
- If a backend enum for deployment status exists, use it instead of literal "DRAFT"/"DEPLOYED" to reduce brittleness. Otherwise, keep as-is.
src/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsx (1)
88-99: Use onCheckedChange and pass next state; fix inverted publish logicCurrent toggles work but are inconsistent (publish uses current state, deploy uses next) and rely on onClick. Prefer onCheckedChange with next-state semantics for correctness and accessibility.
Apply:
-const handlePublishedSwitch = async (checked: boolean) => { - await handleFlowUpdate({ - access_type: checked ? ACCESS_TYPE.PRIVATE : ACCESS_TYPE.PUBLIC, - }); -}; +const handlePublishedSwitch = async (checked: boolean) => { + await handleFlowUpdate({ + access_type: checked ? ACCESS_TYPE.PUBLIC : ACCESS_TYPE.PRIVATE, + }); +}; -<Switch +<Switch data-testid="publish-switch" className="scale-[85%]" checked={isPublished} disabled={!hasIO} - onClick={(e) => { - e.preventDefault(); - e.stopPropagation(); - handlePublishedSwitch(isPublished); - }} + onCheckedChange={(checked) => handlePublishedSwitch(checked)} /> -<Switch +<Switch data-testid="deploy-switch" className="scale-[85%]" checked={isDeployed} - onClick={(e) => { - e.preventDefault(); - e.stopPropagation(); - handleDeployedSwitch(!isDeployed); - }} + onCheckedChange={(checked) => handleDeployedSwitch(checked)} />Also applies to: 210-221, 254-263
src/frontend/src/pages/FlowPage/components/PageComponent/MemoizedComponents.tsx (1)
46-62: Optional: extract timeout into a constantReplace magic number 2000 with a named constant (e.g., const LABEL_TIMEOUT_MS = 2000) to centralize behavior and ease testing/tuning.
src/backend/base/langflow/services/database/service.py (2)
233-235: Ensure scalar name extraction (avoid Row objects in set)Selecting a single column may yield Row objects. Use
scalars()to guaranteeset[str].- existing_names: set[str] = set( - (await session.exec(select(models.Flow.name).where(models.Flow.user_id == superuser.id))).all() - ) + result = await session.exec(select(models.Flow.name).where(models.Flow.user_id == superuser.id)) + existing_names: set[str] = set(result.scalars().all())
237-246: Unique endpoint_name constraint may still violate on commitConstraint UniqueConstraint("user_id", "endpoint_name") can still conflict when reassigning. Consider normalizing conflicting endpoint_name (e.g., set to None or generate a unique suffix) similarly to names to avoid IntegrityError.
Option inside the loop before add/commit:
+ # Ensure unique endpoint_name per user as well + if getattr(flow, "endpoint_name", None): + exists = await session.exec( + select(models.Flow.id).where( + models.Flow.user_id == superuser.id, + models.Flow.endpoint_name == flow.endpoint_name, + ) + ) + if exists.first(): + flow.endpoint_name = NoneAlternatively, keep current try/except but log which flows conflicted for later remediation.
src/backend/base/langflow/services/utils.py (1)
299-307: Good sequencing; minor resilience suggestionAssign-orphaned, cleanup, then cache-load is sensible. Consider moving load_flow_cache before commit boundary-sensitive tasks only if you want cache warm to happen even when cleanup fails; otherwise current order is fine.
src/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsx (2)
367-384: The tooltip test doesn’t assert tooltip contentComment says it should show specific text, but the test only checks item presence. Add a hover and assert the tooltip text.
Example:
- // The tooltip should show "Deploy this flow to make it available" for DRAFT status - await waitFor(() => { - const deployedStatus = screen.getByTestId("deployed-status"); - expect(deployedStatus).toBeInTheDocument(); - }); + const item = await screen.findByTestId("deployed-status"); + await userEvent.hover(item); + expect(await screen.findByText(/Deploy to make this flow available via API/i)).toBeInTheDocument();
1-3: Nit: Remove unused importfireEvent is imported but unused.
-import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react";src/lfx/src/lfx/graph/vertex/base.py (1)
379-387: Clear class reference on reset to avoid stale referencesreset_component only nulls the instance. Also null _custom_component_class to fully release references between runs.
def reset_component(self) -> None: - self.custom_component = None + self.custom_component = None + self._custom_component_class = Nonesrc/frontend/tests/core/features/deploy-flow.spec.ts (2)
44-62: Replace arbitrary timeouts with condition-based waits to reduce flakinessMultiple waitForTimeout calls can cause flakes. Prefer waiting on locators or state.
Examples:
- After opening dropdown:
- await page.waitForTimeout(3000); - await page.waitForSelector('[data-testid="deployed-status"]', { timeout: 10000 }); + await page.getByTestId("deployed-status").waitFor({ state: "visible", timeout: 10000 });
- After toggling:
- await page.waitForTimeout(2000); - await expect(page.getByTestId("deploy-switch")).toBeChecked({ checked: true }); + await expect(page.getByTestId("deploy-switch")).toBeChecked({ checked: true });
- Use expect visibility/checked assertions directly; they inherently wait.
Also applies to: 62-76, 86-94
145-165: Lock behavior not assertedThe test doesn’t verify that nodes can’t be moved/edited when deployed. Add a concrete assertion (e.g., drag attempt has no effect or lock icon appears).
Example:
const node = page.locator('[data-testid*="title-Chat Input"]'); const boxBefore = await node.boundingBox(); await node.dragTo(page.getByTestId("rf__wrapper")); // or simulate drag by mouse const boxAfter = await node.boundingBox(); expect(boxAfter).toEqual(boxBefore); // unchanged if locked
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (36)
src/backend/base/langflow/alembic/versions/ea8c52f13171_add_status_column_in_flow.py(1 hunks)src/backend/base/langflow/api/v1/endpoints.py(6 hunks)src/backend/base/langflow/api/v1/flows.py(7 hunks)src/backend/base/langflow/api/v1/mcp_projects.py(2 hunks)src/backend/base/langflow/api/v1/mcp_utils.py(3 hunks)src/backend/base/langflow/api/v1/schemas.py(1 hunks)src/backend/base/langflow/helpers/flow.py(2 hunks)src/backend/base/langflow/interface/initialize/loading.py(1 hunks)src/backend/base/langflow/services/database/models/flow/model.py(3 hunks)src/backend/base/langflow/services/database/models/flow/utils.py(1 hunks)src/backend/base/langflow/services/database/service.py(1 hunks)src/backend/base/langflow/services/deps.py(2 hunks)src/backend/base/langflow/services/flow_cache/factory.py(1 hunks)src/backend/base/langflow/services/flow_cache/service.py(1 hunks)src/backend/base/langflow/services/schema.py(1 hunks)src/backend/base/langflow/services/socket/utils.py(1 hunks)src/backend/base/langflow/services/utils.py(4 hunks)src/backend/tests/unit/api/v1/test_flows.py(3 hunks)src/backend/tests/unit/api/v1/test_mcp_projects.py(3 hunks)src/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsx(1 hunks)src/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsx(4 hunks)src/frontend/src/constants/flows.ts(1 hunks)src/frontend/src/controllers/API/queries/flows/use-patch-update-flow.ts(1 hunks)src/frontend/src/hooks/flows/use-save-flow.ts(2 hunks)src/frontend/src/modals/apiModal/index.tsx(3 hunks)src/frontend/src/modals/toolsModal/components/toolsTable/__tests__/deployment-status.test.tsx(1 hunks)src/frontend/src/modals/toolsModal/components/toolsTable/index.tsx(3 hunks)src/frontend/src/pages/FlowPage/components/PageComponent/MemoizedComponents.tsx(2 hunks)src/frontend/src/types/flow/index.ts(1 hunks)src/frontend/src/types/mcp/index.ts(1 hunks)src/frontend/tests/core/features/deploy-flow.spec.ts(1 hunks)src/frontend/tests/core/features/mcp-deployment.spec.ts(1 hunks)src/lfx/src/lfx/graph/graph/base.py(2 hunks)src/lfx/src/lfx/graph/vertex/base.py(3 hunks)src/lfx/src/lfx/interface/initialize/loading.py(1 hunks)src/lfx/tests/unit/services/cache/test_cache_miss.py(1 hunks)
🧰 Additional context used
📓 Path-based instructions (15)
{src/backend/**/*.py,tests/**/*.py,Makefile}
📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc)
{src/backend/**/*.py,tests/**/*.py,Makefile}: Run make format_backend to format Python code before linting or committing changes
Run make lint to perform linting checks on backend Python code
Files:
src/backend/base/langflow/services/schema.pysrc/backend/base/langflow/services/database/service.pysrc/backend/base/langflow/api/v1/mcp_projects.pysrc/backend/base/langflow/interface/initialize/loading.pysrc/backend/base/langflow/services/database/models/flow/model.pysrc/backend/tests/unit/api/v1/test_mcp_projects.pysrc/backend/base/langflow/api/v1/schemas.pysrc/backend/base/langflow/helpers/flow.pysrc/backend/base/langflow/api/v1/endpoints.pysrc/backend/base/langflow/services/database/models/flow/utils.pysrc/backend/base/langflow/alembic/versions/ea8c52f13171_add_status_column_in_flow.pysrc/backend/base/langflow/services/deps.pysrc/backend/base/langflow/api/v1/flows.pysrc/backend/base/langflow/api/v1/mcp_utils.pysrc/backend/base/langflow/services/flow_cache/factory.pysrc/backend/tests/unit/api/v1/test_flows.pysrc/backend/base/langflow/services/flow_cache/service.pysrc/backend/base/langflow/services/socket/utils.pysrc/backend/base/langflow/services/utils.py
src/frontend/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
src/frontend/src/**/*.{ts,tsx,js,jsx}: All frontend TypeScript and JavaScript code should be located under src/frontend/src/ and organized into components, pages, icons, stores, types, utils, hooks, services, and assets directories as per the specified directory layout.
Use React 18 with TypeScript for all UI components in the frontend.
Format all TypeScript and JavaScript code using the make format_frontend command.
Lint all TypeScript and JavaScript code using the make lint command.
Files:
src/frontend/src/types/mcp/index.tssrc/frontend/src/types/flow/index.tssrc/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsxsrc/frontend/src/controllers/API/queries/flows/use-patch-update-flow.tssrc/frontend/src/modals/toolsModal/components/toolsTable/index.tsxsrc/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsxsrc/frontend/src/constants/flows.tssrc/frontend/src/modals/apiModal/index.tsxsrc/frontend/src/hooks/flows/use-save-flow.tssrc/frontend/src/pages/FlowPage/components/PageComponent/MemoizedComponents.tsxsrc/frontend/src/modals/toolsModal/components/toolsTable/__tests__/deployment-status.test.tsx
src/frontend/src/types/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
All TypeScript type definitions should be placed in the types directory.
Files:
src/frontend/src/types/mcp/index.tssrc/frontend/src/types/flow/index.ts
src/frontend/**/*.@(test|spec).{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)
src/frontend/**/*.@(test|spec).{ts,tsx,js,jsx}: Frontend test files should be located in 'src/frontend/' and use '.test.{ts,tsx,js,jsx}' or '.spec.{ts,tsx,js,jsx}' extensions.
Test both sync and async code paths in frontend test files.
Mock external dependencies appropriately in frontend test files to isolate unit tests from external services.
Test error handling and edge cases in frontend test files.
Validate input/output behavior and test component initialization and configuration in frontend test files.
Each frontend test should have a clear description or comment explaining its purpose, especially for complex setups or mocks.
Files:
src/frontend/tests/core/features/deploy-flow.spec.tssrc/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsxsrc/frontend/tests/core/features/mcp-deployment.spec.tssrc/frontend/src/modals/toolsModal/components/toolsTable/__tests__/deployment-status.test.tsx
**/{test_*.py,*.test.ts,*.test.tsx}
📄 CodeRabbit inference engine (coderabbit-custom-pre-merge-checks-unique-id-file-non-traceable-F7F2B60C-1728-4C9A-8889-4F2235E186CA.txt)
**/{test_*.py,*.test.ts,*.test.tsx}: Check test files for excessive use of mocks that obscure what's actually being tested
Warn when mocks replace testing real behavior/interactions
Suggest using real objects or simpler test doubles when mocks become excessive
Use mocks primarily for external dependencies, not core logic
Recommend integration tests when unit tests are overly mocked
Verify tests actually exercise the new/changed functionality (not placeholders)
Test files should use descriptive test names explaining what is being tested
Organize tests logically with proper setup/teardown
Include edge cases and error conditions for comprehensive coverage
Cover both positive and negative scenarios where appropriate
Tests should cover the main functionality being implemented
Tests should not be mere smoke tests; they must validate behavior
Files:
src/lfx/tests/unit/services/cache/test_cache_miss.pysrc/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsxsrc/backend/tests/unit/api/v1/test_mcp_projects.pysrc/backend/tests/unit/api/v1/test_flows.pysrc/frontend/src/modals/toolsModal/components/toolsTable/__tests__/deployment-status.test.tsx
**/test_*.py
📄 CodeRabbit inference engine (coderabbit-custom-pre-merge-checks-unique-id-file-non-traceable-F7F2B60C-1728-4C9A-8889-4F2235E186CA.txt)
**/test_*.py: Backend test files must follow naming convention test_*.py
Backend tests should use proper pytest structure
For async backend functions, use proper async testing patterns with pytest
Backend tests should follow pytest conventions
For API endpoints, verify both success and error response tests are present
Files:
src/lfx/tests/unit/services/cache/test_cache_miss.pysrc/backend/tests/unit/api/v1/test_mcp_projects.pysrc/backend/tests/unit/api/v1/test_flows.py
src/frontend/src/components/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
All components should be styled using Tailwind CSS utility classes.
Files:
src/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsxsrc/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsx
src/frontend/src/@(components|hooks)/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
Implement dark mode support in components and hooks where needed.
Files:
src/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsxsrc/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsxsrc/frontend/src/hooks/flows/use-save-flow.ts
src/frontend/src/**/__tests__/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
All frontend code should be tested using appropriate component and integration tests.
Files:
src/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsxsrc/frontend/src/modals/toolsModal/components/toolsTable/__tests__/deployment-status.test.tsx
**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (coderabbit-custom-pre-merge-checks-unique-id-file-non-traceable-F7F2B60C-1728-4C9A-8889-4F2235E186CA.txt)
**/*.test.{ts,tsx}: Frontend test files must follow naming convention *.test.ts or *.test.tsx
Frontend tests should use Playwright
Frontend tests should follow Playwright conventions
Files:
src/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsxsrc/frontend/src/modals/toolsModal/components/toolsTable/__tests__/deployment-status.test.tsx
src/backend/base/langflow/services/database/models/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc)
Place database models in src/backend/base/langflow/services/database/models/
Files:
src/backend/base/langflow/services/database/models/flow/model.pysrc/backend/base/langflow/services/database/models/flow/utils.py
src/backend/tests/unit/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc)
Test component integration within flows using create_flow, build_flow, and get_build_events utilities
Files:
src/backend/tests/unit/api/v1/test_mcp_projects.pysrc/backend/tests/unit/api/v1/test_flows.py
src/backend/tests/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)
src/backend/tests/**/*.py: Unit tests for backend code must be located in the 'src/backend/tests/' directory, with component tests organized by component subdirectory under 'src/backend/tests/unit/components/'.
Test files should use the same filename as the component under test, with an appropriate test prefix or suffix (e.g., 'my_component.py' → 'test_my_component.py').
Use the 'client' fixture (an async httpx.AsyncClient) for API tests in backend Python tests, as defined in 'src/backend/tests/conftest.py'.
When writing component tests, inherit from the appropriate base class in 'src/backend/tests/base.py' (ComponentTestBase, ComponentTestBaseWithClient, or ComponentTestBaseWithoutClient) and provide the required fixtures: 'component_class', 'default_kwargs', and 'file_names_mapping'.
Each test in backend Python test files should have a clear docstring explaining its purpose, and complex setups or mocks should be well-commented.
Test both sync and async code paths in backend Python tests, using '@pytest.mark.asyncio' for async tests.
Mock external dependencies appropriately in backend Python tests to isolate unit tests from external services.
Test error handling and edge cases in backend Python tests, including using 'pytest.raises' and asserting error messages.
Validate input/output behavior and test component initialization and configuration in backend Python tests.
Use the 'no_blockbuster' pytest marker to skip the blockbuster plugin in tests when necessary.
Be aware of ContextVar propagation in async tests; test both direct event loop execution and 'asyncio.to_thread' scenarios to ensure proper context isolation.
Test error handling by mocking internal functions using monkeypatch in backend Python tests.
Test resource cleanup in backend Python tests by using fixtures that ensure proper initialization and cleanup of resources.
Test timeout and performance constraints in backend Python tests using 'asyncio.wait_for' and timing assertions.
Test Langflow's Messag...
Files:
src/backend/tests/unit/api/v1/test_mcp_projects.pysrc/backend/tests/unit/api/v1/test_flows.py
src/frontend/src/@(hooks|services)/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
All API calls must implement proper error handling, such as using try/catch and setting error state.
Files:
src/frontend/src/hooks/flows/use-save-flow.ts
src/frontend/src/hooks/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
All custom React hooks should be placed in the hooks directory.
Files:
src/frontend/src/hooks/flows/use-save-flow.ts
🧠 Learnings (6)
📚 Learning: 2025-10-07T13:29:59.567Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: coderabbit-custom-pre-merge-checks-unique-id-file-non-traceable-F7F2B60C-1728-4C9A-8889-4F2235E186CA.txt:0-0
Timestamp: 2025-10-07T13:29:59.567Z
Learning: Applies to **/*.test.{ts,tsx} : Frontend tests should follow Playwright conventions
Applied to files:
src/frontend/tests/core/features/deploy-flow.spec.tssrc/frontend/tests/core/features/mcp-deployment.spec.ts
📚 Learning: 2025-10-07T13:29:59.567Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: coderabbit-custom-pre-merge-checks-unique-id-file-non-traceable-F7F2B60C-1728-4C9A-8889-4F2235E186CA.txt:0-0
Timestamp: 2025-10-07T13:29:59.567Z
Learning: Applies to **/*.test.{ts,tsx} : Frontend tests should use Playwright
Applied to files:
src/frontend/tests/core/features/deploy-flow.spec.tssrc/frontend/tests/core/features/mcp-deployment.spec.ts
📚 Learning: 2025-07-21T14:16:14.125Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-21T14:16:14.125Z
Learning: Applies to src/frontend/**/*.@(test|spec).{ts,tsx,js,jsx} : Mock external dependencies appropriately in frontend test files to isolate unit tests from external services.
Applied to files:
src/frontend/tests/core/features/mcp-deployment.spec.ts
📚 Learning: 2025-06-23T12:46:42.048Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/frontend_development.mdc:0-0
Timestamp: 2025-06-23T12:46:42.048Z
Learning: Custom React Flow node types should be implemented as memoized components, using Handle components for connection points and supporting optional icons and labels.
Applied to files:
src/frontend/src/pages/FlowPage/components/PageComponent/MemoizedComponents.tsx
📚 Learning: 2025-07-21T14:16:14.125Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-21T14:16:14.125Z
Learning: Applies to src/backend/tests/**/*.py : Test flows using predefined JSON data and utility functions such as 'create_flow', 'build_flow', 'get_build_events', and 'consume_and_assert_stream' in backend Python tests.
Applied to files:
src/backend/tests/unit/api/v1/test_flows.py
📚 Learning: 2025-07-18T18:25:54.487Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/backend_development.mdc:0-0
Timestamp: 2025-07-18T18:25:54.487Z
Learning: Applies to src/backend/tests/unit/**/*.py : Test component integration within flows using create_flow, build_flow, and get_build_events utilities
Applied to files:
src/backend/tests/unit/api/v1/test_flows.py
🧬 Code graph analysis (25)
src/backend/base/langflow/services/database/service.py (5)
src/backend/base/langflow/services/deps.py (1)
get_settings_service(112-125)src/backend/base/langflow/services/database/models/flow/model.py (1)
Flow(204-230)src/backend/base/langflow/services/database/models/folder/model.py (1)
Folder(21-36)src/backend/base/langflow/services/database/session.py (4)
exec(51-62)all(56-57)add(18-19)commit(21-22)src/backend/base/langflow/services/database/models/user/crud.py (1)
get_user_by_username(14-16)
src/backend/base/langflow/api/v1/mcp_projects.py (1)
src/backend/base/langflow/services/database/models/flow/model.py (1)
Flow(204-230)
src/frontend/tests/core/features/deploy-flow.spec.ts (2)
src/frontend/tests/utils/await-bootstrap-test.ts (1)
awaitBootstrapTest(4-49)src/frontend/tests/utils/adjust-screen-view.ts (1)
adjustScreenView(3-36)
src/frontend/src/components/core/flowToolbarComponent/components/__tests__/deploy-dropdown.test.tsx (1)
src/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsx (1)
PublishDropdown(32-285)
src/backend/base/langflow/interface/initialize/loading.py (1)
src/lfx/src/lfx/interface/initialize/loading.py (9)
build_component(263-271)build_custom_component(274-317)convert_kwargs(97-111)convert_params_to_sets(88-94)get_instance_results(57-78)get_params(81-85)instantiate_class(28-54)update_params_with_load_from_db_fields(211-260)update_table_params_with_load_from_db_fields(129-208)
src/lfx/src/lfx/graph/vertex/base.py (1)
src/lfx/src/lfx/interface/initialize/loading.py (1)
instantiate_class(28-54)
src/frontend/tests/core/features/mcp-deployment.spec.ts (1)
src/frontend/tests/utils/await-bootstrap-test.ts (1)
awaitBootstrapTest(4-49)
src/backend/tests/unit/api/v1/test_mcp_projects.py (5)
src/backend/base/langflow/services/deps.py (1)
session_scope(152-174)src/backend/base/langflow/services/auth/utils.py (1)
get_password_hash(344-346)src/backend/base/langflow/services/database/models/folder/model.py (1)
Folder(21-36)src/backend/base/langflow/services/database/models/flow/model.py (1)
Flow(204-230)src/backend/base/langflow/api/v1/mcp_projects.py (1)
list_project_tools(199-283)
src/backend/base/langflow/api/v1/schemas.py (1)
src/backend/base/langflow/services/task/backends/anyio.py (1)
status(24-27)
src/backend/base/langflow/helpers/flow.py (2)
src/backend/base/langflow/services/deps.py (1)
get_flow_cache_service(247-251)src/backend/base/langflow/services/flow_cache/service.py (1)
get_cached_graph(79-102)
src/backend/base/langflow/api/v1/endpoints.py (3)
src/backend/base/langflow/helpers/flow.py (2)
get_flow_by_id_or_endpoint_name(282-297)get_flow_by_id_or_endpoint_name_from_cache(300-323)src/backend/base/langflow/services/deps.py (1)
get_flow_cache_service(247-251)src/backend/base/langflow/services/flow_cache/service.py (1)
FlowCacheService(17-178)
src/frontend/src/modals/toolsModal/components/toolsTable/index.tsx (4)
src/frontend/src/controllers/API/queries/flows/use-patch-update-flow.ts (1)
usePatchUpdateFlow(20-49)src/frontend/src/constants/flows.ts (1)
DEPLOYMENT_STATUS(4-7)src/frontend/src/components/common/genericIconComponent/index.tsx (1)
ForwardedIconComponent(15-146)src/frontend/src/utils/utils.ts (1)
cn(38-40)
src/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsx (2)
src/frontend/src/constants/flows.ts (2)
ACCESS_TYPE(15-19)DEPLOYMENT_STATUS(4-7)src/frontend/src/utils/utils.ts (1)
cn(38-40)
src/backend/base/langflow/services/database/models/flow/utils.py (1)
src/backend/tests/conftest.py (1)
flow(544-560)
src/frontend/src/modals/apiModal/index.tsx (1)
src/frontend/src/constants/flows.ts (1)
DEPLOYMENT_STATUS(4-7)
src/backend/base/langflow/services/deps.py (3)
src/backend/base/langflow/services/flow_cache/service.py (1)
FlowCacheService(17-178)src/backend/base/langflow/services/flow_cache/factory.py (1)
FlowCacheServiceFactory(5-14)src/backend/base/langflow/services/schema.py (1)
ServiceType(4-23)
src/backend/base/langflow/api/v1/flows.py (3)
src/backend/base/langflow/services/database/models/flow/model.py (1)
DeploymentStateEnum(32-34)src/backend/base/langflow/services/deps.py (1)
get_flow_cache_service(247-251)src/backend/base/langflow/services/flow_cache/service.py (5)
FlowCacheService(17-178)add_flow_to_cache(26-59)refresh_flow_in_cache(104-121)remove_flow_from_cache(61-77)get_cache_stats(123-178)
src/backend/base/langflow/api/v1/mcp_utils.py (2)
src/backend/base/langflow/services/deps.py (1)
get_flow_cache_service(247-251)src/backend/base/langflow/services/flow_cache/service.py (1)
get_cached_graph(79-102)
src/backend/base/langflow/services/flow_cache/factory.py (1)
src/backend/base/langflow/services/flow_cache/service.py (1)
FlowCacheService(17-178)
src/frontend/src/pages/FlowPage/components/PageComponent/MemoizedComponents.tsx (3)
src/frontend/src/constants/flows.ts (1)
DEPLOYMENT_STATUS(4-7)src/frontend/src/components/common/genericIconComponent/index.tsx (1)
ForwardedIconComponent(15-146)src/frontend/src/utils/utils.ts (1)
cn(38-40)
src/backend/tests/unit/api/v1/test_flows.py (2)
src/backend/base/langflow/services/database/models/flow/model.py (1)
Flow(204-230)src/backend/tests/conftest.py (2)
logged_in_headers(495-501)flow(544-560)
src/backend/base/langflow/services/flow_cache/service.py (2)
src/backend/base/langflow/services/cache/service.py (1)
AsyncInMemoryCache(294-355)src/backend/base/langflow/services/database/models/flow/model.py (1)
Flow(204-230)
src/backend/base/langflow/services/socket/utils.py (2)
src/lfx/src/lfx/graph/vertex/base.py (1)
Vertex(46-831)src/backend/base/langflow/services/database/models/vertex_builds/crud.py (1)
log_vertex_build(52-130)
src/frontend/src/modals/toolsModal/components/toolsTable/__tests__/deployment-status.test.tsx (1)
src/frontend/src/modals/toolsModal/components/toolsTable/index.tsx (1)
ToolsTable(28-515)
src/backend/base/langflow/services/utils.py (4)
src/backend/base/langflow/services/deps.py (5)
session_scope(152-174)get_db_service(128-137)get_flow_cache_service(247-251)get_service(31-53)get_settings_service(112-125)src/backend/base/langflow/services/database/models/flow/model.py (2)
DeploymentStateEnum(32-34)Flow(204-230)src/backend/base/langflow/services/flow_cache/service.py (2)
FlowCacheService(17-178)add_flow_to_cache(26-59)src/backend/base/langflow/services/database/service.py (1)
assign_orphaned_flows_to_superuser(200-246)
🔇 Additional comments (12)
src/backend/base/langflow/services/schema.py (1)
22-22: LGTM!The new
FLOW_CACHE_SERVICEenum member follows the existing naming and structure conventions.src/backend/base/langflow/services/flow_cache/service.py (2)
26-60: LGTM with minor suggestions.The
add_flow_to_cachemethod has good error handling and silent mode support. Two minor observations:
Line 41 uses a shallow copy (
flow.data.copy()). Ifflow.datacontains nested mutable objects, they could be shared between the original and the copy. However, sinceGraph.from_payloadlikely doesn't mutate the input, this should be safe.Lines 46-49 catch broad exception types. While this is acceptable for graceful degradation, consider whether more specific exception types could improve diagnostics.
79-102: Cache miss detection logic is correct.CACHE_MISSis aCacheMissinstance with__bool__returning False, soif not cachedreliably only catches misses and won’t misclassify validGraphobjects.src/lfx/src/lfx/graph/graph/base.py (2)
1345-1348: LGTM!The new
_reset_components_in_verticesmethod provides clean post-run cleanup by resetting component state in all vertices. The implementation is straightforward and follows the existing naming conventions.
812-812: LGTM!Good placement of the reset call at the end of
_run, ensuring components are reset after collecting vertex outputs. This prevents stale state from affecting subsequent runs.src/frontend/src/hooks/flows/use-save-flow.ts (1)
72-72: LGTM!The
statusfield is correctly added to both the destructuring (line 72) and the mutation payload (line 84), ensuring proper propagation of deployment status through the save flow.Also applies to: 84-84
src/lfx/src/lfx/interface/initialize/loading.py (1)
54-54: Allinstantiate_classcallers updated for 3-element return signature Call sites inbase.pynow unpack three values; no two-variable unpacking remains.src/backend/tests/unit/api/v1/test_mcp_projects.py (1)
518-518: Good: allowlisted secret in test payloadPragmas are correctly used to avoid false-positive secret scans.
src/backend/base/langflow/interface/initialize/loading.py (1)
3-27: Re-export looks correct and preserves API surfaceModule-level re-exports and all are consistent. Please ensure lfx path is available wherever this module is imported at runtime.
src/backend/base/langflow/services/utils.py (1)
230-237: Flow cache preload looks goodPreloading only DEPLOYED flows and delegating parsing/guarding to FlowCacheService is correct. No issues.
src/lfx/src/lfx/graph/vertex/base.py (1)
401-410: LGTM: capture class object during buildStoring class_object for potential reuse/inspection aligns with the new loading flow. No issues.
src/frontend/tests/core/features/deploy-flow.spec.ts (1)
201-277: Nice coverage of independent switchesGood validation of deploy vs publish independence and states.
| # Fetch orphaned flows | ||
| stmt = ( | ||
| select(models.Flow) | ||
| .join(models.Folder) | ||
| .where( | ||
| models.Flow.user_id == None, # noqa: E711 | ||
| models.Folder.name != STARTER_FOLDER_NAME, | ||
| ) | ||
| orphaned_flows = (await session.exec(stmt)).all() | ||
| ) | ||
| orphaned_flows = (await session.exec(stmt)).all() | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Outer-join and proper NULL comparisons to include folderless flows
Current inner join excludes orphaned flows without a folder. Also uses == None and an implicit join target. Use an outer join on Flow.folder and is_(None) plus an OR to include NULL folders.
Apply:
- stmt = (
- select(models.Flow)
- .join(models.Folder)
- .where(
- models.Flow.user_id == None, # noqa: E711
- models.Folder.name != STARTER_FOLDER_NAME,
- )
- )
+ stmt = (
+ select(models.Flow)
+ .join(models.Flow.folder, isouter=True)
+ .where(
+ models.Flow.user_id.is_(None),
+ sa.or_(models.Folder.name != STARTER_FOLDER_NAME, models.Folder.name.is_(None)),
+ )
+ )📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Fetch orphaned flows | |
| stmt = ( | |
| select(models.Flow) | |
| .join(models.Folder) | |
| .where( | |
| models.Flow.user_id == None, # noqa: E711 | |
| models.Folder.name != STARTER_FOLDER_NAME, | |
| ) | |
| orphaned_flows = (await session.exec(stmt)).all() | |
| ) | |
| orphaned_flows = (await session.exec(stmt)).all() | |
| # Fetch orphaned flows | |
| stmt = ( | |
| select(models.Flow) | |
| .join(models.Flow.folder, isouter=True) | |
| .where( | |
| models.Flow.user_id.is_(None), | |
| sa.or_(models.Folder.name != STARTER_FOLDER_NAME, models.Folder.name.is_(None)), | |
| ) | |
| ) | |
| orphaned_flows = (await session.exec(stmt)).all() |
🤖 Prompt for AI Agents
In src/backend/base/langflow/services/database/service.py around lines 207 to
217, the current inner join and `== None` check exclude flows that have no
folder; change to use an outer (left) join against the Flow.folder relationship
and replace `== None` with SQLAlchemy's `is_(None)`; then adjust the WHERE so
flows with user_id is_(None) AND (Folder.name != STARTER_FOLDER_NAME OR
Folder.id is_(None)) so folderless flows are included. Ensure you use an
explicit outerjoin on the relationship (e.g. outerjoin(models.Flow.folder)) and
import and use sqlalchemy.or_ or the `|` operator for the OR condition.
| from lfx.graph.graph.base import Graph | ||
| from lfx.graph.graph.utils import layered_topological_sort | ||
| from lfx.graph.vertex.base import Vertex | ||
| from lfx.log.logger import logger | ||
| from sqlmodel import select | ||
|
|
||
| from langflow.api.utils import format_elapsed_time | ||
| from langflow.api.v1.schemas import ResultDataResponse, VertexBuildResponse | ||
| from langflow.graph.graph.base import Graph | ||
| from langflow.graph.graph.utils import layered_topological_sort | ||
| from langflow.graph.utils import log_vertex_build | ||
| from langflow.graph.vertex.base import Vertex | ||
| from langflow.services.database.models.flow.model import Flow | ||
| from langflow.services.database.models.vertex_builds.crud import log_vertex_build | ||
| from langflow.services.deps import get_session |
There was a problem hiding this comment.
Critical: log_vertex_build signature mismatch — missing DB session and schema
The imported crud.log_vertex_build expects (db: AsyncSession, vertex_build: VertexBuildBase, ...). Current call passes flow_id/vertex_id/etc. without a DB session, causing a TypeError at runtime. Fix by creating a session and passing a VertexBuildBase payload.
Apply:
@@
-from lfx.graph.graph.base import Graph
+from lfx.graph.graph.base import Graph
from lfx.graph.graph.utils import layered_topological_sort
from lfx.graph.vertex.base import Vertex
from lfx.log.logger import logger
from sqlmodel import select
from langflow.api.utils import format_elapsed_time
from langflow.api.v1.schemas import ResultDataResponse, VertexBuildResponse
from langflow.services.database.models.flow.model import Flow
-from langflow.services.database.models.vertex_builds.crud import log_vertex_build
+from langflow.services.database.models.vertex_builds.crud import log_vertex_build
+from langflow.services.database.models.vertex_builds.schemas import VertexBuildBase # adjust path if different
from langflow.services.deps import get_session
@@
- await log_vertex_build(
- flow_id=flow_id,
- vertex_id=vertex_id,
- valid=valid,
- params=params,
- data=result_dict,
- artifacts=artifacts,
- )
+ db = await anext(get_session())
+ await log_vertex_build(
+ db,
+ VertexBuildBase(
+ flow_id=flow_id,
+ vertex_id=vertex_id, # use 'id=vertex_id' if schema uses 'id'
+ valid=valid,
+ params=params,
+ data=result_dict,
+ artifacts=artifacts,
+ ),
+ )Based on learnings
Also applies to: 90-97
🤖 Prompt for AI Agents
In src/backend/base/langflow/services/socket/utils.py around lines 5-15 (and
also apply same fix to lines 90-97), the call to log_vertex_build is using the
wrong signature: the CRUD expects an Async DB session and a VertexBuildBase
schema instance but the current code passes primitive values (flow_id,
vertex_id, etc.) without a session. Fix by acquiring an async DB session via
get_session(), construct the proper VertexBuildBase (or the expected
vertex_build schema) with the flow/vertex/status/message/timings data, and call
await log_vertex_build(db_session, vertex_build_payload, ...) instead of the
current argument list; ensure the session is closed/used as an async context
manager if required.
| from typing import TYPE_CHECKING | ||
|
|
||
| from lfx.log.logger import logger | ||
| from lfx.services.deps import session_scope |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
Confirm correct import source for session_scope
This imports session_scope from lfx.services.deps. The project also defines session_scope under langflow.services.deps. Verify this is intentional and the symbol exists at the lfx path; otherwise switch to the local deps module.
Run to confirm:
🏁 Script executed:
#!/bin/bash
# Find session_scope definitions and imports
rg -nP -C2 'async\s+def\s+session_scope' | sed -n '1,200p'
rg -nP -C2 '\bfrom\s+lfx\.services\.deps\s+import\s+session_scope\b' | sed -n '1,200p'
rg -nP -C2 '\bfrom\s+langflow\.services\.deps\s+import\s+session_scope\b' | sed -n '1,200p'Length of output: 9297
Import session_scope from langflow.services.deps
In src/backend/base/langflow/services/utils.py:7, replace
from lfx.services.deps import session_scopewith
from langflow.services.deps import session_scopeto use the local implementation.
🤖 Prompt for AI Agents
In src/backend/base/langflow/services/utils.py around line 7, the file imports
session_scope from the external lfx.services.deps module instead of using the
local implementation; replace the import statement to import session_scope from
langflow.services.deps so the local dependency implementation is used.
| it("displays deployment status for deployed flow", () => { | ||
| const { container } = render( | ||
| <ToolsTable | ||
| rows={[mockDeployedFlow]} | ||
| data={[mockDeployedFlow]} | ||
| setData={jest.fn()} | ||
| isAction={true} | ||
| placeholder="Search" | ||
| open={true} | ||
| handleOnNewValue={jest.fn()} | ||
| />, | ||
| ); | ||
|
|
||
| // The component uses ag-grid which needs special handling | ||
| // Just verify it renders without crashing | ||
| expect(container).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it("displays deployment status for draft flow", () => { | ||
| const { container } = render( | ||
| <ToolsTable | ||
| rows={[mockDraftFlow]} | ||
| data={[mockDraftFlow]} | ||
| setData={jest.fn()} | ||
| isAction={true} | ||
| placeholder="Search" | ||
| open={true} | ||
| handleOnNewValue={jest.fn()} | ||
| />, | ||
| ); | ||
|
|
||
| expect(container).toBeInTheDocument(); | ||
| }); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Enhance tests to validate actual deployment status behavior.
These tests only verify that the component renders without crashing but don't validate the deployment status functionality. They should test:
- That deployment status badges/indicators are actually visible in the UI
- That the deploy toggle switch works correctly
- That toggling deployment status calls the mock API (
mockMutateAsync) - That status changes update the UI state appropriately
Consider enhancing the tests like this:
it("displays deployment status for deployed flow", () => {
const { container } = render(
<ToolsTable
rows={[mockDeployedFlow]}
data={[mockDeployedFlow]}
setData={jest.fn()}
isAction={true}
placeholder="Search"
open={true}
handleOnNewValue={jest.fn()}
/>,
);
- // The component uses ag-grid which needs special handling
- // Just verify it renders without crashing
- expect(container).toBeInTheDocument();
+ // Verify deployment status is visible
+ // This will depend on how the component displays status - add appropriate assertions
+ expect(container).toBeInTheDocument();
+
+ // Example: Verify deployment badge/indicator exists
+ // expect(screen.getByText(/deployed/i)).toBeInTheDocument();
+ // or expect(screen.getByTestId("deployment-badge")).toHaveTextContent("DEPLOYED");
});Based on coding guidelines.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| it("displays deployment status for deployed flow", () => { | |
| const { container } = render( | |
| <ToolsTable | |
| rows={[mockDeployedFlow]} | |
| data={[mockDeployedFlow]} | |
| setData={jest.fn()} | |
| isAction={true} | |
| placeholder="Search" | |
| open={true} | |
| handleOnNewValue={jest.fn()} | |
| />, | |
| ); | |
| // The component uses ag-grid which needs special handling | |
| // Just verify it renders without crashing | |
| expect(container).toBeInTheDocument(); | |
| }); | |
| it("displays deployment status for draft flow", () => { | |
| const { container } = render( | |
| <ToolsTable | |
| rows={[mockDraftFlow]} | |
| data={[mockDraftFlow]} | |
| setData={jest.fn()} | |
| isAction={true} | |
| placeholder="Search" | |
| open={true} | |
| handleOnNewValue={jest.fn()} | |
| />, | |
| ); | |
| expect(container).toBeInTheDocument(); | |
| }); | |
| it("displays deployment status for deployed flow", () => { | |
| const { container } = render( | |
| <ToolsTable | |
| rows={[mockDeployedFlow]} | |
| data={[mockDeployedFlow]} | |
| setData={jest.fn()} | |
| isAction={true} | |
| placeholder="Search" | |
| open={true} | |
| handleOnNewValue={jest.fn()} | |
| />, | |
| ); | |
| // Verify deployment status is visible | |
| // This will depend on how the component displays status - add appropriate assertions | |
| expect(container).toBeInTheDocument(); | |
| // Example: Verify deployment badge/indicator exists | |
| // expect(screen.getByText(/deployed/i)).toBeInTheDocument(); | |
| // or expect(screen.getByTestId("deployment-badge")).toHaveTextContent("DEPLOYED"); | |
| }); |
| it("includes status field in flow data", () => { | ||
| const mockSetData = jest.fn(); | ||
| render( | ||
| <ToolsTable | ||
| rows={[mockDeployedFlow, mockDraftFlow]} | ||
| data={[mockDeployedFlow, mockDraftFlow]} | ||
| setData={mockSetData} | ||
| isAction={true} | ||
| placeholder="Search" | ||
| open={true} | ||
| handleOnNewValue={jest.fn()} | ||
| />, | ||
| ); | ||
|
|
||
| // Verify the component can handle flows with status field | ||
| expect(mockDeployedFlow.status).toBe("DEPLOYED"); | ||
| expect(mockDraftFlow.status).toBe("DRAFT"); | ||
| }); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Test validates mock data structure instead of component behavior.
This test only verifies properties on the mock objects rather than testing how the component handles and displays the status field. It should verify that:
- The component correctly reads the
statusfield from flow data - The UI reflects different status values (DEPLOYED vs DRAFT) appropriately
- User interactions with status-related controls work as expected
Consider testing actual component behavior:
it("includes status field in flow data", () => {
const mockSetData = jest.fn();
render(
<ToolsTable
rows={[mockDeployedFlow, mockDraftFlow]}
data={[mockDeployedFlow, mockDraftFlow]}
setData={mockSetData}
isAction={true}
placeholder="Search"
open={true}
handleOnNewValue={jest.fn()}
/>,
);
- // Verify the component can handle flows with status field
- expect(mockDeployedFlow.status).toBe("DEPLOYED");
- expect(mockDraftFlow.status).toBe("DRAFT");
+ // Verify the component displays different status values correctly
+ // Add assertions that check how the UI reflects deployed vs draft status
+ // For example:
+ // const deployedBadges = screen.getAllByText(/deployed/i);
+ // expect(deployedBadges).toHaveLength(1);
+ // const draftBadges = screen.getAllByText(/draft/i);
+ // expect(draftBadges).toHaveLength(1);
});Based on coding guidelines.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| it("includes status field in flow data", () => { | |
| const mockSetData = jest.fn(); | |
| render( | |
| <ToolsTable | |
| rows={[mockDeployedFlow, mockDraftFlow]} | |
| data={[mockDeployedFlow, mockDraftFlow]} | |
| setData={mockSetData} | |
| isAction={true} | |
| placeholder="Search" | |
| open={true} | |
| handleOnNewValue={jest.fn()} | |
| />, | |
| ); | |
| // Verify the component can handle flows with status field | |
| expect(mockDeployedFlow.status).toBe("DEPLOYED"); | |
| expect(mockDraftFlow.status).toBe("DRAFT"); | |
| }); | |
| it("includes status field in flow data", () => { | |
| const mockSetData = jest.fn(); | |
| render( | |
| <ToolsTable | |
| rows={[mockDeployedFlow, mockDraftFlow]} | |
| data={[mockDeployedFlow, mockDraftFlow]} | |
| setData={mockSetData} | |
| isAction={true} | |
| placeholder="Search" | |
| open={true} | |
| handleOnNewValue={jest.fn()} | |
| />, | |
| ); | |
| // Verify the component displays different status values correctly | |
| // Add assertions that check how the UI reflects deployed vs draft status | |
| // For example: | |
| // const deployedBadges = screen.getAllByText(/deployed/i); | |
| // expect(deployedBadges).toHaveLength(1); | |
| // const draftBadges = screen.getAllByText(/draft/i); | |
| // expect(draftBadges).toHaveLength(1); | |
| }); |
| const handleDeployToggle = async (flowId: string, currentStatus: string) => { | ||
| const newStatus = | ||
| currentStatus === DEPLOYMENT_STATUS.DEPLOYED | ||
| ? DEPLOYMENT_STATUS.DRAFT | ||
| : DEPLOYMENT_STATUS.DEPLOYED; | ||
| try { | ||
| await mutateAsync({ | ||
| id: flowId, | ||
| status: newStatus, | ||
| }); | ||
| // Update the focused row to reflect the change | ||
| if (focusedRow && focusedRow.id === flowId) { | ||
| setFocusedRow({ ...focusedRow, status: newStatus }); | ||
| } | ||
| // Also update the data array | ||
| setData( | ||
| data.map((row) => | ||
| row.id === flowId ? { ...row, status: newStatus } : row, | ||
| ), | ||
| ); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Prevent stale state when toggling deployment
handleDeployToggle relies on the captured focusedRow/data values from the render that kicked off the mutation. If anything else updates those states before the patch resolves (e.g., another toggle or external list refresh), the subsequent setFocusedRow/setData(data.map(...)) call will overwrite newer changes with stale copies. Use the functional form of the setters so the update is applied to the latest state instead of the closed-over snapshot.
- if (focusedRow && focusedRow.id === flowId) {
- setFocusedRow({ ...focusedRow, status: newStatus });
- }
+ setFocusedRow((prev) =>
+ prev && prev.id === flowId ? { ...prev, status: newStatus } : prev,
+ );
@@
- setData(
- data.map((row) =>
- row.id === flowId ? { ...row, status: newStatus } : row,
- ),
- );
+ setData((prev) =>
+ prev.map((row) =>
+ row.id === flowId ? { ...row, status: newStatus } : row,
+ ),
+ );📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handleDeployToggle = async (flowId: string, currentStatus: string) => { | |
| const newStatus = | |
| currentStatus === DEPLOYMENT_STATUS.DEPLOYED | |
| ? DEPLOYMENT_STATUS.DRAFT | |
| : DEPLOYMENT_STATUS.DEPLOYED; | |
| try { | |
| await mutateAsync({ | |
| id: flowId, | |
| status: newStatus, | |
| }); | |
| // Update the focused row to reflect the change | |
| if (focusedRow && focusedRow.id === flowId) { | |
| setFocusedRow({ ...focusedRow, status: newStatus }); | |
| } | |
| // Also update the data array | |
| setData( | |
| data.map((row) => | |
| row.id === flowId ? { ...row, status: newStatus } : row, | |
| ), | |
| ); | |
| const handleDeployToggle = async (flowId: string, currentStatus: string) => { | |
| const newStatus = | |
| currentStatus === DEPLOYMENT_STATUS.DEPLOYED | |
| ? DEPLOYMENT_STATUS.DRAFT | |
| : DEPLOYMENT_STATUS.DEPLOYED; | |
| try { | |
| await mutateAsync({ | |
| id: flowId, | |
| status: newStatus, | |
| }); | |
| // Update the focused row to reflect the change | |
| setFocusedRow((prev) => | |
| prev && prev.id === flowId ? { ...prev, status: newStatus } : prev, | |
| ); | |
| // Also update the data array | |
| setData((prev) => | |
| prev.map((row) => | |
| row.id === flowId ? { ...row, status: newStatus } : row, | |
| ), | |
| ); |
🤖 Prompt for AI Agents
In src/frontend/src/modals/toolsModal/components/toolsTable/index.tsx around
lines 59 to 78, the handler updates focusedRow and data using closed-over values
which can overwrite newer state; change the calls to use the functional updater
form: call setFocusedRow(prev => prev && prev.id === flowId ? { ...prev, status:
newStatus } : prev) and setData(prev => prev.map(row => row.id === flowId ? {
...row, status: newStatus } : row)) so the updates apply to the latest state
rather than a stale snapshot.
| test( | ||
| "MCP modal allows toggling deployment status", | ||
| { tag: ["@release", "@workspace", "@api"] }, | ||
| async ({ page }) => { | ||
| test.setTimeout(60000); | ||
| try { | ||
| await awaitBootstrapTest(page); | ||
|
|
||
| // Close overlays | ||
| try { | ||
| await page.keyboard.press("Escape"); | ||
| await page.waitForTimeout(300); | ||
| } catch { | ||
| // Ignore | ||
| } | ||
|
|
||
| const mcpBtn = page.getByTestId("mcp-btn"); | ||
| if (!(await mcpBtn.isVisible({ timeout: 3000 }).catch(() => false))) { | ||
| test.skip(); | ||
| return; | ||
| } | ||
|
|
||
| await mcpBtn.click({ timeout: 3000 }); | ||
| await page.waitForTimeout(500); | ||
|
|
||
| const editToolsBtn = page.getByTestId("button_open_actions"); | ||
| if ( | ||
| !(await editToolsBtn.isVisible({ timeout: 3000 }).catch(() => false)) | ||
| ) { | ||
| test.skip(); | ||
| return; | ||
| } | ||
|
|
||
| await editToolsBtn.click({ timeout: 3000 }); | ||
| await page.waitForTimeout(500); | ||
|
|
||
| // Test passes if we got this far | ||
| expect(true).toBe(true); | ||
| } catch (error) { | ||
| console.log("Test skipped:", error); | ||
| test.skip(); | ||
| } | ||
| }, | ||
| ); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Test doesn't validate deployment toggle behavior.
This test is overly defensive and doesn't actually verify that deployment status can be toggled. Key issues:
- Lines 70-71:
expect(true).toBe(true)is a placeholder assertion that always passes - The extensive try/catch with
test.skip()means the test passes even if it encounters any error - No verification that clicking the toggle actually calls the API or updates the status
This violates the coding guideline: "Tests should not be mere smoke tests; they must validate behavior"
Either enhance the test to validate actual toggle behavior or rename it to reflect that it only checks element visibility:
- test(
- "MCP modal allows toggling deployment status",
+ test(
+ "MCP modal and tools button are accessible",
{ tag: ["@release", "@workspace", "@api"] },
async ({ page }) => {
test.setTimeout(60000);
try {
await awaitBootstrapTest(page);
// ... existing code ...
- // Test passes if we got this far
- expect(true).toBe(true);
+ // Verify the modal opened successfully
+ await expect(
+ page.getByRole("heading", { name: "MCP Server Tools" })
+ ).toBeVisible({ timeout: 5000 });
} catch (error) {
console.log("Test skipped:", error);
test.skip();
}
},
);Or implement actual toggle testing if elements are reliably present.
Based on coding guidelines.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| test( | |
| "MCP modal allows toggling deployment status", | |
| { tag: ["@release", "@workspace", "@api"] }, | |
| async ({ page }) => { | |
| test.setTimeout(60000); | |
| try { | |
| await awaitBootstrapTest(page); | |
| // Close overlays | |
| try { | |
| await page.keyboard.press("Escape"); | |
| await page.waitForTimeout(300); | |
| } catch { | |
| // Ignore | |
| } | |
| const mcpBtn = page.getByTestId("mcp-btn"); | |
| if (!(await mcpBtn.isVisible({ timeout: 3000 }).catch(() => false))) { | |
| test.skip(); | |
| return; | |
| } | |
| await mcpBtn.click({ timeout: 3000 }); | |
| await page.waitForTimeout(500); | |
| const editToolsBtn = page.getByTestId("button_open_actions"); | |
| if ( | |
| !(await editToolsBtn.isVisible({ timeout: 3000 }).catch(() => false)) | |
| ) { | |
| test.skip(); | |
| return; | |
| } | |
| await editToolsBtn.click({ timeout: 3000 }); | |
| await page.waitForTimeout(500); | |
| // Test passes if we got this far | |
| expect(true).toBe(true); | |
| } catch (error) { | |
| console.log("Test skipped:", error); | |
| test.skip(); | |
| } | |
| }, | |
| ); | |
| test( | |
| "MCP modal and tools button are accessible", | |
| { tag: ["@release", "@workspace", "@api"] }, | |
| async ({ page }) => { | |
| test.setTimeout(60000); | |
| try { | |
| await awaitBootstrapTest(page); | |
| // Close overlays | |
| try { | |
| await page.keyboard.press("Escape"); | |
| await page.waitForTimeout(300); | |
| } catch { | |
| // Ignore | |
| } | |
| const mcpBtn = page.getByTestId("mcp-btn"); | |
| if (!(await mcpBtn.isVisible({ timeout: 3000 }).catch(() => false))) { | |
| test.skip(); | |
| return; | |
| } | |
| await mcpBtn.click({ timeout: 3000 }); | |
| await page.waitForTimeout(500); | |
| const editToolsBtn = page.getByTestId("button_open_actions"); | |
| if ( | |
| !(await editToolsBtn.isVisible({ timeout: 3000 }).catch(() => false)) | |
| ) { | |
| test.skip(); | |
| return; | |
| } | |
| await editToolsBtn.click({ timeout: 3000 }); | |
| await page.waitForTimeout(500); | |
| // Verify the modal opened successfully | |
| await expect( | |
| page.getByRole("heading", { name: "MCP Server Tools" }) | |
| ).toBeVisible({ timeout: 5000 }); | |
| } catch (error) { | |
| console.log("Test skipped:", error); | |
| test.skip(); | |
| } | |
| }, | |
| ); |
🤖 Prompt for AI Agents
In src/frontend/tests/core/features/mcp-deployment.spec.ts around lines 34-77,
the test currently contains placeholder assertions and broad try/catch that
cause it to always pass or be skipped; replace the placeholder with real
validation: remove the outer catch that swallows errors, only use conditional
test.skip() before any interactions if prerequisites aren't present, then locate
the deployment toggle element, assert its initial state, click it, and assert
the state changed in the UI; also spy or wait for the relevant network/API
request and assert it was called with expected payload (or mock the response) to
confirm backend interaction; ensure failures throw so the test fails when
behavior is incorrect and keep timeouts minimal and explicit.
Added docstrings to the FlowCacheServiceFactory class and its methods to improve code clarity and maintainability. The new documentation includes descriptions of the class purpose, initialization, and the singleton creation logic for FlowCacheService instances, ensuring better understanding for future developers.
Deleted the unused `/cache` endpoint from the API, streamlining the codebase and improving maintainability. Additionally, removed unnecessary imports related to the flow cache service, enhancing code clarity.
Refactored existing tests in the ToolsTable component to improve clarity and accuracy. Renamed test cases to better reflect their purpose, ensuring they check for proper rendering and handling of deployment statuses. Added new tests to verify the component's response to status updates and the invocation of the mutateAsync function during deployment toggles, enhancing the robustness of the test suite.
Enhanced the flow creation process by locking flows with a DEPLOYED status prior to committing to the database. Updated comments to clarify the caching behavior for deployed flows, ensuring better understanding and maintainability of the flow management logic.
Introduced a new test to verify that flows created with a DEPLOYED status are automatically locked upon creation. This enhances the test suite by ensuring the correct behavior of flow management during the creation process, contributing to the overall robustness of the application.
Changed the status field in the MCPSettings model to use a Literal type for "DRAFT" and "DEPLOYED" values. This improves type safety and clarity in the model definition, ensuring that only valid status values are used throughout the application.
Refactored the get_components_versions function to streamline the process of retrieving graph_data or data from the flow object. The new implementation uses getattr for safer access and includes checks to ensure the retrieved data is a dictionary, improving code clarity and robustness.
Updated the flow cache service to support removal of stale cache entries when a flow's endpoint name changes. The `remove_flow_from_cache` and `refresh_flow_in_cache` methods now accept an optional `old_endpoint_name` parameter, allowing for more robust cache management during flow updates. This change improves the handling of renamed flows and ensures that all associated cache keys are properly cleaned up.
|
Codecov Report❌ Patch coverage is ❌ Your project check has failed because the head coverage (47.82%) is below the target coverage (55.00%). You can increase the head coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #7948 +/- ##
==========================================
+ Coverage 23.42% 24.95% +1.52%
==========================================
Files 1075 1089 +14
Lines 39962 40168 +206
Branches 5530 5567 +37
==========================================
+ Hits 9363 10024 +661
+ Misses 30438 29939 -499
- Partials 161 205 +44
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|




The goal here is two fold: allow users to promote flows into something akin to staging/production status, cache flows in-memory to avoid db queries when running the flow.
To achieve the first one, Flows now have a
statuscolumn that can be set toDRAFTorDEPLOYEDwhich puts or removes flows from theFlow Cache. The key thing here is that what we want is for flows to be inacessible through the API if not deployed which is a breaking change, so, for now, "DRAFT" status means <1.7.0 behavior (not cached), "DEPLOYED" status means cached.To achieve the second one a "FlowCacheService" was created and endpoints updated, including mcp endpoints.
Changes to the UI include:
Sharebutton.Flow lockedstateSummary by CodeRabbit
New Features
Refactor
Tests